You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- The link register is `x30` (a.k.a. `lr`), and functions typically save `x29`/`x30` with `stp x29, x30, [sp, #-16]!` and restore them with `ldp x29, x30, [sp], #16; ret`.
48
48
- This means the saved return address lives at `sp+8` relative to the frame base. With a `char buffer[64]` placed below, the usual overwrite distance to the saved `x30` is 64 (buffer) + 8 (saved x29) = 72 bytes — exactly what we’ll find below.
49
-
- The stack pointer must remain 16‑byte aligned at function boundaries. If you build ROP chains later for more complex scenarios, keep the SP alignment or you may crash on function epilogues.
49
+
- The stack pointer must remain 16-byte aligned at function boundaries. If you build ROP chains later for more complex scenarios, keep the SP alignment or you may crash on function epilogues.
50
+
51
+
### Why partial overwrites work so well on AArch64
52
+
53
+
- AArch64 Linux is usually **little-endian**, so the first byte you overwrite in memory is the **least significant byte** of the saved `x30`. That is why a short overwrite with `p8()`/`p16()` can retarget the return address without touching the higher bytes.
54
+
- On PIE binaries, **the page offset stays constant** after relocation. In practice the lowest **12 bits** of a function address are preserved by ASLR, so a **1-byte overwrite** can only move within the same `0x100` window and a **2-byte overwrite** can only move within the same `0x10000` window.
55
+
- Therefore, before attempting a partial ret2win, compare the original saved return address with the target `win()` address. If they differ outside those low bytes, a 1- or 2-byte overwrite is not enough and you need either a leak or a larger overwrite primitive.
50
56
51
57
## Finding the offset
52
58
@@ -177,7 +183,17 @@ You can find another off-by-one example in ARM64 in [https://8ksec.io/arm64-reve
177
183
178
184
### Off-by-2
179
185
180
-
Without a leak we don't know the exact address of the winning function but we can know the offset of the function from the binary and knowing that the return address we are overwriting is already pointing to a close address, it's possible to leak the offset to the win function (**0x7d4**) in this case and just use that offset:
186
+
Without a leak we don't know the exact address of the winning function but we can know the offset of the function from the binary and, because the return address we are overwriting already points inside the same PIE image, we can often redirect it by changing only the low bytes. In this example the relevant offset to `win()` is **0x7d4** and a 2-byte overwrite is enough because the saved return address and `win()` still share the same higher bytes.
187
+
188
+
A quick way to sanity-check this before writing the exploit is to compare both addresses in the debugger and keep only the low bytes you really need to change:
189
+
190
+
```text
191
+
saved x30 : 0x0000aaaaaa00079c
192
+
win() : 0x0000aaaaaa0007d4
193
+
^^^^
194
+
```
195
+
196
+
Only the last two bytes differ here, so `p16(0x07d4)` is enough. If your target looked like `0x0000aaaaab1207d4`, the higher bytes changed as well and the same trick would fail.
## Notes on modern AArch64 hardening (PAC/BTI) and ret2win
484
500
501
+
- Current GCC/Clang toolchains support `-mbranch-protection=standard`, which enables the common PAC/BTI hardening profile. For labs, keep using `-mbranch-protection=none` so your saved-`x30` overwrite behaves like a classic ret2win.
485
502
- If the binary is compiled with AArch64 Branch Protection, you may see `paciasp`/`autiasp` or `bti c` emitted in function prologues/epilogues. In that case:
486
503
- Returning to an address that is not a valid BTI landing pad may raise a `SIGILL`. Prefer targeting the exact function entry that contains `bti c`.
487
-
- If PAC is enabled for returns, naive return‑address overwrites may fail because the epilogue authenticates `x30`. For learning scenarios, rebuild with `-mbranch-protection=none` (shown above). When attacking real targets, prefer non‑return hijacks (e.g., function pointer overwrites) or build ROP that never executes an `autiasp`/`ret` pair that authenticates your forged LR.
504
+
-`pac-ret` signs functions that actually spill the return address to memory, so non-leaf functions are usually affected first. A leaf `win()` may still lack PAC unless the binary was built with `pac-ret+leaf`.
505
+
- If PAC is enabled for returns, naive return-address overwrites may fail because the epilogue authenticates `x30`. For learning scenarios, rebuild with `-mbranch-protection=none` (shown above). When attacking real targets, prefer non-return hijacks (e.g., function pointer overwrites) or build ROP that never executes an `autiasp`/`ret` pair that authenticates your forged LR.
488
506
- To check features quickly:
489
507
-`readelf --notes -W ./ret2win` and look for `AARCH64_FEATURE_1_BTI` / `AARCH64_FEATURE_1_PAC` notes.
490
508
-`objdump -d ./ret2win | head -n 40` and look for `bti c`, `paciasp`, `autiasp`.
509
+
-`readelf -n ./ret2win | grep -A1 'AArch64 feature'` is useful to confirm whether the linker actually kept the GNU property note.
491
510
492
511
## Running on non‑ARM64 hosts (qemu‑user quick tip)
-Enabling PAC and BTI on AArch64 for Linux (Arm Community, Nov 2024). https://community.arm.com/arm-community-blogs/b/operating-systems-blog/posts/enabling-pac-and-bti-on-aarch64-for-linux
526
-
-Procedure Call Standard for the Arm 64-bit Architecture (AAPCS64). https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst
-Enabling PAC and BTI on AArch64 for Linux (Arm Community, Nov 2024). https://developer.arm.com/community/arm-community-blogs/b/architectures-and-processors-blog/posts/enabling-pac-and-bti-on-aarch64
0 commit comments