You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++中CreateProcess()提权问题与UAC解决方案:获取子进程输出

解决方案:实现提权进程的输出捕获

由于UAC权限隔离限制,普通权限进程无法直接通过CreateProcess的管道方式捕获提权子进程的输出,以下两种方案可解决该问题:

方案一:命名管道+自定义输出重定向(需修改子进程)

这种方案需要子进程配合将输出写入父进程创建的命名管道,适合可控的子进程场景:

1. 父进程创建带权限的命名管道

创建允许管理员进程访问的命名管道,避免权限隔离导致写入失败:

#include <windows.h>
#include <sddl.h>

HANDLE CreateAccessibleNamedPipe() {
    PSECURITY_DESCRIPTOR pSD = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
    InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION);
    LPCWSTR szSD = L"D:(A;;GA;;;WD)";
    ConvertStringSecurityDescriptorToSecurityDescriptor(szSD, SDDL_REVISION_1, &pSD, NULL);

    SECURITY_ATTRIBUTES sa = {0};
    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = pSD;
    sa.bInheritHandle = FALSE;

    HANDLE hPipe = CreateNamedPipe(
        L"\\\\.\\pipe\\MyElevatedOutputPipe",
        PIPE_ACCESS_INBOUND,
        PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
        1,
        4096,
        4096,
        NMPWAIT_USE_DEFAULT_WAIT,
        &sa
    );

    LocalFree(pSD);
    return hPipe;
}

2. 父进程启动提权子进程

用ShellExecuteEx触发UAC提权,并将管道名作为参数传递给子进程:

BOOL StartElevatedProcess(LPCWSTR szExePath, LPCWSTR szPipeName) {
    SHELLEXECUTEINFOW sei = {0};
    sei.cbSize = sizeof(SHELLEXECUTEINFOW);
    sei.fMask = SEE_MASK_NOCLOSEPROCESS;
    sei.lpVerb = L"runas";
    sei.lpFile = szExePath;

    wchar_t szCmdLine[512];
    swprintf_s(szCmdLine, L"\"%s\" %s", szExePath, szPipeName);
    sei.lpParameters = szCmdLine + wcslen(szExePath) + 2;
    sei.nShow = SW_SHOWNORMAL;

    if (!ShellExecuteExW(&sei)) {
        return FALSE;
    }
    ConnectNamedPipe(sei.hProcess, NULL);
    return TRUE;
}

3. 子进程重定向输出到管道

子进程解析参数中的管道名,将标准输出/错误写入管道:

#include <windows.h>
#include <stdio.h>

int wmain(int argc, wchar_t* argv[]) {
    if (argc < 2) return 1;

    HANDLE hPipe = CreateFileW(
        argv[1],
        GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );

    if (hPipe == INVALID_HANDLE_VALUE) return 2;

    SetStdHandle(STD_OUTPUT_HANDLE, hPipe);
    SetStdHandle(STD_ERROR_HANDLE, hPipe);

    // 示例输出
    printf("Elevated process output: Hello from admin mode!\n");
    fprintf(stderr, "Elevated process error log: Test error message\n");

    CloseHandle(hPipe);
    return 0;
}

4. 父进程读取管道内容

启动子进程后,从管道读取输出:

void ReadPipeData(HANDLE hPipe) {
    char buffer[4096];
    DWORD dwRead;
    while (ReadFile(hPipe, buffer, sizeof(buffer)-1, &dwRead, NULL) && dwRead > 0) {
        buffer[dwRead] = '\0';
        printf("Captured output: %s", buffer);
    }
    CloseHandle(hPipe);
}

方案二:任务计划程序API(无需修改子进程)

针对第三方不可修改的程序,可通过创建临时任务提权运行,并将输出重定向到临时文件:

核心代码示例

#include <taskschd.h>
#pragma comment(lib, "taskschd.lib")
#pragma comment(lib, "comsupp.lib")

BOOL RunElevatedAndCapture(LPCWSTR szExePath, LPCWSTR szTempOutput) {
    HRESULT hr = CoInitialize(NULL);
    if (FAILED(hr)) return FALSE;

    ITaskService* pService = NULL;
    hr = CoCreateInstance(CLSID_TaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskService, (void**)&pService);
    if (FAILED(hr)) { CoUninitialize(); return FALSE; }

    hr = pService->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t());
    ITaskFolder* pRootFolder = NULL;
    hr = pService->GetFolder(_bstr_t(L"\\"), &pRootFolder);
    pService->Release();
    if (FAILED(hr)) { CoUninitialize(); return FALSE; }

    ITaskDefinition* pTaskDef = NULL;
    hr = pService->NewTask(0, &pTaskDef);
    IPrincipal* pPrincipal = NULL;
    hr = pTaskDef->get_Principal(&pPrincipal);
    hr = pPrincipal->put_RunLevel(TASK_RUNLEVEL_HIGHEST);
    pPrincipal->Release();

    IActionCollection* pActions = NULL;
    hr = pTaskDef->get_Actions(&pActions);
    IAction* pAction = NULL;
    hr = pActions->Create(TASK_ACTION_EXEC, &pAction);
    IExecAction* pExecAction = NULL;
    hr = pAction->QueryInterface(IID_IExecAction, (void**)&pExecAction);
    hr = pExecAction->put_Path(_bstr_t(szExePath));

    wchar_t szArgs[512];
    swprintf_s(szArgs, L"> \"%s\" 2>&1", szTempOutput);
    hr = pExecAction->put_Arguments(_bstr_t(szArgs));

    pExecAction->Release();
    pAction->Release();
    pActions->Release();

    wchar_t szTaskName[64];
    swprintf_s(szTaskName, L"TempElevTask_%u", GetCurrentProcessId());
    IRunningTask* pRunningTask = NULL;
    hr = pRootFolder->RegisterTaskDefinition(
        _bstr_t(szTaskName),
        pTaskDef,
        TASK_CREATE_OR_UPDATE,
        _variant_t(),
        _variant_t(),
        TASK_LOGON_INTERACTIVE_TOKEN,
        _variant_t(L""),
        &pRunningTask
    );

    pTaskDef->Release();
    if (FAILED(hr)) { pRootFolder->Release(); CoUninitialize(); return FALSE; }

    TASK_STATE state;
    do {
        Sleep(100);
        pRunningTask->get_State(&state);
    } while (state == TASK_STATE_RUNNING);

    pRootFolder->DeleteTask(_bstr_t(szTaskName), 0);
    pRunningTask->Release();
    pRootFolder->Release();
    CoUninitialize();

    return TRUE;
}

调用该函数后,父进程直接读取szTempOutput指定的临时文件即可获取输出。

注意事项

  • 命名管道方案需确保子进程可修改,任务计划方案适配所有程序但流程更复杂。
  • 临时文件需放在双方都有权限访问的路径(如系统临时目录GetTempPath)。
  • 命名管道的安全描述符必须配置正确,否则提权进程会因权限不足无法访问。

内容的提问来源于stack exchange,提问作者Noctisik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 23:13:10