在windows7以上系统,使用system权限启动的进程默认是没有ui界面,这是windows系统出于安全考虑的限制。防止用户在高权限下误删重要的文件。
解决的办法:降权
,即将权限降低。
下面给出降权的实例代码:
本质上是拿到当前用户的token,才能降权成功。
DWORD GetActiveSessionID() {
DWORD dwSessionId = 0;
PWTS_SESSION_INFO pSessionInfo = NULL;
DWORD dwCount = 0;
WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSessionInfo,
&dwCount);
for (DWORD i = 0; i < dwCount; i++) {
WTS_SESSION_INFO si = pSessionInfo[i];
if (WTSActive == si.State) {
dwSessionId = si.SessionId;
break;
}
}
WTSFreeMemory(pSessionInfo);
return dwSessionId;
}
const wstring GetAppPath() {
static wstring szRootPath;
if (szRootPath == (L"")) {
TCHAR szPath[_MAX_PATH];
GetModuleFileName(NULL, szPath, _MAX_PATH);
wstring strFullPath = szPath;
szRootPath = strFullPath;
}
return szRootPath;
}
BOOL TriggerAppExecute(std::wstring wstrCmdLine /*, INT32& n32ExitResult*/) {
DWORD dwProcesses = 0;
BOOL bResult = FALSE;
DWORD dwSid = GetActiveSessionID();
DWORD dwRet = 0;
PROCESS_INFORMATION pi;
STARTUPINFO si;
HANDLE hProcess = NULL, hPToken = NULL, hUserTokenDup = NULL;
if (!WTSQueryUserToken(dwSid, &hPToken)) {
PROCESSENTRY32 procEntry;
DWORD dwPid = 0;
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap == INVALID_HANDLE_VALUE) {
return FALSE;
}
procEntry.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hSnap, &procEntry)) {
do {
if (_wcsicmp(procEntry.szExeFile, (L"explorer.exe")) == 0) {
DWORD exeSessionId = 0;
if (ProcessIdToSessionId(procEntry.th32ProcessID, &exeSessionId) &&
exeSessionId == dwSid) {
dwPid = procEntry.th32ProcessID;
break;
}
}
} while (Process32Next(hSnap, &procEntry));
}
CloseHandle(hSnap);
// explorer进程不存在
if (dwPid == 0) {
return FALSE;
}
hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwPid);
if (hProcess == NULL) {
return FALSE;
}
if (!::OpenProcessToken(hProcess, TOKEN_ALL_ACCESS_P, &hPToken)) {
CloseHandle(hProcess);
return FALSE;
}
}
if (hPToken == NULL) {
return FALSE;
}
TOKEN_LINKED_TOKEN admin;
bResult =
GetTokenInformation(hPToken, (TOKEN_INFORMATION_CLASS)TokenLinkedToken,
&admin, sizeof(TOKEN_LINKED_TOKEN), &dwRet);
if (!bResult) // vista 以前版本不支持TokenLinkedToken
{
TOKEN_PRIVILEGES tp;
LUID luid;
if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
}
//复制token
DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification,
TokenPrimary, &hUserTokenDup);
} else {
hUserTokenDup = admin.LinkedToken;
}
LPVOID pEnv = NULL;
DWORD dwCreationFlags = CREATE_PRESERVE_CODE_AUTHZ_LEVEL;
// hUserTokenDup为当前登陆用户的令牌
if (CreateEnvironmentBlock(&pEnv, hUserTokenDup, TRUE)) {
//如果传递了环境变量参数,CreateProcessAsUser的
// dwCreationFlags参数需要加上CREATE_UNICODE_ENVIRONMENT,
//这是MSDN明确说明的
dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
} else {
//环境变量创建失败仍然可以创建进程,
//但会影响到后面的进程获取环境变量内容
pEnv = NULL;
}
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOW;
ZeroMemory(&pi, sizeof(pi));
bResult = CreateProcessAsUser(hUserTokenDup, // client's access token
GetAppPath().c_str(), // file to execute
(LPTSTR)wstrCmdLine.c_str(), // command line
NULL, // pointer to process SECURITY_ATTRIBUTES
NULL, // pointer to thread SECURITY_ATTRIBUTES
FALSE, // handles are not inheritable
dwCreationFlags, // creation flags
pEnv, // pointer to new environment block
NULL, // name of current directory
&si, // pointer to STARTUPINFO structure
&pi // receives information about new process
);
if (!bResult) {
OutputDebugStringW(
(std::wstring(L"error code:") + std::to_wstring(GetLastError()))
.c_str());
} else {
OutputDebugStringW((std::wstring(L"create as user ok:")).c_str());
}
if (pi.hProcess) {
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
if (hUserTokenDup != NULL) CloseHandle(hUserTokenDup);
if (hProcess != NULL) CloseHandle(hProcess);
if (hPToken != NULL) CloseHandle(hPToken);
if (pEnv != NULL) DestroyEnvironmentBlock(pEnv);
return TRUE;
}
int main(int argc, char *argv[])
{
bool result = TriggerAppExecute(parameter);
// 如果成功 直接返回,失败
if (result) {
OutputDebugStringW(L"降权失败,等待。。。。");
}
return 0;
}
上面降权有个前置条件就是用户必须是登录的状态。
举个例子:如果用户把机器开起来了。但是没有登录到系统中。此时由于拿不到当前的用户的token,所以会造成降权失败。
那该怎么办?
试想下如果用户没有登录,那么你的UI是不是没有必要显示。即使是显示了,也没有人看。
所以用户登录到系统中,此时再显示ui时机刚刚好。
那么问题来了?如何知道用户登录到了系统。
嘿嘿,windows中有个消息通知,我们可以注册回调函数。只要用户登录了,就会收到消息。此时,你在进行降权操作就没有问题。
给出实例代码,新建一个控制程序:
进程启动后,先把退出系统,再输入账号和密码,此时就会打印attention, user logon
消息,说明消息监听成功。
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
LPARAM lParam) {
switch (uMsg) {
case WM_WTSSESSION_CHANGE: {
// 处理用户登录和注销消息
switch (wParam) {
case WTS_SESSION_LOGON:
// 用户登录
printf("attention, user logon")
break;
case WTS_SESSION_LOGOFF:
// 用户注销
printf("attention, user logoff")
break;
}
} break;
}
// 调用默认窗口过程函数处理其他消息
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
int main() {
// 注册回调函数
bool result = WTSRegisterSessionNotification(GetConsoleWindow(),
NOTIFY_FOR_ALL_SESSIONS);
SetWindowLongPtr(GetConsoleWindow(), GWLP_WNDPROC, (LONG_PTR)WindowProc);
getchar();
// 取消消息监听
WTSUnRegisterSessionNotification(GetConsoleWindow());
return 0;
}
至此,计划任务遇到的问题也已经解决了。