Trellis is an iOS application security analysis toolkit designed to bridge static and dynamic analysis.
Trellis provides static analysis of iOS binaries to detect security-relevant function usage and generates static analysis reports. It also generates Frida scripts for dynamic instrumentation. It runs as a Ghidra script for interactive analysis. The goal of the security analysis is to provide a good balance between eliminating false-positives and false-negatives. Memory addresses and offsets are provided in report findings to aid the analyst's investigation.
Trellis is not designed to provide findings ready to be copied and pasted into an iOS app pentest report. It's designed to perform triage and provide the iOS security analyst with a map of what to investigate and where to find it. Static analysis report findings should be combined with Frida script dynamic analysis for a full investigation.
- Cryptographic Analysis: Detect CCCrypt, CCHmac, SecKey, PBKDF2, and CryptoKit operations with security checks for weak algorithms, hardcoded keys/IVs, ECB mode, and weak key derivation parameters
- TLS/SSL Analysis: Identify URLSession delegates, certificate validation issues, pinning implementations, and weak protocol versions
- Keychain Analysis: Find insecure accessibility levels, iCloud sync issues, and missing biometric protection
- Jailbreak Detection: Map jailbreak detection techniques including file path checks, fork detection, dylib enumeration, and string-table fallback for Swift binaries
- Anti-Debug Detection: Identify ptrace, sysctl, getppid, and other anti-debugging techniques
- Storage Security: Analyze NSUserDefaults, file writes, plist storage for sensitive data
- WebView Security: Detect JavaScript bridges, loadHTMLString XSS risks, loadRequest content loading, and WKScriptMessageHandler issues
- Deep Link Analysis: URL scheme handlers, Universal Links, SwiftUI onOpenURL, input validation, and dangerous destination flow warnings
- SQLite Security: SQL injection risks, unencrypted databases, and SQLCipher usage
- Logging Analysis: Detect sensitive data in NSLog, os_log, and print statements with severity tiering to reduce noise
- Endpoint Discovery: Extract and analyze API endpoints and network URLs
- Deserialization: NSKeyedUnarchiver and NSCoding vulnerability detection
- String-Table Scanning: Detect hardcoded passwords, API keys, HTTP cleartext URLs, and embedded credentials directly from the binary's string table (no decompiler required)
- Biometric Authentication: Detect insecure client-side-only LAContext biometric checks that are trivially bypassable
- Runtime Manipulation: Detect hardcoded credential comparisons, client-side authentication checks, and validation logic that can be bypassed via Frida hooking
- Enhanced Detection: ARM64 backward-slice parameter extraction fallback, caller-name heuristic checks, ObjC selector matching via
objc_msgSend, string cross-referencing with security-critical call sites, and interprocedural call-chain analysis (BFS up to 3 hops). Details - Swift Support: Automatic Swift symbol demangling
- Report Generation: Markdown reports saved to a user-chosen directory
- Frida Script Generation: Generate ready-to-use instrumentation scripts
This has been tested only on Kali Linux and macOS. While I'm sure it will work on other Linux distros or Windows, the others will not be supported here.
- Install Ghidra if not already installed.
- Create an alias in
~/.zshrcto start Ghidra with Python support: Kali:macOS:ghidra='/usr/share/ghidra/support/pyghidraRun'ghidra='/opt/homebrew/Cellar/ghidra/12.0.4/libexec/support/pyghidraRun' - Source your alias:
source ~/.zshrc - Start Ghidra from the terminal to create the Python virtual environment:
ghidra - Note the Ghidra version path in the terminal output.
- Ensure PyYAML is available in Ghidra's Python environment: (replace the version with the version found in step 5)
Kali:
macOS:
~/.config/ghidra/ghidra_<version>/venv/bin/pip install pyyaml
~/Library/ghidra/ghidra_<version>_PUBLIC/venv/bin/pip3 install pyyaml
- Start ghidra from the cli alias, open the binary you want to analyze and allow Ghidra to finish the analysis
- Click on Window -> Script Manager, then click the Manage Script Directories button. Next, click the plus button, and choose the path:
/path/to/Trellis/ghidra_scripts - In the Script Manager window, find the
iOS Securityfolder and ensure that both scripts are checked.
You should now see two options under the Tools -> Trellis menu, Analyze All and Generate Frida Scripts.
Critical! The iOS app binary MUST be decrypted. If you extracted the app binary from a production build of an iOS IPA file, it's probably encrypted. See frida-decrypt if you need a decryption tool.
- Open an iOS binary (IPA/Mach-O) in Ghidra
- Let Ghidra complete auto-analysis
- In the Ghidra menu, click
Tools->Trellis, and select eitherAnalyze AllorGenerate Frida Scriptsas desired - Run the script
- When prompted, choose an output directory for reports or Frida scripts
trellis_headless.py runs the full pipeline — Ghidra auto-analysis, Markdown reports, and Frida scripts — without opening the GUI. It uses PyGhidra to drive Ghidra headlessly.
Prerequisites: Run the script with the Python interpreter from Ghidra's virtual environment (the same one created by pyghidraRun):
~/.config/ghidra/ghidra_<version>/venv/bin/python trellis_headless.py --helpFull pipeline (Ghidra analysis + Markdown reports + Frida scripts):
python trellis_headless.py -b /path/to/DecryptedApp -o /tmp/trellis-resultsReports only (skip Frida script generation):
python trellis_headless.py -b /path/to/DecryptedApp -o /tmp/trellis-results --skip-fridaFrida scripts only (re-generate scripts from a prior analysis run without re-running Ghidra):
python trellis_headless.py -b /path/to/DecryptedApp -o /tmp/trellis-results --skip-analysisPersistent Ghidra project (saves the Ghidra project so subsequent runs skip re-analysis):
python trellis_headless.py -b /path/to/DecryptedApp -o /tmp/trellis-results --project-dir /tmp/ghidra-proj| Argument | Description |
|---|---|
-b / --binary |
Path to the decrypted iOS Mach-O binary |
-o / --output |
Output directory for reports and Frida scripts (created if needed) |
--project-dir |
Ghidra project directory (default: temp dir, deleted after run) |
--skip-analysis |
Skip Markdown report generation |
--skip-frida |
Skip Frida script generation |
The output directory will contain per-category .md reports, findings .json files (used by findings-driven Frida hooks), and .js Frida scripts — identical to what the GUI workflow produces.
| Category | Description |
|---|---|
| crypto | CommonCrypto functions (CCCrypt, CCHmac, PBKDF2, etc.) |
| cryptokit | Swift CryptoKit framework (AES-GCM, ChaChaPoly, HKDF, etc.) |
| networking | Network/TLS configuration and security concerns |
| tls_delegate | TLS delegate implementation and certificate validation |
| keychain | Keychain Services (SecItemAdd, SecItemCopyMatching, etc.) |
| jailbreak | Jailbreak detection (file checks, fork, URL schemes) |
| antidebug | Anti-debugging techniques (ptrace, sysctl, getppid) |
| storage | Data storage patterns (NSUserDefaults, file writes, plists) |
| deserialization | Object deserialization vulnerabilities (NSCoding, etc.) |
| webview | WebView security (JavaScript bridges, content loading) |
| deeplinks | Deep link and URL scheme handling security |
| sqlite | SQLite database security (SQL injection, encryption) |
| logging | Sensitive data logging (NSLog, os_log, print, etc.) |
| endpoints | API endpoint discovery and security analysis |
| biometric | Biometric authentication (Touch ID/Face ID) security |
| runtime | Runtime manipulation vulnerabilities (client-side auth, hardcoded comparisons) |
| insecure_storage | Insecure data storage patterns (NSUserDefaults, plist files, etc.) |
| string_scan | String-table scan for hardcoded credentials, API keys, HTTP URLs |
Reports are saved to the directory you select at launch, with timestamps:
Trellis-Crypto-25-02-19-143022.mdTrellis-Keychain-25-02-19-143022.mdTrellis-StringScan-25-02-19-143022.md
Each report contains:
- Summary: Finding counts by severity
- Security Findings: Detailed issues with evidence, impact, and recommendations
- Functions Analyzed: List of security-relevant functions found
| Severity | Emoji | Meaning |
|---|---|---|
| CRITICAL | 🔴 | Immediate security risk, easily exploitable |
| HIGH | 🟠 | Significant vulnerability |
| MEDIUM | 🟡 | Security concern requiring review |
| LOW | 🔵 | Minor issue or best practice violation |
| INFO | ⚪ | Informational finding |
- Weak Algorithms: DES, 3DES, RC4, MD5, SHA-1 usage
- ECB Mode: Insecure block cipher mode
- Hardcoded Keys/IVs: Keys from constant data sections
- NULL IV: Missing initialization vector for CBC mode
- Weak PBKDF2: Low iteration count (<10,000 = CRITICAL, <100,000 = HIGH), SHA-1 PRF, hardcoded salt, short derived key length
- Hardcoded Keys: AES-GCM/ChaChaPoly keys from constant data
- Hardcoded Nonces: Static IVs causing nonce reuse
- Weak HKDF Salt: Empty or zero salt in key derivation
- SymmetricKey Creation: Hardcoded symmetric key data
- Trust Evaluation: Missing SecTrustEvaluate calls
- Unconditional Trust: Delegates accepting all certificates
- Deprecated APIs: Using old SecTrustEvaluate vs WithError variant
- Certificate Pinning: Presence or absence of pinning
- Weak TLS Version: SSL 3.0, TLS 1.0, TLS 1.1 minimum versions
- ATS Bypass: NSAllowsArbitraryLoads detection
- HTTP URLs: Insecure HTTP endpoint usage
- Insecure Accessibility: kSecAttrAccessibleAlways usage
- iCloud Sync: Sensitive data with synchronization enabled
- Missing Biometrics: No access control for sensitive items
- Data Exposure: Broad queries returning all items
- File Checks: Cydia, Substrate, and jailbreak tool paths
- Fork Detection: Using fork() to detect sandbox escape
- Dylib Enumeration: Checking for injected libraries
- String-Table Fallback: Scans string table for jailbreak paths, URL schemes, and dylib names when parameter extraction fails (common with Swift)
- ptrace: PT_DENY_ATTACH usage
- sysctl: Process info queries for debugger detection
- getppid: Parent process ID checks
- Exception Handlers: Signal-based debug detection
- NSUserDefaults: Sensitive data in user defaults
- File Writes: Insecure file permissions and locations
- Plist Storage: Property list data exposure
- Temporary Files: Sensitive data in temp directories
- NSKeyedUnarchiver: Insecure unarchiving without secure coding
- NSCoding: Legacy serialization vulnerabilities
- Class Restrictions: Missing requiresSecureCoding checks
- JavaScript Bridges: WKScriptMessageHandler exposure
- Insecure Content: Mixed content and HTTP loading
- JavaScript Injection: evaluateJavaScript with user data
- File Access: Local file system access via WebView
- loadHTMLString XSS: Detects loadHTMLString:baseURL: with nil baseURL (HIGH) or dynamic HTML (MEDIUM)
- loadRequest: WebView loading external content without content validation
- URL Scheme Handlers: application:openURL: implementations
- Universal Links: userActivity handling
- Input Validation: Missing URL parameter sanitization
- Dangerous Destinations: Warns when URL data may flow into WebView loading, JavaScript execution, or file operations
- SQL Injection: sqlite3_exec with dynamic queries
- Unencrypted Storage: Plain SQLite without SQLCipher
- FMDB Format Strings: Format specifier SQL injection
- Database Location: Insecure temp/cache storage
- NSLog: Base findings at INFO severity to reduce noise; promoted to HIGH when sensitive keywords detected
- os_log: Missing privacy modifiers
- Swift Print: Debug print statements in release builds
- Sensitive Keywords: Password, token, key detection in logs (HIGH severity)
- Debug Endpoints: /debug, /test, localhost URLs
- API Keys in URLs: Hardcoded credentials in query strings
- HTTP Endpoints: Unencrypted API communications
- WebSocket Security: ws:// vs wss:// usage
- Hardcoded Passwords: High-entropy strings with special characters (CRITICAL)
- API Keys/Tokens: AWS keys, hex/base64 key patterns, key=value formats (HIGH)
- HTTP Cleartext URLs: Unencrypted HTTP URLs (MEDIUM), with sensitive paths like /auth/, /payment/ (HIGH)
- Jailbreak Indicators: Known jailbreak paths, URL schemes, and dylib names found in string table
- Client-Side Biometric: LAContext.evaluatePolicy without Keychain binding (HIGH — trivially bypassable)
- Biometric Availability: canEvaluatePolicy checks (INFO)
- Access Control: evaluateAccessControl with SecAccessControl (INFO — more secure pattern)
- Hardcoded Credentials:
isEqualToString:comparisons against static strings in auth functions (CRITICAL) - Client-Side Auth Checks: String comparison in authentication context with unresolved parameters (MEDIUM)
- Hardcoded Validation Values: Static comparison values in validation functions (HIGH)
- Numeric Auth Values:
integerValue/intValue/boolValuein authentication context (MEDIUM)
Trellis provides two modes for Frida script generation:
Generate comprehensive hooks for entire API surfaces:
from trellis_ghidra.generators import generate_crypto_tracer, generate_keychain_tracer
# Generate crypto tracer script
script = generate_crypto_tracer(found_functions)
# Save to file
with open("crypto_hooks.js", "w") as f:
f.write(script)Generate targeted hooks for specific security findings identified during static analysis.
This mode creates address-specific hooks that only monitor the exact call sites where issues were detected, dramatically reducing noise and enabling runtime verification of static analysis results.
Workflow:
- Run TrellisAnalyze on your binary → Generates Markdown report + findings JSON
- Run TrellisFrida → Select "{Category} Findings" option
- Deploy generated script to device
- Verify findings at runtime with intelligent verification logic
Example Output:
🟡 [FINDING #3 TRIGGERED] 🟡
Severity: MEDIUM
Issue: Missing Keychain Accessibility Attribute
Function: SecItemAdd
Ghidra: 0x1001141bc
Description: Keychain item stored without explicit accessibility level
Evidence (from static analysis):
kSecAttrAccessible: not set (defaults to kSecAttrAccessibleWhenUnlocked)
// Runtime verification:
[✓] FINDING CONFIRMED: kSecAttrAccessible NOT set at runtime
Runtime arguments:
[0] = 0x102345678 (query dict)
Backtrace:
0x102345678 (Ghidra: 0x1001141bc) storeString:forKey:
...Available Findings-Driven Options:
- All Tracers (Findings-Driven) - Generate for all categories
- Crypto Findings
- Keychain Findings
- TLS Findings
- Anti-Debug Findings
- Jailbreak Findings
- WebView Findings
- Deeplinks Findings
- Storage Findings
- Deserialization Findings
Benefits:
- Targeted: Hooks only problematic call sites (e.g., 8 calls vs 100+)
- Verified: Runtime verification confirms or refutes static findings
- Cross-referenced: Direct links between Frida output → Ghidra addresses → code
- Low noise: Only alerts on verified security issues
- Actionable: Each alert includes finding metadata and recommendations
Run with Frida:
frida -U -f com.example.app -l trellis-keychain-findings-*.jsImplementation Details:
See FINDINGS_DRIVEN_IMPLEMENTATION.md for complete architecture documentation.
Trellis-Ghidra/
├── ghidra_scripts/
│ ├── TrellisAnalyze.py # Main analysis script
│ ├── TrellisFrida.py # Frida script generator
│ ├── README.md # Detailed Ghidra usage
│ └── trellis_ghidra/ # Trellis module
│ ├── ghidra_api.py # Ghidra API wrapper
│ ├── analysis/ # Analysis modules
│ │ ├── finder.py # Function finder
│ │ ├── calltree.py # Call tree builder
│ │ ├── extractor.py # Parameter extractor
│ │ ├── findings_storage.py # NEW: Findings JSON serialization
│ │ ├── security_checks.py # Base security classes
│ │ ├── security_checks_crypto.py
│ │ ├── security_checks_cryptokit.py
│ │ ├── security_checks_tls.py
│ │ ├── security_checks_networking.py
│ │ ├── security_checks_keychain.py
│ │ ├── security_checks_jailbreak.py
│ │ ├── security_checks_antidebug.py
│ │ ├── security_checks_storage.py
│ │ ├── security_checks_deserialization.py
│ │ ├── security_checks_webview.py
│ │ ├── security_checks_deeplinks.py
│ │ ├── security_checks_sqlite.py
│ │ ├── security_checks_logging.py
│ │ ├── security_checks_endpoints.py
│ │ ├── security_checks_strings.py # String-table credential scanner
│ │ ├── security_checks_biometric.py # Biometric auth checker
│ │ ├── security_checks_runtime.py # Runtime manipulation checker
│ │ └── swift_demangle.py # Swift demangling
│ ├── signatures/ # YAML signature databases
│ │ ├── crypto.yaml
│ │ ├── networking.yaml
│ │ ├── keychain.yaml
│ │ ├── biometric.yaml
│ │ ├── runtime.yaml
│ │ └── ...
│ └── generators/ # Frida script generators
│ └── frida/
│ ├── base.py # Generic hook generation
│ ├── findings.py # NEW: Findings-driven generation
│ ├── crypto.py
│ ├── keychain.py
│ └── ...
├── FINDINGS_DRIVEN_IMPLEMENTATION.md # NEW: Architecture docs
└── README.md # This file
- Ghidra: 11.0 or later (requires Python 3 support via PyGhidra)
- Python 3: PyYAML for signature loading (
pip3 install pyyaml) - Frida: 17.0+ for generated scripts (on target device)
Ensure the trellis_ghidra directory is in the same location as TrellisAnalyze.py.
- Verify the binary is an iOS/macOS Mach-O file and is decrypted
- Ensure Ghidra has completed auto-analysis
- Check that the binary imports the relevant frameworks
Install PyYAML for your Python 3 environment: pip3 install pyyaml. Ensure Ghidra's PyGhidra is configured to use the same Python installation.
- Load binary in Ghidra and let analysis complete
- Run
TrellisAnalyze.pyfrom Script Manager - Review the report in the output directory you selected
- Look for CRITICAL/HIGH findings
- Generate Frida script for runtime validation
- Test with
frida -U -f <bundle_id> -l <script.js>
Ghidra historically used Jython (Python 2.7 on the JVM) for scripting. Starting with Ghidra 11.0, Python 3 support was added via PyGhidra (CPython 3), and Jython support is effectively deprecated.
This project targets Python 3 exclusively — it uses dataclasses, pathlib, type hints, and other Python 3 features throughout. Do not add from __future__ imports or other Python 2 compatibility patterns.
Edit the YAML files in ghidra_scripts/trellis_ghidra/signatures/:
functions:
- name: MySecurityFunction
library: Security
parameters:
- name: key
type: void*
- name: keyLength
type: size_t
return:
type: int
security_notes:
- Check for hardcoded key- Create a new file
security_checks_mycheck.pyinanalysis/ - Inherit from
SecurityChecker - Implement
check_call_site()
from .security_checks import SecurityChecker, SecurityFinding, Severity
class MySecurityChecker(SecurityChecker):
def check_call_site(self, function_sig, call_site, extracted_info):
findings = []
# Your analysis logic here
return findings- Register in
TrellisAnalyze.py'sget_security_checker()
Trellis is licensed under the Business Source License 1.1.
- Commercial use as a tool is permitted — security consultants, researchers, and other professionals may freely use Trellis in the course of paid work.
- Productization is not permitted — you may not embed, bundle, or integrate Trellis into a commercial product or hosted service without a separate license from Cylent Security LLC.
- Free redistribution of original or modified versions is permitted with attribution.
- On March 22, 2031, the license automatically converts to the Apache License 2.0.
- Attribution required: All permitted uses must display
Powered by Trellis — https://github.com/cylentsec/Trellis. - Contributors: By submitting a pull request you agree to the Contributor License Agreement embedded in the LICENSE file, granting Cylent Security LLC relicensing and copyright rights over your contribution.
Copyright (c) Cylent Security LLC