前言

本文是 Windows 沙箱技术全景 的编程实战篇。使用 Job Object 对进程施加资源限制和 UI 限制,配合受限令牌构建一个轻量级的进程管控沙箱。

Job Object 本身不做文件/网络隔离,但它是自定义沙箱方案中不可或缺的组件——限制资源 + 管理进程树 + UI 限制

环境准备

项目要求
操作系统Windows 10 / 11
IDEVisual Studio 2022
SDKWindows 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 并设置限制

C++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. 进程树监控(可选扩展)

C++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_CLOSEJob 句柄关闭 → 杀灭所有进程
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   │  │  • 文件隔离              │  │
│  │ (写隔离)      │  │  • 网络隔离              │  │
│  └──────────────┘  └─────────────────────────┘  │
└─────────────────────────────────────────────────┘
C++cpp
// 完整组合示例(伪代码)
HANDLE hJob = CreateSandboxJob();              // Job Object
HANDLE hToken = CreateRestrictedTokenForSandbox(); // 受限令牌
// + SetTokenInformation(hToken, TokenIntegrityLevel, &lowIL)  // 低完整性
// + AppContainer SID + Capability SIDs (可选)
CreateProcessAsUser(hToken, ...);
AssignProcessToJobObject(hJob, hProcess);

系列文章导航

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

参考资料


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