この記事は、2019年5月27日に旧ブログに投稿したものです。
チームsecurity_gangにABCとして参加しました。チーム全体の順位は33位(2270点)、個人の順位は115位(803点)。
[Reversing] Linear Operation (293)
基本ブロックの数が凄いのでangrを使います。IDAのグラフを見るとcorrectとwrongで基本ブロックが分かれています。 そのアドレスをもとに、http://inaz2.hatenablog.com/entry/2016/03/16/190756やhttps://qiita.com/RKX1209/items/a514e5727119796e9fd5を参考にスクリプトを書きました。
1import angr
2
3p = angr.Project('linear_operation')
4
5ok_addr = 0x40cf78
6ng_addr = 0x40cf86
7
8state = p.factory.entry_state()
9m = p.factory.simulation_manager(state)
10m.explore(find=(ok_addr), avoid=(ng_addr))
11
12print(m.found[0].posix.dumps(0))
ctf4b{5ymbol1c_3xecuti0n_1s_3ffect1ve_4ga1nst_l1n34r_0p3r4ti0n}
[Reversing] linkage (186)
IDAでmainから辿ってみると、is_correctというサブルーチンがあります。それを見てみると、何らかの文字数が34文字の場合、何らかの処理が行われる事がわかります。 おそらく入力値でしょう。
1.text:00000000004005FA call _strlen
2.text:00000000004005FF cmp rax, 22h
3.text:0000000000400603 jz short loc_40060C
34文字の場合に何が行われるのかを見ていくと、loc_400615の基本ブロックにおいて、convertサブルーチンが実行された後にそれと何らかの比較を行っている事がわかります。
1.text:0000000000400643 cmp [rbp+var_5], al
2.text:0000000000400646 jz short loc_40064F
そこで、gdbで0x400643
にbreakpointを張り、34文字の入力値を与え実行してみます。そして、breakpointにおける$rbp-0x5
と$al
を見てみます。
1(gdb) b *0x400643
2Breakpoint 1 at 0x400643
3(gdb) r AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
4Starting program: ~/leakage_80a8c3c2bd63254a033ea21093944b1e/leakage AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
5
6Breakpoint 1, 0x0000000000400643 in is_correct ()
7(gdb) x/s $rbp-0x5
80x7fffffffe0eb: "c"
9(gdb) p/x $al
10$1 = 0x41
$rbp-0x5
に1文字目のflagが、$al
に入力値の1文字目が代入されているようです。
IDAのグラフを見てみると、この箇所はループになっており、$rbp-0x5
と$al
が等しくない場合はループを抜けるようになっています。ゆえに、ループされるたびに0x400643
にて$rbp-0x5
を見ればフラグが入手できそうです。
そこで、無理矢理ループを行うために、ゼロフラグを強制的に有効にします。gdb zf
でググればその方法がヒットするので、それを繰り返し行います。
1(gdb) ni
20x0000000000400646 in is_correct ()
3(gdb) set $eflags |= (1 << 6)
4(gdb) c
5Continuing.
6
7Breakpoint 1, 0x0000000000400643 in is_correct ()
8(gdb) b *0x0000000000400646
9Breakpoint 2 at 0x400646
10(gdb) del 1
11(gdb) x/s $rbp-0x5
120x7fffffffe0eb: "t\001"
13(gdb) set $eflags |= (1 << 6)
14(gdb) c
15Continuing.
16
17Breakpoint 2, 0x0000000000400646 in is_correct ()
18(gdb) x/s $rbp-0x5
190x7fffffffe0eb: "t\001"
20(gdb) set $eflags |= (1 << 6)
21(gdb) c
22Continuing.
23
24Breakpoint 2, 0x0000000000400646 in is_correct ()
25(gdb) x/s $rbp-0x5
260x7fffffffe0eb: "f\002"
x/sはNULL文字に到達するまで出力するため一部ゴミが含まれますが、最初の一文字以外は無視して構いません。
これを34回繰り返せばflagが入手できます。ctf4b{le4k1ng_th3_f1ag_0ne_by_0ne}
[Misc] Dump (138)
Wiresharkを用いてTCPストリームを追跡すると、webshellを行った形跡が見つかります。 1番目のTCPストリームには何らかのデータがダンプされているようです。 そこで、そのストリームを保存し、ヘッダのエスケープ文字を復元してみます。
1>> decodeURIComponent("/webshell.php?cmd=hexdump%20%2De%20%2716%2F1%20%22%2502%2E3o%20%22%20%22%5Cn%22%27%20%2Fhome%2Fctf4b%2Fflag")
2<- "/webshell.php?cmd=hexdump -e '16/1 \"%02.3o \" \"\\n\"' /home/ctf4b/flag"
どうもこのデータは、/home/ctf4b/flag
を8進数でダンプしたものらしいです。
そこで、ダンプデータのみを抽出し、それをバイナリに変換します。
1$ cat data
2037 213 010 000 012 325 251 134 000 003 354 375 007 124 023 133
3327 007 214 117 350 115 272 110 047 012 212 122 223 320 022 252
4(以下略)
1data = ""
2with open("data", "r") as f:
3 for _ in f.readlines():
4 data += _.replace("\n", " ")
5with open("output", "wb") as f:
6 f.write(bytearray(map(lambda x: int(x, 8), data.split(" ")[:-1])))
するとtar.gz形式のoutputが出力されます。それを解凍するとflagが書かれたflag.jpgが展開されます。
ctf4b{hexdump_is_very_useful}
[Crypto] So Tired (115)
解凍するとbase64のテキストファイルが展開され、それをデコードするとzlibファイルが生成され…が繰り返されます。
なのでそれを自動化します。私は2回目のzlibファイルが生産された時点でこのスクリプトを書いたため、encrypt.txt
ではなくzlibのファイルがsrc
となっています。
1import zlib
2import base64
3
4src = open('file', 'rb').read()
5
6while (True):
7 try:
8 decompressed = zlib.decompress(src)
9 src = base64.b64decode(decompressed)
10 except:
11 zlib.decompress(src)
12 f = open('result', 'wb')
13 f.write(decompressed)
14 f.close()
15 break
1$ cat encrypted.txt | base64 -d > file
2$ python solve.py
3$ cat result
4ctf4b{very_l0ng_l0ng_BASE64_3nc0ding}
[Misc] containers (71)
バイナリエディタで見てみると、pngファイルのヘッダが複数現れています。
データ 抽出 png
でググると、DataCutterというWindows用のソフトウェアがヒットしたため、Wineでそれを実行してpngを抽出しました。
最後の方にあったフラグ検証のpyファイルは抽出できなかったので、手動でコピペしました。