株式会社Ninjastars 技術系ブログ

「本質的安全を提供し、デジタル社会を進化させる!!!」

Fridaを使用してOWASPが提供するAndroid用のCTFを解く

株式会社Ninjastars
セキュリティエンジニア:ツヨシ

今回はOWASPが提供するAndroid用のCTF「Android UnCrackable L2」のWriteupを書きたいと思います。
問題は以下のサイトで公開されています。
mas.owasp.org
Androidのルート化検出を回避し、secret stringを見つけることが目標です。
この問題を通して、アプリケーションの脆弱性を診断する上で強力なツールであるFridaの理解や知識を深めていきましょう。
過去にもこのブログでFridaについて紹介した記事が存在しますので、そちらもよろしければご覧ください。
www.ninjastars-net.com

環境

Android 13 Pixel 5 (root化済み)
Windows 11 Home

使用するツール

  1. Frida 16.0.10
  2. jadx version: 1.4.5

ルート化検出の回避

早速APKをAndroid端末にインストールしてアプリケーションを起動してみましょう。


しかし起動直後にルート化を検知したことを警告するアラートが表示されてしまいます。
「OK」を押すとアプリが終了してしまい、このままでは先に進めることができません。
この検知機構を何とかバイパスすることが最初の目標となります。
とりあえずjadxなどのAPKファイルを逆コンパイルするツールを用いてソースコードの解析を試みます。
どのメソッドが怪しいか目星をつけるため、アラートに表示される文字列「Root detected!」で検索してみます。
すると、ルート化検知に関わっていそうなクラスを発見することができました。

ここで画像の一番上に表示されている「a」というメソッドに注目してみましょう。
このメソッドは大まかに以下のような流れになっています。

  1. ルート化検知のアラートダイアログを作成する。
  2. 「OK」をタップするとJavaのSystemクラスからexitメソッドを呼び出し、アプリを終了させる。

Fridaを使用し2通りの方法で検知をバイパスしてみましょう!

方法1 「a」メソッドを何もしないメソッドに置き換える

おそらく最も簡単な方法は「a」メソッドを何もしないメソッドに置き換えて、アラートを表示させなくすることでしょう。
jadxではメソッド名を右クリックし、「Copy as frida snippet」を選択することで簡単にFridaでメソッドを改ざんするコードが得られます。
以下のコードのように先ほどコピーしたスニペットを関数の中身をただreturnするだけに変更し、適当な名前のjsファイル(ここでは「bypass.js」)に保存します。

Java.perform(() => {
    let MainActivity = Java.use("sg.vantagepoint.uncrackable2.MainActivity");
    MainActivity["a"].overload('java.lang.String').implementation = function (str) {
        return;
    };
});

後はコマンドプロンプト

frida -U -f owasp.mstg.uncrackable2 -l bypass.js

と入力し、Frida CLIからこのスクリプトを実行するとアラートは表示されず、無事ルート化検知をバイパスすることができました。

方法2 JavaのSystemクラスの「exit」メソッドを置き換える

もう一つは「exit」メソッドを何もしないメソッドに置き換えて、「OK」を押してもアプリが強制終了しないようにする方法です。
「exit」メソッドは方法1のように「Copy as frida snippet」が使えないため自分でコードを組む必要がありますが、難しいことはなく
「sg.vantagepoint.uncrackable2.MainActivity」クラスが「java.lang.System」クラスに、「a」メソッドが「exit」メソッドに変わるだけです。

Java.perform(() => {
    let System = Java.use("java.lang.System");
    System["exit"].overload('int').implementation = function (a) {
        return;
    };
});

方法1と同様にこのスクリプトを実行すると「OK」を押してもアプリが実行され続けるため、ルート化検知をバイパスできました。

secret stringを見つける

さて、無事ルート化検知をバイパスすることができたので次はsecret stringを探し出しましょう。
先ほどの「a」メソッドと同じクラス内に「verify」といういかにもなメソッドがあるので、それを深堀りしていきましょう。

どうやら、「this.m.a(obj)」の返り値の真偽によって入力した文字列がsecret stringであるかどうかを判定しているようです。
「this.m」は「CodeCheck」のインスタンスなので「CodeCheck」クラスを見てみましょう。

最終的にはネイティブメソッドの「bar」の返り値の真偽がsecret stringであるかどうかの判定に関わっていることが分かりました。
「bar」の処理はAPKの共有ライブラリフォルダ内にある「libfoo.so」に存在します。
この記事ではAArch64(ARM64)の「libfoo.so」をGhidraを用いて解析します。
Ghidraで「libfoo.so」を開くと、「Java_sg_vantagepoint_uncrackable2_CodeCheck_bar」というシンボルの関数が見つかり、これが「bar」メソッドの正体だということが推測できます。

コード内の「strncmp」は文字列同士を比較する関数であるため、ここで入力した文字列とsecret stringを比較しているという検討がつきます。
変数「local_50」に格納された文字列をGhidra上で読み取ることも可能ですが、今回はここでもFridaを使用して動的にsecret stringを取得してみましょう。

Ghidraの逆アセンブリされたコードを見てみると、「strncmp」でもともとx21レジスタに格納された文字列とスタックポインタに格納された文字列を比較しているということが分かります。
また、「strcmp」以前でスタックポインタが関与する操作は「libfoo.so」からオフセット「0xe14」で終わっています。
そこでFridaで「libfoo.so」からオフセット「0xe18」のアドレスをフックし、スタックポインタに格納された値を読み取ってみましょう。
ルート検知のバイパスに使用したスクリプトに以下のコードを書き加えます。

let libfoo = Module.getBaseAddress("libfoo.so");
let targetAddr = libfoo.add(0xE18);
Interceptor.attach(targetAddr,{
    onEnter:function(args){
        console.log(this.context.sp.readCString());
    },
});

Fridaでは「this.context」で特定のレジスタの値を読み取ることが可能です。
今回は「this.context.sp」でスタックポインタに格納された値を読み取り、その値が示すメモリ上の文字列を「readCString」で読み取ります。
Frida CLI上でスクリプトを実行し、アプリの入力欄に適当な文字を入れて「VERIFY」を押してみましょう。

上記の画像のようにスタックポインタに格納された値は「Thanks for all the fish」であることが分かりました。
試しにこの文字列を入力して「VERIFY」を押すと「Success!」というアラートが表示されます。
これにて「Android UnCrackable L2」はクリアとなります。お疲れ様でした。

まとめ

いかがだったでしょうか。今回はなるべくFridaの機能を活かした解答方法を書かせていただきました。
この解説を通してFridaというツールの有用性や、アプリケーションの実行ロジックを追う楽しさを少しでも知っていただけたら幸いです。

アプリケーション診断やゲームセキュリティ診断などをご希望の方は、ぜひ一度弊社にお問い合わせください!
ninjastars.ninja

注意事項

本記事に記載されている内容を許可されていないアプリケーションに対して実行すると、犯罪行為となる可能性があります。そのため、記事の内容を試す際には許可されたアプリケーションに対してのみ実施するようにしてください。

本レポートについて
お問い合せ
E-mail: engineer@ninjastars-net.com

株式会社Ninjastars セキュリティエンジニア
ツヨシ