Androidアプリ動的解析ツールFridaを使う

Harekaze Advent Calender21日目の記事です

adventar.org

前日はbanbanさんでした。Harekazeらしいいい記事です!!

banban.hateblo.jp

それで私が話すのは最近見つけたCTFに使えるツールの使い方です。

Frida

FridaとはAndroidアプリへの動的解析ツールでありアプリの解析やセキュリティに関する調査も行えるツールだそうです。

つい最近知ったのですが今回はそれを使うところまで行こうかなと思います

ちなみにこの記事ではAndroid Emulatorなどは使える前提で進めます。具体的に言うとadb devicesコマンドを打ったときにデバイスが認識されている状況にしてください。

私の試した環境はこんな感じ

$ uname -a
Linux durian 3.13.0-121-generic #170-Ubuntu SMP Wed Jun 14 09:04:33 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 14.04.5 LTS
Release:    14.04
Codename:   trusty
$ adb --version
Android Debug Bridge version 1.0.39
Version 27.0.0-4455170
Installed as ~/android/android-sdk-linux/platform-tools/adb

Android Emulatorの環境

環境
OS Android7.0.0
CPU ARM(armeabi-v7a)

Fridaのインストール

基本的にはFridaのページに書いてあるとおりにやれば問題ないです。

Linux側での準備

$ sudo pip install frida

Android側の準備

まずここからfrida-serverをダウンロード展開します。

$ adb root # might be required
$ adb push frida-server /data/local/tmp/ 
$ adb shell "chmod 755 /data/local/tmp/frida-server"
$ adb shell "/data/local/tmp/frida-server &"

という感じでfrida-serverを実行しておけば準備完了です。

Fridaを使う

サンプルコードを用いて解説していきます。このサンプルコードですがSECCON2015 reverse-engineering-android-apk-1の問題です。

import frida, sys

def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)

jscode = """
Java.perform(function () {
    // Function to hook is defined here
    var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');

    // Whenever button is clicked
    MainActivity.onClick.implementation = function (v) {
        // Show a message to know that the function got called
        send('onClick');

        // Call the original onClick handler
        this.onClick(v);

        // Set our values after running the original onClick handler
        this.m.value = 0;
        this.n.value = 1;
        this.cnt.value = 999;

        // Log to the console that it's done, and we should have the flag!
        console.log('Done:' + JSON.stringify(this.cnt));
    };
});
"""

process = frida.get_usb_device().attach('com.example.seccon2015.rock_paper_scissors')
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] Running CTF')
script.load()
sys.stdin.read()

基本的な使い方ですが

process = frida.get_usb_device().attach(packagename)のpackageに解析するapkのパッケージを入力することで動的解析ができる環境になり

process.create_script(jscode)で実行させるJavaScriptを読み込みscript.load()でスクリプト実行させます。Fridaで使えるJavaScriptのメソッドはここを参照してください。

このapkのMainActivityですが下のようになっています。

