• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Gallopsled / pwntools / 5d18b41e5399f67b223feb3fdd6a8fceae155c30

pending completion
5d18b41e5399f67b223feb3fdd6a8fceae155c30

push

github-actions

web-flow
Merge branch 'stable' into ssh_log_error

3878 of 6371 branches covered (60.87%)

12199 of 16604 relevant lines covered (73.47%)

0.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.16
/pwnlib/rop/rop.py
1
r"""
2
Return Oriented Programming
3

4
Manual ROP
5
-------------------
6

7
The ROP tool can be used to build stacks pretty trivially.
8
Let's create a fake binary which has some symbols which might
9
have been useful.
10

11
    >>> context.clear(arch='i386')
12
    >>> binary = ELF.from_assembly('add esp, 0x10; ret; pop eax; ret; pop ecx; pop ebx; ret')
13
    >>> binary.symbols = {'read': 0xdeadbeef, 'write': 0xdecafbad, 'execve': 0xcafebabe, 'exit': 0xfeedface}
14

15
Creating a ROP object which looks up symbols in the binary is pretty straightforward.
16

17
    >>> rop = ROP(binary)
18

19
Once to ROP object has been loaded, you can trivially find gadgets, by using magic properties on the ``ROP`` object.  
20
Each :class:`Gadget` has an ``address`` property which has the real address as well.
21

22
    >>> rop.eax
23
    Gadget(0x10000004, ['pop eax', 'ret'], ['eax'], 0x8)
24
    >>> hex(rop.eax.address)
25
    '0x10000004'
26

27
Other, more complicated gadgets also happen magically
28

29
    >>> rop.ecx
30
    Gadget(0x10000006, ['pop ecx', 'pop ebx', 'ret'], ['ecx', 'ebx'], 0xc)
31

32
The easiest way to set up individual registers is to invoke the ``ROP`` object as a callable, with the registers as arguments.
33
This has the benefit of using multi-pop gadgets to set multiple registers with one gadget.
34
    
35
    >>> rop(eax=0x11111111, ecx=0x22222222)
36

37
Setting register values this way accounts for padding and extra registers which are popped off the stack.
38
Values which are filled with garbage (i.e. are not used) are filled with the :func:`cyclic` pattern
39
which corresponds to their offset, which is useful when debuggging your exploit.
40

41
    >>> print(rop.dump())
42
    0x0000:       0x10000006 pop ecx; pop ebx; ret
43
    0x0004:       0x22222222
44
    0x0008:          b'caaa' <pad ebx>
45
    0x000c:       0x10000004 pop eax; ret
46
    0x0010:       0x11111111
47

48

49
If you really want to set one register at a time, you can also use the assignment form.
50
It's generally advised to use the rop(eax=..., ecx=...) form, since there may be an
51
e.g. ``pop eax; pop ecx; ret`` gadget that can be taken advantage of.
52

53
    >>> rop = ROP(binary)
54
    >>> rop.eax = 0xdeadf00d
55
    >>> rop.ecx = 0xc01dbeef
56
    >>> rop.raw(0xffffffff)
57
    >>> print(rop.dump())
58
    0x0000:       0x10000004 pop eax; ret
59
    0x0004:       0xdeadf00d
60
    0x0008:       0x10000006 pop ecx; pop ebx; ret
61
    0x000c:       0xc01dbeef
62
    0x0010:          b'eaaa' <pad ebx>
63
    0x0014:       0xffffffff
64

65
If you just want to FIND a ROP gadget, you can access them as a property on the ``ROP``
66
object by register name.
67

68
    >>> rop = ROP(binary)
69
    >>> rop.eax
70
    Gadget(0x10000004, ['pop eax', 'ret'], ['eax'], 0x8)
71
    >>> hex(rop.eax.address)
72
    '0x10000004'
73
    >>> rop.raw(rop.eax)
74
    >>> rop.raw(0x12345678)
75
    >>> print(rop.dump())
76
    0x0000:       0x10000004 pop eax; ret
77
    0x0004:       0x12345678
78

79
Let's re-create our ROP object now to show for some other examples.:
80

81
    >>> rop = ROP(binary)
82

83
With the ROP object, you can manually add stack frames.
84

85
    >>> rop.raw(0)
86
    >>> rop.raw(unpack(b'abcd'))
87
    >>> rop.raw(2)
88

89
Inspecting the ROP stack is easy, and laid out in an easy-to-read
90
manner.
91

92
    >>> print(rop.dump())
93
    0x0000:              0x0
94
    0x0004:       0x64636261
95
    0x0008:              0x2
96

97
The ROP module is also aware of how to make function calls with
98
standard Linux ABIs.
99

100
    >>> rop.call('read', [4,5,6])
101
    >>> print(rop.dump())
102
    0x0000:              0x0
103
    0x0004:       0x64636261
104
    0x0008:              0x2
105
    0x000c:       0xdeadbeef read(4, 5, 6)
106
    0x0010:          b'eaaa' <return address>
107
    0x0014:              0x4 arg0
108
    0x0018:              0x5 arg1
109
    0x001c:              0x6 arg2
110

111
You can also use a shorthand to invoke calls.
112
The stack is automatically adjusted for the next frame
113

114
    >>> rop.write(7,8,9)
115
    >>> rop.exit()
116
    >>> print(rop.dump())
117
    0x0000:              0x0
118
    0x0004:       0x64636261
119
    0x0008:              0x2
120
    0x000c:       0xdeadbeef read(4, 5, 6)
121
    0x0010:       0x10000000 <adjust @0x24> add esp, 0x10; ret
122
    0x0014:              0x4 arg0
123
    0x0018:              0x5 arg1
124
    0x001c:              0x6 arg2
125
    0x0020:          b'iaaa' <pad>
126
    0x0024:       0xdecafbad write(7, 8, 9)
127
    0x0028:       0xfeedface exit()
128
    0x002c:              0x7 arg0
129
    0x0030:              0x8 arg1
130
    0x0034:              0x9 arg2
131

132
You can also append complex arguments onto stack when the stack pointer is known.
133

134
    >>> rop = ROP(binary, base=0x7fffe000)
135
    >>> rop.call('execve', [b'/bin/sh', [[b'/bin/sh'], [b'-p'], [b'-c'], [b'ls']], 0])
136
    >>> print(rop.dump())
137
    0x7fffe000:       0xcafebabe execve([b'/bin/sh'], [[b'/bin/sh'], [b'-p'], [b'-c'], [b'ls']], 0)
138
    0x7fffe004:          b'baaa' <return address>
139
    0x7fffe008:       0x7fffe014 arg0 (+0xc)
140
    0x7fffe00c:       0x7fffe01c arg1 (+0x10)
141
    0x7fffe010:              0x0 arg2
142
    0x7fffe014:   b'/bin/sh\x00'
143
    0x7fffe01c:       0x7fffe02c (+0x10)
144
    0x7fffe020:       0x7fffe034 (+0x14)
145
    0x7fffe024:       0x7fffe038 (+0x14)
146
    0x7fffe028:       0x7fffe03c (+0x14)
147
    0x7fffe02c:   b'/bin/sh\x00'
148
    0x7fffe034:       b'-p\x00$'
149
    0x7fffe038:       b'-c\x00$'
150
    0x7fffe03c:       b'ls\x00$'
151

152
ROP also detects 'jmp $sp' gadget to help exploit binaries with NX disabled.
153
You can get this gadget on 'i386':
154

155
    >>> context.clear(arch='i386')
156
    >>> elf = ELF.from_assembly('nop; jmp esp; ret')
157
    >>> rop = ROP(elf)
158
    >>> jmp_gadget = rop.jmp_esp
159
    >>> elf.read(jmp_gadget.address, 2) == asm('jmp esp')
160
    True
161

162
You can also get this gadget on 'amd64':
163

164
    >>> context.clear(arch='amd64')
165
    >>> elf = ELF.from_assembly('nop; jmp rsp; ret')
166
    >>> rop = ROP(elf)
167
    >>> jmp_gadget = rop.jmp_rsp
168
    >>> elf.read(jmp_gadget.address, 2) == asm('jmp rsp')
169
    True
170

171
Gadgets whose address has badchar are filtered out:
172

173
    >>> context.clear(arch='i386')
174
    >>> elf = ELF.from_assembly('nop; pop eax; jmp esp; int 0x80; jmp esp; ret')
175
    >>> rop = ROP(elf, badchars=b'\x02')
176
    >>> jmp_gadget = rop.jmp_esp    # It returns the second gadget
177
    >>> elf.read(jmp_gadget.address, 2) == asm('jmp esp')
178
    True
179
    >>> rop = ROP(elf, badchars=b'\x02\x06')
180
    >>> rop.jmp_esp == None         # The address of both gadgets has badchar
181
    True
182

183
ROP Example
184
-------------------
185

186
Let's assume we have a trivial binary that just reads some data
187
onto the stack, and returns.
188

189
    >>> context.clear(arch='i386')
190
    >>> c = constants
191
    >>> assembly =  'read:'      + shellcraft.read(c.STDIN_FILENO, 'esp', 1024)
192
    >>> assembly += 'ret\n'
193

194
Let's provide some simple gadgets:
195

196
    >>> assembly += 'add_esp: add esp, 0x10; ret\n'
197

198
And perhaps a nice "write" function.
199

200
    >>> assembly += 'write: enter 0,0\n'
201
    >>> assembly += '    mov ebx, [ebp+4+4]\n'
202
    >>> assembly += '    mov ecx, [ebp+4+8]\n'
203
    >>> assembly += '    mov edx, [ebp+4+12]\n'
204
    >>> assembly += shellcraft.write('ebx', 'ecx', 'edx')
205
    >>> assembly += '    leave\n'
206
    >>> assembly += '    ret\n'
207
    >>> assembly += 'flag: .asciz "The flag"\n'
208

209
And a way to exit cleanly.
210

211
    >>> assembly += 'exit: ' + shellcraft.exit(0)
212
    >>> binary   = ELF.from_assembly(assembly)
213

214
Finally, let's build our ROP stack
215

216
    >>> rop = ROP(binary)
217
    >>> rop.write(c.STDOUT_FILENO, binary.symbols['flag'], 8)
218
    >>> rop.exit()
219
    >>> print(rop.dump())
220
    0x0000:       0x10000012 write(STDOUT_FILENO, 0x10000026, 8)
221
    0x0004:       0x1000002f exit()
222
    0x0008:              0x1 STDOUT_FILENO
223
    0x000c:       0x10000026 flag
224
    0x0010:              0x8 arg2
225

226
The raw data from the ROP stack is available via `r.chain()` (or `bytes(r)`).
227

228
    >>> raw_rop = rop.chain()
229
    >>> print(enhex(raw_rop))
230
    120000102f000010010000002600001008000000
231

232
Let's try it out!
233

234
    >>> p = process(binary.path)
235
    >>> p.send(raw_rop)
236
    >>> print(repr(p.recvall(timeout=5)))
237
    b'The flag'
238

239
ROP Example (amd64)
240
-------------------
241

242
For amd64 binaries, the registers are loaded off the stack.  Pwntools can do
243
basic reasoning about simple "pop; pop; add; ret"-style gadgets, and satisfy
244
requirements so that everything "just works".
245

246
    >>> context.clear(arch='amd64')
247
    >>> assembly = 'pop rdx; pop rdi; pop rsi; add rsp, 0x20; ret; target: ret'
248
    >>> binary = ELF.from_assembly(assembly)
249
    >>> rop = ROP(binary)
250
    >>> rop.target(1,2,3)
251
    >>> print(rop.dump())
252
    0x0000:       0x10000000 pop rdx; pop rdi; pop rsi; add rsp, 0x20; ret
253
    0x0008:              0x3 [arg2] rdx = 3
254
    0x0010:              0x1 [arg0] rdi = 1
255
    0x0018:              0x2 [arg1] rsi = 2
256
    0x0020:      b'iaaajaaa' <pad 0x20>
257
    0x0028:      b'kaaalaaa' <pad 0x18>
258
    0x0030:      b'maaanaaa' <pad 0x10>
259
    0x0038:      b'oaaapaaa' <pad 0x8>
260
    0x0040:       0x10000008 target
261
    >>> rop.target(1)
262
    >>> print(rop.dump())
263
    0x0000:       0x10000000 pop rdx; pop rdi; pop rsi; add rsp, 0x20; ret
264
    0x0008:              0x3 [arg2] rdx = 3
265
    0x0010:              0x1 [arg0] rdi = 1
266
    0x0018:              0x2 [arg1] rsi = 2
267
    0x0020:      b'iaaajaaa' <pad 0x20>
268
    0x0028:      b'kaaalaaa' <pad 0x18>
269
    0x0030:      b'maaanaaa' <pad 0x10>
270
    0x0038:      b'oaaapaaa' <pad 0x8>
271
    0x0040:       0x10000008 target
272
    0x0048:       0x10000001 pop rdi; pop rsi; add rsp, 0x20; ret
273
    0x0050:              0x1 [arg0] rdi = 1
274
    0x0058:      b'waaaxaaa' <pad rsi>
275
    0x0060:      b'yaaazaab' <pad 0x20>
276
    0x0068:      b'baabcaab' <pad 0x18>
277
    0x0070:      b'daabeaab' <pad 0x10>
278
    0x0078:      b'faabgaab' <pad 0x8>
279
    0x0080:       0x10000008 target
280

281
Pwntools will also filter out some bad instructions while setting the registers
282
( e.g. syscall, int 0x80... )
283

284
    >>> assembly = 'syscall; pop rdx; pop rsi; ret ; pop rdi ; int 0x80; pop rsi; pop rdx; ret ; pop rdi ; ret'
285
    >>> binary = ELF.from_assembly(assembly)
286
    >>> rop = ROP(binary)
287
    >>> rop.call(0xdeadbeef, [1, 2, 3])
288
    >>> print(rop.dump())
289
    0x0000:       0x1000000b pop rdi; ret
290
    0x0008:              0x1 [arg0] rdi = 1
291
    0x0010:       0x10000002 pop rdx; pop rsi; ret
292
    0x0018:              0x3 [arg2] rdx = 3
293
    0x0020:              0x2 [arg1] rsi = 2
294
    0x0028:       0xdeadbeef
295

296
ROP + Sigreturn
297
-----------------------
298

299
In some cases, control of the desired register is not available.
300
However, if you have control of the stack, EAX, and can find a
301
`int 0x80` gadget, you can use sigreturn.
302

303
Even better, this happens automagically.
304

305
Our example binary will read some data onto the stack, and
306
not do anything else interesting.
307

308
    >>> context.clear(arch='i386')
309
    >>> c = constants
310
    >>> assembly =  'read:'      + shellcraft.read(c.STDIN_FILENO, 'esp', 1024)
311
    >>> assembly += 'ret\n'
312
    >>> assembly += 'pop eax; ret\n'
313
    >>> assembly += 'int 0x80\n'
314
    >>> assembly += 'binsh: .asciz "/bin/sh"'
315
    >>> binary    = ELF.from_assembly(assembly)
316

317
Let's create a ROP object and invoke the call.
318

319
    >>> context.kernel = 'amd64'
320
    >>> rop   = ROP(binary)
321
    >>> binsh = binary.symbols['binsh']
322
    >>> rop.execve(binsh, 0, 0)
323

324
That's all there is to it.
325

326
    >>> print(rop.dump())
327
    0x0000:       0x1000000e pop eax; ret
328
    0x0004:             0x77 [arg0] eax = SYS_sigreturn
329
    0x0008:       0x1000000b int 0x80; ret
330
    0x000c:              0x0 gs
331
    0x0010:              0x0 fs
332
    0x0014:              0x0 es
333
    0x0018:              0x0 ds
334
    0x001c:              0x0 edi
335
    0x0020:              0x0 esi
336
    0x0024:              0x0 ebp
337
    0x0028:              0x0 esp
338
    0x002c:       0x10000012 ebx = binsh
339
    0x0030:              0x0 edx
340
    0x0034:              0x0 ecx
341
    0x0038:              0xb eax = SYS_execve
342
    0x003c:              0x0 trapno
343
    0x0040:              0x0 err
344
    0x0044:       0x1000000b int 0x80; ret
345
    0x0048:             0x23 cs
346
    0x004c:              0x0 eflags
347
    0x0050:              0x0 esp_at_signal
348
    0x0054:             0x2b ss
349
    0x0058:              0x0 fpstate
350

351
Let's try it out!
352

353
    >>> p = process(binary.path)
354
    >>> p.send(rop.chain())
355
    >>> time.sleep(1)
356
    >>> p.sendline(b'echo hello; exit')
357
    >>> p.recvline()
358
    b'hello\n'
359
"""
360
from __future__ import absolute_import
1✔
361
from __future__ import division
1✔
362

