等待由 IShellDispatch2.ShellExecute 启动的进程
Posted
技术标签:
【中文标题】等待由 IShellDispatch2.ShellExecute 启动的进程【英文标题】:Wait for the process started by IShellDispatch2.ShellExecute 【发布时间】:2019-03-07 18:22:37 【问题描述】:我正在使用IShellDispatch2.ShellExecute
从我提升的进程中以标准用户运行一个进程,如Raymond Chen's article 中所述。与ShellExecuteEx 不同,此方法不返回有关进程的任何信息。
我需要知道启动的进程何时结束,我可能需要它的退出代码。有没有办法获得这个过程的句柄(除了拍摄快照之外的任何其他方式)?
【问题讨论】:
为什么不能使用 ShellExecuteEx ? @battlmonstr - 用于在另一个未提升的令牌下运行进程 如果您知道用户详细信息,也许调用“runas”命令可能会降级运行。这似乎也相关:social.msdn.microsoft.com/Forums/windowsdesktop/en-US/… 如果你有调试权限,你可以获取系统进程令牌,模拟它,然后使用WTSQueryUserToken
获取你的会话令牌,或者通过进程枚举中的logonsid找到它,最后使用CreateProcessAsUserW
How do I start a program as the desktop user from an elevated app?
【参考方案1】:
您不能这样做,因为 shell 没有公开 ShellExecuteEx 方法,即使公开了,返回的进程句柄在您的进程中也是无效的。
我能想到的最简单的解决方案是创建一个小助手应用程序,充当 shell 和您要启动的实际应用程序之间的中间人。这个中间人应用程序可以调用ShellExecuteEx
,并在子进程退出时向您的实际应用程序发送一条消息。
【讨论】:
当然可以在没有辅助进程的情况下创建所有内容,但代码会有点大 @RbMm 这真的取决于,在进程列表中搜索在某个时间戳之后启动的特定进程名称/路径仍然可以给出误报。中间人进程(甚至可以在您的主 .EXE 中实现)保证不会给出误报。 不,我有解决方案,它不依赖于进程名称/路径/时间并且没有误报 我们需要使用CreateProcessAsUserW
。但这里有 2 个问题 - 获取用户令牌和权限(用于调用 CreateProcessAsUserW
并可能获取用户令牌,取决于方式)。如果我们最初具有调试权限,我们可以获得所需的权限:枚举进程、打开它的令牌、查看包含所需权限的令牌、复制和模拟。或者获取我们可以使用的用户令牌或WTSQueryUserToken
(来自同一个终端会话)或再次从令牌中具有相同LogonId sid的进程中搜索【参考方案2】:
您可以使用CreateProcessAsUserW
来启动没有提升用户令牌的进程。但这里存在几个问题 - 如果 hToken 是调用者主令牌的受限版本,则调用此 api 需要 SE_INCREASE_QUOTA_NAME
和 SE_ASSIGNPRIMARYTOKEN_NAME
权限。
其次如何获取用户令牌?您可以为此使用WTSQueryUserToken
,但要调用此api,您需要具有SE_TCB_NAME
权限
所以你需要拥有/获得 3 个特权 SE_ASSIGNPRIMARYTOKEN_NAME
、 SE_TCB_NAME
和 SE_INCREASE_QUOTA_NAME
。一般LocalSystem
进程都有它。如果我们有SE_DEBUG_PRIVILEGE
,我们可以打开这个进程的一部分。
所以一般来说我们需要做下一步:
get selfSessionId
(需致电WTSQueryUserToken
)
在进程或线程令牌中启用SE_DEBUG_PRIVILEGE
(提升
token 通常有这个权限)
使用令牌搜索进程,需要 as 权限
使用此令牌模拟
致电WTSQueryUserToken
致电CreateProcessAsUserW
-----代码:---------------
inline ULONG BOOL_TO_ERROR(BOOL f)
return f ? NOERROR : GetLastError();
// use in _alloca(guz) because _alloca(0) work incorrect
// return 0 pointer instead allocates a zero-length item
volatile UCHAR guz;
ULONG takePrivileges(HANDLE hToken, ::PTOKEN_PRIVILEGES ptp, ULONG cb, BOOL& bContinue)
if (ULONG PrivilegeCount = ptp->PrivilegeCount)
int n = 3;
BOOL fAdjust = FALSE;
::PLUID_AND_ATTRIBUTES Privileges = ptp->Privileges;
do
switch (Privileges->Luid.LowPart)
case SE_ASSIGNPRIMARYTOKEN_PRIVILEGE:
case SE_INCREASE_QUOTA_PRIVILEGE:
case SE_TCB_PRIVILEGE:
if (!(Privileges->Attributes & SE_PRIVILEGE_ENABLED))
Privileges->Attributes |= SE_PRIVILEGE_ENABLED;
fAdjust = TRUE;
if (!--n)
bContinue = FALSE;
ULONG dwError = BOOL_TO_ERROR(DuplicateTokenEx(hToken,
TOKEN_ADJUST_PRIVILEGES|TOKEN_IMPERSONATE,
0, ::SecurityImpersonation, ::TokenImpersonation,
&hToken));
if (dwError == NOERROR)
if (fAdjust)
AdjustTokenPrivileges(hToken, FALSE, ptp, cb, NULL, NULL);
dwError = GetLastError();
if (dwError == NOERROR)
dwError = BOOL_TO_ERROR(SetThreadToken(0, hToken));
CloseHandle(hToken);
return dwError;
while (Privileges++, --PrivilegeCount);
return ERROR_NOT_FOUND;
ULONG GetPrivileges()
ULONG dwError;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot != INVALID_HANDLE_VALUE)
dwError = ERROR_NOT_FOUND;
PROCESSENTRY32W pe = sizeof(pe) ;
if (Process32FirstW(hSnapshot, &pe))
ULONG cb = 0, rcb = 0x100;
PVOID stack = alloca(guz);
union
PVOID buf;
::PTOKEN_PRIVILEGES ptp;
;
BOOL bContinue = TRUE;
do
if (HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe.th32ProcessID))
HANDLE hToken;
if (OpenProcessToken(hProcess, TOKEN_QUERY|TOKEN_DUPLICATE, &hToken))
do
if (cb < rcb)
cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
if (GetTokenInformation(hToken, ::TokenPrivileges, buf, cb, &rcb))
dwError = takePrivileges(hToken, ptp, rcb, bContinue);
break;
while (GetLastError() == ERROR_INSUFFICIENT_BUFFER);
CloseHandle(hToken);
CloseHandle(hProcess);
while (bContinue && Process32NextW(hSnapshot, &pe));
CloseHandle(hSnapshot);
else
dwError = GetLastError();
return dwError;
ULONG RunNotElevated(PCWSTR lpApplicationName, PWSTR lpCommandLine, PCWSTR lpCurrentDirectory)
HANDLE hToken, hDupToken = 0;
ULONG SessionId;
ULONG dwError = BOOL_TO_ERROR(ProcessIdToSessionId(GetCurrentProcessId(), &SessionId));
if (NOERROR == dwError &&
(NOERROR == (dwError = BOOL_TO_ERROR(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY|TOKEN_DUPLICATE, &hToken)))))
dwError = BOOL_TO_ERROR(DuplicateTokenEx(hToken,
TOKEN_IMPERSONATE|TOKEN_ADJUST_PRIVILEGES, 0,
::SecurityImpersonation, ::TokenImpersonation, &hDupToken));
CloseHandle(hToken);
if (dwError == NOERROR)
// get SE_DEBUG_PRIVILEGE
static ::TOKEN_PRIVILEGES tp = 1, SE_DEBUG_PRIVILEGE , SE_PRIVILEGE_ENABLED ;
AdjustTokenPrivileges(hDupToken, FALSE, &tp, 0, 0, 0);
if ((dwError = GetLastError()) == NOERROR)
dwError = BOOL_TO_ERROR(SetThreadToken(0, hDupToken));
CloseHandle(hDupToken);
if (dwError == NOERROR)
if (NOERROR == (dwError = GetPrivileges()))
STARTUPINFOW si = sizeof(si) ;
PROCESS_INFORMATION pi;
PVOID lpEnvironment;
if (WTSQueryUserToken(SessionId, &hToken))
dwError = BOOL_TO_ERROR(CreateEnvironmentBlock(&lpEnvironment, hToken, FALSE));
if (dwError == NOERROR)
dwError = BOOL_TO_ERROR(CreateProcessAsUserW(
hToken, lpApplicationName, lpCommandLine,
NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT,
lpEnvironment, lpCurrentDirectory, &si, &pi));
DestroyEnvironmentBlock(lpEnvironment);
CloseHandle(hToken);
if (dwError == NOERROR)
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
SetThreadToken(0, 0);
return dwError;
void test_r()
WCHAR cmd[MAX_PATH];
if (GetEnvironmentVariable(L"comspec", cmd, RTL_NUMBER_OF(cmd)))
RunNotElevated1(cmd, L"cmd /k whoami /all",0);
【讨论】:
以上是关于等待由 IShellDispatch2.ShellExecute 启动的进程的主要内容,如果未能解决你的问题,请参考以下文章
等待由 IShellDispatch2.ShellExecute 启动的进程