本文は frida の使用を復習し、いくつかの例題を示します。カバー画像は AI によるものです。
frida の一般的なコマンドリファレンス#
frida-ls-devices # すべてのデバイスをリスト表示
frida-ps -U # USBデバイスのプロセスをリスト表示
frida-ps -Ua # 実行中のアプリをリスト表示
frida-ps -Uai # インストールされているすべてのアプリをリスト表示
frida-ps -D xxxxx # 指定したデバイスに接続、デバイスIDは最初のコマンドから取得可能
# アプリを起動してスクリプトを注入(後でこの2つの違いについて説明します)
frida -U -l exploit.js -f com.xxx.yyy
# アプリが既に実行中の場合、スクリプトを注入
frida -U -l exploit.js "XXXX"
frida の基礎#
frida
は Python と JavaScript に基づくフックフレームワークで、Android、iOS、Linux、Windows などのさまざまなプラットフォームで実行可能で、主に動的バイナリインストゥルメンテーション技術を使用します。
pip コマンドでfrida
とfrida-tools
をインストール:
pip install frida
pip install frida-tools
Python にインストールされた frida のバージョンを確認する
frida --version
その後、PC とモバイル端末の両方に frida と frida-server をインストールし、バージョンを一致させます。
次に、モバイル端末で frida-server を実行し、モバイル端末のポートを PC に転送して、PC の Python スクリプトとモバイル端末の frida-server が通信できるようにします。adb コマンドを使用してポート転送を実現できます。このステップは必須ではないようで、転送しなくても動作するようです。例えば:
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
frida-ps -U コマンドを使用して、モバイル端末上のプロセス情報を確認し、フックするターゲットプロセスを選択できます。
簡単にまとめると:
- 対応する frida server をダウンロードします。これは公式の GitHub リリースページで探します。注意点として、PC エミュレーター(例:夜神エミュレーター)を使用する場合、対応するアーキテクチャは x86 であり、arm ではありません。
- adb でエミュレーターに接続し、最初にダウンロードした server を /data/local/tmp ディレクトリ(一般的にはこのディレクトリ)に push し、chmod a+x で実行可能にして起動します。
- pip install frida frida-tools を実行します。
- frida-ps -U を入力すると、モバイル端末のプロセスリストが表示され、これで設定が成功したことになります。
フックスクリプトの作成と実行#
フックスクリプトを起動して注入します。
frida -U -l exploit.js -f com.xxx.yyy
簡単な記述方法の例で、Java のリフレクションに慣れていれば理解しやすいです。
文字列を使用する場合、java.lang.String
はクラスなので、new する必要があります(他のクラスも同様に、クラスメソッドを使用する場合は new が必要ですが、静的メソッドの場合は不要です)。
var x = Java.use("java.lang.String").$new("xxxxxxxx")
特定のクラスの静的メソッドを使用する場合(クラスメソッドの場合は先にオブジェクトを new する必要があります)。
Java.use("com.xxx.yyy").method(param1, param2)
特定のメソッドをフックする場合。
Java.perform(function() {
var class = Java.use("com.xxx.yyy")
classs.方法名.implementation = function(param1, param2){
send(); // ログを出力
return this.xxxx // フックされた戻り値
}
})
基本的な使い方はこれくらいです。
落とし穴とテクニック#
jadx は frida コードスニペットを直接コピーすることをサポートしており、かなりの手間を省くことができます。
フックメソッドにはパラメータを指定する必要があります:
// JSコード
Java.perform(function () {
var MainActivity = Java.use("com.example.MainActivity"); // クラス参照を取得
var a = MainActivity.a.overload("int", "int", "long"); // フックするオーバーロードメソッドのパラメータタイプを指定
a.implementation = function (x, y, z) { // オーバーロードメソッドの実装を置き換え
console.log("x: " + x + ", y: " + y + ", z: " + z); // パラメータを出力
var result = this.a(x, y, z); // 元のオーバーロードメソッドを呼び出す
console.log("result: " + result); // 戻り値を出力
return result; // 元の値を返す
};
});
パラメータがない関数の場合、overload
を指定する必要はありません。
// JSコード
Java.perform(function () {
var MainActivity = Java.use("com.example.MainActivity"); // クラス参照を取得
var onCreate = MainActivity.onCreate.overload(); // フックする関数のオーバーロードタイプを指定、パラメータは渡さない
onCreate.implementation = function () { // 関数の実装を置き換え
console.log("onCreate()が呼ばれました"); // ログを出力
this.onCreate(); // 元の関数を呼び出す
};
});
例#
MainActivity メソッドフックの例#
公式ドキュメントの例を参考に、apk もダウンロード可能です:Android | Frida・世界クラスの動的インストゥルメンテーションツールキット
jadx でロジックを分析すると、this.n
とthis.m
の差が 1 であり、this.cnt+1
が 1000 になる必要があることがわかります。
これらの変数がいつ変更されるかを確認すると、onClick
メソッドが見つかりました。
それで、js スクリプトを作成し、onClick
メソッドをフックします。
Java.perform (() => {
// クラスを選択
const MainActivity = Java.use ('com.example.seccon2015.rock_paper_scissors.MainActivity');
// onClickメソッドを選択
const onClick = MainActivity.onClick;
// onClickをフック
onClick.implementation = function (v) {
// 関数が呼ばれたときにメッセージを出力
send ('onClick');
// 元のonClickメソッドを呼び出す
onClick.call (this, v);
// 呼び出し後にm,n,cntの値を変更
this.m.value = 0;
this.n.value = 1;
this.cnt.value = 999;
// fridaコンソールに結果を出力
// フック終了
console.log ('完了:' JSON.stringify (this.cnt));
};
});
実行コマンド
frida -U -l .\seccon2015.js rock_paper_scissors
実行後、frida のシェルに入り、ログメッセージが出力されます。
frida は Python を使った呼び出しも提供していますが、コードのコメントにあるように、個人的には JS の方法が好みです。
import frida, sys
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
jscode = """
...上記のjscodeをコピー
"""
# 注意点として、attachする際に完全なパッケージ名を入力する必要はありません。
process = frida.get_usb_device().attach('rock_paper_scissors')
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] CTFを実行中')
script.load()
sys.stdin.read()
理由を探してみると:
import frida
process = frida.get_usb_device().enumerate_processes() # すべてのプロセスを列挙
この時点で実際のプロセス名がわかるので、コード内ではrock_paper_scissors
と書くことができます。
Process(pid=2982, name="rock_paper_scissors", parameters={})
実行結果:
この時点でアプリ上にフラグが出力されました。
吾愛破解红包第三题#
jadx でロジックを分析すると、check
関数が返す結果が 999 であればフラグを取得できることがわかります。
そのため、MainActivity
の下の check メソッドを直接フックし、999 を返します。
function main(){
Java.perform(function(){
var activity = Java.use("com.zj.wuaipojie2023_3.MainActivity");
var check_method = activity.check.overload(); // ここでは実際にオーバーロードはないため、指定する必要はありません。
check_method.implementation = function(arg){
// send('check');
check_method.call(this);
return 999
}
})
}
setImmediate(main)
数回クリックするだけでフラグを取得できます。
RSA 暗号のフック#
あるコンペティションで、APK を解読する課題がありました(ソースファイルが失われた)。jadx でソースコードを取得すると、RSA 暗号が使用されていることがわかりました。この点で少し手間がかかりました。
Android の RSA と Java パッケージ内の実装は異なるため、手動で BouncyCastleProvider.jar パッケージをダウンロードし、プロジェクトに追加してコードを修正する必要があります。
実際、最も簡単な方法は frida を使用してフックすることです。
実装コードは以下の通りです:
function main(){
Java.perform(function(){
var enc = Java.use("java.lang.String").$new("F3RSCi59ewpMm+dIGtiAn752tBQ0TPx7tCayrCXrrwyG2gtlUJ3C82rcrfgg2A4X7XfhXWGidsJgGDzbD9JwvzGbKMHTK5kxJUf1EJ605R/Rv2N+6Jtr54wQAKbqGyU8g/rUhLUf/EgNqPPYTQ6fa4LqFEMTrPm/UZMvL/4yTNhFoM5vQbceQeD9QNATh6kdYEzLfRdWbv/ESllURlgZbQsc7+pHC8Sg9wVh/2MGZHKzZI8a67Usqhk8ojmfgN5ABodQ2z1DOCICg7AH+l+3fx6eOn1dup/Q1hpCHVCgtojMOPfoLcxICY5LkSonrFjAKzv4JHO/f5ihFXX8Dr4MFw=="); // 新しい文字列を作成
var PrivateKey = Java.use("java.lang.String").$new("MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDcscawF6Fy4Smk\nqwOy2W/IcewNTYzHe9ZFV6w1dqrZ+sRBRD6Sapv7k4deLF9ziYsWXfcDNuvG2HN6\nAgk6CeN5QHUj7OiJmTnitHAQFIw7Ed6O3uyE64i3dhZIKh7oAies6EMpfKusXIZ6\nt1lFwVVpPjlrW6435j/nW5X0OeaRRk89zOxIbzAhS0yxArkMSecy9lr+UwzWQhFO\nW4+O/ywesds9VdCuycx2S/1zbG7BwJP507nTxstLkUnet0BHqXH3tH8cRXXDyFo1\n13gBgrBZ7Y79Z7T39B+21qq7RxfwvPDqu6Rq3p9cOUQCwfJSWlHYBVtYOME04vZP\nkZj/PqLhAgMBAAECggEBAL9QLRkBoydfISZqSW6FJ23vs91kWKGlLH44HkTKdhk4\nVWDus/9hA472uOZeOPCDT1s4YaUbuxBJGvGtSipR4CuAQQu/l2WQuqlYGb0W0ulx\n+CiJ/ybKt12ytGYifWBKXfJ59Z7FQFzOaJWA05awnkX5dvUmKMS0iLkan8dzWfic\nCn379Jn/bLmRXSTKgWNMi9toT4kNhnr2/cf1ah1i2MHHZb4ZMdC9rqnSvyzFRAFM\nuess0Qg8ckyoNzx2daP9vXapn8rg6B/MFBZqjorwyv0KJms/S6W/YKw88OixM/J3\nxlBsk8QHJqaL5IZW6/QeIHJk4K6o9+3fTxtHEjcWoAECgYEA8ASApzgATrHUFVdT\nZ6ZohLFMyQQZzaKLtZhBRfenASrWQSpHHcGdF15C45fik/vohZvF/byrHLkwvosp\nvC6BHyF7toJDrTEWBo/xymGNYmovr0keMUc3T4aRomaERz0gY8CLhKpHMKJrVtgj\nkHlAGqkRZB6AAlcEQUSOyvYziyECgYEA62Pg4xMw1BfJKSfLhCoaWHeWBMAbuZ8K\ncEOLIO8CtGKwXRLBe6MUKTDnHt7aXawVd/j6jk27uhD8p1noYqbsAexzMIKAAzic\nI6fsCVgUfvRZzhaQ98xyBO34AmAtzhJQEg65TnybyEf8tC5xPSegCzzYA5w8QfaM\necRq8V8R38ECgYEAkRNXpDuATBDGzaN8AXGfSV1VuUcmRZpTcg82nREFO/pliPwC\nAmVuC5rpOWIsDSC9ukyezzEECJeTdAjv0BQX7fYyLe3s9DlwQ8Ur9BUk/XCLpBrB\nCi4uq33+dzgaCmlTM5hFfInj/+ycjuCUFpaDfdnlbivVWhS4uK601M8d9qECgYEA\nuxel1ZaZRtqo/tcsgZ3dVtemG5x97OmmKFjnKoQOiEWwnCmeM6EJEzxVV0oWEkIG\nxlbU/2p1fYym5HUnqdG24EiJvdLb4LOMFyMPDtY9ZDLfdlilXO/Y6GYkq+66OYKA\ntfmR+/o679OX3bbUNqFaKdRwdV4m3t8SBG7D+Zlw4YECgYAp2B4F1WOu5H/P0yC6\n6Hn/FGcQec8ty6MCPlH1km9dQBS3jMvBAFpmnMv+hWiR4W93tOHRqRndR6VFC/dX\n+io76NklgjQGteUJ/Nawhg0sDpWCUGECRypwbNTgmhcrmu/RF1B22rO8sckPXXeB\nDzVmjKFXgpDDIJ0eDUTQbqEEMw=="); // 新しい文字列を作成
var encB64Decode = Java.use("com.chaos.view.example.Base64Utils").decode(enc); // Base64Utilsのdecodeメソッドを呼び出す
var loadPrivateKey = Java.use("com.chaos.view.example.RSAUtils").loadPrivateKey(PrivateKey); // RSAUtilsのloadPrivateKeyメソッドを呼び出す
var res = Java.use("java.lang.String").$new(Java.use("com.chaos.view.example.RSAUtils").decryptDataPrivate(encB64Decode, loadPrivateKey)); // RSAUtilsのdecryptDataPrivateメソッドを呼び出して復号化
var qaq = Java.use("com.chaos.view.example.Qaq").decode(res, 127);
console.log(qaq);
})
}
setImmediate(main)
frida -U -l xxx.js
を呼び出します。
owasp.mstg.uncrackable1 の root 検出回避#
apk には root 検出があり、jadx でロジックを確認すると、判断はa.a
関数に書かれています。
a.a
メソッドを追跡すると、単純にフックして戻り値を true にするだけで済みます。
まず root 検出を回避するために、ある大神が公開したスクリプトを見つけました:
それを試してみると、root を回避することに成功しました。
frida -U --codeshare dzonerzy/fridantiroot -f "owasp.mstg.uncrackable1"
次にフックします。
Java.perform(() => {
// aクラスを見つける
const a = Java.use('sg.vantagepoint.uncrackable1.a');
// aメソッドを見つける。オーバーロードはないが、パラメータタイプを指定する。
const a_method = a.a.overload("java.lang.String");
a_method.implementation = function (v) {
// フックしたことを示すメッセージを出力
send('aメソッドが呼ばれました');
// 元のメソッドを呼び出す
a_method.call(this, v);
return true; // フックされた関数の戻り値
};
});
次に、-l
を使用して自分のスクリプトを実行し、--codeshare
は root 検出を回避するスクリプトを実行します。
frida -U --codeshare dzonerzy/fridantiroot -l .\uncrackable1.js -f "owasp.mstg.uncrackable1"
owasp.mstg.uncrackable2#
ここでのロジックは最初のものと似ていますが、メソッドはネイティブライブラリ内にあるため、libfoo.so
内の関数をフックする必要があります。
ネイティブメソッドをフックするテンプレートは以下の通りです:
Java.perform(() => {
setTimeout(function(){
Interceptor.attach(Module.findExportByName('libfoo.so', 'xxxx'),{
onEnter: function(args){
// 関数に入るときのパラメータを変更
},
onLeave: function(retval){
// 関数の戻り値を変更
}
});
},2000);
});
テンプレートコードを参考にして、以下のようなフックスクリプトを書くことができます:
Java.perform(() => {
setTimeout(function(){
Interceptor.attach(Module.findExportByName('libfoo.so', 'Java_sg_vantagepoint_uncrackable2_CodeCheck_bar'),{
onEnter: function(args){
},
onLeave: function(retval){
console.log(retval);
retval.replace(ptr(1));
console.log(retval);
}
});
},2000);
});
ここでの注意点:
frida フックの 2 つの方法を区別する必要があります。
- 注入
frida -U -l .\uncrackable2.js "Uncrackable Level 2" # 対応するプロセスに注入
- 起動
frida -U -l .\uncrackable2.js -f owasp.mstg.uncrackable2 # 対応するプロセスを起動
最初は-f
を使用していたため、プログラムが最初にlibsfoo.so
を読み込んでいないという問題を解決するのにかなりの時間を費やしました。夜神エミュレーターがサポートしていないのではないかと一度思ったこともありました。
最終的に整理した 2 つのコードは、setTimeout
を追加するだけで動作します。
最初の実行は失敗し、2 回目で成功しました。
frida -U --codeshare dzonerzy/fridantiroot -l .\uncrackable2.js -f owasp.mstg.uncrackable2
参考:https://1337.dcodx.com/mobile-security/owasp-mstg-crackme-2-writeup-android ここではネイティブ内のstrncmp
メソッドをフックするコードが示されています。
Java.perform(() => {
console.log("[*] onClickボタンをハイジャック中")
var clazz_main = Java.use('sg.vantagepoint.uncrackable2.MainActivity$1')
clazz_main.onClick.implementation = function () {
console.log('onCLick()が置き換えられました');
};
console.log()
console.log('[*] 必要なアクション: "I want your secret asap"という文字列を入力してください')
console.log()
setTimeout(function(){
Interceptor.attach(Module.findExportByName('libfoo.so', 'strncmp'),{
onEnter: function(args){
if( Memory.readUtf8String(args[1]).length == 23 && Memory.readUtf8String(args[0]).includes("I want your secret asap")){
console.log()
console.log()
console.log("*******SECRET********")
console.log(Memory.readUtf8String(args[1]))
console.log("*******SECRET********")
console.log()
console.log()
}
},
onLeave: function(retval){
}
});
},2000);
});
実行するだけです。
frida -U -l .\uncrackable2.js "Uncrackable Level 2"
owasp.mstg.uncrackable3#
後で補充します~