前言

本文是 Windows 沙箱技术全景 的编程实战篇。通过 C++ 代码将一个进程运行在 AppContainer 中,体验 Windows 应用级沙箱的隔离能力。

环境准备

项目要求
操作系统Windows 10 / 11
IDEVisual Studio 2022
SDKWindows SDK 10.0.26100+(或最新版)
语言C++
权限普通用户即可(不需要管理员)

VS 项目配置

  1. 新建「Windows 桌面应用程序」或「控制台应用程序」
  2. 链接器 → 输入 → 附加依赖项:userenv.lib
  3. C++ 语言标准:C++17 或更高

核心概念

AppContainer 沙箱的构建需要三个组件:

┌──────────────────────────────────────────────┐
│              AppContainer 沙箱进程             │
│                                              │
│  ┌──────────────┐  ┌──────────────────────┐  │
│  │ AppContainer │  │   Capability SID     │  │
│  │     SID      │  │   列表(能力声明)      │  │
│  │ (容器身份)    │  │  internetClient 等    │  │
│  └──────────────┘  └──────────────────────┘  │
│                                              │
│  ┌──────────────────────────────────────────┐ │
│  │         受限令牌(自动生成)               │ │
│  │  去除了高危权限 + AppContainer 限制       │ │
│  └──────────────────────────────────────────┘ │
└──────────────────────────────────────────────┘
  • AppContainer SID:唯一标识这个容器,进程以此身份运行
  • Capability SID:声明此容器拥有的能力(如联网、访问文档库等)
  • 受限令牌:系统自动生成,去掉不必要的权限

完整代码

1. 创建 AppContainer 并启动沙箱进程

C++cpp
// sandbox_appcontainer.cpp
// AppContainer 沙箱 Demo —— 将子进程运行在 AppContainer 中
 
#include <windows.h>
#include <userenv.h>
#include <sddl.h>
#include <stdio.h>
#include <string>
#include <vector>
 
#pragma comment(lib, "userenv.lib")
 
// 生成 AppContainer SID
// 返回值:调用方负责用 LocalFree 释放
PSID CreateAppContainerSid(const wchar_t* packageName)
{
    PSID sid = nullptr;
    HRESULT hr = ::CreateAppContainerToken(
        /* TokenAttributes */ nullptr,
        /* AppContainerSid  */ nullptr,
        /* CapabilitySid    */ nullptr,
        /* CapabilityCount  */ 0,
        /* Token            */ nullptr,
        &sid);
    // 注:实际应使用 CreateWellKnownSid 或自定义方式
    // 这里使用更简单的方式:通过字符串 SID 转换
 
    // AppContainer SID 格式: S-1-15-3-<package hash>
    // 简化 Demo:使用固定字符串
    if (!::ConvertStringSidToSidW(
        L"S-1-15-3-1024-1065365936-1281604719-2598793407-2231168624-3287680628-2223465836-1031541426-2922674032",
        &sid))
    {
        wprintf(L"[-] ConvertStringSidToSidW failed: %lu\n", GetLastError());
        return nullptr;
    }
    return sid;
}
 
// 将能力 SID 转换为字符串形式(调试用)
std::wstring SidToString(PSID sid)
{
    LPWSTR str = nullptr;
    if (::ConvertSidToStringSidW(sid, &str))
    {
        std::wstring result(str);
        ::LocalFree(str);
        return result;
    }
    return L"<invalid>";
}
 
