SECCON 2015 Online Writeup - Reverse-Engineering Android APK 1

Androidのコードを読み書きしたことはないけど,調べながら解くことができたのでWriteup.

問題

じゃんけんに1000回連続で勝ち続けよ

[添付]rps.apk

github.com

とりあえず手持ちの泥に入れて遊んでみる.グーチョキパーを選択すると相手も出してきて勝ち負けが判定される簡単なじゃんけんアプリだった.

解法

とりあえずコードを確認する

APKファイルはzipと同様に解凍できるのでunzip.Windowsでも拡張子をzipにすればダブルクリックで開ける.

root@vm:~/Downloads# unzip rps.apk -d apk1

root@vm:~/Downloads# ls -l apk1
合計 2780
-rw-r--r--  1 root root    1948 10月 16 11:32 AndroidManifest.xml
drwxr-xr-x  2 root root    4096 12月  6 15:53 META-INF
-rw-r--r--  1 root root 2646356 10月 16 11:32 classes.dex
drwxr-xr-x  6 root root    4096 12月  6 15:53 lib
drwxr-xr-x 27 root root    4096 12月  6 15:53 res
-rw-r--r--  1 root root  176708 10月  6 10:55 resources.arsc

class.dexというファイルにプログラムのメインが入っているので,取り出してclassファイルをjadファイルに.

root@vm:~/Downloads# cd apk1

root@vm:~/Downloads/apk1# unzip classes_dex2jar.jar -d classes

root@vm:~/Downloads/apk1# ls -l classes
合計 8
drwxr-xr-x 3 root root 4096 12月  6 15:54 android
drwxr-xr-x 3 root root 4096 12月  6 15:54 com

root@vm:~/Downloads/apk1# cd classes/com/example/seccon2015/rock_paper_scissors/

root@vm:~/Downloads/apk1/classes/com/example/seccon2015/rock_paper_scissors# ls
BuildConfig.class     R$bool.class      R$integer.class  R$styleable.class
MainActivity$1.class  R$color.class     R$layout.class   R.class
MainActivity.class    R$dimen.class     R$mipmap.class
R$anim.class          R$drawable.class  R$string.class
R$attr.class          R$id.class        R$style.class


root@vm:~/Downloads/apk1/classes/com/example/seccon2015/rock_paper_scissors# jad *.class