public class MainActivity extends Activity
    implements android.view.View.OnClickListener
{

    public MainActivity()
    {
        cnt = 0;
    }

    public native int calc();

    public void onClick(View view)
    {
        if(flag == 1)
            return;
        flag = 1;
        ((TextView)findViewById(0x7f0c0052)).setText("");
        TextView textview = (TextView)findViewById(0x7f0c0050);
        TextView textview1 = (TextView)findViewById(0x7f0c0051);
        m = 0;
        n = (new Random()).nextInt(3);
        int i = n;
        textview1.setText((new String[] {
            "CPU: Paper", "CPU: Rock", "CPU: Scissors"
        })[i]);
        if(view == P)
        {
            textview.setText("YOU: Paper");
            m = 0;
        }
        if(view == r)
        {
            textview.setText("YOU: Rock");
            m = 1;
        }
        if(view == S)
        {
            textview.setText("YOU: Scissors");
            m = 2;
        }
        handler.postDelayed(showMessageTask, 1000L);
    }

    protected void onCreate(Bundle bundle)
    {
        super.onCreate(bundle);
        setContentView(0x7f040018);
        P = (Button)findViewById(0x7f0c004d);
        S = (Button)findViewById(0x7f0c004f);
        r = (Button)findViewById(0x7f0c004e);
        P.setOnClickListener(this);
        r.setOnClickListener(this);
        S.setOnClickListener(this);
        flag = 0;
    }

    Button P;
    Button S;
    int cnt;
    int flag;
    private final Handler handler = new Handler();
    int m;
    int n;
    Button r;
    private final Runnable showMessageTask = new Runnable() {

        public void run()
        {
            TextView textview = (TextView)findViewById(0x7f0c0052);
            if(n - m == 1)
            {
                MainActivity mainactivity = MainActivity.this;
                mainactivity.cnt = mainactivity.cnt + 1;
                textview.setText((new StringBuilder()).append("WIN! +").append(String.valueOf(cnt)).toString());
            } else
            if(m - n == 1)
            {
                cnt = 0;
                textview.setText("LOSE +0");
            } else
            if(m == n)
                textview.setText((new StringBuilder()).append("DRAW +").append(String.valueOf(cnt)).toString());
            else
            if(m < n)
            {
                cnt = 0;
                textview.setText("LOSE +0");
            } else
            {
                MainActivity mainactivity1 = MainActivity.this;
                mainactivity1.cnt = mainactivity1.cnt + 1;
                textview.setText((new StringBuilder()).append("WIN! +").append(String.valueOf(cnt)).toString());
            }
            if(1000 == cnt)
                textview.setText((new StringBuilder()).append("SECCON{").append(String.valueOf((cnt + calc()) * 107)).append("}").toString());
            flag = 0;
        }

        final MainActivity this$0;

            
            {
                this$0 = MainActivity.this;
                super();
            }
    };

    static 
    {
        System.loadLibrary("calc");
    }
}

onClickでじゃんけんが開始されてmとnとcntの値次第でflagが出力されるようです。 m=1,n=0,cnt=999をセットしてrun関数が実行できればcalcのディスアセンブルをしなくても答えが出そうです

Fridaですが既存のJavaメソッドに拡張を施すことができます。下のは公式で載せられているサンプルです。

Java.perform(function () {
    var Activity = Java.use("android.app.Activity");
    Activity.onResume.implementation = function () {
        send("onResume() got called! Let's call the original implementation");
        this.onResume();
    };
});

Java.performJavaを使うことを宣言し、Java.useで拡張したいJavaのクラスを設定、Activity.---.implementationでそのクラスの中の関数に拡張を施します。

エクスプロイトコードはcom.example.seccon2015.rock_paper_scissors.MainActivitymncntを変更したいので下のようになります

Java.perform(function () {
    // Function to hook is defined here
    var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');

    // Whenever button is clicked
    MainActivity.onClick.implementation = function (v) {
        // Show a message to know that the function got called
        send('onClick');

        // Call the original onClick handler
        this.onClick(v);

        // Set our values after running the original onClick handler
        this.m.value = 0;
        this.n.value = 1;
        this.cnt.value = 999;

        // Log to the console that it's done, and we should have the flag!
        console.log('Done:' + JSON.stringify(this.cnt));
    };

これで実行すると次の画面になります(スクショが死んでたのでsurfaceカメラですいません)

f:id:kataware8136:20171221134925j:plain

Fridaでできること

現状自分も調べてる真っ最中なのでまだまだできることはあると思いますがCTFですぐ使えそうなのが

  • Javaメソッドを拡張する
  • 実行中のメモリの中身を見る

あたりかなと....

終わり

ちなみにAndroid Hacking Event 2017 AES-Decrypt Writeup - ごちうさ民の覚え書きもFridaを使っています。個人的には苦渋を飲んでいるHelloDalvikもFridaでいけないかなと考えています。がんばります

明日はうさぎさんです!CBCTFの暗号問の解説ってすごい..

素晴らしい人の前日が自分ですいません.....よいCTFライフを送ってください!!