frida-tools チートシート

接続可能なプロセスやデバイスを一覧表示したり、プロセスを終了させたりします。

コマンド 説明 使用例
frida-ps 実行中のプロセスを一覧表示します。接続先を指定できます。
# ローカルマシン上のプロセス一覧
frida-ps

# USB接続されたデバイス上のプロセス一覧
frida-ps -U

# リモートデバイス上のプロセス一覧
frida-ps -R

# アプリケーションのプロセスのみ表示 (iOS/Android)
frida-ps -Ua

# インストールされているアプリケーション一覧 (iOS/Android)
frida-ps -Uai
frida-ls-devices Fridaが認識しているデバイスを一覧表示します。
frida-ls-devices
frida-kill 指定したプロセスを終了させます。
# プロセスID (PID) を指定して終了 (ローカル)
frida-kill 1234

# USBデバイス上のプロセスIDを指定して終了
frida-kill -U 5678

# リモートデバイス上のプロセスIDを指定して終了
frida-kill -R 9012

ターゲットへの接続と起動

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パターンが使用可能です。
# open関数をトレース
frida-trace -U -i "open" -p 1234

# libc.so 内の全ての関数をトレース
frida-trace -U -i "libc.so!*" -f com.example.app

# 特定のクラスのメソッドをトレース (Objective-C)
frida-trace -U -i "-[NSString UTF8String]" -f com.apple.AppStore

# 特定のクラスの全てのメソッドをトレース (Objective-C)
frida-trace -U -i "-[MyClass *]" -f com.example.myapp

# 特定のクラスのメソッドをトレース (Java)
frida-trace -U -i "Java:com.example.MainActivity.onCreate" -f com.example.app

# 特定のJavaクラスの全メソッドをトレース
frida-trace -U -i "Java:com.example.SecretClass.*" -f com.example.app
-I "module" 指定したモジュール内のすべての関数をトレース対象に含めます。
# libcommon.so 内の全関数をトレース
frida-trace -U -I "libcommon.so" -f com.example.app
-x "function" 指定した関数をトレース対象から除外します。-i-I と組み合わせて使用します。
# libc.so の関数をトレースするが、read と write は除外
frida-trace -U -i "libc.so!*" -x "libc.so!read" -x "libc.so!write" -p 1234
-X "module" 指定したモジュール内のすべての関数をトレース対象から除外します。
# 全ての関数をトレースするが、libSystem.B.dylib は除外 (macOS/iOS)
frida-trace -f /Applications/Calculator.app -i "*" -X "libSystem.B.dylib"
-a "module!offset" モジュール名とオフセットで関数を指定してトレースします。シンボルがない場合に有効です。
# mylib.so のベースアドレス + 0x1234 の位置にある関数をトレース
frida-trace -U -a "mylib.so!0x1234" -f com.example.app
-T / --include-imports インポートされた関数もトレース対象に含めます (通常はエクスポートされた関数のみ)。
frida-trace -U -i "recv" -T -f com.example.networkapp
-m "objc_method" Objective-C のメソッドをトレースします。-i "-[Class method]"-i "+[Class method]" と同等です。
# -[NSURLRequest initWithURL:] をトレース
frida-trace -U -m "-[NSURLRequest initWithURL:]" -f com.apple.mobilesafari
-M "objc_class" 指定したObjective-Cクラスのすべてのメソッドをトレースします。
# MyViewController クラスの全メソッドをトレース
frida-trace -U -M MyViewController -f com.example.myapp
-j "java_method" Javaのメソッドをトレースします。* をワイルドカードとして使用できます。クラス名とメソッド名を指定します。
# java.io.File クラスのコンストラクタをトレース
frida-trace -U -j 'java.io.File.$init' -f com.example.app

# com.example.Utils クラスの全てのメソッドをトレース
frida-trace -U -j 'com.example.Utils.*' -f com.example.app

# 特定のオーバーロードされたメソッドをトレース (シグネチャ指定)
frida-trace -U -j 'com.example.MyClass.myMethod(int, java.lang.String)' -f com.example.app
-J "java_class" 指定したJavaクラスのすべてのメソッドとコンストラクタをトレースします。
# android.security.keystore.KeyGenParameterSpec クラスの全メソッドをトレース
frida-trace -U -J 'android.security.keystore.KeyGenParameterSpec' -f com.android.settings
-o output.txt トレース結果を指定したファイルに出力します。
frida-trace -U -i "write" -p 1234 -o trace_output.log
-h / --help ヘルプメッセージを表示します。
frida-trace -h
トレース対象が多すぎるとパフォーマンスに影響が出ることがあります。対象を適切に絞り込むことが重要です。

インタラクティブ操作 (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.attachJava.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.")

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です