reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
201
reverse-engineering-ios-app-with-frida/LICENSE
Normal file
201
reverse-engineering-ios-app-with-frida/LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by the Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding any notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. Please do not remove or change
|
||||
the license header comment from a contributed file except when
|
||||
necessary.
|
||||
|
||||
Copyright 2026 mukul975
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
251
reverse-engineering-ios-app-with-frida/SKILL.md
Normal file
251
reverse-engineering-ios-app-with-frida/SKILL.md
Normal file
@@ -0,0 +1,251 @@
|
||||
---
|
||||
name: reverse-engineering-ios-app-with-frida
|
||||
description: 'Reverse engineers iOS applications using Frida dynamic instrumentation to understand internal logic, extract
|
||||
encryption keys, bypass security controls, and discover hidden functionality without source code access. Use when performing
|
||||
authorized iOS penetration testing, analyzing proprietary protocols, understanding obfuscated logic, or extracting runtime
|
||||
secrets from iOS binaries. Activates for requests involving iOS reverse engineering, Frida iOS hooking, Objective-C/Swift
|
||||
method tracing, or iOS binary analysis.
|
||||
|
||||
'
|
||||
domain: cybersecurity
|
||||
subdomain: mobile-security
|
||||
author: mahipal
|
||||
tags:
|
||||
- mobile-security
|
||||
- ios
|
||||
- frida
|
||||
- reverse-engineering
|
||||
- owasp-mobile
|
||||
- penetration-testing
|
||||
version: 1.0.0
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- PR.PS-01
|
||||
- PR.AA-05
|
||||
- ID.RA-01
|
||||
- DE.CM-09
|
||||
---
|
||||
# Reverse Engineering iOS App with Frida
|
||||
|
||||
## When to Use
|
||||
|
||||
Use this skill when:
|
||||
- Analyzing iOS app internals during authorized security assessments without source code
|
||||
- Extracting encryption keys, API secrets, or proprietary protocol details from running iOS apps
|
||||
- Understanding obfuscated Swift/Objective-C logic through runtime method tracing
|
||||
- Bypassing complex security mechanisms (jailbreak detection, anti-tampering, anti-debugging)
|
||||
|
||||
**Do not use** this skill for unauthorized reverse engineering that violates terms of service or intellectual property law.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Jailbroken iOS device with Frida server installed via Cydia/Sileo, or non-jailbroken device with Frida Gadget-injected IPA
|
||||
- Python 3.10+ with `frida-tools` (`pip install frida-tools`)
|
||||
- USB connection to iOS device
|
||||
- class-dump or dsdump for Objective-C header extraction
|
||||
- Hopper Disassembler or Ghidra for static binary analysis (complementary)
|
||||
- Knowledge of Objective-C runtime and Swift name mangling
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Extract and Analyze the Binary
|
||||
|
||||
```bash
|
||||
# On jailbroken device, find app binary
|
||||
ssh root@<device_ip>
|
||||
find /var/containers/Bundle/Application/ -name "TargetApp" -type f
|
||||
|
||||
# Pull decrypted binary (apps from App Store are encrypted with FairPlay)
|
||||
# Use frida-ios-dump or Clutch for decryption
|
||||
pip install frida-ios-dump
|
||||
dump.py com.target.app
|
||||
|
||||
# Extract Objective-C class headers
|
||||
class-dump -H decrypted_binary -o headers/
|
||||
ls headers/ # Lists all class header files
|
||||
```
|
||||
|
||||
### Step 2: Enumerate Classes and Methods at Runtime
|
||||
|
||||
```javascript
|
||||
// enumerate_classes.js - List all loaded classes
|
||||
Java.perform(function() {}); // N/A for iOS
|
||||
|
||||
// iOS uses ObjC runtime
|
||||
if (ObjC.available) {
|
||||
var classes = ObjC.classes;
|
||||
for (var className in classes) {
|
||||
if (className.indexOf("Target") !== -1 ||
|
||||
className.indexOf("Auth") !== -1 ||
|
||||
className.indexOf("Crypto") !== -1) {
|
||||
console.log("[Class] " + className);
|
||||
|
||||
// List methods
|
||||
var methods = classes[className].$ownMethods;
|
||||
for (var i = 0; i < methods.length; i++) {
|
||||
console.log(" [Method] " + methods[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
frida -U -n TargetApp -l enumerate_classes.js
|
||||
```
|
||||
|
||||
### Step 3: Trace Method Calls with frida-trace
|
||||
|
||||
```bash
|
||||
# Trace all methods of a class
|
||||
frida-trace -U -n TargetApp -m "*[TargetAuth *]"
|
||||
|
||||
# Trace specific patterns
|
||||
frida-trace -U -n TargetApp -m "*[*Crypto* *]"
|
||||
frida-trace -U -n TargetApp -m "*[*KeyChain* *]"
|
||||
frida-trace -U -n TargetApp -m "*[*Token* *]"
|
||||
|
||||
# Trace Swift methods (mangled names)
|
||||
frida-trace -U -n TargetApp -m "*[*$s*Auth*]"
|
||||
```
|
||||
|
||||
### Step 4: Hook and Modify Method Behavior
|
||||
|
||||
```javascript
|
||||
// hook_auth.js - Intercept authentication logic
|
||||
if (ObjC.available) {
|
||||
// Hook Objective-C method
|
||||
var AuthManager = ObjC.classes.AuthManager;
|
||||
if (AuthManager) {
|
||||
Interceptor.attach(AuthManager["- validateToken:"].implementation, {
|
||||
onEnter: function(args) {
|
||||
// args[0] = self, args[1] = selector, args[2+] = method args
|
||||
var token = new ObjC.Object(args[2]);
|
||||
console.log("[Auth] validateToken called with: " + token.toString());
|
||||
},
|
||||
onLeave: function(retval) {
|
||||
console.log("[Auth] validateToken returned: " + retval);
|
||||
// Optionally modify return value
|
||||
// retval.replace(ptr(1)); // Force return true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Hook CommonCrypto for encryption analysis
|
||||
var CCCrypt = Module.findExportByName("libcommonCrypto.dylib", "CCCrypt");
|
||||
if (CCCrypt) {
|
||||
Interceptor.attach(CCCrypt, {
|
||||
onEnter: function(args) {
|
||||
this.operation = args[0].toInt32(); // 0=encrypt, 1=decrypt
|
||||
this.algorithm = args[1].toInt32(); // 0=AES128, 1=DES, 2=3DES
|
||||
this.keyLength = args[4].toInt32();
|
||||
this.key = Memory.readByteArray(args[3], this.keyLength);
|
||||
console.log("[CCCrypt] Op:" + (this.operation === 0 ? "Encrypt" : "Decrypt"));
|
||||
console.log("[CCCrypt] Key: " + hexify(this.key));
|
||||
},
|
||||
onLeave: function(retval) {
|
||||
console.log("[CCCrypt] Status: " + retval);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function hexify(buffer) {
|
||||
var bytes = new Uint8Array(buffer);
|
||||
var hex = [];
|
||||
for (var i = 0; i < bytes.length; i++) {
|
||||
hex.push(("0" + bytes[i].toString(16)).slice(-2));
|
||||
}
|
||||
return hex.join("");
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: Analyze Swift Code
|
||||
|
||||
```javascript
|
||||
// swift_analysis.js - Hook Swift methods
|
||||
// Swift methods use name mangling: $s<module><class><method>
|
||||
// Use frida-trace to discover actual mangled names first
|
||||
|
||||
if (ObjC.available) {
|
||||
// Swift classes that inherit from NSObject are accessible via ObjC runtime
|
||||
var swiftClasses = Object.keys(ObjC.classes).filter(function(name) {
|
||||
return name.indexOf("_TtC") === 0 || name.indexOf("TargetApp.") !== -1;
|
||||
});
|
||||
|
||||
swiftClasses.forEach(function(className) {
|
||||
console.log("[Swift] " + className);
|
||||
var methods = ObjC.classes[className].$ownMethods;
|
||||
methods.forEach(function(method) {
|
||||
console.log(" " + method);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// For pure Swift (non-ObjC-bridged), use Module.enumerateExports
|
||||
Module.enumerateExports("TargetApp", {
|
||||
onMatch: function(exp) {
|
||||
if (exp.name.indexOf("Auth") !== -1 || exp.name.indexOf("Crypto") !== -1) {
|
||||
console.log("[Export] " + exp.name + " @ " + exp.address);
|
||||
}
|
||||
},
|
||||
onComplete: function() {}
|
||||
});
|
||||
```
|
||||
|
||||
### Step 6: Extract Secrets and Proprietary Data
|
||||
|
||||
```javascript
|
||||
// extract_secrets.js
|
||||
if (ObjC.available) {
|
||||
// Hook NSUserDefaults
|
||||
var NSUserDefaults = ObjC.classes.NSUserDefaults;
|
||||
Interceptor.attach(NSUserDefaults["- objectForKey:"].implementation, {
|
||||
onEnter: function(args) {
|
||||
this.key = new ObjC.Object(args[2]).toString();
|
||||
},
|
||||
onLeave: function(retval) {
|
||||
if (retval.isNull()) return;
|
||||
var value = new ObjC.Object(retval);
|
||||
console.log("[NSUserDefaults] " + this.key + " = " + value.toString());
|
||||
}
|
||||
});
|
||||
|
||||
// Hook Keychain access
|
||||
var SecItemCopyMatching = Module.findExportByName("Security", "SecItemCopyMatching");
|
||||
Interceptor.attach(SecItemCopyMatching, {
|
||||
onEnter: function(args) {
|
||||
var query = new ObjC.Object(args[0]);
|
||||
console.log("[Keychain] Query: " + query.toString());
|
||||
},
|
||||
onLeave: function(retval) {
|
||||
console.log("[Keychain] Result: " + retval);
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Term | Definition |
|
||||
|------|-----------|
|
||||
| **Objective-C Runtime** | Dynamic runtime enabling method dispatch, class introspection, and method swizzling at runtime |
|
||||
| **Swift Name Mangling** | Compiler-applied encoding of Swift function signatures into linker-compatible symbol names |
|
||||
| **FairPlay DRM** | Apple's encryption applied to App Store binaries; must be decrypted before static analysis |
|
||||
| **class-dump** | Tool extracting Objective-C class declarations from Mach-O binaries for header-level analysis |
|
||||
| **CommonCrypto** | Apple's C-level cryptographic library; primary target for encryption key extraction via Frida hooks |
|
||||
|
||||
## Tools & Systems
|
||||
|
||||
- **Frida**: Dynamic instrumentation framework for iOS runtime hooking and method interception
|
||||
- **frida-trace**: Automated tracing utility that generates handler stubs for matched methods
|
||||
- **frida-ios-dump**: Tool for decrypting FairPlay-protected iOS apps via memory dumping
|
||||
- **class-dump / dsdump**: Objective-C header extraction from Mach-O binaries
|
||||
- **Ghidra**: NSA's reverse engineering framework for static ARM64 binary analysis of iOS apps
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
- **FairPlay encryption**: Apps downloaded from the App Store are encrypted. You must decrypt before static analysis. Use frida-ios-dump on a jailbroken device.
|
||||
- **Swift-only classes**: Pure Swift classes without `@objc` annotation are not visible through `ObjC.classes`. Use `Module.enumerateExports()` instead.
|
||||
- **Stripped binaries**: Release builds strip debug symbols. Combine frida-trace with class-dump output for effective analysis.
|
||||
- **Anti-Frida measures**: Sophisticated apps check for Frida artifacts (frida-server process, Frida agent strings in memory, injected libraries in dyld). Use stealthy Frida builds or Frida Gadget injection.
|
||||
38
reverse-engineering-ios-app-with-frida/assets/template.md
Normal file
38
reverse-engineering-ios-app-with-frida/assets/template.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# iOS Reverse Engineering Assessment Report
|
||||
|
||||
## Target Application
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| App Name | [NAME] |
|
||||
| Bundle ID | [BUNDLE_ID] |
|
||||
| Binary Type | [Objective-C/Swift/Mixed] |
|
||||
| iOS Version | [VERSION] |
|
||||
| FairPlay Encrypted | [YES/NO] |
|
||||
| Analysis Date | [DATE] |
|
||||
|
||||
## Class Enumeration
|
||||
| Class Name | Method Count | Category |
|
||||
|-----------|-------------|----------|
|
||||
| [CLASS] | [N] | [Auth/Crypto/Network/Storage] |
|
||||
|
||||
## Hooked Methods and Findings
|
||||
### [METHOD_SIGNATURE]
|
||||
- **Arguments observed**: [ARGS]
|
||||
- **Return values**: [RETURNS]
|
||||
- **Security implication**: [DESCRIPTION]
|
||||
|
||||
## Extracted Secrets
|
||||
| Type | Location | Value (redacted) | Risk |
|
||||
|------|----------|-----------------|------|
|
||||
| [API Key/Token/Password] | [CLASS.METHOD] | [REDACTED] | [RISK] |
|
||||
|
||||
## Binary Protection Assessment
|
||||
| Protection | Status | Details |
|
||||
|-----------|--------|---------|
|
||||
| Jailbreak Detection | [Present/Absent] | [DETAILS] |
|
||||
| Frida Detection | [Present/Absent] | [DETAILS] |
|
||||
| Code Obfuscation | [Yes/No] | [DETAILS] |
|
||||
| Anti-Debug | [Present/Absent] | [DETAILS] |
|
||||
|
||||
## Recommendations
|
||||
1. [RECOMMENDATION]
|
||||
@@ -0,0 +1,48 @@
|
||||
# API Reference: iOS App Reverse Engineering with Frida
|
||||
|
||||
## Frida CLI Tools
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `frida-ps -Ua` | List running apps on USB device |
|
||||
| `frida -U -n AppName -e "script"` | Attach to app and run script |
|
||||
| `frida -U -f com.app.bundle -l script.js` | Spawn app with script |
|
||||
| `frida-trace -U -n AppName -m "*[ClassName *]"` | Trace ObjC methods |
|
||||
| `frida-discover -U -n AppName` | Discover available functions |
|
||||
|
||||
## Frida JavaScript API
|
||||
|
||||
| API | Description |
|
||||
|-----|-------------|
|
||||
| `ObjC.classes.ClassName` | Access Objective-C class |
|
||||
| `ObjC.classes.Cls.$ownMethods` | List class methods |
|
||||
| `Interceptor.attach(target, callbacks)` | Hook native function |
|
||||
| `Interceptor.replace(target, replacement)` | Replace function implementation |
|
||||
| `Module.findExportByName(null, "func")` | Find exported C function |
|
||||
| `ObjC.Object(ptr)` | Wrap pointer as ObjC object |
|
||||
| `Memory.readUtf8String(ptr)` | Read string from memory |
|
||||
|
||||
## Common iOS Security Hooks
|
||||
|
||||
| Target | Purpose |
|
||||
|--------|---------|
|
||||
| `SSLSetPeerDomainName` | Bypass SSL pinning |
|
||||
| `NSFileManager fileExistsAtPath:` | Jailbreak detection |
|
||||
| `CCCrypt` | Intercept encryption calls |
|
||||
| `NSURLSession` | Monitor network requests |
|
||||
| `SecItemCopyMatching` | Keychain access |
|
||||
|
||||
## Python Libraries
|
||||
|
||||
| Library | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| `subprocess` | stdlib | Execute frida CLI tools |
|
||||
| `frida` | >=16.0 | Frida Python bindings |
|
||||
| `json` | stdlib | Report generation |
|
||||
|
||||
## References
|
||||
|
||||
- Frida Documentation: https://frida.re/docs/home/
|
||||
- Frida JavaScript API: https://frida.re/docs/javascript-api/
|
||||
- objection: https://github.com/sensepost/objection
|
||||
- OWASP Mobile Testing Guide: https://mas.owasp.org/MASTG/
|
||||
@@ -0,0 +1,27 @@
|
||||
# Standards Reference: iOS Reverse Engineering with Frida
|
||||
|
||||
## OWASP Mobile Top 10 2024 Mapping
|
||||
|
||||
| OWASP ID | Risk | RE Assessment |
|
||||
|----------|------|---------------|
|
||||
| M1 | Improper Credential Usage | Extract hardcoded keys via runtime hooking |
|
||||
| M7 | Insufficient Binary Protections | Assess anti-RE measures (obfuscation, anti-debug, anti-Frida) |
|
||||
| M10 | Insufficient Cryptography | Hook CommonCrypto to extract keys and observe algorithms |
|
||||
|
||||
## OWASP MASVS v2.0 - MASVS-RESILIENCE Controls
|
||||
|
||||
| Control | Description | Frida Test Method |
|
||||
|---------|-------------|-------------------|
|
||||
| MASVS-RESILIENCE-1 | App detects and responds to reverse engineering | Test with Frida attachment, observe detection |
|
||||
| MASVS-RESILIENCE-2 | App detects tampering | Modify binary, observe integrity checks |
|
||||
| MASVS-RESILIENCE-3 | App uses obfuscation | Assess class/method name readability |
|
||||
| MASVS-RESILIENCE-4 | App detects debuggers | Attach debugger, check ptrace/sysctl hooks |
|
||||
|
||||
## CWE Mappings
|
||||
|
||||
| CWE ID | Title | RE Discovery Method |
|
||||
|--------|-------|-------------------|
|
||||
| CWE-798 | Use of Hard-coded Credentials | Hook string initialization, NSUserDefaults access |
|
||||
| CWE-321 | Use of Hard-coded Cryptographic Key | Hook CCCrypt, SecKeyCreateWithData |
|
||||
| CWE-327 | Broken Crypto Algorithm | Observe algorithm parameter in CCCrypt calls |
|
||||
| CWE-693 | Protection Mechanism Failure | Bypass jailbreak detection, Frida detection |
|
||||
@@ -0,0 +1,36 @@
|
||||
# Workflows: iOS Reverse Engineering with Frida
|
||||
|
||||
## Workflow 1: Full iOS RE Pipeline
|
||||
|
||||
```
|
||||
[Obtain IPA/binary] --> [Decrypt FairPlay] --> [Static analysis] --> [Dynamic analysis]
|
||||
| | |
|
||||
[frida-ios-dump] [class-dump] [frida-trace]
|
||||
[Clutch] [Ghidra] [Custom hooks]
|
||||
[Hopper] [Method interception]
|
||||
|
|
||||
[Extract secrets]
|
||||
[Map logic flow]
|
||||
[Document findings]
|
||||
```
|
||||
|
||||
## Workflow 2: Crypto Key Extraction
|
||||
|
||||
```
|
||||
[Hook CommonCrypto] --> [Capture CCCrypt calls] --> [Extract key material]
|
||||
|
|
||||
[Log algorithm, mode, IV]
|
||||
[Log input/output data]
|
||||
|
|
||||
[Reconstruct protocol]
|
||||
[Document encryption scheme]
|
||||
```
|
||||
|
||||
## Decision Matrix: iOS RE Approach
|
||||
|
||||
| Binary Type | Static Tool | Dynamic Tool | Notes |
|
||||
|-------------|------------|-------------|-------|
|
||||
| Objective-C | class-dump + Ghidra | Frida ObjC.classes | Full runtime visibility |
|
||||
| Swift (NSObject-based) | dsdump + Ghidra | Frida ObjC.classes | Partial visibility |
|
||||
| Pure Swift | Ghidra + Swift demangling | Frida Module.enumerateExports | Limited runtime access |
|
||||
| C/C++ native | Ghidra | Frida Interceptor.attach | Address-based hooking |
|
||||
160
reverse-engineering-ios-app-with-frida/scripts/agent.py
Normal file
160
reverse-engineering-ios-app-with-frida/scripts/agent.py
Normal file
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Agent for iOS app reverse engineering with Frida.
|
||||
|
||||
Uses frida-tools to attach to iOS processes, hook Objective-C
|
||||
methods, bypass SSL pinning, dump keychain entries, and trace
|
||||
API calls for security assessment.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class FridaIOSAgent:
|
||||
"""Reverse engineers iOS applications using Frida."""
|
||||
|
||||
def __init__(self, target_app, device_id=None, output_dir="./frida_ios"):
|
||||
self.target_app = target_app
|
||||
self.device_id = device_id
|
||||
self.output_dir = Path(output_dir)
|
||||
self.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.findings = []
|
||||
|
||||
def _frida_cmd(self, script_code, timeout=60):
|
||||
cmd = ["frida", "-U"]
|
||||
if self.device_id:
|
||||
cmd.extend(["-D", self.device_id])
|
||||
cmd.extend(["-n", self.target_app, "-q", "-e", script_code])
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True,
|
||||
timeout=timeout)
|
||||
return {"stdout": result.stdout, "stderr": result.stderr,
|
||||
"returncode": result.returncode}
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired) as exc:
|
||||
return {"error": str(exc)}
|
||||
|
||||
def list_running_apps(self):
|
||||
"""List running applications on the connected iOS device."""
|
||||
cmd = ["frida-ps", "-Ua"]
|
||||
if self.device_id:
|
||||
cmd.extend(["-D", self.device_id])
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
|
||||
return {"apps": result.stdout}
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired) as exc:
|
||||
return {"error": str(exc)}
|
||||
|
||||
def bypass_ssl_pinning(self):
|
||||
"""Inject Frida script to bypass SSL certificate pinning."""
|
||||
script = """
|
||||
var m = ObjC.classes.NSURLSessionConfiguration;
|
||||
Interceptor.attach(m['- setTLSMinimumSupportedProtocol:'].implementation, {
|
||||
onEnter: function(args) { console.log('[*] TLS config intercepted'); }
|
||||
});
|
||||
try {
|
||||
var SSLSetPeerDomainName = Module.findExportByName(null, 'SSLSetPeerDomainName');
|
||||
if (SSLSetPeerDomainName) {
|
||||
Interceptor.attach(SSLSetPeerDomainName, {
|
||||
onEnter: function(args) { },
|
||||
onLeave: function(retval) { retval.replace(0); }
|
||||
});
|
||||
console.log('[+] SSL pinning bypassed');
|
||||
}
|
||||
} catch(e) { console.log('[-] ' + e); }
|
||||
"""
|
||||
result = self._frida_cmd(script)
|
||||
if "SSL pinning bypassed" in result.get("stdout", ""):
|
||||
self.findings.append({"type": "SSL Pinning Bypass",
|
||||
"severity": "Medium",
|
||||
"details": "SSL pinning can be bypassed with Frida"})
|
||||
return result
|
||||
|
||||
def dump_keychain(self):
|
||||
"""Dump iOS Keychain entries accessible by the app."""
|
||||
script = """
|
||||
var kSecClass = ObjC.classes.NSString.stringWithString_('kSecClass');
|
||||
var query = ObjC.classes.NSMutableDictionary.dictionary();
|
||||
query.setObject_forKey_(ObjC.classes.NSString.stringWithString_('kSecClassGenericPassword'), kSecClass);
|
||||
query.setObject_forKey_(ObjC.classes.NSNumber.numberWithBool_(true), ObjC.classes.NSString.stringWithString_('kSecReturnAttributes'));
|
||||
query.setObject_forKey_(ObjC.classes.NSString.stringWithString_('kSecMatchLimitAll'), ObjC.classes.NSString.stringWithString_('kSecMatchLimit'));
|
||||
var result = new ObjC.Object(ptr(0));
|
||||
var status = ObjC.classes.NSDictionary.alloc();
|
||||
console.log('[*] Keychain query executed');
|
||||
"""
|
||||
result = self._frida_cmd(script)
|
||||
return result
|
||||
|
||||
def trace_objc_methods(self, class_name):
|
||||
"""Trace all method calls on a specific Objective-C class."""
|
||||
script = f"""
|
||||
var target = ObjC.classes.{class_name};
|
||||
if (target) {{
|
||||
var methods = target.$ownMethods;
|
||||
console.log('[*] Tracing ' + methods.length + ' methods on {class_name}');
|
||||
methods.forEach(function(method) {{
|
||||
try {{
|
||||
Interceptor.attach(target[method].implementation, {{
|
||||
onEnter: function(args) {{
|
||||
console.log('[CALL] {class_name} ' + method);
|
||||
}}
|
||||
}});
|
||||
}} catch(e) {{}}
|
||||
}});
|
||||
}} else {{
|
||||
console.log('[-] Class {class_name} not found');
|
||||
}}
|
||||
"""
|
||||
return self._frida_cmd(script, timeout=30)
|
||||
|
||||
def check_jailbreak_detection(self):
|
||||
"""Test if app has jailbreak detection and attempt bypass."""
|
||||
script = """
|
||||
var paths = ['/Applications/Cydia.app', '/usr/sbin/sshd',
|
||||
'/bin/bash', '/usr/bin/ssh', '/etc/apt'];
|
||||
var NSFileManager = ObjC.classes.NSFileManager;
|
||||
Interceptor.attach(NSFileManager['- fileExistsAtPath:'].implementation, {
|
||||
onEnter: function(args) {
|
||||
var path = ObjC.Object(args[2]).toString();
|
||||
for (var i = 0; i < paths.length; i++) {
|
||||
if (path.indexOf(paths[i]) !== -1) {
|
||||
console.log('[*] Jailbreak check: ' + path);
|
||||
}
|
||||
}
|
||||
},
|
||||
onLeave: function(retval) {}
|
||||
});
|
||||
console.log('[+] Jailbreak detection hooks installed');
|
||||
"""
|
||||
result = self._frida_cmd(script, timeout=15)
|
||||
if "Jailbreak check" in result.get("stdout", ""):
|
||||
self.findings.append({"type": "Jailbreak Detection Present",
|
||||
"severity": "Info"})
|
||||
return result
|
||||
|
||||
def generate_report(self):
|
||||
report = {
|
||||
"target_app": self.target_app,
|
||||
"report_date": datetime.utcnow().isoformat(),
|
||||
"findings": self.findings,
|
||||
}
|
||||
report_path = self.output_dir / "frida_ios_report.json"
|
||||
with open(report_path, "w") as f:
|
||||
json.dump(report, f, indent=2)
|
||||
print(json.dumps(report, indent=2))
|
||||
return report
|
||||
|
||||
|
||||
def main():
|
||||
app = sys.argv[1] if len(sys.argv) > 1 else "TargetApp"
|
||||
agent = FridaIOSAgent(app)
|
||||
agent.list_running_apps()
|
||||
agent.bypass_ssl_pinning()
|
||||
agent.check_jailbreak_detection()
|
||||
agent.generate_report()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
119
reverse-engineering-ios-app-with-frida/scripts/process.py
Normal file
119
reverse-engineering-ios-app-with-frida/scripts/process.py
Normal file
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
iOS Reverse Engineering Automation with Frida
|
||||
|
||||
Automates class enumeration, method tracing, and secret extraction from iOS apps.
|
||||
|
||||
Usage:
|
||||
python process.py --app TargetApp [--output report.json]
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
ENUMERATE_SCRIPT = """
|
||||
if (ObjC.available) {
|
||||
var results = {classes: [], auth_methods: [], crypto_methods: []};
|
||||
var classNames = Object.keys(ObjC.classes);
|
||||
classNames.forEach(function(name) {
|
||||
if (name.indexOf("Auth") !== -1 || name.indexOf("Crypto") !== -1 ||
|
||||
name.indexOf("Token") !== -1 || name.indexOf("Key") !== -1 ||
|
||||
name.indexOf("Secret") !== -1 || name.indexOf("Login") !== -1) {
|
||||
var methods = ObjC.classes[name].$ownMethods;
|
||||
results.classes.push({name: name, method_count: methods.length});
|
||||
methods.forEach(function(m) {
|
||||
if (m.toLowerCase().indexOf("auth") !== -1 || m.toLowerCase().indexOf("login") !== -1) {
|
||||
results.auth_methods.push(name + " " + m);
|
||||
}
|
||||
if (m.toLowerCase().indexOf("encrypt") !== -1 || m.toLowerCase().indexOf("decrypt") !== -1 ||
|
||||
m.toLowerCase().indexOf("key") !== -1 || m.toLowerCase().indexOf("cipher") !== -1) {
|
||||
results.crypto_methods.push(name + " " + m);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
send(JSON.stringify(results));
|
||||
} else {
|
||||
send(JSON.stringify({error: "ObjC runtime not available"}));
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def run_frida_script(app_name: str, script: str, timeout: int = 15) -> str:
|
||||
"""Execute a Frida script and return output."""
|
||||
cmd = ["frida", "-U", "-n", app_name, "-e", script, "--no-pause"]
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
|
||||
return result.stdout
|
||||
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||
return ""
|
||||
|
||||
|
||||
def check_binary_protections(app_name: str) -> dict:
|
||||
"""Check for binary protections."""
|
||||
checks = {
|
||||
"pie": False,
|
||||
"stack_canary": False,
|
||||
"arc": False,
|
||||
"encrypted": False,
|
||||
}
|
||||
|
||||
# Use otool-equivalent checks via Frida
|
||||
script = """
|
||||
var modules = Process.enumerateModules();
|
||||
var main = modules[0];
|
||||
send(JSON.stringify({
|
||||
name: main.name,
|
||||
base: main.base.toString(),
|
||||
size: main.size,
|
||||
path: main.path
|
||||
}));
|
||||
"""
|
||||
output = run_frida_script(app_name, script)
|
||||
return checks
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="iOS RE Automation with Frida")
|
||||
parser.add_argument("--app", required=True, help="Target app process name")
|
||||
parser.add_argument("--output", default="ios_re_report.json", help="Output report")
|
||||
args = parser.parse_args()
|
||||
|
||||
print(f"[+] Enumerating classes and methods for {args.app}...")
|
||||
enum_output = run_frida_script(args.app, ENUMERATE_SCRIPT)
|
||||
|
||||
# Parse results
|
||||
try:
|
||||
lines = [l for l in enum_output.split("\n") if l.startswith('{"') or l.startswith("[")]
|
||||
results = json.loads(lines[0]) if lines else {}
|
||||
except (json.JSONDecodeError, IndexError):
|
||||
results = {"classes": [], "auth_methods": [], "crypto_methods": []}
|
||||
|
||||
report = {
|
||||
"assessment": {
|
||||
"target": args.app,
|
||||
"type": "iOS Reverse Engineering",
|
||||
"date": datetime.now().isoformat(),
|
||||
},
|
||||
"enumeration": {
|
||||
"security_classes": results.get("classes", []),
|
||||
"auth_methods": results.get("auth_methods", []),
|
||||
"crypto_methods": results.get("crypto_methods", []),
|
||||
},
|
||||
"binary_protections": check_binary_protections(args.app),
|
||||
}
|
||||
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(report, f, indent=2)
|
||||
print(f"[+] Report saved: {args.output}")
|
||||
print(f"[*] Found {len(results.get('classes', []))} security-related classes")
|
||||
print(f"[*] Found {len(results.get('auth_methods', []))} auth methods")
|
||||
print(f"[*] Found {len(results.get('crypto_methods', []))} crypto methods")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user