363
import collections
1✔
364
import copy
1✔
365
import hashlib
1✔
366
import itertools
1✔
367
import os
1✔
368
import re
1✔
369
import shutil
1✔
370
import six
1✔
371
import string
1✔
372
import struct
1✔
373
import sys
1✔
374
import tempfile
1✔
375

376
from pwnlib import abi
1✔
377
from pwnlib import constants
1✔
378
from pwnlib.context import LocalContext
1✔
379
from pwnlib.context import context
1✔
380
from pwnlib.elf import ELF
1✔
381
from pwnlib.log import getLogger
1✔
382
from pwnlib.rop import srop
1✔
383
from . import ret2dlresolve
1✔
384
from pwnlib.rop.call import AppendedArgument
1✔
385
from pwnlib.rop.call import Call
1✔
386
from pwnlib.rop.call import CurrentStackPointer
1✔
387
from pwnlib.rop.call import NextGadgetAddress
1✔
388
from pwnlib.rop.call import StackAdjustment
1✔
389
from pwnlib.rop.call import Unresolved
1✔
390
from pwnlib.rop.gadgets import Gadget
1✔
391
from pwnlib.util import lists
1✔
392
from pwnlib.util import packing
1✔
393
from pwnlib.util.cyclic import cyclic
1✔
394
from pwnlib.util.packing import pack
1✔
395
from pwnlib.util.misc import python_2_bytes_compatible
1✔
396

