プロセスとデバイスの操作
接続可能なプロセスやデバイスを一覧表示したり、プロセスを終了させたりします。
コマンド | 説明 | 使用例 |
---|---|---|
frida-ps |
実行中のプロセスを一覧表示します。接続先を指定できます。 |
|
frida-ls-devices |
Fridaが認識しているデバイスを一覧表示します。 |
|
frida-kill |
指定したプロセスを終了させます。 |
|
ターゲットへの接続と起動
frida
コマンドを使用して、既存のプロセスにアタッチしたり、新しいプロセスを起動してインジェクトしたりします。
オプション | 説明 | 使用例 |
---|---|---|
-U / --usb |
USB経由で接続されたデバイスを指定します。 | frida -U -n Twitter |
-R / --remote |
リモートのfrida-server(デフォルト: localhost:27042)に接続します。host:port の指定も可能です。 |
frida -R -p 1234 frida -R 192.168.1.100:12345 -f com.example.app |
-H HOST / --host HOST |
リモートのfrida-serverのホストとポートを指定します。-R と似ていますが、より明示的です。 |
frida -H 10.0.0.5:27042 -f com.example.app |
-f FILE / --file FILE |
指定したアプリケーションを起動し、メインスレッドが実行される前にインジェクトします。(例: Androidのパッケージ名、iOSのバンドルID、実行ファイルパス) | frida -U -f com.android.settings frida -U -f com.apple.Preferences frida -f /bin/ls |
-n NAME / --attach-name NAME |
指定した名前のプロセスにアタッチします。 | frida -U -n WeChat frida -n explorer.exe |
-p PID / --attach-pid PID |
指定したプロセスID (PID) のプロセスにアタッチします。 | frida -p 1337 |
--no-pause |
-f オプションでアプリを起動する際に、初期状態で一時停止させずに即座に実行を開始します。 |
frida -U -f com.example.test --no-pause |
-l SCRIPT / --load SCRIPT |
指定したJavaScriptファイルを読み込んで実行します。 | frida -U -n Twitter -l myscript.js |
-e CODE / --eval CODE |
指定したJavaScriptコードを直接実行します。REPLに入る代わりにコード実行後に終了します。 | frida -U -f com.app.id -e "console.log('Hello from Frida!');" |
--runtime=qjs|v8 |
使用するJavaScriptランタイムを指定します (デフォルトはV8)。QJSは軽量です。 | frida -U -f com.example.app --runtime=qjs -l script.js |
--realm=native|emulated |
ネイティブプロセスまたはエミュレートされたプロセスにアタッチするかどうかを指定します。 | frida -U --realm=emulated -n "emulator-process" |
-f
, -n
, -p
は通常、どれか1つを指定します。接続先デバイスに応じて -U
, -R
, -H
を組み合わせます。
関数トレース (frida-trace)
特定の関数呼び出しをトレースします。引数や戻り値、呼び出し元の情報を表示できます。
オプション | 説明 | 使用例 |
---|---|---|
-i "function" |
指定した関数をトレース対象に含めます。globパターンが使用可能です。 |
|
-I "module" |
指定したモジュール内のすべての関数をトレース対象に含めます。 |
|
-x "function" |
指定した関数をトレース対象から除外します。-i や -I と組み合わせて使用します。 |
|
-X "module" |
指定したモジュール内のすべての関数をトレース対象から除外します。 |
|
-a "module!offset" |
モジュール名とオフセットで関数を指定してトレースします。シンボルがない場合に有効です。 |
|
-T / --include-imports |
インポートされた関数もトレース対象に含めます (通常はエクスポートされた関数のみ)。 |
|
-m "objc_method" |
Objective-C のメソッドをトレースします。-i "-[Class method]" や -i "+[Class method]" と同等です。 |
|
-M "objc_class" |
指定したObjective-Cクラスのすべてのメソッドをトレースします。 |
|
-j "java_method" |
Javaのメソッドをトレースします。* をワイルドカードとして使用できます。クラス名とメソッド名を指定します。 |
|
-J "java_class" |
指定したJavaクラスのすべてのメソッドとコンストラクタをトレースします。 |
|
-o output.txt |
トレース結果を指定したファイルに出力します。 |
|
-h / --help |
ヘルプメッセージを表示します。 |
|
トレース対象が多すぎるとパフォーマンスに影響が出ることがあります。対象を適切に絞り込むことが重要です。
インタラクティブ操作 (Frida REPL)
frida
コマンドでアタッチ後、対話的にJavaScript APIを実行してターゲットプロセスを操作します。
基本的なREPLコマンド
コマンド | 説明 |
---|---|
%resume |
-f で起動時に一時停止しているプロセスを再開します。 |
%reload |
-l でロードしたスクリプトを再読み込みします。 |
%load script.js |
新しいスクリプトファイルをロードします。 |
%unload |
現在ロードされているスクリプトをアンロードします。 |
%exit |
Frida REPLを終了します(プロセスはデタッチされます)。 |
(JavaScriptコード) |
任意のJavaScriptコードを実行します。 |
JavaScript API の基本例
REPL内でよく使われるAPIの例です。
// モジュール一覧表示
Process.enumerateModules().forEach(function(m){ console.log(JSON.stringify(m)); });
// 特定モジュールのエクスポート関数一覧表示
Module.enumerateExports('libc.so').forEach(function(exp){ console.log(exp.name + ': ' + exp.address); });
// モジュールのベースアドレス取得
var baseAddr = Module.findBaseAddress('libnative-lib.so');
console.log('Base address: ' + baseAddr);
// アドレスから関数ポインタ作成
var myFunc = new NativeFunction(baseAddr.add(0x1234), 'int', ['int', 'pointer']);
// 関数呼び出し
var result = myFunc(10, Memory.allocUtf8String('hello'));
console.log('Result: ' + result);
// Javaクラスの利用 (Android)
if (Java.available) {
Java.perform(function() {
var StringClass = Java.use('java.lang.String');
var myString = StringClass.$new('Hello from Java!');
console.log(myString.toUpperCase());
var MainActivity = Java.use('com.example.app.MainActivity');
// 静的メソッド呼び出し
var result = MainActivity.staticMethod(5);
console.log('Static method result: ' + result);
// インスタンス取得 (例: 既存のインスタンスを探す)
Java.choose('com.example.app.MainActivity', {
onMatch: function(instance) {
console.log('Found instance: ' + instance);
// インスタンスメソッド呼び出し
console.log(instance.getSomeValue());
instance.setSomeValue(100);
},
onComplete: function() {
console.log('Instance search complete.');
}
});
});
}
// Objective-C クラス/メソッドの利用 (iOS/macOS)
if (ObjC.available) {
var NSAutoreleasePool = ObjC.classes.NSAutoreleasePool;
var pool = NSAutoreleasePool.alloc().init(); // メモリ管理のため
try {
var NSString = ObjC.classes.NSString;
var nsString = NSString.stringWithString_('Hello from ObjC!');
console.log(nsString.UTF8String()); // C文字列に変換して表示
var UIAlertController = ObjC.classes.UIAlertController;
if (UIAlertController) {
// クラスメソッド呼び出し
var alert = UIAlertController.alertControllerWithTitle_message_preferredStyle_(
NSString.stringWithString_('Frida Alert'),
NSString.stringWithString_('Hello from Frida!'),
1 // UIAlertControllerStyleAlert
);
console.log('Alert Controller created: ' + alert.$ivars.message); // インスタンス変数アクセス (注意: 非公開API)
}
} finally {
pool.release();
}
}
関数フックと書き換え (JavaScript API)
Interceptor.attach
や Java.use
, ObjC.classes
を使用して、関数の呼び出しを監視したり、引数や戻り値を改変したりします。
ネイティブ関数 (Interceptor)
// libc の open 関数をフック
var openPtr = Module.findExportByName('libc.so', 'open');
Interceptor.attach(openPtr, {
onEnter: function(args) {
// args[0] は 첫 번째 인수 (pathname)
this.filePath = args[0].readUtf8String();
console.log('open() called with path: ' + this.filePath);
// 引数の書き換え例: 特定ファイルを別のファイルにリダイレクト
// if (this.filePath.includes('target.txt')) {
// args[0] = Memory.allocUtf8String('/data/local/tmp/redirected.txt');
// console.log('Redirecting open() to /data/local/tmp/redirected.txt');
// }
},
onLeave: function(retval) {
// retval は戻り値 (ファイルディスクリプタ)
console.log('open("' + this.filePath + '") returned: ' + retval.toInt32());
// 戻り値の書き換え例: エラーを返す
// if (this.filePath.includes('secret.txt')) {
// console.log('Forcing open() to fail for secret.txt');
// retval.replace(-1); // EACCES などに対応する errno を返すのがより正確
// }
}
});
// 特定アドレスの関数をフック (オフセット指定)
var moduleBase = Module.findBaseAddress('libnative-lib.so');
var targetAddr = moduleBase.add(0x1A2B); // 対象関数のアドレス
Interceptor.attach(targetAddr, {
onEnter: function(args) {
console.log('Function at ' + targetAddr + ' called.');
console.log('Arg0: ' + args[0].toInt32() + ', Arg1: ' + args[1].readPointer().readUtf8String());
},
onLeave: function(retval) {
console.log('Function at ' + targetAddr + ' returned: ' + retval);
}
});
// 関数の実装を置き換え (Interceptor.replace)
var mallocPtr = Module.findExportByName(null, 'malloc'); // null は全モジュール検索
var myMalloc = new NativeCallback(function(size) {
console.log('malloc() called with size: ' + size);
// 元の malloc を呼び出す必要はない。ここで独自のアロケータを実装したり、
// 固定のポインタを返したりできる。ここでは単純にログだけ表示して 0 を返す。
return ptr(0); // NULL を返す例
}, 'pointer', ['size_t']);
// replace する場合、元の関数を呼び出したいなら Interceptor.attach を使う
Interceptor.replace(mallocPtr, myMalloc);
// 注意: replace は危険を伴う。元の実装を完全に置き換えるため、
// プログラムの動作に重大な影響を与える可能性がある。
Java メソッド (Android)
Java.perform(function() {
// 特定クラスのメソッドをフック
var TargetClass = Java.use('com.example.app.CryptoUtils');
TargetClass.encrypt.implementation = function(data) {
console.log('CryptoUtils.encrypt() called with data: ' + data);
// 元のメソッドを呼び出す
var result = this.encrypt(data);
console.log('CryptoUtils.encrypt() returned: ' + result);
// 戻り値を書き換える例
// var modifiedResult = "MODIFIED_" + result;
// console.log('Returning modified result: ' + modifiedResult);
// return modifiedResult;
return result;
};
// オーバーロードされたメソッドのフック (シグネチャ指定)
TargetClass.processData.overload('int', 'java.lang.String').implementation = function(num, str) {
console.log('processData(int, String) called with num=' + num + ', str=' + str);
// 引数を書き換える例
// var modifiedStr = str + "_hooked";
// console.log('Calling original with modified string: ' + modifiedStr);
// return this.processData(num, modifiedStr);
return this.processData(num, str);
};
// コンストラクタのフック
var FileClass = Java.use('java.io.File');
FileClass.$init.overload('java.lang.String').implementation = function(path) {
console.log('new File("' + path + '")');
// コンストラクタ呼び出しを書き換えるのは複雑な場合がある
// 基本的には元のコンストラクタを呼び出す
return this.$init(path);
};
// メソッドの実装を完全に乗っ取る (元のメソッドを呼び出さない)
TargetClass.getSecretKey.implementation = function() {
console.log('getSecretKey() called, returning fake key!');
var StringClass = Java.use('java.lang.String');
return StringClass.$new("FAKE_SECRET_KEY_FROM_FRIDA");
};
});
Objective-C メソッド (iOS/macOS)
if (ObjC.available) {
try {
// クラスメソッドをフック
var NSURL = ObjC.classes.NSURL;
Interceptor.attach(NSURL['+ URLWithString:'].implementation, {
onEnter: function(args) {
// args[0] は self (クラスオブジェクト), args[1] は selector
// args[2] が 첫 번째 인수 (NSString *)
this.urlString = ObjC.Object(args[2]).toString();
console.log('[NSURL URLWithString:@"' + this.urlString + '"]');
// 引数の書き換え
// if (this.urlString.startsWith('http://example.com')) {
// var newUrlString = ObjC.classes.NSString.stringWithString_('https://safe.example.com');
// args[2] = newUrlString;
// console.log('Rewriting URL to https://safe.example.com');
// }
},
onLeave: function(retval) {
// retval は戻り値 (NSURL *)
console.log('[NSURL URLWithString:] returned: ' + ObjC.Object(retval));
}
});
// インスタンスメソッドをフック
var LAContext = ObjC.classes.LAContext; // ローカル認証 (Face ID/Touch ID)
if (LAContext) {
Interceptor.attach(LAContext['- evaluatePolicy:localizedReason:reply:'].implementation, {
onEnter: function(args) {
console.log('[LAContext evaluatePolicy...]');
// args[2] は policy (LAPolicy)
// args[3] は localizedReason (NSString *)
// args[4] は reply (block)
this.replyBlock = new ObjC.Block(args[4]); // ブロックを取得
var originalReply = this.replyBlock.implementation; // 元のブロック実装
// フックした認証を常に成功させる例
console.log('Forcing authentication success!');
// 新しい実装を持つブロックを作成して呼び出す
var newReply = new ObjC.Block(function(success, error) {
console.log('Original reply block would have received: success=' + success + ', error=' + error);
// 常に success = YES, error = nil で元のコールバックを呼び出す
originalReply(true, null);
}, 'void', ['bool', 'pointer']);
// 元の reply block を置き換える (ここでは直接呼び出す)
newReply(true, null); // ダミーの値で呼び出す(上書きされる)
// 重要: 元のメソッドの実行をスキップするために reply block を置き換える場合、
// 元のメソッド呼び出し自体を避ける必要がある。
// Interceptor.replace を使うか、onEnter で早期リターンするなど。
// ここでは単純化のため、元のメソッドが呼ばれる前提で reply block を操作している。
// 実際には Interceptor.replace の方が安全な場合がある。
// 元の reply block のポインタを書き換えてしまうことも可能だが、複雑。
// args[4] = newReply.handle;
},
onLeave: function(retval) {
// このメソッドは非同期なので onLeave はあまり意味がない
console.log('[LAContext evaluatePolicy...] returned (async)');
}
});
// Interceptor.replace を使った認証バイパス例 (より直接的)
// Interceptor.replace(LAContext['- evaluatePolicy:localizedReason:reply:'].implementation,
// new NativeCallback(function(self, sel, policy, reason, reply) {
// console.log('[REPLACED LAContext evaluatePolicy...] - Forcing success!');
// var replyBlock = new ObjC.Block(reply);
// // 認証成功として即座にコールバックを呼び出す
// replyBlock.implementation(true, null);
// }, 'void', ['pointer', 'pointer', 'long', 'pointer', 'pointer'])
// );
} else {
console.log('LAContext not available.');
}
} catch (e) {
console.error('Error setting up ObjC hooks: ' + e);
}
} else {
console.log('Objective-C runtime not available.');
}
メモリ操作
ターゲットプロセスのメモリを読み書きしたり、特定のパターンを検索したりします。
// 特定アドレスのメモリ読み込み
var addr = ptr('0x12345678'); // 対象アドレス
// 4バイト整数として読み込み
var valueInt = Memory.readInt(addr);
console.log('Int at ' + addr + ': ' + valueInt);
// ポインタとして読み込み
var valuePtr = Memory.readPointer(addr);
console.log('Pointer at ' + addr + ': ' + valuePtr);
// UTF-8文字列として読み込み (長さ指定)
var valueStr = Memory.readUtf8String(addr, 64); // 最大64バイト読む
console.log('String at ' + addr + ': ' + valueStr);
// バイト列として読み込み (ArrayBuffer)
var valueBytes = Memory.readByteArray(addr, 16); // 16バイト読み込み
console.log('Bytes at ' + addr + ': ' + hexdump(valueBytes, { ansi: true }));
// 特定アドレスへのメモリ書き込み
var targetAddr = Module.findExportByName('libtarget.so', 'global_flag');
if (targetAddr) {
// 32ビット整数値を書き込む
Memory.writeInt(targetAddr, 1);
console.log('Wrote 1 to ' + targetAddr);
// 文字列を書き込む
var strAddr = Memory.allocUtf8String('Modified by Frida');
// グローバル変数が文字列へのポインタだと仮定して書き換え
// Memory.writePointer(targetAddr, strAddr);
// console.log('Wrote string pointer to ' + targetAddr);
// バイト列を書き込む
Memory.writeByteArray(targetAddr.add(8), [0xDE, 0xAD, 0xBE, 0xEF]);
console.log('Wrote bytes to ' + targetAddr.add(8));
} else {
console.log('Could not find global_flag export.');
}
// メモリ確保
var buffer = Memory.alloc(1024); // 1024バイト確保
console.log('Allocated buffer at: ' + buffer);
// 確保したメモリに書き込み
Memory.writeUtf8String(buffer, 'Hello from allocated memory!');
console.log(Memory.readUtf8String(buffer));
// メモリ検索 (Memory.scan)
var moduleName = 'libnative-lib.so';
var searchPattern = '48 8d 3d ?? ?? ?? ?? 48 8d 15'; // x86_64 の LEA 命令などの一部 (?? はワイルドカード)
var module = Process.findModuleByName(moduleName);
if (module) {
console.log('Scanning module ' + moduleName + ' (' + module.size + ' bytes) for pattern: ' + searchPattern);
Memory.scan(module.base, module.size, searchPattern, {
onMatch: function(address, size) {
console.log('Pattern found at address: ' + address);
// マッチした場所の周辺をダンプ
// console.log(hexdump(address, { length: 64, ansi: true }));
// ここで見つかったアドレスに対して Interceptor.attach などができる
// return 'stop'; // 最初に見つかった時点でスキャンを停止する場合
},
onError: function(reason) {
console.error('Memory scan error: ' + reason);
},
onComplete: function() {
console.log('Memory scan complete.');
}
});
} else {
console.log('Module ' + moduleName + ' not found.');
}
// Java ヒープ検索 (Android)
if (Java.available) {
Java.perform(function() {
console.log('Searching for instances of java.lang.String on the heap...');
Java.choose('java.lang.String', {
onMatch: function(instance) {
// 大量のインスタンスが見つかる可能性があるので注意
if (instance.length() > 50) { // 例: 50文字以上の文字列のみ表示
console.log('Found String instance: "' + instance + '"');
}
},
onComplete: function() {
console.log('Heap search complete.');
}
});
console.log('\nSearching for instances of com.example.SecretData...');
Java.choose('com.example.SecretData', {
onMatch: function(instance) {
console.log('Found SecretData instance: ' + instance);
// インスタンスのフィールドにアクセス
console.log(' - key: ' + instance.key.value);
console.log(' - value: ' + instance.data.value);
},
onComplete: function() {
console.log('SecretData search complete.');
}
});
});
}
高度な機能
より複雑な解析や操作を行うための機能です。
Stalker (コードトレース)
スレッドの実行を追跡し、実行された命令レベルの情報を収集します。パフォーマンスへの影響が大きいですが、詳細な分析が可能です。
// 特定のスレッドの実行をトレース
var threadId = Process.getCurrentThreadId(); // 現在の REPL スレッド ID (通常はフック対象スレッドを指定)
// 例: 特定関数が呼ばれたらそのスレッドのトレースを開始する
var targetFuncPtr = Module.findExportByName(null, 'sensitive_function');
Interceptor.attach(targetFuncPtr, {
onEnter: function(args) {
var currentTid = this.threadId;
console.log('sensitive_function called in thread ' + currentTid + '. Starting Stalker.');
// このスレッドの追跡を開始
Stalker.follow(currentTid, {
events: {
call: true, // 関数呼び出しをトレース
ret: false, // リターンはトレースしない (ノイズが多い場合)
exec: false, // 個々の命令はトレースしない (非常に多い)
block: false, // 基本ブロックの実行はトレースしない
compile: false // コンパイルイベントはトレースしない
},
// 特定の呼び出しイベントが発生したときに呼ばれるコールバック
onCallSummary: function(summary) {
// summary オブジェクトには呼び出し先と呼び出し元の情報が含まれる
// console.log(JSON.stringify(summary));
},
// より詳細な情報が必要な場合 (カスタムコールバック)
// transform: function (iterator) {
// var instruction = iterator.next();
// do {
// // instruction オブジェクトで命令の詳細を取得
// // console.log(instruction.address + ': ' + instruction.mnemonic + ' ' + instruction.opStr);
// // 特定の命令や呼び出しを検知したら何かする
// if (instruction.mnemonic === 'call') {
// // ...
// }
// iterator.keep(); // 命令を実行させるために必要
// } while ((instruction = iterator.next()) !== null);
// },
// onReceive: function (events) {
// Stalker.follow で収集されたイベントデータを受け取る (上記 transform とは排他)
// var decodedEvents = Stalker.parse(events);
// console.log(JSON.stringify(decodedEvents));
// }
});
// 一定時間後にストークを停止する例
setTimeout(function() {
console.log('Stopping Stalker for thread ' + currentTid);
Stalker.unfollow(currentTid);
// 必要なら収集したデータを処理
// Stalker.flush(); // キューに残っているデータを強制的に送信
}, 5000); // 5秒間トレース
},
onLeave: function(retval) {
// Stalker の停止は非同期で行うことが多い
}
});
// 注意: Stalker.follow は対象スレッドを一時停止させ、コードを書き換えるため、
// 著しいパフォーマンス低下や、場合によってはクラッシュを引き起こす可能性があります。
// イベントフィルタリングや対象範囲の限定が重要です。
RPC (Remote Procedure Call)
Fridaスクリプト内で関数を定義し、それを外部のPythonスクリプトなどから呼び出すことができます。データのやり取りや複雑な制御に便利です。
Frida スクリプト側 (agent.js)
// Python から呼び出せる関数を定義してエクスポート
function addNumbers(a, b) {
console.log('[Agent] addNumbers called with:', a, b);
return a + b;
}
function getDeviceInfo() {
console.log('[Agent] getDeviceInfo called');
var info = {
platform: Process.platform,
arch: Process.arch,
pid: Process.id,
mainModule: Process.enumerateModules()[0].name
};
// Java/ObjC API なども利用可能
if (Java.available) {
try {
Java.performNow(function() {
var Build = Java.use('android.os.Build');
var VERSION = Java.use('android.os.Build$VERSION');
info.androidSdk = VERSION.SDK_INT.value;
info.deviceModel = Build.MODEL.value;
});
} catch (e) {
info.javaError = e.message;
}
}
return info;
}
// 非同期処理を含む関数の例 (結果を Promise で返す)
function readFileContent(path) {
console.log('[Agent] readFileContent called for path:', path);
return new Promise(function(resolve, reject) {
try {
var file = new File(path, 'r');
var content = file.readAllText();
file.close();
console.log('[Agent] Read ' + content.length + ' bytes.');
resolve(content);
} catch (e) {
console.error('[Agent] Error reading file:', e.message);
reject(e.message); // エラーメッセージを返す
}
});
}
// 関数をエクスポート
rpc.exports = {
add: addNumbers,
deviceInfo: getDeviceInfo,
readFile: readFileContent // Promise を返す関数もエクスポート可能
};
console.log('[Agent] RPC exports ready.');
// Pythonからの呼び出しを待機
// (このスクリプト自体は特にループなどは不要)
Python 側 (control.py)
import frida
import sys
import time
def on_message(message, data):
""" Fridaスクリプトからのメッセージを受信したときのコールバック """
if message['type'] == 'send':
print(f"[Frida Agent] {message['payload']}")
elif message['type'] == 'error':
print(f"[Frida Error] {message['stack']}")
else:
print(f"[Frida Message] {message}")
# ターゲットプロセスにアタッチ (ここでは例として実行中の 'cat' にアタッチ)
# 実際には frida-ps などで PID や名前を確認して指定する
target_process = "cat" # または PID, Android/iOS アプリ名など
try:
# USBデバイス上のプロセスにアタッチする場合
# device = frida.get_usb_device(timeout=1)
# session = device.attach(target_process)
# ローカルプロセスにアタッチする場合
session = frida.attach(target_process)
except frida.ProcessNotFoundError:
print(f"Error: Process '{target_process}' not found.")
sys.exit(1)
except frida.TimedOutError:
print("Error: Timed out waiting for USB device.")
sys.exit(1)
except Exception as e:
print(f"Error attaching to process: {e}")
sys.exit(1)
# エージェントスクリプトを読み込む
script_path = "agent.js"
try:
with open(script_path, "r", encoding="utf-8") as f:
script_code = f.read()
except FileNotFoundError:
print(f"Error: Script file '{script_path}' not found.")
session.detach()
sys.exit(1)
script = session.create_script(script_code)
# メッセージハンドラを設定
script.on('message', on_message)
# スクリプトをロード
print("[Python] Loading script...")
script.load()
print("[Python] Script loaded. Accessing RPC exports...")
# RPC関数が利用可能になるまで少し待つ (状況による)
time.sleep(1)
# エクスポートされた関数を呼び出す
try:
# 単純な同期呼び出し
result_add = script.exports.add(10, 25)
print(f"[Python] RPC call add(10, 25) returned: {result_add}")
# 情報を取得する同期呼び出し
dev_info = script.exports.device_info()
print("[Python] RPC call deviceInfo() returned:")
for key, value in dev_info.items():
print(f" - {key}: {value}")
# Promise を返す非同期関数を呼び出す (Python側では同期的に待機)
# 存在しないファイルを読み込もうとしてエラーを発生させる例
try:
print("[Python] Calling RPC readFile('/path/to/nonexistent/file.txt')...")
content = script.exports.read_file('/path/to/nonexistent/file.txt')
print(f"[Python] RPC readFile() returned: {len(content)} bytes")
except frida.InvalidOperationError as e:
# スクリプト側で reject(errorMessage) された場合、
# Python側では frida.InvalidOperationError が発生し、
# エラーメッセージが取得できる。
print(f"[Python] RPC readFile() failed as expected: {e}")
# 存在するファイルを読み込む例 (ターゲット環境に依存)
# 例: Android の場合
# target_file = "/data/local/tmp/mydata.txt"
# try:
# print(f"[Python] Calling RPC readFile('{target_file}')...")
# content = script.exports.read_file(target_file)
# print(f"[Python] RPC readFile('{target_file}') returned:\n{content[:200]}...") # 最初の200文字表示
# except frida.InvalidOperationError as e:
# print(f"[Python] RPC readFile('{target_file}') failed: {e}")
except frida.RPCException as e:
print(f"[Python] RPC Error: {e}")
except AttributeError:
print("[Python] Error: RPC exports not available yet or script error.")
except Exception as e:
print(f"[Python] An unexpected error occurred: {e}")
finally:
# スクリプトをアンロードし、セッションをデタッチ
print("[Python] Unloading script and detaching...")
try:
script.unload()
session.detach()
except frida.TransportError:
print("[Python] Target process may have already terminated.")
except Exception as e:
print(f"[Python] Error during cleanup: {e}")
print("[Python] Finished.")