在Windows 64位上使用自定义堆栈实现沙箱

时间:2013-01-09 19:11:48

标签: windows assembly exception-handling sandbox x86-64

我目前正在研究如何实现一个沙箱(类似于Google's NaCl project),我可以运行不受信任的x86代码(受限制的指令集),这样就不会损害我的剩余进程。

与NaCl不同,不受信任的代码不会在单独的进程中运行,而是与主机应用程序运行相同的进程。因此,一个关键步骤是使Windows的结构化异常处理正确,以便捕获错误(如无效的内存访问或div 0),并在Windows杀死我的主机应用程序之前正常终止沙箱。 (NaCl不会遇到这些问题。沙箱是一个单独的过程,如果发生错误就会被杀死。)

此外,沙盒代码不应使用宿主应用程序堆栈,而是在我自己分配的单独“堆栈”上运行。

正是这种组合(存在自定义分配堆栈时的异常处理)扭曲了我的想法。我已经检查了GoFactor的语言实现,它们做了类似的事情,并且在这个帮助下运行了一些东西。

但仍有一些悬而未决的问题和不确定因素。所以我想我会利用Stack Overflow的神奇知识来获得一些意见: - )

以下是一个工作代码段,内容涉及核心问题:

code.cpp

#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);
}

code.asm

EXTERN FrameExceptionHandler:PROC
EXTERN malloc:PROC

.code

Handler:
    jmp FrameExceptionHandler

Sandbox PROC FRAME : Handler
    ; function prologue compliant with Windows x86_64 calling conventions
    ; saves rsp to the "frame-pointer" r15
    push r15
    .PUSHREG r15
    sub rsp, 20h
    .ALLOCSTACK(20h)
    mov r15, rsp
    .SETFRAME r15, 0h
    .ENDPROLOG

    ; set rsp to the top of a "heap allocated stack" of size 0x10000 bytes
    mov rcx, 10000h
    call malloc
    lea rsp, [rax+10000h]

    ; got this from implementation of the Go language runtime:
    ; while unwinding the stack, Windows sanity checks the values of
    ; RSP to be within stack-bounds. Of course RSP is set to our
    ; "heap allocated stack" and not within the bounds of what Windows
    ; thinks should be the stack.
    ; Fix this by adjusting StackBase and StackEnd in the TIB (thread
    ; information block), so that basically the stack is unbounded:
    ; StackBase = 0xffffffffffffffff, StackEnd = 0x0000000000000000
    mov rcx, 0FFFFFFFFFFFFFFFFh
    mov gs:[008h], rcx
    mov rcx, 0
    mov gs:[010h], rcx


    ; trigger an access error by reading invalid memory
    mov rax, 0DEADBEEFh
    mov rax, [rax]

    ; function epilogue - will never get here
    mov rax, 0
    add rsp, 28h
    ret
Sandbox ENDP

end

运行它将打印“GEH FEH”,然后正常退出代码42。

有没有人对此集StackBase & StackEnd“hack”有更深入的了解? 我试图将堆栈限制缩小到:

    mov gs:[008h], rsp
    mov gs:[010h], rax    ; rax is the address returned by malloc

但它不起作用。它打印“GEH”然后由于未处理的异常而崩溃。 FrameExceptionHandler()永远不会被执行。

我还尝试了更宽松的边界,包括“堆分配堆栈”以及Windows分配的堆栈。但它没有帮助。

另一个问题是,你是否知道我可能遇到的其他陷阱。例如,我注意到如果RSP不均匀,Windows不喜欢它(我猜是因为你不能通过在16字节对齐的堆栈指针上执行2/4/8字节的PUSH和POP来获得不均匀的RSP。)

谢谢, 纳斯

2 个答案:

答案 0 :(得分:1)

在同一进程中运行不受信任的第三方代码会遇到麻烦。该代码可以通过各种方式杀死您的进程。例如。它可以在失败时调用exit(),请求大量内存,或者写入由线程分配的内存。

更安全但不那么难的解决方案是在不同的流程中运行此代码,类似于Chrome正在执行的操作。每个Chrome扩展程序都在不同的进程中运行,数据在进程之间传递。

您的应用程序可以启动一个单独的进程,并通过管道,Windows消息,共享内存(内存映射文件)与其进行通信,以共享大数据等。

插件(通常)实现一个接口,因此您需要编写一个代理对象来抽象插件驻留在另一个进程中的事实,并隐藏多进程应用程序带来的IPC复杂性。 gSoap就是这样一个框架,其他的存在。

答案 1 :(得分:0)

您想要执行代码并检查其有效性。您可以使用自己的沙箱来完成。请参阅x86处理器的虚拟框实现。它可以帮助。 但是所有虚拟机都与价格相关:模拟处理器运行的底层代码比你的应用程序慢5-10倍。

如果您只需要进行错误检查并在核心cpu上运行app,那么您需要在线程中运行它。当线程挂起时,应用程序不受影响。并且您可以为线程注入一些代码以查找它的执行情况。 但是这种情况不太安全,因为恶意代码可能会破坏您的检查例程并利用它来对您的系统进行后门备份/生根。

所以,我的回答是:为了安全 - 使用你自己的虚拟机,为了速度 - 在另一个线程中执行。

祝你好运,祝你好运 弗拉基米尔