397
log = getLogger(__name__)
1✔
398
__all__ = ['ROP']
1✔
399

400
enums = Call, constants.Constant
1✔
401
try:
1✔
402
    from enum import Enum
1✔
403
except ImportError:
×
404
    pass
×
405
else:
406
    enums += Enum,
1✔
407

408
class Padding(object):
1✔
409
    """
410
    Placeholder for exactly one pointer-width of padding.
411
    """
412
    def __init__(self, name='<pad>'):
1✔
413
        self.name = name
1✔
414

415
def _slot_len(x):
1✔
416
    if isinstance(x, six.integer_types+(Unresolved, Padding, Gadget)):
1✔
417
        return context.bytes
1✔
418
    else:
419
        return len(packing.flat(x))
1✔
420

421
class DescriptiveStack(list):
1✔
422
    """
423
    List of resolved ROP gadgets that correspond to the ROP calls that
424
    the user has specified.
425
    """
426

427
    #: Base address
428
    address = 0
1✔
429

430
    #: Dictionary of \`{address: [list of descriptions]}`
431
    descriptions = {}
1✔
432

433
    def __init__(self, address):
1✔
434
        self.descriptions = collections.defaultdict(list)
1✔
435
        self.address      = address or 0
1✔
436
        self._next_next   = 0
1✔
437
        self._next_last   = 0
1✔
438

439
    @property
1✔
440
    def next(self):
441
        for x in self[self._next_last:]:
1✔
442
            self._next_next += _slot_len(x)
1✔
443
        self._next_last = len(self)
1✔
444
        return self.address + self._next_next
1✔
445

446
    def describe(self, text, address = None):
1✔
447
        if address is None:
1✔
448
            address = self.next
1✔
449
        self.descriptions[address] = text
1✔
450

451
    def dump(self):
1✔
452
        rv = []
1✔
453
        addr = self.address
1✔
454
        for i, data in enumerate(self):
1✔
455
            off = None
1✔
456
            line = '0x%04x:' % addr
1✔
457
            if isinstance(data, (str, bytes)):
1✔
458
                line += ' %16r' % data
1✔
459
            elif isinstance(data, six.integer_types):
1!
460
                line += ' %#16x' % data
1✔
461
                if self.address != 0 and self.address < data < self.next:
1✔
462
                    off = data - addr
1✔
463
            else:
464
                log.error("Don't know how to dump %r" % data)
×
465
            desc = self.descriptions.get(addr, '')
1✔
466
            if desc:
1✔
467
                line += ' %s' % desc
1✔
468
            if off is not None:
1✔
469
                line += ' (+%#x)' % off
1✔
470
            rv.append(line)
1✔
471
            addr += _slot_len(data)
1✔
472

473
        return '\n'.join(rv)
1✔
474

475

476
@python_2_bytes_compatible
1✔
477
class ROP(object):
1✔
478
    r"""Class which simplifies the generation of ROP-chains.
479

480
    Example:
481

482
    .. code-block:: python
483

484
       elf = ELF('ropasaurusrex')
485
       rop = ROP(elf)
486
       rop.read(0, elf.bss(0x80))
487
       rop.dump()
488
       # ['0x0000:        0x80482fc (read)',
489
       #  '0x0004:       0xdeadbeef',
490
       #  '0x0008:              0x0',
491
       #  '0x000c:        0x80496a8']
492
       bytes(rop)
493
       # '\xfc\x82\x04\x08\xef\xbe\xad\xde\x00\x00\x00\x00\xa8\x96\x04\x08'
494

495
    >>> context.clear(arch = "i386", kernel = 'amd64')
496
    >>> assembly = 'int 0x80; ret; add esp, 0x10; ret; pop eax; ret'
497
    >>> e = ELF.from_assembly(assembly)
498
    >>> e.symbols['funcname'] = e.entry + 0x1234
499
    >>> r = ROP(e)
500
    >>> r.funcname(1, 2)
501
    >>> r.funcname(3)
502
    >>> r.execve(4, 5, 6)
503
    >>> print(r.dump())
504
    0x0000:       0x10001234 funcname(1, 2)
505
    0x0004:       0x10000003 <adjust @0x18> add esp, 0x10; ret
506
    0x0008:              0x1 arg0
507
    0x000c:              0x2 arg1
508
    0x0010:          b'eaaa' <pad>
509
    0x0014:          b'faaa' <pad>
510
    0x0018:       0x10001234 funcname(3)
511
    0x001c:       0x10000007 <adjust @0x24> pop eax; ret
512
    0x0020:              0x3 arg0
513
    0x0024:       0x10000007 pop eax; ret
514
    0x0028:             0x77 [arg0] eax = SYS_sigreturn
515
    0x002c:       0x10000000 int 0x80; ret
516
    0x0030:              0x0 gs
517
    0x0034:              0x0 fs
518
    0x0038:              0x0 es
519
    0x003c:              0x0 ds
520
    0x0040:              0x0 edi
521
    0x0044:              0x0 esi
522
    0x0048:              0x0 ebp
523
    0x004c:              0x0 esp
524
    0x0050:              0x4 ebx
525
    0x0054:              0x6 edx
526
    0x0058:              0x5 ecx
527
    0x005c:              0xb eax = SYS_execve
528
    0x0060:              0x0 trapno
529
    0x0064:              0x0 err
530
    0x0068:       0x10000000 int 0x80; ret
531
    0x006c:             0x23 cs
532
    0x0070:              0x0 eflags
533
    0x0074:              0x0 esp_at_signal
534
    0x0078:             0x2b ss
535
    0x007c:              0x0 fpstate
536

537
    >>> r = ROP(e, 0x8048000)
538
    >>> r.funcname(1, 2)
539
    >>> r.funcname(3)
540
    >>> r.execve(4, 5, 6)
541
    >>> print(r.dump())
542
    0x8048000:       0x10001234 funcname(1, 2)
543
    0x8048004:       0x10000003 <adjust @0x8048018> add esp, 0x10; ret
544
    0x8048008:              0x1 arg0
545
    0x804800c:              0x2 arg1
546
    0x8048010:          b'eaaa' <pad>
547
    0x8048014:          b'faaa' <pad>
548
    0x8048018:       0x10001234 funcname(3)
549
    0x804801c:       0x10000007 <adjust @0x8048024> pop eax; ret
550
    0x8048020:              0x3 arg0
551
    0x8048024:       0x10000007 pop eax; ret
552
    0x8048028:             0x77 [arg0] eax = SYS_sigreturn
553
    0x804802c:       0x10000000 int 0x80; ret
554
    0x8048030:              0x0 gs
555
    0x8048034:              0x0 fs
556
    0x8048038:              0x0 es
557
    0x804803c:              0x0 ds
558
    0x8048040:              0x0 edi
559
    0x8048044:              0x0 esi
560
    0x8048048:              0x0 ebp
561
    0x804804c:        0x8048080 esp
562
    0x8048050:              0x4 ebx
563
    0x8048054:              0x6 edx
564
    0x8048058:              0x5 ecx
565
    0x804805c:              0xb eax = SYS_execve
566
    0x8048060:              0x0 trapno
567
    0x8048064:              0x0 err
568
    0x8048068:       0x10000000 int 0x80; ret
569
    0x804806c:             0x23 cs
570
    0x8048070:              0x0 eflags
571
    0x8048074:              0x0 esp_at_signal
572
    0x8048078:             0x2b ss
573
    0x804807c:              0x0 fpstate
574

575

576
    >>> elf = ELF.from_assembly('ret')
577
    >>> r = ROP(elf)
578
    >>> r.ret.address == 0x10000000
579
    True
580
    >>> r = ROP(elf, badchars=b'\x00')
581
    >>> r.gadgets == {}
582
    True
583
    >>> r.ret is None
584
    True
585
    """
586
    BAD_ATTRS = [
1✔
587
        'trait_names',          # ipython tab-complete
588
        'download',             # frequent typo
589
        'upload',               # frequent typo
590
    ]
591
    X86_SUFFIXES = ['ax', 'bx', 'cx', 'dx', 'bp', 'sp', 'di', 'si',
1✔
592
                    'r8', 'r9', '10', '11', '12', '13', '14', '15']
593

594
    def __init__(self, elfs, base = None, badchars = b'', **kwargs):
1✔
595
        """
596
        Arguments:
597
            elfs(list): List of :class:`.ELF` objects for mining
598
            base(int): Stack address where the first byte of the ROP chain lies, if known.
599
            badchars(str): Characters which should not appear in ROP gadget addresses.
600
        """
601
        import ropgadget
1✔
602

603
        # Permit singular ROP(elf) vs ROP([elf])
604
        if isinstance(elfs, ELF):
1✔
605
            elfs = [elfs]
1✔
606
        elif isinstance(elfs, (bytes, six.text_type)):
1!
607
            elfs = [ELF(elfs)]
×
608

609
        #: List of individual ROP gadgets, ROP calls, SROP frames, etc.