int wmain()
{
    wprintf(L"[+] AppContainer Sandbox Demo\n\n");
 
    // ---- Step 1: 创建 AppContainer SID ----
    // 实际项目中应使用 CreateAppContainerToken 或推导 SID
    // 这里简化为手动构造
 
    PSID appContainerSid = nullptr;
    // 使用一个固定的 AppContainer SID(Demo 用途)
    // 实际应通过 DeriveAppContainerSidFromAppContainerName 等方式获取
    if (!::ConvertStringSidToSidW(
        L"S-1-15-3-1024-1065365936-1281604719-2598793407-2231168624-3287680628-2223465836-1031541426-2922674032",
        &appContainerSid))
    {
        wprintf(L"[-] Failed to create AppContainer SID: %lu\n", GetLastError());
        return 1;
    }
    wprintf(L"[+] AppContainer SID: %s\n", SidToString(appContainerSid).c_str());
 
    // ---- Step 2: 创建能力 SID 列表 ----
    // 声明 internetClient 能力(允许出站网络访问)
    // 格式: S-1-15-3-1-<capability hash>
 
    // internetClient Capability SID
    PSID capabilitySids[1] = {};
    if (!::ConvertStringSidToSidW(
        L"S-1-15-3-1-1078098293-1189865237-1206238697-3092301247-2234247256-1372704403-1082273694",
        &capabilitySids[0]))
    {
        wprintf(L"[-] Failed to create Capability SID: %lu\n", GetLastError());
        return 1;
    }
    wprintf(L"[+] Capability SID (internetClient): %s\n", SidToString(capabilitySids[0]).c_str());
 
    // ---- Step 3: 创建受限令牌 ----
    // 使用 CreateAppContainerToken 创建包含 AppContainer 信息的令牌
 
    HANDLE hToken = nullptr;
    HRESULT hr = ::CreateAppContainerToken(
        nullptr,              // TokenAttributes
        appContainerSid,      // AppContainerSid
        capabilitySids,       // CapabilitySid 数组
        1,                    // CapabilityCount
        &hToken,              // 输出令牌
        nullptr);             // AdditionalInformation
 
    if (FAILED(hr) || hToken == nullptr)
    {
        wprintf(L"[-] CreateAppContainerToken failed: 0x%08X\n", hr);
        return 1;
    }
    wprintf(L"[+] AppContainer token created\n");
 
    // ---- Step 4: 准备启动参数 ----
 
    SIZE_T attrListSize = 0;
    ::InitializeProcThreadAttributeList(nullptr, 1, 0, &attrListSize);
 
    auto attrListBuffer = std::vector<BYTE>(attrListSize);
    LPPROC_THREAD_ATTRIBUTE_LIST attrList =
        reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(attrListBuffer.data());
 
    if (!::InitializeProcThreadAttributeList(attrList, 1, 0, &attrListSize))
    {
        wprintf(L"[-] InitializeProcThreadAttributeList failed: %lu\n", GetLastError());
        return 1;
    }
 
    // 设置 SECURITY_CAPABILITIES 属性
    SECURITY_CAPABILITIES secCaps = {};
    secCaps.AppContainerSid = appContainerSid;
    secCaps.CapabilitySid = capabilitySids;
    secCaps.CapabilityCount = 1;
    secCaps.Reserved = 0;
 
    if (!::UpdateProcThreadAttribute(
        attrList,
        0,
        PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES,
        &secCaps,
        sizeof(secCaps),
        nullptr,
        nullptr))
    {
        wprintf(L"[-] UpdateProcThreadAttribute failed: %lu\n", GetLastError());
        return 1;
    }
 
    STARTUPINFOEXW si = {};
    si.StartupInfo.cb = sizeof(si);
    si.lpAttributeList = attrList;
 
    PROCESS_INFORMATION pi = {};
 
    // 目标程序:notepad.exe
    std::wstring cmdLine = L"notepad.exe";
 
    // ---- Step 5: 创建进程 ----
    if (!::CreateProcessAsUserW(
        hToken,                // 受限令牌
        nullptr,               // 应用程序名
        cmdLine.data(),        // 命令行
        nullptr,               // 进程安全属性
        nullptr,               // 线程安全属性
        FALSE,                 // 不继承句柄
        EXTENDED_STARTUPINFO_PRESENT, // 扩展启动信息
        nullptr,               // 环境块
        nullptr,               // 当前目录
        &si.StartupInfo,       // 启动信息
        &pi))                  // 进程信息
    {
        wprintf(L"[-] CreateProcessAsUserW failed: %lu\n", GetLastError());
        return 1;
    }
 
    wprintf(L"[+] Process created in AppContainer! PID=%lu\n", pi.dwProcessId);
    wprintf(L"\n[>] notepad 正在沙箱中运行。\n");
    wprintf(L"[>] 尝试在 notepad 中保存文件到桌面 → 会被拒绝\n");
    wprintf(L"[>] 尝试在 notepad 中保存到 %%TEMP%% → 会被拒绝\n");
    wprintf(L"[>] 关闭 notepad 后程序退出。\n\n");
 
    // 等待子进程退出
    ::WaitForSingleObject(pi.hProcess, INFINITE);
 
    // ---- 清理 ----
    ::CloseHandle(pi.hProcess);
    ::CloseHandle(pi.hThread);
    ::CloseHandle(hToken);
    ::DeleteProcThreadAttributeList(attrList);
    ::FreeSid(appContainerSid);
    ::FreeSid(capabilitySids[0]);
 
    wprintf(L"[+] Clean up done. Demo finished.\n");
    return 0;
}

