Defcon 2023 rest-and-attest
字数 1474 2025-08-06 12:21:05
Defcon 2023 rest-and-attest Rust Pwn 题目分析与利用
题目概述
这是一个Rust编写的Pwn题目,模拟了TPM(Trusted Platform Module)安全模块的功能。题目包含多个组件,主要漏洞存在于SFM(Secure Firmware Module)模块中,通过精心构造的输入可以实现栈溢出攻击。
文件结构
bin/
launcher
run_challenge.sh
sfm
uploader
wrapper.sh
lib/
libcrypto.so.3
libc.so.6
libgcc_s.so.1
src/
Cargo.lock
Cargo.toml
sfm/
Cargo.toml
src/
lib.rs
main.rs
sfm_proto.rs
sfm-sys/
build.rs
Cargo.toml
src/
lib.rs
vendor/
uploader/
Cargo.toml
src/
main.rs
trusted_firmware.raw
程序流程分析
主程序入口 - Uploader
Uploader程序提供四个主要功能:
upload- 上传一段shellcode二进制程序download- 下载现有的shellcode二进制run- 使用launcher运行对应的shellcodequit- 退出程序
Launcher程序 - 沙箱
Launcher是一个C编写的程序,主要功能:
- 设置seccomp过滤器,只允许以下系统调用:
- read
- write
- recvmsg
- munmap
- 通过mmap创建hollow_and_jump_buffer函数跳转到上传的shellcode
通信流程
+-launcher
| |
| raw
| |
| sfm
input | sock_client
output | sock_server
漏洞分析
1. Attest功能信息泄露
在attest_quote函数中,存在算法ID验证不严格的问题:
let alg = cmd.alg_id;
if alg > SfmHashAlgorithm::HashAlgMax as u16 {
return Err(SfmError::InvalidAlgorithmType);
}
当alg_id=4时,会泄露libcrypto.so.3的地址。
2. OwnershipRecord修改导致的栈溢出
关键点在于from_utf8_lossy函数对非UTF-8字符串的处理:
impl From<OwnershipRecordRaw> for OwnershipRecord {
fn from(item: OwnershipRecordRaw) -> Self {
Self {
country_code: String::from_utf8_lossy(&item.country_code[..]).to_string(),
owner_name: String::from_utf8_lossy(&item.owner_name[..]).to_string(),
// ...
}
}
}
当输入非UTF-8字符时,from_utf8_lossy会添加额外的替换字符(FF FD),导致内存扩展,最终在certify_ownership_record函数中造成栈溢出。
利用步骤
1. 认证绕过
通过压缩原始固件并在运行时解压,绕过PCR校验:
- 将原始
trusted_firmware.raw使用LZ4算法压缩 - 在自定义固件中包含压缩后的数据
- 运行时解压并分块更新bank
2. 信息泄露
使用alg_id=4调用attest功能,泄露libcrypto.so.3的地址和堆地址。
3. 堆布局构造
- 创建NvStorage对象作为ROP链存储
- 计算ROP链地址:
exp_rop_addr = leak_heap - 0x11620 + 0x18610
4. 栈溢出利用
构造恶意OwnershipRecord:
- 填充owner_name为51个0xff和1个'B'
- device_name设置为pop_rsp_ret gadget地址
- serial_number设置为ROP链地址
5. ROP链构造
使用ropper工具生成execve ROP链:
ropper -f ./libcrypto.so.3 --chain execve
关键gadget:
- pop_rsp_ret
- pop_rax_ret
- pop_rcx_ret
- mov_rcx_rax_ret
- pop_rdi_ret
- pop_rsi_ret
- pop_rdx_ret
- syscall
完整利用代码
#include <stddef.h>
#include <stdint.h>
#include <sys/socket.h>
// 系统调用定义
#define __NR_write 1
#define __NR_read 0
#define __NR_recvmsg 47
#define __NR_exit 60
// 结构体定义
typedef struct __attribute__((__packed__)) {
unsigned int _reservered;
unsigned short command_code;
unsigned short pad_;
} SfmCommand;
// ... 其他结构体定义 ...
unsigned char blob[] __attribute__((section(".text")));
int _start(void) {
int fd = 3;
// 1. 握手
// 2. 解压并更新bank
// 3. 泄露地址
// 4. 创建NvStorage存放ROP链
// 5. 触发栈溢出
// 6. 执行ROP链
return 0;
}
调试技巧
- 使用socketpair创建通信管道:
import os
import socket
import subprocess
sock1, sock2 = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
os.set_inheritable(sock1.fileno(), True)
os.set_inheritable(sock2.fileno(), True)
os.environ['SFM_FD'] = str(sock2.fileno())
os.environ['FIRMWARE_FD'] = str(sock1.fileno())
subprocess.call(['bash', '-i'], env=os.environ, pass_fds=(sock1.fileno(), sock2.fileno()))
- 后台启动sfm:
./sfm &
- 使用patchelf修改二进制库路径
总结
本题展示了在Rust程序中由于与不安全C代码交互导致的漏洞,以及如何绕过TPM-like的安全验证机制。关键点包括:
- 利用UTF-8字符串处理特性造成内存扩展
- 通过压缩固件绕过完整性校验
- 在严格沙箱限制下实现代码执行
- Rust与C交互时的安全隐患
这种类型的漏洞在现实世界的安全模块中同样值得警惕,特别是在处理跨语言边界的数据转换时。