diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dd23f2a5..013ff043a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -93,7 +93,9 @@ The table below shows which release corresponds to each branch, and what date th - [#2574][2574] Allow creating an ELF from in-memory bytes - [#2575][2575] Detect when Terminator is being used as terminal - [#2578][2578] Add gnome-terminal, Alacritty, Ttilix for run_in_new_terminal +- [#2583][2583] Add new `ROP` feature: relative stack offset +[2583]: https://github.com/Gallopsled/pwntools/pull/2583 [2419]: https://github.com/Gallopsled/pwntools/pull/2419 [2551]: https://github.com/Gallopsled/pwntools/pull/2551 [2519]: https://github.com/Gallopsled/pwntools/pull/2519 diff --git a/docs/source/rop/rop.rst b/docs/source/rop/rop.rst index 553cac97d..36eecf80d 100644 --- a/docs/source/rop/rop.rst +++ b/docs/source/rop/rop.rst @@ -8,7 +8,7 @@ from pwnlib.context import context from pwnlib.elf import ELF from pwnlib.rop import ROP - from pwnlib.rop.call import Call, AppendedArgument + from pwnlib.rop.call import Call, AppendedArgument, StackRelative from pwnlib.elf.maps import CAT_PROC_MAPS_EXIT from pwnlib.util.packing import * from pwnlib.util.fiddling import * diff --git a/pwnlib/rop/call.py b/pwnlib/rop/call.py index 49c852123..1bd9a1da7 100644 --- a/pwnlib/rop/call.py +++ b/pwnlib/rop/call.py @@ -55,6 +55,36 @@ class StackAdjustment(Unresolved): """ pass +class StackRelative(Unresolved): + """ + During the parsing process, it will be replaced with the relative offset + of the stack position where the current slot is located. + + Examples + + >>> context.clear() + >>> context.arch = 'amd64' + >>> u = StackRelative(+8) + >>> u.resolve(1000) + 1008 + >>> u = StackRelative(-8) + >>> u.resolve(1000) + 992 + """ + def __init__(self, offset): + self.offset = offset + + def resolve(self, base): + """ + Resolve the stack-relative address based on the given base address. + + Arguments: + base(int): The base address to resolve against. + + Returns: + int: The resolved address. + """ + return base + self.offset class AppendedArgument(Unresolved): r""" diff --git a/pwnlib/rop/rop.py b/pwnlib/rop/rop.py index d4963bd41..594b982c9 100644 --- a/pwnlib/rop/rop.py +++ b/pwnlib/rop/rop.py @@ -356,6 +356,93 @@ >>> p.sendline(b'echo hello; exit') >>> p.recvline() b'hello\n' + +ROP + StackRelative +----------------------- + +In certain situations, you might prefer to use a stack-relative address. + +This is an example. Sometimes, we need to use `pop rdx; leave; ret;` to control rdx. + + >>> context.clear(arch='amd64') + >>> assembly = 'special_gadget: pop rdx; leave; ret; pop rdi; ret; pop rsi; ret; pop rbp; ret' + >>> binary = ELF.from_assembly(assembly) + >>> binary.symbols['funcname'] = binary.entry + 0x1000 + >>> rop = ROP(binary, base=0xdead0000) + >>> rop.rbp = 0xdead0000 + len(rop.chain()) + 0x10 # set rbp next to the special_gadget + >>> rop.raw(binary.symbols['special_gadget']) + >>> rop.raw(0xdeadbeef) # control rdx + >>> rop.call("funcname", [b"hello", b"world"]) + >>> print(rop.dump()) + 0xdead0000: 0x10000007 pop rbp; ret + 0xdead0008: 0xdead0010 (+0x8) + 0xdead0010: 0x10000000 special_gadget + 0xdead0018: 0xdeadbeef + 0xdead0020: 0x10000005 pop rsi; ret + 0xdead0028: 0xdead0048 [arg1] rsi = AppendedArgument([b'world'], 0x0) (+0x20) + 0xdead0030: 0x10000003 pop rdi; ret + 0xdead0038: 0xdead0050 [arg0] rdi = AppendedArgument([b'hello'], 0x0) (+0x18) + 0xdead0040: 0x10001000 funcname + 0xdead0048: b'world\x00$$' + 0xdead0050: b'hello\x00$$' + +This might be right. +But the following situation is not right. + + >>> rop = ROP(binary, base=0xdead0000) + >>> rop.call("funcname", [b"arg0", b"arg1"]) + >>> rop.rbp = 0xdead0000 + len(rop.chain()) + 0x10 # this offset is wrong + >>> rop.raw(binary.symbols['special_gadget']) + >>> rop.raw(0xdeadbeef) # control rdx + >>> rop.call("funcname", [b"hello", b"world"]) + >>> print(rop.dump()) + 0xdead0000: 0x10000005 pop rsi; ret + 0xdead0008: 0xdead0070 [arg1] rsi = AppendedArgument([b'arg1'], 0x0) (+0x68) + 0xdead0010: 0x10000003 pop rdi; ret + 0xdead0018: 0xdead0078 [arg0] rdi = AppendedArgument([b'arg0'], 0x0) (+0x60) + 0xdead0020: 0x10001000 funcname + 0xdead0028: 0x10000007 pop rbp; ret + 0xdead0030: 0xdead0048 (+0x18) + 0xdead0038: 0x10000000 special_gadget + 0xdead0040: 0xdeadbeef + 0xdead0048: 0x10000005 pop rsi; ret + 0xdead0050: 0xdead0080 [arg1] rsi = AppendedArgument([b'world'], 0x0) (+0x30) + 0xdead0058: 0x10000003 pop rdi; ret + 0xdead0060: 0xdead0088 [arg0] rdi = AppendedArgument([b'hello'], 0x0) (+0x28) + 0xdead0068: 0x10001000 funcname + 0xdead0070: b'arg1\x00$$$' + 0xdead0078: b'arg0\x00$$$' + 0xdead0080: b'world\x00$$' + 0xdead0088: b'hello\x00$$' + +For this purpose, you can use :class:`StackRelative` as a placeholder for a stack-relative address. + + >>> rop = ROP(binary, base=0xdead0000) + >>> rop.call("funcname", [b"arg0", b"arg1"]) + >>> rop.rbp = StackRelative(+0x8) # this offset is wrong + >>> rop.raw(binary.symbols['special_gadget']) + >>> rop.raw(0xdeadbeef) # control rdx + >>> rop.call("funcname", [b"hello", b"world"]) + >>> print(rop.dump()) + 0xdead0000: 0x10000005 pop rsi; ret + 0xdead0008: 0xdead0070 [arg1] rsi = AppendedArgument([b'arg1'], 0x0) (+0x68) + 0xdead0010: 0x10000003 pop rdi; ret + 0xdead0018: 0xdead0078 [arg0] rdi = AppendedArgument([b'arg0'], 0x0) (+0x60) + 0xdead0020: 0x10001000 funcname + 0xdead0028: 0x10000007 pop rbp; ret + 0xdead0030: 0xdead0038 (+0x8) + 0xdead0038: 0x10000000 special_gadget + 0xdead0040: 0xdeadbeef + 0xdead0048: 0x10000005 pop rsi; ret + 0xdead0050: 0xdead0080 [arg1] rsi = AppendedArgument([b'world'], 0x0) (+0x30) + 0xdead0058: 0x10000003 pop rdi; ret + 0xdead0060: 0xdead0088 [arg0] rdi = AppendedArgument([b'hello'], 0x0) (+0x28) + 0xdead0068: 0x10001000 funcname + 0xdead0070: b'arg1\x00$$$' + 0xdead0078: b'arg0\x00$$$' + 0xdead0080: b'world\x00$$' + 0xdead0088: b'hello\x00$$' + """ from __future__ import absolute_import from __future__ import division @@ -385,6 +472,7 @@ from pwnlib.rop.call import CurrentStackPointer from pwnlib.rop.call import NextGadgetAddress from pwnlib.rop.call import StackAdjustment +from pwnlib.rop.call import StackRelative from pwnlib.rop.call import Unresolved from pwnlib.rop.gadgets import Gadget from pwnlib.util import lists @@ -464,7 +552,7 @@ def dump(self): if desc: line += ' %s' % desc if off is not None: - line += ' (+%#x)' % off + line += (' (+%#x)' % off) if off >= 0 else ' (-%#x)' % -off rv.append(line) addr += _slot_len(data) @@ -1029,6 +1117,11 @@ def build(self, base = None, description = None): stack[i] = slot.address stack.describe(self.describe(slot), slot_address) + elif isinstance(slot, StackRelative): + address = slot.resolve(slot_address) + stack[i] = address + stack.describe(self.describe(address), slot_address) + # Everything else we can just leave in place. # Maybe the user put in something on purpose? # Also, it may work in pwnlib.util.packing.flat()