⚠️ 注意:以上代码为教学 Demo,简化了 SID 获取过程。实际项目中应使用 DeriveAppContainerSidFromAppContainerNameCreateAppContainerToken 的完整参数。完整示例可参考 Microsoft 的 AppContainer 示例代码

2. 授予文件访问权限

AppContainer 进程默认无法访问大部分文件系统。需要通过 ACL 授予特定路径访问权:

C++cpp
#include <aclapi.h>
#pragma comment(lib, "advapi32.lib")
 
// 授予 AppContainer 对指定文件夹的访问权限
BOOL GrantAppContainerAccess(PSID appContainerSid, const wchar_t* filePath, DWORD accessMask)
{
    // 获取当前 DACL
    PACL oldAcl = nullptr;
    PACL newAcl = nullptr;
    PSECURITY_DESCRIPTOR sd = nullptr;
 
    DWORD result = ::GetNamedSecurityInfoW(
        filePath, SE_FILE_OBJECT,
        DACL_SECURITY_INFORMATION,
        nullptr, nullptr, &oldAcl, nullptr, &sd);
 
    if (result != ERROR_SUCCESS) {
        wprintf(L"[-] GetNamedSecurityInfo failed: %lu\n", result);
        return FALSE;
    }
 
    // 添加 ACE(允许 AppContainer SID 访问)
    EXPLICIT_ACCESSW ea = {};
    ea.grfAccessPermissions = accessMask;  // GENERIC_READ | GENERIC_WRITE
    ea.grfAccessMode = GRANT_ACCESS;
    ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
    ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
    ea.Trustee.TrusteeType = TRUSTEE_IS_GROUP;
    ea.Trustee.ptstrName = (LPWSTR)appContainerSid;
 
    result = ::SetEntriesInAclW(1, &ea, oldAcl, &newAcl);
    ::LocalFree(sd);
 
    if (result != ERROR_SUCCESS) {
        wprintf(L"[-] SetEntriesInAcl failed: %lu\n", result);
        return FALSE;
    }
 
    // 应用新 DACL
    result = ::SetNamedSecurityInfoW(
        (LPWSTR)filePath, SE_FILE_OBJECT,
        DACL_SECURITY_INFORMATION,
        nullptr, nullptr, newAcl, nullptr);
 
    ::LocalFree(newAcl);
 
    if (result != ERROR_SUCCESS) {
        wprintf(L"[-] SetNamedSecurityInfo failed: %lu\n", result);
        return FALSE;
    }
 
    wprintf(L"[+] Granted access to: %s\n", filePath);
    return TRUE;
}
 
// 使用示例:
// GrantAppContainerAccess(appContainerSid, L"C:\\SandboxShare",
//     GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE);

3. 测试沙箱隔离效果

C++cpp
// sandbox_test.cpp
// 在 AppContainer 中运行此程序,测试隔离效果
 
