模糊测试开发实战:系统调用沙箱化技术解析

本文深入探讨了如何通过修改Musl C库实现系统调用拦截,构建基于Bochs模拟器的模糊测试框架。详细介绍了上下文切换机制、寄存器状态保存和扩展状态处理等核心技术实现。

模糊测试开发第二篇:系统调用沙箱化

引言

近期我们正在开发一个模糊测试器(fuzzer),或者更准确地说是一个执行引擎。我们构建的模糊测试器会将静态编译的Bochs模拟器加载到自身中,在执行Bochs逻辑的同时为其维护沙箱环境。本质上,我们因为不想从头实现x86_64模拟器,而直接使用了完整的Bochs模拟器。

本篇文章将深入探讨如何通过系统调用沙箱化来防止Bochs逃逸或从外部环境获取数据。我们将重点介绍首次实现Bochs到模糊测试器上下文切换来处理系统调用的技术细节。

系统调用基础

系统调用是用户态程序主动切换到内核态以使用内核功能的机制。上下文切换意味着改变代码执行的上下文环境。当发生系统调用时,操作系统首先保存当前执行状态,然后执行请求的内核代码,完成后再优雅地返回到用户态进程。

在我们的场景中,当Bochs需要内核帮助时,我们需要:

  1. 识别Bochs尝试进行系统调用
  2. 拦截执行并重定向到适当代码路径
  3. 保存Bochs的执行状态
  4. 执行Lucid逻辑(作为Bochs的内核)
  5. 通过恢复状态优雅返回Bochs

C库的作用

通常程序员不直接进行系统调用,而是使用C库函数。这些函数实际上是系统调用的包装器。这为我们的目的提供了一个很好的切入点,因为Bochs程序员也使用C库函数而不是直接调用系统调用。

使用Musl C库

Musl是一个轻量级C库,特别适合静态链接,这正是我们构建静态PIE Bochs所需的。我们可以手动修改Musl代码来改变系统调用包装函数的工作方式,从而将执行劫持到Lucid而不是内核。

执行上下文跟踪

我们创建了一个头文件lucid.h放在Musl中,定义了Bochs需要访问的所有Lucid特定数据结构。关键是一个全局的lucid_ctx执行上下文结构体:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
typedef struct lucid_ctx {
    size_t exit_handler;
    int save_inst;
    size_t save_size;
    size_t lucid_save_area;
    size_t bochs_save_area;
    struct register_bank register_bank;
    size_t magic;
} lucid_ctx_t;

lucid_ctx_t *g_lucid_ctx;

修改Musl系统调用

我们修改了Musl中的系统调用函数,在x86_64架构下,这些函数位于arch/x86_64/syscall_arch.h中。修改后的函数首先检查全局Lucid执行上下文,如果存在,就设置调用约定并调用Lucid而不是内核。

1
2
3
4
5
6
7
8
9
static __inline long __syscall6(long n, long a1, long a2, long a3, long a4, long a5, long a6)
{
    if (!g_lucid_ctx) { return __syscall6_original(n, a1, a2, a3, a4, a5, a6); }
    
    register long ret;
    register long r12 __asm__("r12") = (size_t)(g_lucid_ctx->exit_handler);
    register long r13 __asm__("r13") = (size_t)(&g_lucid_ctx->register_bank);
    register long r14 __asm__("r14") = SYSCALL;
    register long r15 __asm__("r15") = (size_t)(g_lucid_ctx);

实现上下文切换

我们创建了一个exit_handler函数,当Bochs在Lucid下尝试系统调用时会跳转到此函数。首先保存通用寄存器和CPU标志,然后调用Rust函数lucid_handler:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 保存GPR到内存
"mov [r13 + 0x0], rax",
"mov [r13 + 0x8], rbx",
// ... 其他寄存器保存

// 保存CPU标志
"pushfq",

// 设置lucid_handler的函数参数
"mov rdi, r15",
"mov rsi, r14",
"call lucid_handler",

扩展状态处理

除了通用寄存器,还需要处理处理器的扩展状态,包括浮点寄存器、向量寄存器等。我们根据CPU特性动态选择适当的保存指令:

1
2
3
4
5
6
7
let save_inst = if std::is_x86_feature_detected!("xsave") {
    SaveInst::XSave64
} else if std::is_x86_feature_detected!("fxsr") {
    SaveInst::FxSave64
} else {
    SaveInst::NoSave
};

系统调用实现

在syscall_handler中,我们根据系统调用号分发到适当的处理函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
pub fn syscall_handler(context: *mut LucidContext) {
    let bank = LucidContext::get_register_bank(context);
    let syscall_no = (*bank).rax;
    
    match syscall_no {
        0x10 => { /* ioctl处理 */ },
        0x14 => { /* writev处理 */ },
        0x23 => { /* nanosleep处理 */ },
        0xDA => { /* set_tid_address处理 */ },
        _ => { /* 未处理的系统调用 */ }
    }
}

优雅返回

处理完系统调用后,我们需要恢复状态并返回到Bochs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 恢复标志
"popfq",

// 恢复GPR
"mov rax, [r13 + 0x0]",
"mov rbx, [r13 + 0x8]",
// ... 其他寄存器恢复

// 返回到Bochs!
"ret"

结论

我们成功构建了一个能够处理从Bochs到模糊测试器上下文切换的基础设施。虽然这个实现还需要进一步改进和重构,但核心思想将保持不变。下一步我们将针对Musl编译Bochs,并实现所有必要的系统调用,最终实现能够进行快照模糊测试的完整系统。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计