Skip to content

Add new ROP feature: relative stack offset#2583

Closed
CsomePro wants to merge 4 commits into
Gallopsled:devfrom
CsomePro:dev
Closed

Add new ROP feature: relative stack offset#2583
CsomePro wants to merge 4 commits into
Gallopsled:devfrom
CsomePro:dev

Conversation

@CsomePro

Copy link
Copy Markdown

Add new ROP feature: relative stack offset

Sometimes, the ROP needs to be set using the relative offset position on the stack.

This is a temporary solution.

rop = ROP(elf)
rop.rbp = len(rop.chain()) + 8 + offset

But sometimes errors occur.

rop = ROP(elf)
rop.call("open", [b"file", 0])
rop.rbp = len(rop.chain()) + 8 + offset

Because of the existence of AppendedArgument, arguments will be placed at the end of the stack.

len(rop.chain()) will not be the correct offset from base to the current slot.

Therefore, I added a class StackRelative and added the processing logic for StackRelative in the ROP.build function

class StackRelative(Unresolved):
    def __init__(self, offset):
        self.offset = offset
    
    def resolve(self, base):
        return base + self.offset
        ...
        elif isinstance(slot, StackRelative):
            address = slot.resolve(slot_address)
            stack[i] = address
            stack.describe(self.describe(address), slot_address)
        ...

At this point we can use this to solve the above example.

rop = ROP(elf)
rop.call("open", [b"file", 0])
rop.rbp = StackRelative(offset)

@peace-maker

Copy link
Copy Markdown
Member

Nice, can you please add a test to the ROP class itself or the module at the top which verifies this works as expected? You can look at the other tests which use ELF.from_assembly to setup a test binary with required gadgets manually. That way other users can learn about the existence of this new feature too.

@CsomePro

Copy link
Copy Markdown
Author

I added the StackRelative test at the top of rop.py. And this test error seems to be a problem with the libcdb module.

@peace-maker

Copy link
Copy Markdown
Member

This reminds me of labels for flat as proposed in #1026 #1063 but the ROP class is a different space. I don't think that label concept is really applicable for rop chains?

Comment thread pwnlib/rop/rop.py Outdated
Comment thread pwnlib/rop/rop.py
Comment thread pwnlib/rop/rop.py Outdated
@CsomePro

CsomePro commented May 31, 2025

Copy link
Copy Markdown
Author

This reminds me of labels for flat as proposed in #1026 #1063 but the ROP class is a different space. I don't think that label concept is really applicable for rop chains?

I am also thinking about this question. If I want to set an address precisely to the front or back of a gadget or data during ROP writing, due to the encapsulation of some ROP layouts, how should I do this? Since the length of the data after the gadget is uncertain, it seems difficult to achieve this requirement.

@CsomePro
CsomePro requested a review from peace-maker June 6, 2025 16:00
@CsomePro

Copy link
Copy Markdown
Author

The document is complete and can be reviewed.

@peace-maker

Copy link
Copy Markdown
Member

I though about it again. (Happy anniversary to the PR 🎉)
That StackRelative class requires you to know the gadget it is used in in order to enter the right magic offset right after that gadget. You don't know which gadget is used to set the register in rop.rbp = 0x1234. Maybe there is a pop rbp; ret gadget, but maybe there's just a pop rbp; pop rcx; ret. Since you need to skip the rop gadget in your relative offset, you'd have to somehow know which gadget is used. The ROP class doesn't tell you this and you're not supposed to have to worry about it.

Right now you'd even have to manually import the StackRelative class, so a ROP.relative(self, offset: int) -> StackRelative function would be convenient to construct that object for you, but that manual offset calculation is error prone.

I think adding something like labels would be applicable to more use cases:

Add a way to assign a label to the position in the rop chain and allow to reference that label anywhere. Labels would only work when the base address of the rop chain is known and passed to the ROP class.

from pwn import *
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=stack)
rop.call("funcname", [b"arg0", b"arg1"])
# set rbp to address of "step1" - 8
rop.rbp = rop.uselabel("step1", offset=-8)
rop.raw(binary.symbols['special_gadget'])
rop.raw(0xdeadbeef)     # control rdx (and rbp after leave)
# store current offset while building the chain
rop.setlabel("step1")
rop.call("funcname", [b"hello", b"world"])
print(rop.dump())

To set the value of rbp through leave in one go, we'd have to leave out the offset when using the label:

rop = ROP(binary, base=stack)
rop.call("funcname", [b"arg0", b"arg1"])
# set rbp to address of "step1" -> `pop rbp; ret` based on that new `rsp`
rop.rbp = rop.uselabel("step1")
rop.raw(binary.symbols['special_gadget'])
rop.raw(0xdeadbeef)     # control rdx
# store current offset while building the chain
rop.setlabel("step1")
rop.raw(0xbeef)   # rbp after leave
rop.call("funcname", [b"hello", b"world"])

Your use case of gadgets ending in leave; ret could be automated I think.

Thoughts? Would this work for your usecase?

@peace-maker

Copy link
Copy Markdown
Member

Superceded by #2734.

@peace-maker peace-maker closed this Jun 1, 2026
peace-maker added a commit that referenced this pull request Jul 4, 2026
* ROP: Add labels to reference gadgets dynamically

Allow to assign a name to a point in the ROP chain using `ROP.setlabel(name)`
and reference it somewhere else using `ROP.uselabel(name, offset)`.
You have to know the location of your chain in memory to resolve the
right addresses. This allows to specify addresses relative to your ROP chain payload in your chain.

Refs #2583

* Update ret2dlresolve test output

* Update CHANGELOG

* Rename functions to `label` and `ref`

This is less verbose than `setlabel` and `uselabel` but still descriptive enough to be able to guess what they do.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants