Sandboxie源码解读:进程钩子与系统调用拦截实现
【免费下载链接】Sandboxie Sandboxie Plus & Classic 项目地址: https://gitcode.com/gh_mirrors/sa/Sandboxie
1. 引言:沙箱技术的底层基石
你是否曾好奇,当你在沙箱中运行可疑程序时,系统是如何拦截其恶意行为的?Sandboxie作为一款经典的应用程序隔离工具,其核心能力源于对Windows内核机制的深度操控。本文将带你深入Sandboxie的内核驱动层,解析进程钩子(Process Hook)与系统调用(Syscall)拦截的实现原理,揭示沙箱隔离的底层技术细节。
读完本文,你将掌握:
- Sandboxie进程钩子的构建与注入机制
- 系统调用拦截表的初始化与管理流程
- 32位/64位架构下的钩子实现差异
- 沙箱隔离的核心技术原理与代码实现
2. 进程钩子:用户态与内核态的桥梁
2.1 钩子入口构建:Trampoline技术解析
Sandboxie通过Process_BuildHookEntry函数构建钩子入口点,该函数位于Sandboxie/core/drv/process_hook.c文件中。其核心是创建一个蹦床(Trampoline)结构,用于拦截目标函数调用:
ULONG_PTR Process_BuildHookEntry(ULONG_PTR NewProc, ULONG_PTR OldProc, ULONG *IncPtr) {
HOOK_TRAMP *tramp;
UCHAR *code;
ULONG_PTR *pOldProc;
tramp = Hook_BuildTramp(NULL, NULL, FALSE, FALSE);
if (!tramp) return 0;
tramp = HOOK_TRAMP_CODE_TO_TRAMP_HEAD(tramp);
tramp->eyecatcher = tzuk; // 用于验证的魔术值
tramp->target = (void *)NewProc; // 钩子处理函数地址
code = &tramp->code[0];
pOldProc = (ULONG_PTR *)&tramp->code[sizeof(tramp->code) - 8];
#ifdef _WIN64
code[0] = 0xFB; // sti指令,重新启用中断
++code;
#endif
// 构建跳转代码...
return (ULONG_PTR)&tramp->code;
}
蹦床结构包含以下关键部分:
- 魔术值验证:
eyecatcher = tzuk用于后续验证蹦床有效性 - 钩子目标地址:
target字段存储新的处理函数地址 - 机器码区域:
code数组存储动态生成的跳转指令
2.2 钩子代码生成:32位与64位架构适配
Sandboxie的钩子实现需要兼容32位和64位Windows系统,这通过条件编译实现:
#ifdef _WIN64
#define PREFIX64() code[0] = 0x48; ++code; // 64位前缀
#else
#define PREFIX64()
#endif
// 加载Process_FindSandboxed函数地址到寄存器
PREFIX64();
code[0] = 0xB8; // mov rax/eax, address
#ifdef _WIN64
*(ULONG_PTR *)&code[1] = (ULONG_PTR)Process_FindSandboxed64;
#else
*(ULONG_PTR *)&code[1] = (ULONG_PTR)Process_FindSandboxed;
#endif
code += 1 + sizeof(ULONG_PTR);
64位架构下的特殊处理:
- 增加
sti指令重新启用中断 - 使用
0x48前缀指定64位操作数 - 调用
Process_FindSandboxed64专用函数
32位架构下的额外处理:
- 不需要中断重新启用指令
- 函数调用前需要压入两个0作为参数
2.3 钩子禁用机制:动态热修补
Sandboxie实现了钩子的动态禁用功能,通过Process_DisableHookEntry函数修改钩子代码:
void Process_DisableHookEntry(ULONG_PTR HookEntry) {
HOOK_TRAMP *tramp = HOOK_TRAMP_CODE_TO_TRAMP_HEAD(HookEntry);
UCHAR *code = &tramp->code[0];
UCHAR *test;
UCHAR hotpatch[2];
#ifdef _WIN64
++code; // 跳过sti指令
#endif
test = code;
// 定位test eax,eax指令位置
test += 2; // 跳过mov eax,eax
// 处理可选的inc指令
if (*test == 0x90)
test += 1 + PREFIX64 + 1 + sizeof(ULONG_PTR) + 2;
// 跳过mov eax, Process_Find
test += PREFIX64 + 1 + sizeof(ULONG_PTR);
#ifndef _WIN64
test += 4; // 跳过两个push 0指令
#endif
test += 2; // 跳过程序调用
// 将test eax,eax改为xor eax,eax,始终跳转原函数
*(test + PREFIX64) = 0x33;
// 热修补跳转
hotpatch[0] = 0xEB; // jmp short
hotpatch[1] = (UCHAR)(test - (code + 2));
*(USHORT *)code = *(USHORT *)&hotpatch[0];
}
禁用钩子的核心原理:
- 定位钩子代码中的
test eax,eax指令 - 将其修改为
xor eax,eax,使条件跳转始终成立 - 添加短跳转指令,绕过钩子逻辑直接执行原函数
3. 系统调用拦截:内核级监控的实现
3.1 系统调用表初始化流程
Sandboxie的系统调用拦截通过Syscall_Init函数初始化,位于Sandboxie/core/drv/syscall.c文件中,其流程如下:
关键初始化函数Syscall_Init_List负责扫描NTDLL导出的系统调用函数:
BOOLEAN Syscall_Init_List(void) {
List_Init(&Syscall_List);
// 加载NTDLL并扫描导出函数
dll = Dll_Load(Dll_NTDLL);
if (!dll) return FALSE;
proc_offset = Dll_GetNextProc(dll, "Zw", &name, &proc_index);
while (proc_offset) {
if (name[0] != 'Z' || name[1] != 'w') break;
name += 2; // 跳过"Zw"前缀
// 过滤不需要拦截的系统调用
if (IS_PROC_NAME(8, "Continue") ||
IS_PROC_NAME(14, "CallbackReturn") ||
IS_PROC_NAME(14, "RaiseException")) {
goto next_zwxxx;
}
// 分析系统调用索引
ntdll_code = Dll_RvaToAddr(dll, proc_offset);
syscall_index = Syscall_GetIndexFromNtdll(ntdll_code);
// 创建并初始化SYSCALL_ENTRY结构
entry = Mem_AllocEx(Driver_Pool, entry_len, TRUE);
entry->syscall_index = (USHORT)syscall_index;
entry->param_count = (USHORT)param_count;
entry->ntdll_offset = proc_offset;
entry->ntos_func = ntos_addr;
List_Insert_After(&Syscall_List, NULL, entry);
next_zwxxx:
proc_offset = Dll_GetNextProc(dll, NULL, &name, &proc_index);
}
return TRUE;
}
3.2 系统调用索引解析
Syscall_GetIndexFromNtdll函数负责从NTDLL的ZwXxx函数代码中提取系统调用号:
ULONG Syscall_GetIndexFromNtdll(UCHAR *code) {
// 32位系统调用指令模式: mov eax, index; sysenter
if (code[0] == 0xB8 && code[5] == 0x0F && code[6] == 0x34) {
return *(ULONG *)&code[1];
}
// 64位系统调用指令模式: mov rax, index; syscall
if (code[0] == 0x48 && code[1] == 0xB8 && code[10] == 0x0F && code[11] == 0x05) {
return *(ULONG *)&code[2];
}
// 其他模式处理...
return -1;
}
该函数通过识别x86/x64汇编指令模式,从NTDLL导出函数中提取系统调用号,为后续拦截做准备。
3.3 系统调用处理流程
Sandboxie通过Syscall_Invoke函数调用原始系统服务,并通过注册的处理函数进行拦截:
NTSTATUS Syscall_Invoke(SYSCALL_ENTRY *entry, ULONG_PTR *stack) {
NTSTATUS status;
__try {
status = Sbie_InvokeSyscall_asm(entry->ntos_func, entry->param_count, stack);
} __except (EXCEPTION_EXECUTE_HANDLER) {
status = GetExceptionCode();
}
return status;
}
系统调用拦截的核心处理在Syscall_Api_Invoke函数中实现:
NTSTATUS Syscall_Api_Invoke(PROCESS *proc, ULONG64 *parms) {
// 检查调用者权限和参数
if (!proc) return STATUS_NOT_IMPLEMENTED;
syscall_index = (ULONG)parms[1];
// 查找系统调用表项
entry = Syscall_Table[syscall_index];
if (!entry) return STATUS_INVALID_SYSTEM_SERVICE;
// 调用拦截处理函数
if (entry->handler1_func && !proc->open_all_nt) {
status = entry->handler1_func(proc, entry, user_args);
} else {
status = Syscall_Invoke(entry, user_args);
}
return status;
}
3.4 特定系统调用的拦截处理
Sandboxie为关键系统调用注册了专用处理函数,例如文件操作和进程管理相关调用:
// 注册系统调用处理函数
Syscall_Set1("DuplicateObject", Syscall_DuplicateHandle);
Syscall_Set1("GetNextProcess", Syscall_GetNextProcess);
Syscall_Set1("GetNextThread", Syscall_GetNextThread);
Syscall_Set1("DeviceIoControlFile", Syscall_DeviceIoControlFile);
以Syscall_DuplicateHandle为例,其实现了句柄复制操作的拦截与重定向:
NTSTATUS Syscall_DuplicateHandle(PROCESS *proc, SYSCALL_ENTRY *syscall_entry, ULONG_PTR *user_args) {
// 解析参数
HANDLE SourceProcessHandle = (HANDLE)user_args[0];
HANDLE SourceHandle = (HANDLE)user_args[1];
HANDLE TargetProcessHandle = (HANDLE)user_args[2];
PHANDLE TargetHandle = (PHANDLE)user_args[3];
ACCESS_MASK DesiredAccess = (ACCESS_MASK)user_args[4];
ULONG Options = (ULONG)user_args[5];
// 检查是否需要重定向
if (SourceProcessHandle == NtCurrentProcess() &&
Obj_IsSandboxedHandle(proc, SourceHandle)) {
// 沙箱内句柄处理...
return STATUS_SUCCESS;
}
// 调用原始系统服务
return Syscall_Invoke(syscall_entry, user_args);
}
4. 架构差异:32位与64位实现对比
4.1 钩子代码结构差异
| 架构 | 指令前缀 | 中断处理 | 地址长度 | 调用约定 |
|---|---|---|---|---|
| 32位 | 无 | 不需要显式启用 | 4字节 | stdcall |
| 64位 | 0x48前缀 | 需要sti指令启用 | 8字节 | fastcall |
4.2 系统调用处理差异
32位系统调用拦截:
// syscall_32.c
VOID Syscall_Dispatch32(ULONG index, ULONG_PTR *args) {
SYSCALL_ENTRY *entry = Syscall_Table[index];
if (entry && entry->handler1_func) {
status = entry->handler1_func(proc, entry, args);
} else {
// 调用原始系统服务
status = Native32_Syscall(index, args);
}
}
64位系统调用拦截:
// syscall_64.c
VOID Syscall_Dispatch64(ULONG index, ULONG_PTR *args) {
PROCESS *proc = Thread_GetProcess();
SYSCALL_ENTRY *entry = Syscall_Table[index];
// 64位特定的参数验证
ProbeForRead(args, entry->param_count * sizeof(ULONG_PTR), 8);
if (entry && entry->handler1_func) {
status = entry->handler1_func(proc, entry, args);
} else {
// 调用原始系统服务
status = Native64_Syscall(index, args);
}
}
5. 实战分析:进程创建拦截流程
以NtCreateProcessEx系统调用为例,分析Sandboxie的拦截处理流程:
拦截处理的核心代码位于process_hook.c和syscall.c中,通过以下步骤实现:
- 进程检查:
Process_FindSandboxed确定调用进程是否在沙箱中 - 参数过滤:检查并修改进程创建参数,如路径重定向
- 权限验证:验证沙箱进程是否有权限创建新进程
- 资源重定向:将文件系统/注册表访问重定向到沙箱目录
- 原始调用:使用过滤后的参数调用原始系统服务
6. 总结与展望
Sandboxie通过进程钩子和系统调用拦截技术,构建了一个高效的应用程序隔离环境。其核心实现依赖于对Windows内核机制的深入理解和精巧的汇编级代码操控。
关键技术点总结:
- 蹦床钩子:使用动态生成的汇编代码实现函数拦截
- 系统调用表:通过解析NTDLL函数构建完整的系统调用映射
- 条件拦截:基于进程沙箱状态决定是否启用拦截逻辑
- 架构适配:同时支持32位和64位Windows系统
未来可能的改进方向:
- 增加对Windows最新版本内核保护机制的适配
- 优化系统调用拦截性能,减少沙箱运行开销
- 增强钩子代码的抗检测能力,提高与安全软件的兼容性
通过本文的解析,相信你已经对Sandboxie的底层实现有了深入了解。这些技术不仅适用于沙箱隔离,也可应用于系统监控、安全审计等领域,具有广泛的参考价值。
要进一步探索Sandboxie的实现细节,可以重点研究以下文件:
Sandboxie/core/drv/process_hook.c- 进程钩子实现Sandboxie/core/drv/syscall.c- 系统调用拦截Sandboxie/core/drv/obj.c- 对象管理与重定向Sandboxie/core/drv/file.c- 文件系统虚拟化
【免费下载链接】Sandboxie Sandboxie Plus & Classic 项目地址: https://gitcode.com/gh_mirrors/sa/Sandboxie
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