610
        #: This is intended to be the highest-level abstraction that we can muster.
611
        self._chain = []
1✔
612

613
        #: List of ELF files which are available for mining gadgets
614
        self.elfs = elfs
1✔
615

616
        #: Stack address where the first byte of the ROP chain lies, if known.
617
        self.base = base
1✔
618

619
        #: Whether or not the ROP chain directly sets the stack pointer to a value
620
        #: which is not contiguous
621
        self.migrated = False
1✔
622

623
        #: Characters which should not appear in ROP gadget addresses.
624
        self._badchars = set(badchars)
1✔
625

626
        self.__load()
1✔
627

628
    @staticmethod
1✔
629
    @LocalContext
1✔
630
    def from_blob(blob, *a, **kw):
631
        return ROP(ELF.from_bytes(blob, *a, **kw))
×
632

633
    def setRegisters(self, registers):
1✔
634
        """
635
        Returns an list of addresses/values which will set the specified register context.
636

637
        Arguments:
638
            registers(dict): Dictionary of ``{register name: value}``
639

640
        Returns:
641
            A list of tuples, ordering the stack.
642

643
            Each tuple is in the form of ``(value, name)`` where ``value`` is either a
644
            gadget address or literal value to go on the stack, and ``name`` is either
645
            a string name or other item which can be "unresolved".
646

647
        Note:
648
            This is basically an implementation of the Set Cover Problem, which is
649
            NP-hard.  This means that we will take polynomial time N**2, where N is
650
            the number of gadgets.  We can reduce runtime by discarding useless and
651
            inferior gadgets ahead of time.
652
        """
653
        if not registers:
1✔
654
            return []
1✔
655

656
        regset = set(registers)
1✔
657

658
        bad_instructions = set(('syscall', 'sysenter', 'int 0x80'))
1✔
659
        
660
        # Collect all gadgets which use these registers
661
        # Also collect the "best" gadget for each combination of registers
662
        gadgets = []
1✔
663
        best_gadgets = {}
1✔
664

665
        for gadget in self.gadgets.values():
1✔
666
            # Do not use gadgets which doesn't end with 'ret'
667
            if gadget.insns[-1] != 'ret':
1!
668
                continue
×
669
            # Do not use gadgets which contain 'syscall' or 'int'
670
            if set(gadget.insns) & bad_instructions:
1✔
671
                continue
1✔
672

673
            touched = tuple(regset & set(gadget.regs))
1✔
674

675
            if not touched:
1✔
676
                continue
1✔
677

678
            old = best_gadgets.get(touched, gadget)
1✔
679

680
            # if we have a new gadget for the touched registers, choose it
681
            # if the new gadget requires less stack space, choose it
682
            # if both gadgets require same stack space, choose the one with less instructions
683
            if (old is gadget) \
1!
684
              or (old.move > gadget.move) \
685
              or (old.move == gadget.move and len(old.insns) > len(gadget.insns)):
686
                best_gadgets[touched] = gadget
1✔
687

688
        winner = None
1✔
689
        budget = 999999999
1✔
690

691
        for num_gadgets in range(len(registers)):
1✔
692
            for combo in itertools.combinations(sorted(best_gadgets.values(), key=repr, reverse=True), 1+num_gadgets):
1✔
693
                # Is this better than what we can already do?
694
                cost = sum((g.move for g in combo))
1✔
695
                if cost > budget:
1✔
696
                    continue
1✔
697

698
                # Does it hit all of the registers we want?
699
                coverage = set(sum((g.regs for g in combo), [])) & regset
1✔
700

701
                if coverage != regset:
1✔
702
                    continue
1✔
703

704
                # It is better than what we had, and hits all of the registers.
705
                winner = combo
1✔
706
                budget = cost
1✔
707

708
        if not winner:
1!
709
            log.error("Could not satisfy setRegisters(%r)", registers)
×
710

711
        # We have our set of "winner" gadgets, let's build a stack!
712
        stack = []
1✔
713

714
        for gadget in winner:
1✔
715
            moved = context.bytes # Account for the gadget itself
1✔
716
            goodregs = set(gadget.regs) & regset
1✔
717
            name = ",".join(goodregs)
1✔
718
            stack.append((gadget.address, gadget))
1✔
719
            for r in gadget.regs:
1✔
720
                moved += context.bytes
1✔
721
                if r in registers:
1✔
722
                    stack.append((registers[r], r))
1✔
723
                else:
724
                    stack.append((Padding('<pad %s>' % r), r))
1✔
725

726
            for slot in range(moved, gadget.move, context.bytes):
1!
727
                left = gadget.move - slot
×
728
                stack.append((Padding('<pad %#x>' % left), 'stack padding'))
×
729

730
        return stack
1✔
731

732
    def __call__(self, *args, **kwargs):
1✔
733
        """Set the given register(s)' by constructing a rop chain.
734

735
        This is a thin wrapper around :meth:`setRegisters` which
736
        actually executes the rop chain.
737

738
        You can call this :class:`ROP` instance and provide keyword arguments,
739
        or a dictionary.
740

741
        Arguments:
742
            regs(dict): Mapping of registers to values.
743
                        Can instead provide ``kwargs``.
744

745
        >>> context.clear(arch='amd64')
746
        >>> assembly = 'pop rax; pop rdi; pop rsi; ret; pop rax; ret;'
747
        >>> e = ELF.from_assembly(assembly)
748
        >>> r = ROP(e)
749
        >>> r(rax=0xdead, rdi=0xbeef, rsi=0xcafe)
750
        >>> print(r.dump())
751
        0x0000:       0x10000000 pop rax; pop rdi; pop rsi; ret
752
        0x0008:           0xdead
753
        0x0010:           0xbeef
754
        0x0018:           0xcafe
755
        >>> r = ROP(e)
756
        >>> r({'rax': 0xdead, 'rdi': 0xbeef, 'rsi': 0xcafe})
757
        >>> print(r.dump())
758
        0x0000:       0x10000000 pop rax; pop rdi; pop rsi; ret
759
        0x0008:           0xdead
760
        0x0010:           0xbeef
761
        0x0018:           0xcafe
762
        """
763
        if len(args) == 1 and isinstance(args[0], dict):
1✔
764
            for value, name in self.setRegisters(args[0]):
1✔
765
                if isinstance(name, Gadget):
1✔
766
                    self.raw(name)
1✔
767
                else:
768
                    self.raw(value)
1✔
769
        else:
770
            self(kwargs)
1✔
771

772
    def resolve(self, resolvable):
1✔
773
        """Resolves a symbol to an address
774

775
        Arguments:
776
            resolvable(str,int): Thing to convert into an address
777

778
        Returns:
779
            int containing address of 'resolvable', or None
780
        """
781
        if isinstance(resolvable, str):
1!
782
            for elf in self.elfs:
1✔
783
                if resolvable in elf.symbols:
1✔
784
                    return elf.symbols[resolvable]
1✔
785

786
        if isinstance(resolvable, six.integer_types):
1!
787
            return resolvable
×
788

789
    def unresolve(self, value):
1✔
790
        """Inverts 'resolve'.  Given an address, it attempts to find a symbol
791
        for it in the loaded ELF files.  If none is found, it searches all
792
        known gadgets, and returns the disassembly
793

794
        Arguments:
795
            value(int): Address to look up
796

797
        Returns:
798
            String containing the symbol name for the address, disassembly for a gadget
799
            (if there's one at that address), or an empty string.
800
        """
801
        for elf in self.elfs:
1✔
802
            for name, addr in elf.symbols.items():
1✔
803
                if addr == value:
1✔
804
                    return name
1✔
805

806
        if value in self.gadgets:
1!
807
            return '; '.join(self.gadgets[value].insns)
×
808
        return ''
1✔
809

810
    def generatePadding(self, offset, count):
1✔
811
        """
812
        Generates padding to be inserted into the ROP stack.
813

814
        >>> context.clear(arch='i386')
815
        >>> rop = ROP([])
816
        >>> val = rop.generatePadding(5,15)
817
        >>> cyclic_find(val[:4])
818
        5
819
        >>> len(val)
820
        15
821
        >>> rop.generatePadding(0,0)
822
        b''
823

824
        """
825

826
        # Ensure we don't generate a cyclic pattern which contains badchars
827
        alphabet = b''.join(packing.p8(c) for c in bytearray(string.ascii_lowercase.encode()) if c not in self._badchars)
1✔
828

829
        if count:
1✔
830
            return cyclic(offset + count, alphabet=alphabet)[-count:]
1✔
831
        return b''
1✔
832

833
    def describe(self, object):
1✔
834
        """
835
        Return a description for an object in the ROP stack
836
        """
837
        if isinstance(object, enums):
1✔
838
            return str(object)
1✔
839
        if isinstance(object, six.integer_types):
1✔
840
            return self.unresolve(object)
1✔
841
        if isinstance(object, (bytes, six.text_type)):
1✔
842
            return repr(object)
1✔
843
        if isinstance(object, Gadget):
1✔
844
            return '; '.join(object.insns)
1✔
845

