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
相关产品推荐
相关产品推荐

