设置类似 chromium 的沙箱(错误 0xc00000a5)

Oll*_*hak 5 c++ windows security sandbox

我正在尝试设置一个类似于chromium 的沙箱。特别是,我试图复制他们的技巧,即使用低权限令牌创建睡眠进程,然后在运行之前临时设置高权限令牌。这个想法是让进程在高权限模式下完成所有初始化,然后在运行任何不安全代码之前恢复到低权限令牌。

到目前为止,我正在努力进行基本测试并运行。这是我的代码:

#include "stdafx.h"
#include <atlbase.h>
#include <iostream>
#include <cassert>
#include <vector>
#include <string>
#include <AccCtrl.h>
#include <aclapi.h>

#define VERIFY(x) { bool r = x; assert(r); }

uint8_t* GetTokenInfo(const HANDLE& token, TOKEN_INFORMATION_CLASS info_class, DWORD* error)
{
    // Get the required buffer size.
    DWORD size = 0;
    ::GetTokenInformation(token, info_class, NULL, 0, &size);
    if (!size)
    {
        *error = ::GetLastError();
        return nullptr;
    }

    uint8_t* buffer = new uint8_t[size];
    if (!::GetTokenInformation(token, info_class, buffer, size, &size))
    {
        *error = ::GetLastError();
        return nullptr;
    }

    *error = ERROR_SUCCESS;
    return buffer;
}

int main()
{
    // Open the current token
    CHandle processToken;
    VERIFY(::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, &processToken.m_h));

    // Create an impersonation token without restrictions
    HANDLE impersonationToken;
    VERIFY(DuplicateToken(processToken, SecurityImpersonation, &impersonationToken));

    // Build the list of the deny only group SIDs
    DWORD error;
    uint8_t* buffer = GetTokenInfo(processToken, TokenGroups, &error);
    if (!buffer) return error;

    TOKEN_GROUPS* token_groups = reinterpret_cast<TOKEN_GROUPS*>(buffer);
    std::vector<SID*> sids_for_deny_only;

    for (unsigned int i = 0; i < token_groups->GroupCount; ++i)
    {
        if ((token_groups->Groups[i].Attributes & SE_GROUP_INTEGRITY) == 0 &&
            (token_groups->Groups[i].Attributes & SE_GROUP_LOGON_ID) == 0)
        {
            sids_for_deny_only.push_back(reinterpret_cast<SID*>(token_groups->Groups[i].Sid));
        }
    }

    {
        DWORD size = sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE;
        uint8_t* buffer = new uint8_t[size];
        TOKEN_USER* token_user = reinterpret_cast<TOKEN_USER*>(buffer);
        BOOL result = ::GetTokenInformation(processToken, TokenUser, token_user, size, &size);

        if (!result) return ::GetLastError();
        sids_for_deny_only.push_back(reinterpret_cast<SID*>(token_user->User.Sid));
    }

    size_t deny_size = sids_for_deny_only.size();
    SID_AND_ATTRIBUTES *deny_only_array = NULL;
    if (deny_size)
    {
        deny_only_array = new SID_AND_ATTRIBUTES[deny_size];

        for (unsigned int i = 0; i < sids_for_deny_only.size(); ++i)
        {
            deny_only_array[i].Attributes = SE_GROUP_USE_FOR_DENY_ONLY;
            deny_only_array[i].Sid = const_cast<SID*>(sids_for_deny_only[i]);
        }
    }

    // Create restricted sids
    DWORD size_sid = SECURITY_MAX_SID_SIZE;
    BYTE sid_[SECURITY_MAX_SID_SIZE];
    VERIFY(::CreateWellKnownSid(WinNullSid, NULL, sid_, &size_sid));

    SID_AND_ATTRIBUTES sidsToRestrict[] =
    {
        reinterpret_cast<SID*>(const_cast<BYTE*>(sid_)),
        0
    };

    // Create the restricted token
    HANDLE restrictedToken;
    VERIFY(::CreateRestrictedToken(processToken,
        0, // flags
        deny_size,
        deny_only_array,
        0,
        0,
        _countof(sidsToRestrict), // number of SIDs to restrict,
        sidsToRestrict, // no SIDs to restrict,
        &restrictedToken));

    VERIFY(::IsTokenRestricted(restrictedToken));   

    // Create a process using the restricted token (but keep it suspended)
    STARTUPINFO startupInfo = { 0 };
    PROCESS_INFORMATION processInfo;

    VERIFY(::CreateProcessAsUser(restrictedToken,
        L"C:\\Dev\\Projects\\SandboxTest\\Debug\\Naughty.exe",
        0, // cmd line
        0, // process attributes
        0, // thread attributes
        FALSE, // don't inherit handles
        CREATE_SUSPENDED | DETACHED_PROCESS, // flags
        0, // inherit environment
        0, // inherit current directory
        &startupInfo,
        &processInfo));

    // Set impersonation token with more rights
    {
        HANDLE temp_thread = processInfo.hThread;
        if (!::SetThreadToken(&temp_thread, impersonationToken))
        {
            return 1;
        }
    }

    // Run the process
    if (!::ResumeThread(processInfo.hThread)) // Other process crashes immediately when this is run
    {
        return 1;
    }

    std::cout << "Done!" << std::endl;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

还不太确定拒绝列表和限制列表,但如果我理解正确的话,它应该是无关紧要的。在运行线程之前,我使用不受限制的令牌调用 SetThreadToken,因此我认为对restrictedToken 使用什么设置并不重要。然而,这种情况并非如此; 新进程崩溃,错误代码为 0xc00000a5。如果我在 CreateProcessAsUser 中使用 processToken 而不是 restrictedToken,则代码运行得很好。就像 SetThreadToken 没有做任何事情。

我现在在 naughty.exe 中没有做太多事情,只是开始无限循环。

有人知道我在这里做错了什么吗?

编辑1: 根据页面,0xc00000a5 表示“STATUS_BAD_IMPERSONATION_LEVEL”。对此不确定,但我认为我缺少 SeImpersonatePrivilege,导致事情失败。仍在研究选项...

编辑 2: 好的,看来我必须降低模拟令牌的权限才能在其他进程中使用它。不知道为什么,但我不能在没有管理员权限的情况下运行该程序。

但仍然收到错误:/现在是“STATUS_DLL_NOT_FOUND”。检查进程监视器日志的最佳线索是“C:\Windows\SysWOW64\ucrtbased.dll”上的访问被拒绝。奇怪的是,它似乎偶尔会工作一次(即生成的进程有时运行得很好)。回到挖掘...

小智 1

该问题是由启动代码尝试从新线程(无权访问高权限令牌)加载 C 运行时 DLL 引起的。对我有用的是将 CRT 静态链接到沙箱进程中(即/MTd在调试版本中、/MT在发布版本中)。