如Raymond Chen 的文章中所述,我正在使用IShellDispatch2.ShellExecute
提升的进程在标准用户下运行进程。与ShellExecuteEx不同,此方法不返回有关进程的任何信息。
我需要知道启动的进程何时完成,我可能需要它的退出代码。有没有办法获得这个过程的句柄(除了拍摄快照之外的任何其他方式)?
如Raymond Chen 的文章中所述,我正在使用IShellDispatch2.ShellExecute
提升的进程在标准用户下运行进程。与ShellExecuteEx不同,此方法不返回有关进程的任何信息。
我需要知道启动的进程何时完成,我可能需要它的退出代码。有没有办法获得这个过程的句柄(除了拍摄快照之外的任何其他方式)?
您可以使用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
.
所以一般来说我们需要做下一步:
SessionId
(需要通话WTSQueryUserToken
)SE_DEBUG_PRIVILEGE
进程或线程令牌(提升的令牌通常具有此权限)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);
}
}
您不能这样做,因为 shell 没有公开 ShellExecuteEx 方法,即使这样做了,返回的进程句柄在您的进程中也将无效。
我能想到的最简单的解决方案是创建一个小助手应用程序,充当 shell 和您要启动的真实应用程序之间的中间人。当子进程退出时,此中间人应用程序可以调用ShellExecuteEx
并将消息发送回您的真实应用程序。