846
    def build(self, base = None, description = None):
1✔
847
        """
848
        Construct the ROP chain into a list of elements which can be passed
849
        to :func:`.flat`.
850

851
        Arguments:
852
            base(int):
853
                The base address to build the rop-chain from. Defaults to
854
                :attr:`base`.
855
            description(dict):
856
                Optional output argument, which will gets a mapping of
857
                ``address: description`` for each address on the stack,
858
                starting at ``base``.
859
        """
860
        if base is None:
1!
861
            base = self.base or 0
1✔
862

863
        stack = DescriptiveStack(base)
1✔
864
        chain = self._chain
1✔
865

866
        #
867
        # First pass
868
        #
869
        # Get everything onto the stack and save as much descriptive information
870
        # as possible.
871
        #
872
        # The only replacements performed are to add stack adjustment gadgets
873
        # (to move SP to the next gadget after a Call) and NextGadgetAddress,
874
        # which can only be calculated in this pass.
875
        #
876
        iterable = enumerate(chain)
1✔
877
        for idx, slot in iterable:
1✔
878

879
            remaining = len(chain) - 1 - idx
1✔
880
            address   = stack.next
1✔
881

882
            # Integers can just be added.
883
            # Do our best to find out what the address is.
884
            if isinstance(slot, six.integer_types):
1✔
885
                stack.describe(self.describe(slot))
1✔
886
                stack.append(slot)
1✔
887

888

889
            # Byte blobs can also be added, however they must be
890
            # broken down into pointer-width blobs.
891
            elif isinstance(slot, (bytes, six.text_type)):
1✔
892
                stack.describe(self.describe(slot))
1✔
893
                if not isinstance(slot, bytes):
1!
894
                    slot = slot.encode()
×
895

896
                for chunk in lists.group(context.bytes, slot):
1✔
897
                    stack.append(chunk)
1✔
898

899
            elif isinstance(slot, srop.SigreturnFrame):
1✔
900
                stack.describe("Sigreturn Frame")
1✔
901

902
                if slot.sp in (0, None) and self.base:
1✔
903
                    slot.sp = stack.next + len(slot)
1✔
904

905
                registers = [slot.registers[i] for i in sorted(slot.registers.keys())]
1✔
906
                for register in registers:
1✔
907
                    value       = slot[register]
1✔
908
                    description = self.describe(value)
1✔
909
                    if description:
1✔
910
                        stack.describe('%s = %s' % (register, description))
1✔
911
                    else:
912
                        stack.describe('%s' % (register))
1✔
913
                    stack.append(value)
1✔
914

915
            elif isinstance(slot, Call):
1✔
916
                stack.describe(self.describe(slot))
1✔
917

918
                registers    = slot.register_arguments
1✔
919

920
                for value, name in self.setRegisters(registers):
1✔
921
                    if name in registers:
1✔
922
                        index = slot.abi.register_arguments.index(name)
1✔
923
                        description = self.describe(value) or repr(value)
1✔
924
                        stack.describe('[arg%d] %s = %s' % (index, name, description))
1✔
925
                    elif isinstance(name, Gadget):
1✔
926
                        stack.describe('; '.join(name.insns))
1✔
927
                    elif isinstance(name, str):
1!
928
                        stack.describe(name)
1✔
929
                    stack.append(value)
1✔
930

931
                if address != stack.next:
1✔
932
                    stack.describe(slot.name)
1✔
933

934
                stack.append(slot.target)
1✔
935

936
                # For any remaining arguments, put them on the stack
937
                stackArguments = slot.stack_arguments
1✔
938
                for argument in slot.stack_arguments_before:
1✔
939
                    stack.describe("[dlresolve index]")
1✔
940
                    stack.append(argument)
1✔
941
                nextGadgetAddr = stack.next + (context.bytes * len(stackArguments))
1✔
942

943
                # Generally, stack-based arguments assume there's a return
944
                # address on the stack.
945
                #
946
                # We need to at least put padding there so that things line up
947
                # properly, but likely also need to adjust the stack past the
948
                # arguments.
949
                if slot.abi.returns:
1✔
950

951
                    # Save off the address of the next gadget
952
                    if remaining or stackArguments:
1✔
953
                        nextGadgetAddr = stack.next
1✔
954

955
                    # If there were arguments on the stack, we need to stick something
956
                    # in the slot where the return address goes.
957
                    if len(stackArguments) > 0:
1✔
958
                        if remaining and (remaining > 1 or Call.is_flat(chain[-1])):
1✔
959
                            fix_size  = (1 + len(stackArguments))
1✔
960
                            fix_bytes = fix_size * context.bytes
1✔
961
                            adjust   = self.search(move = fix_bytes)
1✔
962

963
                            if not adjust:
1!
964
                                log.error("Could not find gadget to adjust stack by %#x bytes" % fix_bytes)
×
965

966
                            nextGadgetAddr += adjust.move
1✔
967

968
                            stack.describe('<adjust @%#x> %s' % (nextGadgetAddr, self.describe(adjust)))
1✔
969
                            stack.append(adjust.address)
1✔
970

971
                            for pad in range(fix_bytes, adjust.move, context.bytes):
1✔
972
                                stackArguments.append(Padding())
1✔
973

974
                        # We could not find a proper "adjust" gadget, but also didn't need one.
975
                        elif remaining:
1✔
976
                            _, nxslot = next(iterable)
1✔
977
                            stack.describe(self.describe(nxslot))
1✔
978
                            if isinstance(nxslot, Call):
1!
979
                                stack.append(nxslot.target)
1✔
980
                            else:
981
                                stack.append(nxslot)
×
982
                        else:
983
                            stack.append(Padding("<return address>"))
1✔
984

985

986
                for i, argument in enumerate(stackArguments):
1✔
987

988
                    if isinstance(argument, NextGadgetAddress):
1!
989
                        stack.describe("<next gadget>")
×
990
                        stack.append(nextGadgetAddr)
×
991

992
                    else:
993
                        description = self.describe(argument) or 'arg%i' % (i + len(registers))
1✔
994
                        stack.describe(description)
1✔
995
                        stack.append(argument)
1✔
996
            else:
997
                stack.append(slot)
1✔
998
        #
999
        # Second pass
1000
        #
1001
        # All of the register-loading, stack arguments, and call addresses
1002
        # are on the stack.  We can now start loading in absolute addresses.
1003
        #
1004
        start = base
1✔
1005
        end   = stack.next
1✔
1006
        size  = (stack.next - base)
1✔
1007
        slot_address = base
1✔
1008
        for i, slot in enumerate(stack):
1✔
1009
            if isinstance(slot, six.integer_types):
1✔
1010
                pass
1✔
1011

1012
            elif isinstance(slot, (bytes, six.text_type)):
1✔
1013
                pass
1✔
1014

1015
            elif isinstance(slot, AppendedArgument):
1✔
1016
                stack[i] = stack.next
1✔
1017
                stack.extend(slot.resolve(stack.next))
1✔
1018

1019
            elif isinstance(slot, CurrentStackPointer):
1!
1020
                stack[i] = slot_address
×
1021

1022
            elif isinstance(slot, Padding):
1✔
1023
                stack[i] = self.generatePadding(i * context.bytes, context.bytes)
1✔
1024
                stack.describe(slot.name, slot_address)
1✔
1025

1026
            elif isinstance(slot, Gadget):
1!
1027
                stack[i] = slot.address
1✔
1028
                stack.describe(self.describe(slot), slot_address)
1✔
1029

1030
            # Everything else we can just leave in place.
1031
            # Maybe the user put in something on purpose?
1032
            # Also, it may work in pwnlib.util.packing.flat()
1033
            else:
1034
                pass
1035

1036
            slot_address += _slot_len(slot)
1✔
1037

1038
        return stack
1✔
1039

1040

1041
    def find_stack_adjustment(self, slots):
1✔
1042
        self.search(move=slots * context.bytes)
×
1043

1044
    def chain(self, base=None):
1✔
1045
        """Build the ROP chain
1046
        
1047
        Arguments:
1048
            base(int):
1049
                The base address to build the rop-chain from. Defaults to
1050
                :attr:`base`.
1051

1052
        Returns:
1053
            str containing raw ROP bytes
1054
        """
1055
        return packing.flat(self.build(base=base))
1✔
1056

1057
    def dump(self, base=None):
1✔
1058
        """Dump the ROP chain in an easy-to-read manner
1059
        
1060
        Arguments:
1061
            base(int):
1062
                The base address to build the rop-chain from. Defaults to
1063
                :attr:`base`.
1064
        """
1065
        return self.build(base=base).dump()
1✔
1066

1067
    def regs(self, registers=None, **kw):
1✔
1068
        if registers is None:
×
1069
            registers = {}
×
1070
        registers.update(kw)
×
1071

1072

1073

1074
    def call(self, resolvable, arguments = (), abi = None, **kwargs):
1✔
1075
        """Add a call to the ROP chain
1076

1077
        Arguments:
1078
            resolvable(str,int): Value which can be looked up via 'resolve',
1079
                or is already an integer.
1080
            arguments(list): List of arguments which can be passed to pack().
1081
                Alternately, if a base address is set, arbitrarily nested
1082
                structures of strings or integers can be provided.
1083
        """
