GoogleCTF 2020 android
GoogleCTF 2020 android
初見
アプリを開く
keys goes here
になんか入力しCHECK
を押して正解かどうかが判断してくれるようです,flagは正解のあと表示するか,入力内容はflagになるなのかの2通りが想定する.
解析
静的解析
apkをjadxに投げる同時にapktoolでばらします
- AndroidManifest.xmlをチェックし,ActivityやLayoutもそれぞれ一個しかいないことが確認
- jadxの結果にerrorで一番見たいところが見れないが,EditText,TextViewとButtonの定義はとにかく確認できた
- jadxからcom.googlectf.sandbox配下にクラス一個しかいないに対し,apktool方のは
ő$1.smali
とő.smali
2つのsmaliがあるが不自然でした - 念の為objectionのFRIDA-DEXdumpを使い,jadxから全部開いて確認したが,やはり結果は変わらなかった
動的解析
objection使って今読み込んだactivityやら,インスタンスやらを見てみた
adb shell su /data/local/tmp/frida-server &
まずroot済みのスマホ上にfrida-serverを起動する
objection -g com.google.ctf.sandbox explore android hooking list activities > com.google.ctf.sandbox.ő > Found 1 classes android hooking list class > ... > com.google.ctf.sandbox.ő > com.google.ctf.sandbox.ő$1 > ... android hooking list class_methods com.google.ctf.sandbox.ő > protected void com.google.ctf.sandbox.ő.onCreate(android.os.Bundle) > Found 1 method(s) android hooking list class_methods com.google.ctf.sandbox.ő$1 > public void com.google.ctf.sandbox.ő$1.onClick(android.view.View) > Found 1 method(s)
上の結果からボタンのonClick関数はどうやらő$1
の方にあることが判明.
そして他の関数が見かけないことからonClick関数の中に文字判定が含まれると睨んだ.
smaliをひたすら読む(罠に引っかかる)
そうすると,自分の中にő$1
の動作をわかるためにsmaliを読むしかなかったため,素直にドキュメントを観ながらő$1.smali
を読んでみた.
smaliはレジスターベースなので,読んでるうちにレジスター値の変化に対応しないといけません,自分はそれがなれなくて,膨大な時間をかかった
ő$1.smali
は概ね以下の3つの部分に構成された.
1. 長い文字列
2. なんかのfor文
3. 残りの判断ロジック
smaliの序盤は上のような操作の繰り返しとなり,配列にASCII文字をどんどん入れてることがわかる
それを全部復元したらApparently this is not the flag. What's going on?
という長さ49の文字列が発見したが,もちろんこれがflagでも正解でもなく,ただの罠だと思う(多分)
先に読み進むと上の内容が見られて,EditTextつまり入力欄に入っていた文字列の長さは48ぴったりではないとバツになるので,入力内容の長さは48で間違いないでしょう.
cond_2には48文字を4文字1ブロックに分けここではtmp_blockにする,tmp_blockの末尾(0x4番)から逆順で↑のbit演算が絡めた処理を行う.唯一の違いはそれぞれ0x18,0x10,0x8,0x0位左シフトすること.その結果を一つの配列に格納,そうすると長さ12の配列が出来上がり.
さらに読み進むと,配列の値に対して↑の処理を行う,ő.class
を見てみるとちょうどこの関数だけがうまくデコンパイルされて,それが↓のような関数でした.
public static long[] m0(long a, long b) { if (a == 0) { return new long[]{0, 1}; } long[] r = m0(b % a, a); return new long[]{r[1] - ((b / a) * r[0]), r[0]}; }
smaliの最後にinv
という文字列も見えたので,拡張ユークリッド法にすぐ気づき,配列の値に対して全部その逆を求め,入れ直された.
ここで一旦配列を探すことに切り替える.Android Studioを開き,[Attach Debugger to Android Process]をクリックし,問題アプリのプロセスを選択.手元にソースコードがないため,ブレークポイントが思うように設置できないけど,Exceptionの2箇所(?)にどうやら止めるらしいので,とりあえず入れる.
そこで入力欄に仮に'CTF{'+'a'*43+'}'
みたいな長さ48の答えを入れ,CHECK
を押すと,↓のような2つの配列が発見,ő.ő
は進むことに連れてどんどん値が更新される.そこで奇しくも最初の値が一致し,上の配列の先頭がCTF{
になり,フラグにほぼ確定.
this$0 = {ő@9697} class = {long[12]@9781} 0 = 40999019 1 = 2789358025 2 = 656272715 3 = 18374979 4 = 3237618335 5 = 1762529471 6 = 685548119 7 = 382114257 8 = 1436905469 9 = 2126016673 10 = 3318315423 11 = 797150821 ő = 0 ő.ő = {long[12]@9782} 0 = 40999019 1 = 1633771873 2 = 1627389952 3 = 0 4 = 0 5 = 0 6 = 0 7 = 0 8 = 0 9 = 0 10 = 0 11 = 0
更に,ő.smali
を確認したら,同様な配列の存在が確認でき,これでflag決定.
pythonに書き換える
javaが使い慣れていないため,ő$1.smali
をpythonコードに一旦書き換えて,解き方を考えるようにした.
input_array = 'CTF{'+'a'*43+'}' assert len(input_array) == 48 l = len(input_array) chunks = l // 4 idx = 0 # v5 sl = [0x271986b,0xa64239c9,0x271ded4b,0x1186143,0xc0fa229f,0x690e10bf,0x28dca257,0x16c699d1,0x55a56ffd,0x7eb870a1,0xc5c9799f,0x2f838e65] rl = [0]*12 while idx < chunks: tmp_a = ord(input_arrya[idx * 4 + 3]) << 0x18 v = tmp_a tmp_b = ord(input_arrya[idx * 4 + 2]) << 0x10 v |= tmp_b tmp_c = ord(input_arrya[idx * 4 + 1]) << 0x8 v |= tmp_c tmp_d = ord(input_arrya[idx * 4]) v |= tmp_d rl[idx] = v for x,y in zip(sl,rl): if x != inverse(y,0x100000000): print('❌') break else: print('🏁')
to solve
そうすると4文字ずつbruteforceすればよいので,あとは回すだけ.
from string import ascii_letters,digits,printable from Crypto.Util.number import inverse import itertools import json m = 0x100000000 # candi = ascii_letters + digits + '{_!?.}' candi = '{_!?. }@ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' # candi = printable target = [0x271986b,0xa64239c9,0x271ded4b,0x1186143,0xc0fa229f,0x690e10bf,0x28dca257,0x16c699d1,0x55a56ffd,0x7eb870a1,0xc5c9799f,0x2f838e65] def round(a,b,c,d): tmp_a = ord(a) << 0x18 v = tmp_a tmp_b = ord(b) << 0x10 v |= tmp_b tmp_c = ord(c) << 0x8 v |= tmp_c tmp_d = ord(d) v |= tmp_d return v flag = '' for tt in target[1:]: print(tt) for comb in itertools.product(candi,repeat=4): a,b,c,d = comb r = round(a,b,c,d) if inverse(r,m) == tt: tmp = d + c + b + a flag += tmp print(flag) break
感想
最近割とAndroidを勉強したものの,Smaliを読むことがなかったため,すごく勉強になりました. あと久しぶりに点を入れたので,解けてホッとした...