Подтвердить что ты не робот

Реализация песочницы с пользовательским стеком в Windows 64-bit

В настоящее время я изучаю, как реализовать песочницу (похожую на проект Google NaCl), где я могу запустить ненадежный код x86 (набор ограниченных инструкций) таким образом, чтобы это не может повредить остальную часть моего процесса.

В отличие от NaCl, ненадежный код не будет запускаться в отдельном процессе, но тот же процесс, что и хост-приложение. Итак, одним из важных шагов является получение правильной обработки структурированных исключений Windows для того, чтобы ловить ошибки (например, недопустимый доступ к памяти или div на 0) и изящно завершать песочницу до того, как Windows уничтожит мое хост-приложение. (NaCl не сталкивается с этими проблемами. Песочница - отдельный процесс и просто убивается в случае ошибки.)

Кроме того, изолированный код не должен использовать стек хостов приложений, а запускаться на какой-то отдельный "стек", который выделяется мной.

Именно эта комбинация (обработка исключений в присутствии пользовательского выделенного стека) искажает мой разум. Я проверил языковые реализации Go и Factor, которые делают подобные вещи и с этой помощью что-то работает.

Но есть еще некоторые открытые вопросы и неопределенности. Поэтому я подумал, что буду использовать фантастические знания 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 "взломать"? Я попытался сузить границы стека на что-то вроде:

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

Но это не сработает. Он печатает "GEH", а затем сбой из-за необработанных исключений. FrameExceptionHandler() никогда не будет выполнен.

Я также попробовал более расслабленные границы, которые включают "куча выделенного стека", а также стек, выделенный Windows. Но это не помогает.

Другой вопрос: знаете ли вы какие-либо другие ловушки, с которыми я могу столкнуться. Например, я заметил, что Windows не нравится, если RSP неравномерен (я думаю, потому что вы никогда не сможете попасть в неравномерный RSP, выполнив 2/4/8 байт PUSHes и POPs с помощью указателя указателя на 16 байт).

Спасибо, Jonas

4b9b3361

Ответ 1

Запуск ненадежного, стороннего кода в том же процессе требует проблем. Этот код может убить ваш процесс различными способами. Например. он может вызывать exit() при сбоях, запрашивать много памяти или записывать в память, выделенную вашими потоками.

Более безопасным, но не столь сложным решением является запуск этого кода в другом процессе, аналогичном тому, что делает Chrome. Каждое расширение Chrome выполняется в другом процессе, и данные передаются между процессами.

Ваше приложение может запускать отдельный процесс и связываться с ним, хотя канал, сообщение Windows, общая память (файл с отображением памяти) для обмена большими данными и т.д.

Плагин (обычно) реализует интерфейс, поэтому вам нужно будет написать прокси-объект, чтобы абстрагироваться от факта, что плагин находится в другом процессе и скрывает сложность IPC, которая поставляется с несколькими приложениями процесса. gSoap - такая структура, другие существуют.

Ответ 2

Вы хотите выполнить код и проверить его достоверность. Вы можете сделать это с помощью песочницы. См. Виртуальную коробку реализации процессора x86. Это может помочь. Но все виртуальные машины идут по цене: эмулирующий процессор выполняет базовый код в 5-10 раз медленнее, чем ваше приложение.

Если вам нужна только проверка ошибок и запуск приложения на процессоре ядра, вам нужно запустить его в потоке. При зависании нитей приложение не тронуто. И вы можете вставлять некоторый код в поток, чтобы искать его выполнение. Но этот случай менее безопасен, поскольку вредоносный код может нарушить ваши процедуры проверки и использовать его для backdooring/rooting вашей системы.

Итак, мой ответ: для безопасности - используйте свою собственную виртуальную машину, для скорости - выполните в другом потоке.

С наилучшими пожеланиями, и удачи Владимир