pwnlib.rop.rop
— Return Oriented Programming¶
Return Oriented Programming
Manual ROP¶
The ROP tool can be used to build stacks pretty trivially. Let’s create a fake binary which has some symbols which might have been useful.
>>> context.clear(arch='i386')
>>> binary = ELF.from_assembly('add esp, 0x10; ret')
>>> binary.symbols = {b'read': 0xdeadbeef, b'write': 0xdecafbad, b'exit': 0xfeedface}
Creating a ROP object which looks up symbols in the binary is pretty straightforward.
>>> rop = ROP(binary)
With the ROP object, you can manually add stack frames.
>>> rop.raw(0)
>>> rop.raw(unpack(b'abcd'))
>>> rop.raw(2)
Inspecting the ROP stack is easy, and laid out in an easy-to-read manner.
>>> print(rop.dump())
0x0000: 0x0
0x0004: 0x64636261
0x0008: 0x2
The ROP module is also aware of how to make function calls with standard Linux ABIs.
>>> rop.call('read', [4, 5, 6])
>>> print(rop.dump())
0x0000: 0x0
0x0004: 0x64636261
0x0008: 0x2
0x000c: 0xdeadbeef read(4, 5, 6)
0x0010: b'eaaa' <pad>
0x0014: 0x4 arg0
0x0018: 0x5 arg1
0x001c: 0x6 arg2
You can also use a shorthand to invoke calls. The stack is automatically adjusted for the next frame
>>> rop.write(7, 8, 9)
>>> rop.exit()
>>> print(rop.dump())
0x0000: 0x0
0x0004: 0x64636261
0x0008: 0x2
0x000c: 0xdeadbeef read(4, 5, 6)
0x0010: 0x10000000 <adjust: add esp, 0x10; ret>
0x0014: 0x4 arg0
0x0018: 0x5 arg1
0x001c: 0x6 arg2
0x0020: b'iaaa' <pad>
0x0024: 0xdecafbad write(7, 8, 9)
0x0028: 0x10000000 <adjust: add esp, 0x10; ret>
0x002c: 0x7 arg0
0x0030: 0x8 arg1
0x0034: 0x9 arg2
0x0038: b'oaaa' <pad>
0x003c: 0xfeedface exit()
0x0040: b'qaaa' <pad>
ROP Example¶
Let’s assume we have a trivial binary that just reads some data onto the stack, and returns.
>>> context.clear(arch='i386')
>>> c = constants
>>> assembly = 'read:' + shellcraft.read(c.STDIN_FILENO, 'esp', 1024)
>>> assembly += 'ret\n'
Let’s provide some simple gadgets:
>>> assembly += 'add_esp: add esp, 0x10; ret\n'
And perhaps a nice “write” function.
>>> assembly += 'write: enter 0,0\n'
>>> assembly += ' mov ebx, [ebp+4+4]\n'
>>> assembly += ' mov ecx, [ebp+4+8]\n'
>>> assembly += ' mov edx, [ebp+4+12]\n'
>>> assembly += shellcraft.write('ebx', 'ecx', 'edx')
>>> assembly += ' leave\n'
>>> assembly += ' ret\n'
>>> assembly += 'flag: .asciz "The flag"\n'
And a way to exit cleanly.
>>> assembly += 'exit: ' + shellcraft.exit(0)
>>> binary = ELF.from_assembly(assembly)
Finally, let’s build our ROP stack
>>> rop = ROP(binary)
>>> rop.write(c.STDOUT_FILENO, binary.symbols[b'flag'], 8)
>>> rop.exit()
>>> print(rop.dump())
0x0000: 0x10000012 write(STDOUT_FILENO, 268435494, 8)
0x0004: 0x1000000e <adjust: add esp, 0x10; ret>
0x0008: 0x1 arg0
0x000c: 0x10000026 b'flag'
0x0010: 0x8 arg2
0x0014: b'faaa' <pad>
0x0018: 0x1000002f exit()
0x001c: b'haaa' <pad>
The raw data from the ROP stack is available via bytes.
>>> raw_rop = bytes(rop)
>>> print(enhex(raw_rop))
120000100e000010010000002600001008000000666161612f00001068616161
Let’s try it out!
>>> p = process(binary.path)
>>> p.send(raw_rop)
>>> print(p.recvall(timeout=5))
b'The flag'
ROP + Sigreturn¶
In some cases, control of the desired register is not available. However, if you have control of the stack, EAX, and can find a int 0x80 gadget, you can use sigreturn.
Even better, this happens automagically.
Our example binary will read some data onto the stack, and not do anything else interesting.
>>> context.clear(arch='i386')
>>> c = constants
>>> assembly = 'read:' + shellcraft.read(c.STDIN_FILENO, 'esp', 1024)
>>> assembly += 'ret\n'
>>> assembly += 'pop eax; ret\n'
>>> assembly += 'int 0x80\n'
>>> assembly += 'binsh: .asciz "/bin/sh"'
>>> binary = ELF.from_assembly(assembly)
Let’s create a ROP object and invoke the call.
>>> context.kernel = 'amd64'
>>> rop = ROP(binary)
>>> binsh = binary.symbols[b'binsh']
>>> rop.execve(binsh, 0, 0)
That’s all there is to it.
>>> print(rop.dump())
0x0000: 0x1000000e pop eax; ret
0x0004: 0x77
0x0008: 0x1000000b int 0x80
0x000c: 0x0 gs
0x0010: 0x0 fs
0x0014: 0x0 es
0x0018: 0x0 ds
0x001c: 0x0 edi
0x0020: 0x0 esi
0x0024: 0x0 ebp
0x0028: 0x0 esp
0x002c: 0x10000012 ebx = b'binsh'
0x0030: 0x0 edx
0x0034: 0x0 ecx
0x0038: 0xb eax
0x003c: 0x0 trapno
0x0040: 0x0 err
0x0044: 0x1000000b int 0x80
0x0048: 0x23 cs
0x004c: 0x0 eflags
0x0050: 0x0 esp_at_signal
0x0054: 0x2b ss
0x0058: 0x0 fpstate
Let’s try it out!
>>> p = process(binary.path)
>>> p.send(bytes(rop))
>>> time.sleep(1)
>>> p.sendline('echo hello; exit')
>>> p.recvline()
b'hello\n'
-
class
pwnlib.rop.rop.
ROP
(elfs, base=None, should_load_gadgets=True, **kwargs)[source]¶ Class which simplifies the generation of ROP-chains.
Example
>>> context.clear(arch="i386", kernel='amd64') >>> assembly = 'int 0x80; ret; add esp, 0x10; ret; pop eax; ret' >>> e = ELF.from_assembly(assembly) >>> e.symbols[b'funcname'] = e.address + 0x1234 >>> r = ROP(e) >>> r.funcname(1, 2) >>> r.funcname(3) >>> r.execve(4, 5, 6) >>> print(r.dump()) 0x0000: 0x10001234 funcname(1, 2) 0x0004: 0x10000003 <adjust: add esp, 0x10; ret> 0x0008: 0x1 arg0 0x000c: 0x2 arg1 0x0010: b'eaaa' <pad> 0x0014: b'faaa' <pad> 0x0018: 0x10001234 funcname(3) 0x001c: 0x10000007 <adjust: pop eax; ret> 0x0020: 0x3 arg0 0x0024: 0x10000007 pop eax; ret 0x0028: 0x77 0x002c: 0x10000000 int 0x80 0x0030: 0x0 gs 0x0034: 0x0 fs 0x0038: 0x0 es 0x003c: 0x0 ds 0x0040: 0x0 edi 0x0044: 0x0 esi 0x0048: 0x0 ebp 0x004c: 0x0 esp 0x0050: 0x4 ebx 0x0054: 0x6 edx 0x0058: 0x5 ecx 0x005c: 0xb eax 0x0060: 0x0 trapno 0x0064: 0x0 err 0x0068: 0x10000000 int 0x80 0x006c: 0x23 cs 0x0070: 0x0 eflags 0x0074: 0x0 esp_at_signal 0x0078: 0x2b ss 0x007c: 0x0 fpstate
>>> r = ROP(e, 0x8048000) >>> r.funcname(1, 2) >>> r.funcname(3) >>> r.execve(4, 5, 6) >>> print(r.dump()) 0x8048000: 0x10001234 funcname(1, 2) 0x8048004: 0x10000003 <adjust: add esp, 0x10; ret> 0x8048008: 0x1 arg0 0x804800c: 0x2 arg1 0x8048010: b'eaaa' <pad> 0x8048014: b'faaa' <pad> 0x8048018: 0x10001234 funcname(3) 0x804801c: 0x10000007 <adjust: pop eax; ret> 0x8048020: 0x3 arg0 0x8048024: 0x10000007 pop eax; ret 0x8048028: 0x77 0x804802c: 0x10000000 int 0x80 0x8048030: 0x0 gs 0x8048034: 0x0 fs 0x8048038: 0x0 es 0x804803c: 0x0 ds 0x8048040: 0x0 edi 0x8048044: 0x0 esi 0x8048048: 0x0 ebp 0x804804c: 0x8048080 esp 0x8048050: 0x4 ebx 0x8048054: 0x6 edx 0x8048058: 0x5 ecx 0x804805c: 0xb eax 0x8048060: 0x0 trapno 0x8048064: 0x0 err 0x8048068: 0x10000000 int 0x80 0x804806c: 0x23 cs 0x8048070: 0x0 eflags 0x8048074: 0x0 esp_at_signal 0x8048078: 0x2b ss 0x804807c: 0x0 fpstate
-
build
(base=None, description=None)[source]¶ Construct the ROP chain into a list of elements which can be passed to
pwnlib.util.packing.flat
.Parameters:
-
find_gadget
(instructions)[source]¶ Returns a gadget with the exact sequence of instructions specified in the
instructions
argument.
-
migrated
= False[source]¶ Whether or not the ROP chain directly sets the stack pointer to a value which is not contiguous
-
raw
(value)[source]¶ Adds a raw integer or string to the ROP chain.
If your architecture requires aligned values, then make sure that any given string is aligned!
Parameters: data (int, bytes) – The raw value to put onto the rop chain.
-
resolve
(resolvable)[source]¶ Resolves a symbol to an address
Parameters: resolvable (bytes, str, int) – Thing to convert into an address Returns: int containing address of ‘resolvable’, or None
-
search
(move=0, regs=None, order='size')[source]¶ Search for a gadget which matches the specified criteria.
Parameters: The search will try to minimize the number of bytes popped more than requested, the number of registers touched besides the requested and the address.
If
order == 'size'
, then gadgets are compared lexicographically by(total_moves, total_regs, addr)
, otherwise by(total_regs, total_moves, addr)
.Returns: A pwnlib.rop.gadgets.Gadget
object
-
search_iter
(move=None, regs=None)[source]¶ Iterate through all gadgets which move the stack pointer by at least
move
bytes, and which allow you to set all registers inregs
.
-
setRegisters
(registers)[source]¶ Returns an OrderedDict of addresses/values which will set the specified register context.
Parameters: registers (dict) – Dictionary of {register name: value}
Returns: sequence of gadgets, values, etc.}``. Return type: An OrderedDict of ``{register
-
unresolve
(value)[source]¶ Inverts ‘resolve’. Given an address, it attempts to find a symbol for it in the loaded ELF files. If none is found, it searches all known gadgets, and returns the disassembly
Parameters: value (int) – Address to look up Returns: String containing the symbol name for the address, disassembly for a gadget (if there’s one at that address), or an empty string.
-