1084
        if self.migrated:
1!
1085
            log.error('Cannot append to a migrated chain')
×
1086

1087
        # If we can find a function with that name, just call it
1088
        if isinstance(resolvable, str):
1✔
1089
            addr = self.resolve(resolvable)
1✔
1090
        elif hasattr(resolvable, 'name') and hasattr(resolvable, 'address'):
1!
1091
            addr = resolvable.address
×
1092
            resolvable = str(resolvable.name)
×
1093
        else:
1094
            addr = resolvable
1✔
1095
            resolvable = ''
1✔
1096

1097
        if addr:
1✔
1098
            self.raw(Call(resolvable, addr, arguments, abi))
1✔
1099

1100
        # Otherwise, if it is a syscall we might be able to call it
1101
        elif not self._srop_call(resolvable, arguments):
1!
1102
            log.error('Could not resolve %r.' % resolvable)
×
1103

1104

1105

1106
    def _srop_call(self, resolvable, arguments):
1✔
1107
        # Check that the call is a valid syscall
1108
        resolvable    = 'SYS_' + resolvable.lower()
1✔
1109
        syscall_number = getattr(constants, resolvable, None)
1✔
1110
        if syscall_number is None:
1!
1111
            return False
×
1112

1113
        log.info_once("Using sigreturn for %r" % resolvable)
1✔
1114

1115
        # Find an int 0x80 or similar instruction we can use
1116
        syscall_gadget       = None
1✔
1117
        syscall_instructions = srop.syscall_instructions[context.arch]
1✔
1118

1119
        for instruction in syscall_instructions:
1!
1120
            syscall_gadget = self.find_gadget([instruction])
1✔
1121
            if syscall_gadget:
1!
1122
                break
1✔
1123
        else:
1124
            log.error("Could not find any instructions in %r" % syscall_instructions)
×
1125

1126
        # Generate the SROP frame which would invoke the syscall
1127
        with context.local(arch=self.elfs[0].arch):
1✔
1128
            frame         = srop.SigreturnFrame()
1✔
1129
            frame.pc      = syscall_gadget
1✔
1130
            frame.syscall = syscall_number
1✔
1131

1132
            try:
1✔
1133
                SYS_sigreturn  = constants.SYS_sigreturn
1✔
1134
            except AttributeError:
×
1135
                SYS_sigreturn  = constants.SYS_rt_sigreturn
×
1136

1137
            for register, value in zip(frame.arguments, arguments):
1✔
1138
                if not isinstance(value, six.integer_types + (Unresolved,)):
1!
1139
                    frame[register] = AppendedArgument(value)
×
1140
                else:
1141
                    frame[register] = value
1✔
1142

1143
        # Set up a call frame which will set EAX and invoke the syscall
1144
        call = Call('SYS_sigreturn',
1✔
1145
                    syscall_gadget,
1146
                    [SYS_sigreturn],
1147
                    abi.ABI.sigreturn())
1148

1149
        self.raw(call)
1✔
1150
        self.raw(frame)
1✔
1151

1152

1153
        # We do not expect to ever recover after the syscall, as it would
1154
        # require something like 'int 0x80; ret' which does not ever occur
1155
        # in the wild.
1156
        self.migrated = True
1✔
1157

1158
        return True
1✔
1159

1160
    def find_gadget(self, instructions):
1✔
1161
        """
1162
        Returns a gadget with the exact sequence of instructions specified
1163
        in the ``instructions`` argument.
1164
        """
1165
        n = len(instructions)
1✔
1166
        for gadget in self.gadgets.values():
1!
1167
            if tuple(gadget.insns)[:n] == tuple(instructions):
1✔
1168
                return gadget
1✔
1169

1170
    def _flatten(self, initial_list):
1✔
1171
        # Flatten out any nested lists.
1172
        flattened_list = []
1✔
1173
        for data in initial_list:
1✔
1174
            if isinstance(data, (list, tuple)):
1!
1175
                flattened_list.extend(self._flatten(data))
×
1176
            else:
1177
                flattened_list.append(data)
1✔
1178
        return flattened_list
1✔
1179

1180
    def raw(self, value):
1✔
1181
        """Adds a raw integer or string to the ROP chain.
1182

1183
        If your architecture requires aligned values, then make
1184
        sure that any given string is aligned!
1185

1186
        When given a list or a tuple of values, the list is
1187
        flattened before adding every item to the chain.
1188

1189
        Arguments:
1190
            data(int/bytes/list): The raw value to put onto the rop chain.
1191

1192
        >>> context.clear(arch='i386')
1193
        >>> rop = ROP([])
1194
        >>> rop.raw('AAAAAAAA')
1195
        >>> rop.raw('BBBBBBBB')
1196
        >>> rop.raw('CCCCCCCC')
1197
        >>> rop.raw(['DDDD', 'DDDD'])
1198
        >>> print(rop.dump())
1199
        0x0000:          b'AAAA' 'AAAAAAAA'
1200
        0x0004:          b'AAAA'
1201
        0x0008:          b'BBBB' 'BBBBBBBB'
1202
        0x000c:          b'BBBB'
1203
        0x0010:          b'CCCC' 'CCCCCCCC'
1204
        0x0014:          b'CCCC'
1205
        0x0018:          b'DDDD' 'DDDD'
1206
        0x001c:          b'DDDD' 'DDDD'
1207
        """
1208
        if self.migrated:
1!
1209
            log.error('Cannot append to a migrated chain')
×
1210

1211
        if isinstance(value, (list, tuple)):
1✔
1212
            self._chain.extend(self._flatten(value))
1✔
1213
        else:
1214
            self._chain.append(value)
1✔
1215

1216
    def migrate(self, next_base):
1✔
1217
        """Explicitly set $sp, by using a ``leave; ret`` gadget"""
1218
        if isinstance(next_base, ROP):
×
1219
            next_base = next_base.base
×
1220
        pop_sp = self.rsp or self.esp
×
1221
        pop_bp = self.rbp or self.ebp
×
1222
        leave  = self.leave
×
1223
        if pop_sp and len(pop_sp.regs) == 1:
×
1224
            self.raw(pop_sp)
×
1225
            self.raw(next_base)
×
1226
        elif pop_bp and leave and len(pop_bp.regs) == 1:
×
1227
            self.raw(pop_bp)
×
1228
            self.raw(next_base - context.bytes)
×
1229
            self.raw(leave)
×
1230
        else:
1231
            log.error('Cannot find the gadgets to migrate')
×
1232
        self.migrated = True
×
1233

1234
    def __bytes__(self):
1✔
1235
        """Returns: Raw bytes of the ROP chain"""
1236
        return self.chain()
×
1237

1238
    def __flat__(self):
1✔
1239
        return self.chain()
1✔
1240

1241
    def __flat_at__(self, address):
1✔
1242
        return self.chain(address)
×
1243

1244
    def __get_cachefile_name(self, files):
1✔
1245
        """Given an ELF or list of ELF objects, return a cache file for the set of files"""
1246
        if context.cache_dir is None:
1!
1247
            return None
×
1248

1249
        cachedir = os.path.join(context.cache_dir, 'rop-cache')
1✔
1250
        if not os.path.exists(cachedir):
1✔
1251
            os.mkdir(cachedir)
1✔
1252

1253
        if isinstance(files, ELF):
1!
1254
            files = [files]
1✔
1255

1256
        sha256 = hashlib.sha256()
1✔
1257
        for elf_data in sorted(elf.get_data() for elf in files):
1✔
1258
            sha256.update(elf_data)
1✔
1259

1260
        return os.path.join(cachedir, sha256.hexdigest())
1✔
1261

1262
    @staticmethod
1✔
1263
    def clear_cache():
1264
        """Clears the ROP gadget cache"""
1265
        if context.cache_dir is None:
×
1266
            return
×
1267
        cachedir = os.path.join(context.cache_dir, 'rop-cache')
×
1268
        shutil.rmtree(cachedir)
×
1269

1270
    def __cache_load(self, elf):
1✔
1271
        filename = self.__get_cachefile_name(elf)
1✔
1272
        if filename is None or not os.path.exists(filename):
1✔
1273
            return None
1✔
1274
        gadgets = eval(open(filename).read())
1✔
1275
        gadgets = {k - elf.load_addr + elf.address:v for k, v in gadgets.items()}
1✔
1276
        log.info_once('Loaded %s cached gadgets for %r', len(gadgets), elf.file.name)
1✔
1277
        return gadgets
1✔
1278

1279
    def __cache_save(self, elf, data):
1✔
1280
        filename = self.__get_cachefile_name(elf)
1✔
1281
        if filename is None:
1!
1282
            return
×
1283
        data = {k + elf.load_addr - elf.address:v for k, v in data.items()}
1✔
1284
        open(filename, 'w+').write(repr(data))
1✔
1285

1286
    def __load(self):
1✔
1287
        """Load all ROP gadgets for the selected ELF files"""
1288
        #
