Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/source/rop/rop.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
Expand Down
30 changes: 30 additions & 0 deletions pwnlib/rop/call.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand Down
95 changes: 94 additions & 1 deletion pwnlib/rop/rop.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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()
Expand Down
Loading