前言
本文是 Windows 沙箱技术全景 的编程实战篇。使用 Job Object 对进程施加资源限制和 UI 限制,配合受限令牌构建一个轻量级的进程管控沙箱。
Job Object 本身不做文件/网络隔离,但它是自定义沙箱方案中不可或缺的组件——限制资源 + 管理进程树 + UI 限制。
环境准备
| 项目 | 要求 |
|---|---|
| 操作系统 | Windows 10 / 11 |
| IDE | Visual Studio 2022 |
| SDK | Windows SDK 10.0.26100+ |
| 语言 | C++ |
核心概念
┌──────────────── Job Object ────────────────┐
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 父进程 │───→│ 子进程 A │ │
│ │ (自动加入) │ │ (自动加入) │ │
│ └─────────────┘ └─────────────┘ │
│ │ │
│ ↓ │
│ ┌─────────────┐ │
│ │ 子进程 B │ │
│ │ (自动加入) │ │
│ └─────────────┘ │
│ │
│ 限制: │
│ • CPU 速率 ≤ 20% │
│ • 内存 ≤ 256MB │
│ • 不能读剪贴板 │
│ • 不能退出 Windows │
│ • Job 关闭 → 全部终止 │
└────────────────────────────────────────────┘
关键特性:
- 进程一旦加入 Job,不可脱离(
AssignProcessToJobObject) - 子进程自动加入父进程的 Job(默认行为)
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE:Job 句柄关闭时杀灭所有进程- Win8+ 支持嵌套 Job,可以有多层限制
完整代码
1. 创建 Job Object 并设置限制
cpp
// sandbox_jobobject.cpp
// Job Object + 受限令牌 沙箱 Demo
#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>
// 创建配置好的 Job Object
HANDLE CreateSandboxJob()
{
// ---- Step 1: 创建 Job Object ----
HANDLE hJob = ::CreateJobObjectW(nullptr, L"SandboxJob_Demo");
if (!hJob) {
wprintf(L"[-] CreateJobObject failed: %lu\n", GetLastError());
return nullptr;
}
wprintf(L"[+] Job Object created\n");
// ---- Step 2: 设置基本限制 ----
JOBOBJECT_BASIC_LIMIT_INFORMATION basicLimit = {};
basicLimit.LimitFlags =
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | // Job 关闭时杀全部进程
JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION | // 未处理异常 → 终止整个 Job
JOB_OBJECT_LIMIT_BREAKAWAY_OK; // 允许子进程脱离(调试用)
// 进程数限制
basicLimit.LimitFlags |= JOB_OBJECT_LIMIT_ACTIVE_PROCESS;
basicLimit.ActiveProcessLimit = 10;
if (!::SetInformationJobObject(hJob, JobObjectBasicLimitInformation,
&basicLimit, sizeof(basicLimit))) {
wprintf(L"[-] SetInformationJobObject (basic) failed: %lu\n", GetLastError());
::CloseHandle(hJob);
return nullptr;
}
wprintf(L"[+] Basic limits set (max 10 processes, kill-on-close)\n");
// ---- Step 3: 设置扩展限制(内存 + CPU) ----
JOBOBJECT_EXTENDED_LIMIT_INFORMATION extLimit = {};
extLimit.BasicLimitInformation.LimitFlags =
JOB_OBJECT_LIMIT_PROCESS_MEMORY | // 进程内存上限
JOB_OBJECT_LIMIT_JOB_MEMORY | // 整个 Job 内存上限
JOB_OBJECT_LIMIT_CPU_RATE_CONTROL; // CPU 速率控制
// 内存限制:单个进程 256MB,整个 Job 512MB
extLimit.ProcessMemoryLimit = 256 * 1024 * 1024; // 256 MB
extLimit.JobMemoryLimit = 512 * 1024 * 1024; // 512 MB
if (!::SetInformationJobObject(hJob, JobObjectExtendedLimitInformation,
&extLimit, sizeof(extLimit))) {
wprintf(L"[-] SetInformationJobObject (extended) failed: %lu\n", GetLastError());
// 非致命,继续
} else {
wprintf(L"[+] Extended limits set (process: 256MB, job: 512MB)\n");
}
// ---- Step 4: CPU 速率限制 ----
JOBOBJECT_CPU_RATE_CONTROL_INFORMATION cpuRate = {};
cpuRate.ControlFlags =
JOB_OBJECT_CPU_RATE_CONTROL_ENABLE |
JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP; // 硬限制,不可超出
cpuRate.CpuRate = 2000; // 单位:百分比 × 100,即 20%(2000 = 20%)
if (!::SetInformationJobObject(hJob, JobObjectCpuRateControlInformation,
&cpuRate, sizeof(cpuRate))) {
wprintf(L"[-] SetInformationJobObject (CPU rate) failed: %lu\n", GetLastError());
} else {
wprintf(L"[+] CPU rate limit set to 20%%\n");
}
// ---- Step 5: UI 限制 ----
JOBOBJECT_BASIC_UI_RESTRICTIONS uiRestrictions = {};
uiRestrictions.UIRestrictionsClass =
JOB_OBJECT_UILIMIT_EXIT_WINDOWS | // 不能关机/重启
JOB_OBJECT_UILIMIT_READCLIPBOARD | // 不能读剪贴板
JOB_OBJECT_UILIMIT_WRITECLIPBOARD | // 不能写剪贴板
JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS | // 不能改系统参数
JOB_OBJECT_UILIMIT_DISPLAYSETTINGS | // 不能改显示设置
JOB_OBJECT_UILIMIT_GLOBALATOMS; // 隔离全局原子表
if (!::SetInformationJobObject(hJob, JobObjectBasicUIRestrictions,
&uiRestrictions, sizeof(uiRestrictions))) {
wprintf(L"[-] SetInformationJobObject (UI) failed: %lu\n", GetLastError());
} else {
wprintf(L"[+] UI restrictions set (no clipboard, no system params)\n");
}
return hJob;
}
// 创建受限令牌(去掉高危权限)
HANDLE CreateRestrictedTokenForSandbox()
{
HANDLE hCurrentToken = nullptr;
if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_DUPLICATE | TOKEN_QUERY, &hCurrentToken)) {
wprintf(L"[-] OpenProcessToken failed: %lu\n", GetLastError());
return nullptr;
}
HANDLE hRestrictedToken = nullptr;
// 创建受限令牌:移除所有权限,仅保留基本权限
if (!::CreateRestrictedToken(
hCurrentToken,
DISABLE_MAX_PRIVILEGE, // 禁用所有权限
0, nullptr, // 不删除特定 SID
0, nullptr, // 不设置 Deny-Only SID
0, nullptr, // 不限制 SID
&hRestrictedToken))
{
wprintf(L"[-] CreateRestrictedToken failed: %lu\n", GetLastError());
::CloseHandle(hCurrentToken);
return nullptr;
}
::CloseHandle(hCurrentToken);
wprintf(L"[+] Restricted token created (all privileges disabled)\n");
return hRestrictedToken;
}
// 在沙箱中启动进程
BOOL LaunchInSandbox(HANDLE hJob, HANDLE hRestrictedToken, const wchar_t* commandLine)
{
STARTUPINFOW si = {};
si.cb = sizeof(si);
PROCESS_INFORMATION pi = {};
// 使用受限令牌创建进程
BOOL success = ::CreateProcessAsUserW(
hRestrictedToken,
nullptr,
const_cast<LPWSTR>(commandLine),
nullptr, nullptr,
FALSE,
CREATE_SUSPENDED | CREATE_NEW_CONSOLE, // 挂起启动,先加入 Job
nullptr, nullptr,
&si, &pi);
if (!success) {
wprintf(L"[-] CreateProcessAsUserW failed: %lu\n", GetLastError());
return FALSE;
}
// 将进程加入 Job Object(在进程恢复执行前)
if (!::AssignProcessToJobObject(hJob, pi.hProcess)) {
wprintf(L"[-] AssignProcessToJobObject failed: %lu\n", GetLastError());
// Win8 之前的版本如果进程已加入其他 Job 会失败
::TerminateProcess(pi.hProcess, 1);
::CloseHandle(pi.hProcess);
::CloseHandle(pi.hThread);
return FALSE;
}
// 恢复进程执行
::ResumeThread(pi.hThread);
wprintf(L"[+] Process launched in sandbox: PID=%lu\n", pi.dwProcessId);
::CloseHandle(pi.hThread);
// 保留 pi.hProcess 供等待使用
// 调用方负责关闭
// 将进程句柄存入全局或返回
// 这里简化:直接等待
wprintf(L"\n[>] 进程在沙箱中运行中...\n");
wprintf(L"[>] 限制:CPU 20%% / 内存 256MB / 无剪贴板 / 无关机\n");
wprintf(L"[>] 等待进程退出...\n\n");
::WaitForSingleObject(pi.hProcess, INFINITE);
DWORD exitCode = 0;
::GetExitCodeProcess(pi.hProcess, &exitCode);
wprintf(L"[+] Process exited with code: %lu\n", exitCode);
::CloseHandle(pi.hProcess);
return TRUE;
}
int wmain(int argc, wchar_t* argv[])
{
wprintf(L"====================================\n");
wprintf(L" Job Object 沙箱 Demo\n");
wprintf(L"====================================\n\n");
// 默认启动 cmd.exe,也可通过命令行参数指定
std::wstring cmdLine = (argc > 1) ? argv[1] : L"cmd.exe /k echo Job Object Sandbox Ready";
// Step 1: 创建 Job Object
HANDLE hJob = CreateSandboxJob();
if (!hJob) return 1;
// Step 2: 创建受限令牌
HANDLE hRestrictedToken = CreateRestrictedTokenForSandbox();
if (!hRestrictedToken) {
::CloseHandle(hJob);
return 1;
}
// Step 3: 在沙箱中启动进程
wprintf(L"\n[+] Launching: %s\n\n", cmdLine.c_str());
LaunchInSandbox(hJob, hRestrictedToken, cmdLine.c_str());
// 清理
::CloseHandle(hRestrictedToken);
::CloseHandle(hJob); // 关闭 Job 句柄 → 如果有残留进程会被杀灭
wprintf(L"\n[+] Sandbox closed. All processes terminated.\n");
return 0;
}2. 测试脚本
编译后运行,会启动一个受限的 cmd.exe。在其中测试以下操作:
batch
:: 在沙箱 cmd.exe 中执行以下测试:
:: 测试 1: 检查权限
whoami /priv
:: 预期:权限列表为空或极少
:: 测试 2: 尝试读取剪贴板
powershell -command "Get-Clipboard"
:: 预期:被拒绝(UI 限制)
:: 测试 3: 尝试关机
shutdown /s /t 0
:: 预期:被拒绝(UI 限制)
:: 测试 4: 消耗大量内存(测试内存限制)
:: 预期:进程因内存超限被终止
powershell -command "$a = New-Object byte[] 300MB"
:: 测试 5: 占用 CPU(测试 CPU 限制)
:: 预期:CPU 使用率被限制在 20%
powershell -command "while($true) { $x = 1+1 }"3. 进程树监控(可选扩展)
cpp
// 枚举 Job 中所有进程
void EnumerateJobProcesses(HANDLE hJob)
{
JOBOBJECT_BASIC_PROCESS_ID_LIST pidList = {};
DWORD cb = sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST) + sizeof(DWORD) * 100;
auto buffer = std::vector<BYTE>(cb);
auto pPidList = reinterpret_cast<JOBOBJECT_BASIC_PROCESS_ID_LIST*>(buffer.data());
pPidList->NumberOfProcessIdsInList = 100;
if (::QueryInformationJobObject(hJob, JobObjectBasicProcessIdList,
pPidList, cb, nullptr))
{
wprintf(L"[+] Job contains %lu processes:\n",
pPidList->NumberOfProcessIdsInList);
for (DWORD i = 0; i < pPidList->NumberOfProcessIdsInList; i++) {
wprintf(L" PID: %lu\n", pPidList->ProcessIdList[i]);
}
}
}
// 查询 Job 资源使用统计
void QueryJobStats(HANDLE hJob)
{
JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION info = {};
if (::QueryInformationJobObject(hJob,
JobObjectBasicAndIoAccountingInformation,
&info, sizeof(info), nullptr))
{
wprintf(L"\n--- Job Statistics ---\n");
wprintf(L" Total processes: %lu\n", info.BasicInfo.TotalProcesses);
wprintf(L" Active processes: %lu\n", info.BasicInfo.ActiveProcesses);
wprintf(L" Terminated processes:%lu\n", info.BasicInfo.TotalTerminatedProcesses);
wprintf(L" Peak process memory: %llu MB\n",
info.BasicInfo.PeakProcessMemoryUsed / (1024 * 1024));
wprintf(L" Peak job memory: %llu MB\n",
info.BasicInfo.PeakJobMemoryUsed / (1024 * 1024));
wprintf(L" I/O Read bytes: %llu\n", info.IoInfo.ReadOperationCount);
wprintf(L" I/O Write bytes: %llu\n", info.IoInfo.WriteOperationCount);
}
}限制项详解
基本限制 (JOBOBJECT_BASIC_LIMIT_INFORMATION)
| Flag | 说明 |
|---|---|
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | Job 句柄关闭 → 杀灭所有进程 |
JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION | 未处理异常 → 终止整个 Job |
JOB_OBJECT_LIMIT_ACTIVE_PROCESS | 限制最大进程数 |
JOB_OBJECT_LIMIT_PROCESS_TIME | 单进程 CPU 时间上限 |
JOB_OBJECT_LIMIT_JOB_TIME | 整个 Job CPU 时间上限 |
JOB_OBJECT_LIMIT_BREAKAWAY_OK | 允许子进程脱离 Job |
JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK | 子进程自动脱离 |
扩展限制 (JOBOBJECT_EXTENDED_LIMIT_INFORMATION)
| Flag | 说明 |
|---|---|
JOB_OBJECT_LIMIT_PROCESS_MEMORY | 单进程内存上限 |
JOB_OBJECT_LIMIT_JOB_MEMORY | 整个 Job 内存上限 |
JOB_OBJECT_LIMIT_CPU_RATE_CONTROL | 启用 CPU 速率控制 |
CPU 速率控制
| 模式 | 说明 |
|---|---|
JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP | 硬限制,绝对不超 |
JOB_OBJECT_CPU_RATE_CONTROL_SOFT_CAP | 软限制,系统空闲时可超出 |
CpuRate | 百分比 × 100(如 2000 = 20%) |
UI 限制 (JOBOBJECT_BASIC_UI_RESTRICTIONS)
| Flag | 说明 |
|---|---|
JOB_OBJECT_UILIMIT_EXIT_WINDOWS | 不能关机/重启 |
JOB_OBJECT_UILIMIT_READCLIPBOARD | 不能读剪贴板 |
JOB_OBJECT_UILIMIT_WRITECLIPBOARD | 不能写剪贴板 |
JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS | 不能改系统参数 |
JOB_OBJECT_UILIMIT_DISPLAYSETTINGS | 不能改显示设置 |
JOB_OBJECT_UILIMIT_GLOBALATOMS | 隔离全局原子表 |
JOB_OBJECT_UILIMIT_DESKTOP | 隔离桌面 |
JOB_OBJECT_UILIMIT_HANDLES | 隔离句柄 |
组合方案:完整轻量级沙箱
将三种技术组合,可以得到一个实用的自定义沙箱:
┌─────────────────────────────────────────────────┐
│ 自定义轻量级沙箱 │
│ │
│ ┌──────────────┐ ┌─────────────────────────┐ │
│ │ Job Object │ │ Restricted Token │ │
│ │ • CPU 20% │ │ • 移除 SeDebugPrivilege │ │
│ │ • Mem 256MB │ │ • 移除 SeLoadDriver │ │
│ │ • 无剪贴板 │ │ • 移除 SeTcbPrivilege │ │
│ │ • Kill on │ │ • 移除所有特权 │ │
│ │ close │ │ │ │
│ └──────────────┘ └─────────────────────────┘ │
│ │
│ ┌──────────────┐ ┌─────────────────────────┐ │
│ │ Integrity │ │ AppContainer (可选) │ │
│ │ Level: Low │ │ • 文件隔离 │ │
│ │ (写隔离) │ │ • 网络隔离 │ │
│ └──────────────┘ └─────────────────────────┘ │
└─────────────────────────────────────────────────┘
cpp
// 完整组合示例(伪代码)
HANDLE hJob = CreateSandboxJob(); // Job Object
HANDLE hToken = CreateRestrictedTokenForSandbox(); // 受限令牌
// + SetTokenInformation(hToken, TokenIntegrityLevel, &lowIL) // 低完整性
// + AppContainer SID + Capability SIDs (可选)
CreateProcessAsUser(hToken, ...);
AssignProcessToJobObject(hJob, hProcess);系列文章导航
- Windows 沙箱技术全景
- Windows 沙箱 Demo - WSB 配置实战
- Windows 沙箱 Demo - AppContainer 编程
- Windows 沙箱 Demo - Job Object 进程管控(本文)
参考资料
- Job Objects - Microsoft Learn
- CreateJobObject function
- SetInformationJobObject function
- CreateRestrictedToken function
📅 创建时间:2026-08-12 🏷️ 分类:Windows 安全 / 沙箱技术