SECCON 2015 Online Writeup - Reverse-Engineering Android APK 1
Androidのコードを読み書きしたことはないけど,調べながら解くことができたのでWriteup.
問題
じゃんけんに1000回連続で勝ち続けよ
[添付]rps.apk
とりあえず手持ちの泥に入れて遊んでみる.グーチョキパーを選択すると相手も出してきて勝ち負けが判定される簡単なじゃんけんアプリだった.
解法
とりあえずコードを確認する
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, 0x3e8 ↓ const/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回勝つとフラグが出る.
終わりに
嫌いなことから逃げ出してはいけないということを学んだ.
参考
apkファイルを端末に入れたりデコンパイルして新しくapkファイルを作成する | KentaKomai Blog
- smaliファイルのオペコードについて