在Windows 64位上实现具有自定义堆栈的沙盒
我目前正在研究如何实现一个沙箱(类似于),在这里我可以运行不受信任的x86代码(受限制的指令集),这样它就不会影响进程的其余部分 与NaCl不同,不受信任的代码不会在单独的进程中运行,而是在与主机应用程序相同的进程中运行。因此,一个关键步骤是正确处理Windows的结构化异常,以便捕获错误(如无效内存访问或div by 0),并在Windows杀死我的主机应用程序之前优雅地终止沙箱。(NaCl没有面临这些问题。沙箱是一个单独的过程,一旦发生错误就会被终止。) 此外,沙盒代码不应该使用主机应用程序堆栈,而是在我自己分配的某个单独的“堆栈”上运行 正是这种组合(存在自定义分配堆栈时的异常处理)让我心烦意乱。我已经检查了和的语言实现,它们做了类似的事情,在这个帮助下运行了一些东西 但仍有一些悬而未决的问题和不确定性。所以我想我将使用堆栈溢出的奇妙知识来获得一些意见:-) 以下是一段可工作的代码片段,其内容涉及核心问题: 代码.cpp在Windows 64位上实现具有自定义堆栈的沙盒,windows,assembly,exception-handling,sandbox,x86-64,Windows,Assembly,Exception Handling,Sandbox,X86 64,我目前正在研究如何实现一个沙箱(类似于),在这里我可以运行不受信任的x86代码(受限制的指令集),这样它就不会影响进程的其余部分 与NaCl不同,不受信任的代码不会在单独的进程中运行,而是在与主机应用程序相同的进程中运行。因此,一个关键步骤是正确处理Windows的结构化异常,以便捕获错误(如无效内存访问或div by 0),并在Windows杀死我的主机应用程序之前优雅地终止沙箱。(NaCl没有面临这些问题。沙箱是一个单独的过程,一旦发生错误就会被终止。) 此外,沙盒代码不应该使用主机应用程序
#include <Windows.h>
extern "C" void Sandbox();
// just a low level helper to print "msg"
extern "C" void Write(const char* msg)
{
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),
msg, (DWORD)strlen(msg), NULL, NULL);
}
// should be called first on error and continue exception handling
LONG __stdcall GlobalExceptionHandler(_EXCEPTION_POINTERS*)
{
Write("GEH ");
return EXCEPTION_CONTINUE_SEARCH;
}
// should be called afterwards on error and terminate the process
// of course this is just a stub to simplify the issue
// in real world it would just terminate the sandbox
extern "C" EXCEPTION_DISPOSITION __stdcall FrameExceptionHandler(
PEXCEPTION_RECORD, ULONG64, PCONTEXT, PVOID)
{
Write("FEH ");
ExitProcess(42);
}
void main()
{
AddVectoredExceptionHandler(1, GlobalExceptionHandler);
Sandbox();
// never reach this...
ExitProcess(23);
}
运行此命令将打印“GEH-FEH”,然后以代码42优雅地退出
有没有人对这一集有更深入的了解StackBase&StackEnd
“hack”?
我试图将堆栈限制缩小到以下范围:
mov gs:[008h], rsp
mov gs:[010h], rax ; rax is the address returned by malloc
但它不起作用。它打印“GEH”,然后由于未处理的异常而崩溃。
FrameExceptionHandler()
将永远不会执行
我还尝试了更宽松的边界,包括“堆分配堆栈”以及Windows分配的堆栈。但这没用
另一个问题是,你是否知道我会遇到其他陷阱。例如,我注意到Windows不喜欢RSP不均匀的情况(我猜是因为在16字节对齐的堆栈指针上执行2/4/8字节的推送和弹出操作永远无法获得不均匀的RSP)
谢谢,
Jonas您希望执行代码并检查其有效性。你可以用自己的沙盒来做。请参阅x86处理器的虚拟盒实现。这会有帮助。 但所有虚拟机都有价格:模拟处理器运行底层代码的速度比应用程序慢5-10倍 若你们只需要检查错误并在核心cpu上运行应用程序,那个么你们需要在线程中运行它。当线程挂起时,应用程序保持不变。您可以向线程注入一些代码来查找它的执行情况。 但这种情况不太安全,因为恶意代码会破坏您的检查例程,并利用它对系统进行后门/根攻击 所以,我的答案是:为了安全——使用自己的虚拟机,为了速度——在另一个线程中执行 祝你好运
Vladimir在同一进程中运行不受信任的第三方代码是自找麻烦。该代码可以以各种方式终止您的进程。例如,它可以在失败时调用
exit()
,请求大量内存,或者写入线程分配的内存
更安全但不那么困难的解决方案是在不同的进程中运行此代码,类似于Chrome所做的。每个Chrome扩展都在不同的进程中运行,并且在进程之间传递数据
您的应用程序可以启动一个单独的进程,并通过管道、windows消息、共享内存(内存映射文件)等与之通信,以共享大数据
插件(通常)实现一个接口,因此您需要编写一个代理对象来抽象插件驻留在另一个进程中的事实,并隐藏多进程应用程序附带的IPC复杂性。gSoap就是这样一个框架,其他框架也存在。你有没有玩弄过?如果你只是需要一个沙盒工具,这可能会很有帮助。不,不幸的是,这没有帮助。我说的不是在我的操作系统中运行不受信任的应用程序,而是在我的应用程序中运行不受信任的x86代码。例如,视频播放器中新视频编解码器的插件。我不想让一个有缺陷的插件使我的应用程序崩溃。感谢您提供的这个示例,它帮助我找出了我正在处理的类似代码的错误。但是请注意,将实际堆栈边界设置为“沙盒”堆栈空间的开始和结束对我来说完全有效。我必须小心恢复TIB字段的方式,否则你很快就会遇到其他崩溃(duh)!我不明白这样的事情怎么会是完全不可靠的。例如,不受信任的插件可能尝试访问随机内存地址。在最好的情况下,地址将无效,访问将被向量异常处理程序捕获,但在最坏的情况下,地址可能实际上是有效内存(分配的堆、数据页、代码页等)插件可能会破坏这些数据。你可以通过将内存访问限制在只为插件代码保留的某个地址范围来处理。要做到这一点,您必须只允许以“安全”组合的方式编写所有指令。您必须静态地检查插件是否遵守这些限制。当然,你必须以一种特殊的方式编译你的插件。所有这些都包含在谷歌NaCl项目的技术中。一项非常有趣的技术:-)
mov gs:[008h], rsp
mov gs:[010h], rax ; rax is the address returned by malloc