reverse-engineering-ios-app-with-frida について
ソースコードのない iOS アプリを認可されたセキュリティ評価で調べるための、Frida を使ったスキルです。ランタイムトレース、メソッドのフック、Swift の解析、セキュリティ機構のテストを扱います。
主な機能
- 01ランタイムのクラスとメソッドの列挙
- 02Objective-C メソッド呼び出しの追跡
- 03メソッドのフックと動作変更
- 04Swift ランタイムロジックの解析
- 05暗号化とセキュリティ機構の調査
活用シーン
- 01認可された評価での iOS アプリ解析
- 02難読化された Swift・Objective-C ロジックの追跡
- 03暗号化と API データ処理の確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mukul975/ anthropic-cybersecurity-skills --skill reverse-engineering-ios-app-with-fridaインストール後、Claude Code で/reverse-engineering-ios-app-with-fridaを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSEassets/template.mdreferences/api-reference.mdstandards.mdworkflows.mdscripts/agent.pyprocess.py詳細
Frida を使用した iOS アプリのリバースエンジニアリング
使用する場面
次の場合にこのスキルを使用します。
- ソースコードのない iOS アプリを、許可されたセキュリティ評価の一環として解析する
- 実行中の iOS アプリから暗号鍵、API シークレット、または独自プロトコルの詳細を抽出する
- ランタイムのメソッドトレースを通じて、難読化された Swift/Objective-C のロジックを理解する
- 複雑なセキュリティ機構(脱獄検知、改ざん防止、デバッグ防止)を回避する
利用規約または知的財産権法に違反する、許可されていないリバースエンジニアリングには、このスキルを使用しないでください。
前提条件
- Cydia/Sileo 経由で Frida サーバーをインストールした脱獄済み iOS デバイス、または Frida Gadget を注入した IPA を使用する非脱獄デバイス
frida-toolsをインストールした Python 3.10 以降(pip install frida-tools)- iOS デバイスへの USB 接続
- Objective-C ヘッダーの抽出用の class-dump または dsdump
- 静的バイナリ解析用の Hopper Disassembler または Ghidra(補完的に使用)
- Objective-C ランタイムと Swift の名前マングリングに関する知識
ワークフロー
ステップ 1:バイナリを抽出して解析する
# 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
ステップ 2:ランタイムでクラスとメソッドを列挙する
// 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]);
}
}
}
}
frida -U -n TargetApp -l enumerate_classes.js
ステップ 3:frida-trace でメソッド呼び出しをトレースする
# 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*]"
ステップ 4:メソッドをフックして動作を変更する
// 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("");
}
ステップ 5:Swift コードを解析する
// 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() {}
});
ステップ 6:シークレットと独自データを抽出する
// 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);
}
});
}
主な概念
| 用語 | 定義 |
|---|---|
| Objective-C Runtime | ランタイムでのメソッドディスパッチ、クラスのイントロスペクション、メソッドのスウィズリングを可能にする動的ランタイム |
| Swift Name Mangling | Swift 関数のシグネチャをリンカー互換のシンボル名に変換する、コンパイラーによるエンコード |
| FairPlay DRM | App Store のバイナリに適用される Apple の暗号化方式。静的解析の前に復号する必要があります |
| class-dump | ヘッダーレベルの解析用に、Mach-O バイナリから Objective-C のクラス宣言を抽出するツール |
| CommonCrypto | Apple の C レベルの暗号ライブラリ。Frida のフックによる暗号鍵抽出の主な対象です |
ツールとシステム
- Frida:iOS ランタイムのフックとメソッドのインターセプトに使用する動的計装フレームワーク
- frida-trace:該当するメソッド向けのハンドラースタブを生成する自動トレースユーティリティ
- frida-ios-dump:メモリダンプを使用して FairPlay で保護された iOS アプリを復号するツール
- class-dump / dsdump:Mach-O バイナリから Objective-C ヘッダーを抽出するツール
- Ghidra:iOS アプリの ARM64 バイナリを静的解析するための NSA のリバースエンジニアリングフレームワーク
よくある落とし穴
- FairPlay 暗号化:App Store からダウンロードしたアプリは暗号化されています。静的解析の前に復号する必要があります。脱獄済みデバイスで frida-ios-dump を使用してください。
- Swift のみのクラス:
@objcアノテーションのない純粋な Swift クラスは、ObjC.classesからは見えません。代わりにModule.enumerateExports()を使用してください。 - シンボルが削除されたバイナリ:リリースビルドではデバッグシンボルが削除されます。効果的に解析するには、frida-trace と class-dump の出力を組み合わせてください。
- Frida 対策:高度なアプリは Frida の痕跡(frida-server プロセス、メモリ内の Frida エージェント文字列、dyld に注入されたライブラリ)を検知します。ステルス性の高い Frida ビルド、または Frida Gadget の注入を使用してください。
コメント
Markdown 対応