1289
        # We accept only instructions that look like these.
1290
        #
1291
        # - leave
1292
        # - pop reg
1293
        # - add $sp, <hexadecimal value>
1294
        # - ret
1295
        #
1296
        # Currently, ROPgadget does not detect multi-byte "C2" ret.
1297
        # https://github.com/JonathanSalwan/ROPgadget/issues/53
1298
        #
1299

1300
        pop   = re.compile(r'^pop (.{2,3})')
1✔
1301
        add   = re.compile(r'^add [er]sp, ((?:0[xX])?[0-9a-fA-F]+)$')
1✔
1302
        ret   = re.compile(r'^ret$')
1✔
1303
        leave = re.compile(r'^leave$')
1✔
1304
        int80 = re.compile(r'int +0x80')
1✔
1305
        syscall = re.compile(r'^syscall$')
1✔
1306
        sysenter = re.compile(r'^sysenter$')
1✔
1307

1308
        #
1309
        # Validation routine
1310
        #
1311
        # >>> valid('pop eax')
1312
        # True
1313
        # >>> valid('add rax, 0x24')
1314
        # False
1315
        # >>> valid('add esp, 0x24')
1316
        # True
1317
        # >>> valid('add esp, esi')
1318
        # False
1319
        #
1320
        valid = lambda insn: any(map(lambda pattern: pattern.match(insn), [pop,add,ret,leave,int80,syscall,sysenter]))
1✔
1321

1322
        #
1323
        # Currently, ropgadget.args.Args() doesn't take any arguments, and pulls
1324
        # only from sys.argv.  Preserve it through this call.  We also
1325
        # monkey-patch sys.stdout to suppress output from ropgadget.
1326
        #
1327
        argv = sys.argv
1✔
1328
        stdout = sys.stdout
1✔
1329

1330
        class Wrapper:
1✔
1331

1332
            def __init__(self, fd):
1✔
1333
                self._fd = fd
1✔
1334

1335
            def write(self, s):
1✔
1336
                pass
1✔
1337

1338
            def __getattr__(self, k):
1✔
1339
                return getattr(self._fd, k)
1✔
1340

1341
        gadgets = {}
1✔
1342
        for elf in self.elfs:
1✔
1343
            cache = self.__cache_load(elf)
1✔
1344
            if cache:
1✔
1345
                gadgets.update(cache)
1✔
1346
                continue
1✔
1347
            log.info_once('Loading gadgets for %r' % elf.path)
1✔
1348
            try:
1✔
1349
                sys.stdout = Wrapper(sys.stdout)
1✔
1350
                import ropgadget
1✔
1351
                sys.argv = ['ropgadget', '--binary', elf.path, '--only', 'sysenter|syscall|int|add|pop|leave|ret', '--nojop', '--multibr']
1✔
1352
                args = ropgadget.args.Args().getArgs()
1✔
1353
                core = ropgadget.core.Core(args)
1✔
1354
                core.do_binary(elf.path)
1✔
1355
                core.do_load(0)
1✔
1356
            finally:
1357
                sys.argv = argv
1✔
1358
                sys.stdout = stdout
1✔
1359

1360
            elf_gadgets = {}
1✔
1361
            for gadget in core._Core__gadgets:
1✔
1362
                address = gadget['vaddr'] - elf.load_addr + elf.address
1✔
1363
                insns = [ g.strip() for g in gadget['gadget'].split(';') ]
1✔
1364
                if all(map(valid, insns)):
1✔
1365
                    elf_gadgets[address] = insns
1✔
1366

1367
            self.__cache_save(elf, elf_gadgets)
1✔
1368
            gadgets.update(elf_gadgets)
1✔
1369

1370
        #
1371
        # For each gadget we decided to keep, find out how much it moves the stack,
1372
        # and log which registers it modifies.
1373
        #
1374
        self.gadgets = {}
1✔
1375
        self.pivots = {}
1✔
1376
        frame_regs = {
1✔
1377
            4: ['ebp', 'esp'],
1378
            8: ['rbp', 'rsp']
1379
        }[context.bytes]
1380

1381
        for addr, insns in gadgets.items():
1✔
1382

1383
            # Filter out gadgets by address against badchars
1384
            if set(pack(addr)) & self._badchars:
1✔
1385
                continue
1✔
1386

1387
            sp_move = 0
1✔
1388
            regs = []
1✔
1389
            for insn in insns:
1✔
1390
                if pop.match(insn):
1✔
1391
                    regs.append(pop.match(insn).group(1))
1✔
1392
                    sp_move += context.bytes
1✔
1393
                elif add.match(insn):
1✔
1394
                    arg = int(add.match(insn).group(1), 16)
1✔
1395
                    sp_move += arg
1✔
1396
                    while arg >= context.bytes:
1✔
1397
                        regs.append(hex(arg))
1✔
1398
                        arg -= context.bytes
1✔
1399
                elif ret.match(insn):
1✔
1400
                    sp_move += context.bytes
1✔
1401
                elif leave.match(insn):
1✔
1402
                    #
1403
                    # HACK: Since this modifies ESP directly, this should
1404
                    #       never be returned as a 'normal' ROP gadget that
1405
                    #       simply 'increments' the stack.
1406
                    #
1407
                    #       As such, the 'move' is set to a very large value,
1408
                    #       to prevent .search() from returning it unless $sp
1409
                    #       is specified as a register.
1410
                    #
1411
                    sp_move += 9999999999
1✔
1412
                    regs += frame_regs
1✔
1413

1414
            # Permit duplicates, because blacklisting bytes in the gadget
1415
            # addresses may result in us needing the dupes.
1416
            self.gadgets[addr] = Gadget(addr, insns, regs, sp_move)
1✔
1417

1418
            # Don't use 'pop esp' for pivots
1419
            if not set(['rsp', 'esp']) & set(regs):
1✔
1420
                self.pivots[sp_move] = addr
1✔
1421

1422
        leave = self.search(regs=frame_regs, order='regs')
1✔
1423
        if leave and leave.regs != frame_regs:
1!
1424
            leave = None
×
1425
        self.leave = leave
1✔
1426

1427
    def __repr__(self):
1✔
1428
        return 'ROP(%r)' % self.elfs
×
1429

1430
    def search_iter(self, move=None, regs=None):
1✔
1431
        """
1432
        Iterate through all gadgets which move the stack pointer by
1433
        *at least* ``move`` bytes, and which allow you to set all
1434
        registers in ``regs``.
1435
        """
1436
        move = move or 0
1✔
1437
        regs = set(regs or ())
1✔
1438

1439
        for addr, gadget in self.gadgets.items():
1✔
1440
            addr_bytes = set(pack(gadget.address))
1✔
1441
            if addr_bytes & self._badchars:     continue
1!
1442
            if gadget.insns[-1] != 'ret':        continue
1✔
1443
            if gadget.move < move:               continue
1✔
1444
            if not (regs <= set(gadget.regs)):   continue
1✔
1445
            yield gadget
1✔
1446

1447
    def search(self, move = 0, regs = None, order = 'size'):
1✔
1448
        """Search for a gadget which matches the specified criteria.
1449

1450
        Arguments:
1451
            move(int): Minimum number of bytes by which the stack
1452
                pointer is adjusted.
1453
            regs(list): Minimum list of registers which are popped off the
1454
                stack.
1455
            order(str): Either the string 'size' or 'regs'. Decides how to
1456
                order multiple gadgets the fulfill the requirements.
1457

1458
        The search will try to minimize the number of bytes popped more than
1459
        requested, the number of registers touched besides the requested and
1460
        the address.
1461

1462
        If ``order == 'size'``, then gadgets are compared lexicographically
1463
        by ``(total_moves, total_regs, addr)``, otherwise by ``(total_regs, total_moves, addr)``.
1464

1465
        Returns:
1466
            A :class:`.Gadget` object
1467
        """
1468
        matches = self.search_iter(move, regs)
1✔
1469
        if matches is None:
1!
1470
            return None
×
1471

1472
        # Search for an exact match, save the closest match
1473
        key = {
1✔
1474
            'size': lambda g: (g.move, len(g.regs), g.address),
1475
            'regs': lambda g: (len(g.regs), g.move, g.address)
1476
        }[order]
1477

1478
        try:
1✔
1479
            result = min(matches, key=key)
1✔
1480
        except ValueError:
1✔
1481
            return None
1✔
1482

1483
        # Check for magic 9999999... value used by 'leave; ret'
1484
        if move and result.move == 9999999999:
1!
1485
            return None
×
1486

1487
        return result
1✔
1488

1489
    def ret2csu(self, edi=Padding('edi'), rsi=Padding('rsi'),
1✔
1490
                rdx=Padding('rdx'), rbx=Padding('rbx'), rbp=Padding('rbp'),
1491
                r12=Padding('r12'), r13=Padding('r13'), r14=Padding('r14'),
1492
                r15=Padding('r15'), call=None):