root@vm:~/Downloads/apk1/classes/com/example/seccon2015/rock_paper_scissors# ls 
BuildConfig.class     R$anim.class   R$drawable.class  R$string.class
BuildConfig.jad       R$attr.class   R$id.class        R$style.class
MainActivity$1.class  R$bool.class   R$integer.class   R$styleable.class
MainActivity.class    R$color.class  R$layout.class    R.class
MainActivity.jad      R$dimen.class  R$mipmap.class    R.jad```

それっぽい名前のMainActivity.jadを開くとそれっぽいif文がある.

if(1000 == cnt)
                textview.setText((new StringBuilder()).append("SECCON{").append(String.valueOf(107 * (cnt + calc()))).append("}").toString());

この中のcalc()はプログラム中に以下のようにありライブラリに記載されてるようだ.(JNI?)

public native int calc();
static 
    {
        System.loadLibrary("calc");
    }

ライブラリを読む

ライブラリはlibディレクトリに入っているのでファイルを探し逆アセンブルする

root@vm:~/Downloads/apk1/classes/com/example/seccon2015/rock_paper_scissors# cd ~/Downloads/apk1/

root@vm:~/Downloads/apk1# ls lib/
armeabi/     armeabi-v7a/ mips/        x86/         

root@vm:~/Downloads/apk1# cd lib/x86/

root@vm:~/Downloads/apk1/lib/x86# ls
libcalc.so

root@vm:~/Downloads/apk1/lib/x86# objdump -d libcalc.so 

libcalc.so:     ファイル形式 elf32-i386
(略)
00000400 <Java_com_example_seccon2015_rock_1paper_1scissors_MainActivity_calc>:
 400:   b8 07 00 00 00          mov    $0x7,%eax
 405:   c3                      ret    

0x7を返すだけみたいなので107*(1000+7)を計算してSECCON{}で括って終わり.

おまけ:実はアセンブラを読まずにチートした

実はライブラリを逆アセンブラしたあと,目的の2行のアセンブラに気付く前に「アセンブラだ!逃げろ!」とそっとじした. というわけでライブラリを読まずに投げたあと実際に解いた手法を次に記す.

apktoolでデコンパイルする

問題プログラムをapktoolでデコンパイルしsmaliファイルを確認する.

tanakaxa@vm:~/Downloads$ apktool d rps.apk 

tanakaxa@vm:~/Downloads$ cd rps/
lib/      original/ res/      smali/    

tanakaxa@vm:~/Downloads$ cd rps/smali/com/example/seccon2015/rock_paper_scissors/

tanakaxa@vm:~/Downloads/rps/smali/com/example/seccon2015/rock_paper_scissors$ ls
BuildConfig.smali     R$attr.smali   R$drawable.smali  R$mipmap.smali     R.smali
MainActivity$1.smali  R$bool.smali   R$id.smali        R$string.smali
MainActivity.smali    R$color.smali  R$integer.smali   R$style.smali
R$anim.smali          R$dimen.smali  R$layout.smali    R$styleable.smali

プログラムを書き換える

MainActivity$1.smaliにそれっぽいコードがあった. そこで分岐条件に使われるレジスタに1と,cntの値が入るレジスタに1000が入るように書き換えた.

    .line 49
    :goto_0
    const/16 v1, 0x3e8 //ここ

    iget-object v2, p0, Lcom/example/seccon2015/rock_paper_scissors/MainActivity$1;->this$0:Lcom/example/seccon2015/rock_paper_scissors/MainActivity;

    iget v2, v2, Lcom/example/seccon2015/rock_paper_scissors/MainActivity;->cnt:I

    if-ne v1, v2, :cond_0

    .line 50
    new-instance v1, Ljava/lang/StringBuilder;

    invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V

    const-string v2, "SECCON{"

    invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v1

    iget-object v2, p0, Lcom/example/seccon2015/rock_paper_scissors/MainActivity$1;->this$0:Lcom/example/seccon2015/rock_paper_scissors/MainActivity;

    iget v2, v2, Lcom/example/seccon2015/rock_paper_scissors/MainActivity;->cnt:I //ここ

    iget-object v3, p0, Lcom/example/seccon2015/rock_paper_scissors/MainActivity$1;->this$0:Lcom/example/seccon2015/rock_paper_scissors/MainActivity;

    invoke-virtual {v3}, Lcom/example/seccon2015/rock_paper_scissors/MainActivity;->calc()I

    move-result v3

    add-int/2addr v2, v3

    mul-int/lit8 v2, v2, 0x6b

    invoke-static {v2}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;

    move-result-object v2

    invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v1

    const-string v2, "}"

0x3e8(1000)といかにもな数字で分岐に使われていそうなので0x1(1)に変更.

const/16 v1, 0x3e8const/16 v1, 0x1

cntという文字が終盤にあり,v2にcntの値を入れようとしているっぽいのでv2に1000を代入.

iget v2, v2, Lcom/example/seccon2015/rock_paper_scissors/MainActivity;->cnt:I
↓
const/16 v2, 0x3e8

apktoolでコンパイルする

tanakaxa@vm:~/Downloads/rps/smali/com/example/seccon2015/rock_paper_scissors$ cd ~/Download

tanakaxa@vm:~/Downloads$ apktool b rps

tanakaxa@vm:~/Downloads$ cd rps/dist/

tanakaxa@vm:~/Downloads/rps/dist$ ls
rps.apk

実機で動かせるように署名する

実機で動かすにはapkファイルに署名をしなければならない. 鍵を生成して署名.

tanakaxa@vm:~/Downloads/rps/dist$ keytool -genkey -alias hoge -keypass password -keystore hoge.keystore -storepass password -validity 10000

tanakaxa@vm:~/Downloads/rps/dist$ jarsigner -keystore hoge.keystore -storepass password rps.apk hoge

実機にインストールして実行

あとは実機にコピーしてインストール.

じゃんけんで1回勝つとフラグが出る.

f:id:tanakaxa:20151207114959p:plain

終わりに

嫌いなことから逃げ出してはいけないということを学んだ.

参考

apkファイルを端末に入れたりデコンパイルして新しくapkファイルを作成する | KentaKomai Blog

  • smaliファイルのオペコードについて

Dalvik opcodes