Skip to content

Conversation

@lbr77
Copy link
Contributor

@lbr77 lbr77 commented Jan 18, 2026

usage:

chain = rop.sigreturn(..regs)
# or 
chain = rop.sigreturn_syscall(0x3b, [next(elf.search(b"/bin/sh\x00")), 0, 0])

Copilot AI review requested due to automatic review settings January 18, 2026 09:34
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds Sigreturn-Oriented Programming (SROP) functionality to the angrop library, enabling users to build ROP chains that leverage the sigreturn system call to set arbitrary register values. The PR introduces two new files implementing the SROP framework and integrates them into the existing chain builder infrastructure.

Changes:

  • Added SigreturnFrame class to serialize sigreturn frames for multiple architectures (i386, amd64, ARM, MIPS, AARCH64)
  • Added SigreturnBuilder class with sigreturn() and sigreturn_syscall() methods for building SROP chains
  • Extended architecture definitions with sigreturn syscall numbers for i386 and amd64
  • Modified do_syscall() to support a stack_recover parameter for SROP-specific behavior
  • Added test coverage for amd64 SROP chains

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
angrop/sigreturn.py New file implementing SigreturnFrame class with architecture-specific register layouts and serialization
angrop/chain_builder/sigreturn.py New file implementing SigreturnBuilder with methods to construct SROP chains
angrop/arch.py Added sigreturn_num attribute to base class and set values for X86 (0x77) and AMD64 (0xf)
angrop/chain_builder/init.py Integrated SigreturnBuilder and exposed sigreturn/sigreturn_syscall methods, modified do_syscall signature
angrop/chain_builder/func_caller.py Added stack_recover parameter to _func_call to support non-stack-recovering gadgets
angrop/chain_builder/sys_caller.py Propagated stack_recover parameter through do_syscall method
tests/test_ropchain.py Added test_sigreturn_chain_amd64 test case and imported SigreturnFrame
docs/pythonapi.md Added documentation example for sigreturn usage
uv.lock Added uv lock file with Python version requirement

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

chain._values = chain._values[:offset_words]
chain.payload_len = offset_words * self.project.arch.bytes
elif offset_words < 0: # drop values to fit offset.
l.warning("Negative offset, %d frame values would be dropped.",-offset_words)
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The warning message uses an incorrect format. The code has l.warning("Negative offset, %d frame values would be dropped.",-offset_words) but the comma should come after the closing quote and there should be a space after the comma. The correct format should be: l.warning("Negative offset, %d frame values would be dropped.", -offset_words).

Suggested change
l.warning("Negative offset, %d frame values would be dropped.",-offset_words)
l.warning("Negative offset, %d frame values would be dropped.", -offset_words)

Copilot uses AI. Check for mistakes.
self._registers = _REGISTERS[arch_name]
self._values = {reg: 0 for reg in self._registers.values()}
self._values.update(_DEFAULTS.get(arch_name, {}))
self._word_size = 8 if arch_name == "amd64" else 4
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The word size determination only handles amd64 as 8 bytes, defaulting all other architectures to 4 bytes. However, aarch64 also uses 8-byte word size. This will cause incorrect frame serialization for aarch64. Consider using a more comprehensive check or a mapping dictionary for word sizes per architecture.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

word_size is just arch_bytes. Can you use that instead? right now it is wrong for other arches like aarch64

"i386": {"cs": 0x73, "ss": 0x7b},
"i386_on_amd64": {"cs": 0x23, "ss": 0x2b},
"arm": {"trap_no": 0x6, "cpsr": 0x40000010, "VFPU-magic": 0x56465001, "VFPU-size": 0x120},
"mips": {},
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing default values for mipsel architecture. The _DEFAULTS dictionary has an entry for "mips" (empty dict), but not for "mipsel", which is defined in _REGISTERS and _ARCH_NAME_MAP. This could cause issues when creating sigreturn frames for MIPSEL architecture if default values are needed.

Suggested change
"mips": {},
"mips": {},
"mipsel": {},

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you make sure all the references are cleanly aligned?

Comment on lines +116 to +124
if 0 < offset_words < len(chain._values): # should pad to offset(rsp at syscall)
chain._values = chain._values[:offset_words]
chain.payload_len = offset_words * self.project.arch.bytes
elif offset_words < 0: # drop values to fit offset.
l.warning("Negative offset, %d frame values would be dropped.",-offset_words)
frame_words = frame_words[-offset_words:]
elif offset_words > len(chain._values):
for _ in range(offset_words - len(chain._values)):
chain.add_value(filler)
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential logic error: when offset_words equals 0, the frame_words are appended directly without any padding or truncation. However, this case is not explicitly handled in the if-elif chain. While the code will still work (falling through to the final loop), it would be clearer to explicitly handle the offset_words == 0 case or add a comment explaining that no adjustment is needed in this case.

Copilot uses AI. Check for mistakes.
frame.update(**registers)

syscall_num = self.arch.sigreturn_num # syscall(sigreturn)
chain = self.chain_builder.do_syscall(syscall_num, [],stack_recover=False, needs_return=False) # dummy args
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after comma in function call. The code has [] followed immediately by stack_recover without a space after the comma.

Suggested change
chain = self.chain_builder.do_syscall(syscall_num, [],stack_recover=False, needs_return=False) # dummy args
chain = self.chain_builder.do_syscall(syscall_num, [], stack_recover=False, needs_return=False) # dummy args

Copilot uses AI. Check for mistakes.
@angr angr deleted a comment from Copilot AI Jan 18, 2026
@angr angr deleted a comment from Copilot AI Jan 18, 2026
@angr angr deleted a comment from Copilot AI Jan 18, 2026
@angr angr deleted a comment from Copilot AI Jan 18, 2026
@angr angr deleted a comment from Copilot AI Jan 18, 2026
@angr angr deleted a comment from Copilot AI Jan 18, 2026
@angr angr deleted a comment from Copilot AI Jan 18, 2026
@angr angr deleted a comment from Copilot AI Jan 18, 2026
@angr angr deleted a comment from Copilot AI Jan 18, 2026
@angr angr deleted a comment from Copilot AI Jan 18, 2026
@Kyle-Kyle
Copy link
Collaborator

Kyle-Kyle commented Jan 18, 2026

This srop module looks amazing! I would love to merge it!

But I have a few comments:

  1. what is the semantic meaning of stack_recover here? It feels like what it means is that we are not going to append more gadgets after this one. So it feels its equivalent to needs_return. Since it is not exposed to users anyway. Let's just reuse needs_return.
  2. shall we allow a sp argument to sigreturn_syscall so that if users want to invoke read syscalls (in case there is no /bin/sh in the binary`), they can still ROP.
  3. can you add a i386 testcase? because sigreturn is quite different in 32-bit because all the offsets are different
  4. can you add how to sigreturn_syscall to pythonapi.md?
  5. maybe we can even have a sigreturn_execve variant. (and then we can use it in rop.execve) But you don't have to do it now. You can just add it as a TODO in the code.

@Kyle-Kyle
Copy link
Collaborator

Kyle-Kyle commented Jan 18, 2026

also, maybe we want to print sigreturn payload a bit differently in .pp or it will be too much data. But it can be marked as TODO.

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