1493
        """Build a ret2csu ROPchain
1494

1495
        Arguments:
1496
            edi, rsi, rdx: Three primary registers to populate
1497
            rbx, rbp, r12, r13, r14, r15: Optional registers to populate
1498
            call: Pointer to the address of a function to call during
1499
                second gadget. If None then use the address of _fini in the
1500
                .dynamic section. .got.plt entries are a good target. Required
1501
                for PIE binaries.
1502
        Test:
1503
            >>> context.clear(binary=pwnlib.data.elf.ret2dlresolve.get("amd64"))
1504
            >>> r = ROP(context.binary)
1505
            >>> r.ret2csu(1, 2, 3, 4, 5, 6, 7, 8, 9)
1506
            >>> r.call(0xdeadbeef)
1507
            >>> print(r.dump())
1508
            0x0000:         0x40058a
1509
            0x0008:              0x0
1510
            0x0010:              0x1
1511
            0x0018:         0x600e48
1512
            0x0020:              0x1
1513
            0x0028:              0x2
1514
            0x0030:              0x3
1515
            0x0038:         0x400570
1516
            0x0040:      b'qaaaraaa' <add rsp, 8>
1517
            0x0048:              0x4
1518
            0x0050:              0x5
1519
            0x0058:              0x6
1520
            0x0060:              0x7
1521
            0x0068:              0x8
1522
            0x0070:              0x9
1523
            0x0078:       0xdeadbeef 0xdeadbeef()
1524
            >>> open('core','w').close(); os.unlink('core')  # remove any old core file for the tests
1525
            >>> p = process()
1526
            >>> p.send(fit({64+context.bytes: r}))
1527
            >>> p.wait(0.5)
1528
            >>> core = p.corefile
1529
            >>> hex(core.pc)
1530
            '0xdeadbeef'
1531
            >>> core.rdi, core.rsi, core.rdx, core.rbx, core.rbp, core.r12, core.r13, core.r14, core.r15
1532
            (1, 2, 3, 4, 5, 6, 7, 8, 9)
1533
        """
1534
        if self.migrated:
1!
1535
            log.error('Cannot append to a migrated chain')
×
1536

1537
        # Ensure 'edi' argument is packable
1538
        try:
1✔
1539
            packing.p32(edi)
1✔
1540
        except struct.error:
×
1541
            log.error('edi must be a 32bit value')
×
1542

1543
        # Find an appropriate, non-library ELF.
1544
        # Prioritise non-PIE binaries so we can use _fini
1545
        exes = (elf for elf in self.elfs if not elf.library and elf.bits == 64)
1!
1546

1547
        nonpie = csu = None
1✔
1548
        for elf in exes:
1!
1549
            if not elf.pie:
1!
1550
                if '__libc_csu_init' in elf.symbols:
1!
1551
                    break
1✔
1552
                nonpie = elf
×
1553
            elif '__libc_csu_init' in elf.symbols:
×
1554
                csu = elf
×
1555
        else:
1556
            log.error('No non-library binaries in [elfs]')
×
1557

1558
        if elf.pie:
1!
1559
            if nonpie:
×
1560
                elf = nonpie
×
1561
            elif csu:
×
1562
                elf = csu
×
1563

1564
        from .ret2csu import ret2csu
1✔
1565
        ret2csu(self, elf, edi, rsi, rdx, rbx, rbp, r12, r13, r14, r15, call)
1✔
1566

1567
    def ret2dlresolve(self, dlresolve):
1✔
1568
        elf = next(elf for elf in self.elfs if elf.get_section_by_name(".plt"))
1!
1569
        elf_base = elf.address if elf.pie else 0
1✔
1570
        plt_init = elf.get_section_by_name(".plt").header.sh_addr + elf_base
1✔
1571
        log.debug("PLT_INIT: %#x", plt_init)
1✔
1572

1573
        reloc_index = dlresolve.reloc_index
1✔
1574
        real_args = dlresolve.real_args
1✔
1575
        call = Call("[plt_init] " + dlresolve.symbol.decode(),
1✔
1576
                    plt_init,
1577
                    dlresolve.real_args,
1578
                    before=[reloc_index])
1579
        self.raw(call)
1✔
1580

1581
    def __getattr__(self, attr):
1✔
1582
        """Helper to make finding ROP gadgets easier.
1583

1584
        Also provides a shorthand for ``.call()``:
1585
            ``rop.function(args)`` is equivalent to ``rop.call(function, args)``
1586

1587
        >>> context.clear(arch='i386')
1588
        >>> elf=ELF(which('bash'))
1589
        >>> rop=ROP([elf])
1590
        >>> rop.rdi     == rop.search(regs=['rdi'], order = 'regs')
1591
        True
1592
        >>> rop.r13_r14_r15_rbp == rop.search(regs=['r13','r14','r15','rbp'], order = 'regs')
1593
        True
1594
        >>> rop.ret_8   == rop.search(move=8)
1595
        True
1596
        >>> rop.ret is not None
1597
        True
1598
        >>> with context.local(arch='amd64', bits='64'):
1599
        ...     r = ROP(ELF.from_assembly('syscall; ret'))
1600
        >>> r.syscall is not None
1601
        True
1602
        """
1603
        gadget = collections.namedtuple('gadget', ['address', 'details'])
1✔
1604

1605
        if attr in self.__dict__ \
1!
1606
        or attr in self.BAD_ATTRS \
1607
        or attr.startswith('_'):
1608
            raise AttributeError('ROP instance has no attribute %r' % attr)
×
1609

1610
        #
1611
        # Check for 'ret' or 'ret_X'
1612
        #
1613
        if attr.startswith('ret'):
1✔
1614
            count = context.bytes
1✔
1615
            if '_' in attr:
1✔
1616
                count = int(attr.split('_')[1])
1✔
1617
            return self.search(move=count)
1✔
1618

1619
        #
1620
        # Check for 'jmp_esp'('i386') or 'jmp_rsp'('amd64')
1621
        #
1622
        if attr == 'jmp_esp' and context.arch == 'i386' \
1✔
1623
        or attr == 'jmp_rsp' and context.arch == 'amd64':
1624
            jmp_sp = {'i386': 'jmp esp',
1✔
1625
                      'amd64': 'jmp rsp'
1626
                     }[context.arch]
1627

1628
            insn_asm = b'\xff\xe4'
1✔
1629

1630
            for elf in self.elfs:
1✔
1631
                for addr in elf.search(insn_asm, executable = True):
1✔
1632
                    if set(pack(addr)) & self._badchars:
1✔
1633
                        continue
1✔
1634

1635
                    return Gadget(addr, [jmp_sp], [], context.bytes)
1✔
1636
            return None
1✔
1637
        mapping = {'int80': 'int 0x80',
1✔
1638
            'syscall': 'syscall',
1639
            'sysenter': 'sysenter'}
1640
        if attr in mapping:
1✔
1641
            for each in self.gadgets:
1!
1642
                if self.gadgets[each]['insns'][0] == mapping[attr]:
1!
1643
                    return gadget(each, self.gadgets[each])
1✔
1644
            return None
×
1645

1646
        #
1647
        # Check for a '_'-delimited list of registers
1648
        #
1649
        if all(map(lambda x: x[-2:] in self.X86_SUFFIXES, attr.split('_'))):
1✔
1650
            return self.search(regs=attr.split('_'), order='regs')
1✔
1651

1652
        #
1653
        # Otherwise, assume it's a rop.call() shorthand
1654
        #
1655
        def call(*args):
1✔
1656
            return self.call(attr, args)
1✔
1657

1658
        return call
1✔
1659

1660
    def __setattr__(self, attr, value):
1✔
1661
        """Helper for setting registers.
1662

1663
        This convenience feature allows one to set the values of registers
1664
        with simple python assignment syntax.
1665

1666
        Warning:
1667
            Only one register is set at a time (one per rop chain).
1668
            This may lead to some previously set to registers be overwritten!
1669

1670
        Note:
1671
            If you would like to set multiple registers in as few rop chains
1672
            as possible, see :meth:`__call__`.
1673

1674
        >>> context.clear(arch='amd64')
1675
        >>> assembly = 'pop rax; pop rdi; pop rsi; ret; pop rax; ret;'
1676
        >>> e = ELF.from_assembly(assembly)
1677
        >>> r = ROP(e)
1678
        >>> r.rax = 0xdead
1679
        >>> r.rdi = 0xbeef
1680
        >>> r.rsi = 0xcafe
1681
        >>> print(r.dump())
1682
        0x0000:       0x10000004 pop rax; ret
1683
        0x0008:           0xdead
1684
        0x0010:       0x10000001 pop rdi; pop rsi; ret
1685
        0x0018:           0xbeef
1686
        0x0020:      b'iaaajaaa' <pad rsi>
1687
        0x0028:       0x10000002 pop rsi; ret
1688
        0x0030:           0xcafe
1689
        """
1690
        if attr in self.BAD_ATTRS:
1!
1691
            raise AttributeError('ROP instance has no attribute %r' % attr)
×
1692

1693
        if attr[-2:] in self.X86_SUFFIXES:  # handle setting registers
1✔
1694
            self({attr: value})
1✔
1695

1696
        # Otherwise, perform usual setting
1697
        self.__dict__[attr] = value
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc