魚脳の池

CTF:Little Twoos

GoogleCTF 2020 android

GoogleCTF 2020 android

初見

アプリを開く


keys goes hereになんか入力しCHECKを押して正解かどうかが判断してくれるようです,flagは正解のあと表示するか,入力内容はflagになるなのかの2通りが想定する.

解析

静的解析

apkをjadxに投げる同時にapktoolでばらします

  1. AndroidManifest.xmlをチェックし,ActivityやLayoutもそれぞれ一個しかいないことが確認
  2. jadxの結果にerrorで一番見たいところが見れないが,EditText,TextViewとButtonの定義はとにかく確認できた
  3. jadxからcom.googlectf.sandbox配下にクラス一個しかいないに対し,apktool方のはő$1.smaliő.smali2つのsmaliがあるが不自然でした
  4. 念の為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.smalipythonコードに一旦書き換えて,解き方を考えるようにした.

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を読むことがなかったため,すごく勉強になりました. あと久しぶりに点を入れたので,解けてホッとした...