#include <windows.h>
#include <stdio.h>
 
int wmain()
{
    wprintf(L"=== AppContainer 隔离测试 ===\n\n");
 
    // 测试 1: 尝试写入桌面
    HANDLE hFile = ::CreateFileW(
        L"C:\\Users\\Public\\test_sandbox.txt",
        GENERIC_WRITE, 0, nullptr,
        CREATE_ALWAYS, 0, nullptr);
 
    if (hFile == INVALID_HANDLE_VALUE) {
        wprintf(L"[BLOCKED] 写入 C:\\Users\\Public: 错误 %lu\n", GetLastError());
    } else {
        wprintf(L"[ALLOWED] 写入 C:\\Users\\Public: 成功\n");
        ::CloseHandle(hFile);
        ::DeleteFileW(L"C:\\Users\\Public\\test_sandbox.txt");
    }
 
    // 测试 2: 尝试访问 C:\Windows
    hFile = ::CreateFileW(
        L"C:\\Windows\\System32\\config\\SAM",
        GENERIC_READ, 0, nullptr,
        OPEN_EXISTING, 0, nullptr);
 
    if (hFile == INVALID_HANDLE_VALUE) {
        wprintf(L"[BLOCKED] 读取 SAM 文件: 错误 %lu\n", GetLastError());
    } else {
        wprintf(L"[ALLOWED] 读取 SAM 文件: 成功\n");
        ::CloseHandle(hFile);
    }
 
    // 测试 3: 尝试网络访问(如果有 internetClient capability 则允许)
    WSADATA wsaData;
    ::WSAStartup(MAKEWORD(2, 2), &wsaData);
    SOCKET sock = ::socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in addr = {};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = ::inet_addr("127.0.0.1");
    addr.sin_port = ::htons(80);
 
    int ret = ::connect(sock, (sockaddr*)&addr, sizeof(addr));
    if (ret == SOCKET_ERROR) {
        wprintf(L"[BLOCKED] 网络连接 127.0.0.1:80: 错误 %d\n", WSAGetLastError());
    } else {
        wprintf(L"[ALLOWED] 网络连接 127.0.0.1:80: 成功\n");
        ::closesocket(sock);
    }
    ::WSACleanup();
 
    // 测试 4: 尝试打开其他进程
    HANDLE hProc = ::OpenProcess(
        PROCESS_QUERY_INFORMATION, FALSE,
        ::GetCurrentProcessId());  // 自己的 PID
 
    if (hProc) {
        wprintf(L"[INFO] 当前进程 PID: %lu\n", ::GetCurrentProcessId());
        ::CloseHandle(hProc);
    }
 
    wprintf(L"\n=== 测试完成 ===\n");
    return 0;
}

常用 Capability SID

CapabilitySID (缩写)说明
internetClientS-1-15-3-1-...出站 Internet 访问
internetClientServerS-1-15-3-2-...Internet 入站+出站
privateNetworkClientServerS-1-15-3-3-...内网访问
documentsLibraryS-1-15-3-4-...文档库
picturesLibraryS-1-15-3-5-...图片库
microphoneS-1-15-3-6-...麦克风
webcamS-1-15-3-7-...摄像头

实际 SID 较长,可通过 CreateWellKnownSid 或查阅 Windows Capability SID 参考 获取完整值。


验证清单

测试项无 AppContainer有 AppContainer有 AppContainer + 授权
写入桌面❌(未授权桌面)
写入授权目录
读取 SAM 文件❌ (需管理员)
网络连接✅(有 internetClient)
打开其他进程✅ (同权限)

系列文章导航

  1. Windows 沙箱技术全景
  2. Windows 沙箱 Demo - WSB 配置实战
  3. Windows 沙箱 Demo - AppContainer 编程(本文)
  4. Windows 沙箱 Demo - Job Object 进程管控

参考资料


📅 创建时间:2026-08-12 🏷️ 分类:Windows 安全 / 沙箱技术