前言
本文是 Windows 沙箱技术全景 的编程实战篇。通过 C++ 代码将一个进程运行在 AppContainer 中,体验 Windows 应用级沙箱的隔离能力。
环境准备
| 项目 | 要求 |
|---|---|
| 操作系统 | Windows 10 / 11 |
| IDE | Visual Studio 2022 |
| SDK | Windows SDK 10.0.26100+(或最新版) |
| 语言 | C++ |
| 权限 | 普通用户即可(不需要管理员) |
VS 项目配置
- 新建「Windows 桌面应用程序」或「控制台应用程序」
- 链接器 → 输入 → 附加依赖项:
userenv.lib - C++ 语言标准:C++17 或更高
核心概念
AppContainer 沙箱的构建需要三个组件:
┌──────────────────────────────────────────────┐
│ AppContainer 沙箱进程 │
│ │
│ ┌──────────────┐ ┌──────────────────────┐ │
│ │ AppContainer │ │ Capability SID │ │
│ │ SID │ │ 列表(能力声明) │ │
│ │ (容器身份) │ │ internetClient 等 │ │
│ └──────────────┘ └──────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ 受限令牌(自动生成) │ │
│ │ 去除了高危权限 + AppContainer 限制 │ │
│ └──────────────────────────────────────────┘ │
└──────────────────────────────────────────────┘
- AppContainer SID:唯一标识这个容器,进程以此身份运行
- Capability SID:声明此容器拥有的能力(如联网、访问文档库等)
- 受限令牌:系统自动生成,去掉不必要的权限
完整代码
1. 创建 AppContainer 并启动沙箱进程
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 获取过程。实际项目中应使用
DeriveAppContainerSidFromAppContainerName或CreateAppContainerToken的完整参数。完整示例可参考 Microsoft 的 AppContainer 示例代码。
2. 授予文件访问权限
AppContainer 进程默认无法访问大部分文件系统。需要通过 ACL 授予特定路径访问权:
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. 测试沙箱隔离效果
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
| Capability | SID (缩写) | 说明 |
|---|---|---|
internetClient | S-1-15-3-1-... | 出站 Internet 访问 |
internetClientServer | S-1-15-3-2-... | Internet 入站+出站 |
privateNetworkClientServer | S-1-15-3-3-... | 内网访问 |
documentsLibrary | S-1-15-3-4-... | 文档库 |
picturesLibrary | S-1-15-3-5-... | 图片库 |
microphone | S-1-15-3-6-... | 麦克风 |
webcam | S-1-15-3-7-... | 摄像头 |
实际 SID 较长,可通过
CreateWellKnownSid或查阅 Windows Capability SID 参考 获取完整值。
验证清单
| 测试项 | 无 AppContainer | 有 AppContainer | 有 AppContainer + 授权 |
|---|---|---|---|
| 写入桌面 | ✅ | ❌ | ❌(未授权桌面) |
| 写入授权目录 | ✅ | ❌ | ✅ |
| 读取 SAM 文件 | ❌ (需管理员) | ❌ | ❌ |
| 网络连接 | ✅ | ❌ | ✅(有 internetClient) |
| 打开其他进程 | ✅ (同权限) | ❌ | ❌ |
系列文章导航
- Windows 沙箱技术全景
- Windows 沙箱 Demo - WSB 配置实战
- Windows 沙箱 Demo - AppContainer 编程(本文)
- Windows 沙箱 Demo - Job Object 进程管控
参考资料
- AppContainer isolation - Microsoft Learn
- CreateAppContainerToken function
- Windows classic samples (GitHub)
📅 创建时间:2026-08-12 🏷️ 分类:Windows 安全 / 沙箱技术