本文共 7194 字,大约阅读时间需要 23 分钟。
最近项目上踩到一个坑,即偶现升级过程中通过计划任务调起新安装包,程序安装到了错误的地方,并且桌面快捷方式等入口均没有生成,总而言之就是一个“自杀”行为。
通过测试发现原因:在有些情况下,通过计划任务(通过服务也是如此)调起的进程是system权限的。而在system权限下进程可能会遇到很多问题:
- 通过注册表或expand 环境变量等方法得到的系统目录并不是我们想要的,例如通过SHGetSpecialFolderPath获取CSIDL_LOCAL_APPDATA的路径为C:\windows\system32\config\systemprofile\appdata\local;通过GetEnvironmentVariable获取TMP的路径为C:\Windows\TEMP等;这一类的目录包括且不限于:desktop, paograms, appdata, etc..
- 具有system权限的进程创建的子进程也是具有system权限的,这样子进程也会遇到上面第1点的问题
- GetEnvironmentVariable函数获取到的环境变量都是SYSTEM用户的
- 对HKEY_CURRENT_USER的部分注册表的写操作将会被重定向到HKEY_USERS.DEFAULT
正常的方法当然是通过通过权限相关的API来判断,当然也可以有一些小技巧来判断。例如上面说到的通过SHGetSpecialFolderPath获取CSIDL_LOCAL_APPDATA的路径,非system权限下为C:\Users\username\AppData\Local,而在system下为C:\windows\system32\config\systemprofile\appdata\local。因此,代码如下:
bool IsSystemPrivilegeImp(){ static bool isSystemPrivilege = false; if (isSystemPrivilege) { return isSystemPrivilege; } char szPath[MAX_PATH] = {0}; if (::SHGetSpecialFolderPathA(NULL, szPath, CSIDL_APPDATA, TRUE)) { std::string flag("config\\systemprofile"); std::string path(szPath); if (path.find(flag) != std::string::npos) { isSystemPrivilege = true; } } return isSystemPrivilege;}
想要获取的正确的目录,需要模拟当前登陆用户,获取登录用户的token,再调用SHGetSpecialFolderPath传入此token来获取。另外通过此token,还可以通过CreateProcessAsUser来创建登录用户权限的进程,这点下文再详述。
// 若执行成功,传出获取的tokenbool ImpersonateLoggedOnUserWrapper(HANDLE& hToken){ DWORD dwConsoleSessionId = WTSGetActiveConsoleSessionId(); if (WTSQueryUserToken(dwConsoleSessionId, &hToken)) { if (ImpersonateLoggedOnUser(hToken)) { return true; } } return false;}
通过上面获取的token传入SHGetSpecialFolderPath来获取CSIDL_LOCAL_APPDATA等正确的路径。网上资料说这个函数有时会失败,GetLastError返回5。而Windows提供的另外一个API叫做SHGetFolderPath,是可以正常获取路径的,因此我们使用后面这个API。
BOOL WINAPI SHGetSpecialFolderPathWrapper( HWND hwnd, LPWSTR lpszPath, int csidl, BOOL fCreate ){ BOOL ret = FALSE; do { if (false == IsSystemPrivilegeImp()) { if (SHGetSpecialFolderPath(hwnd, lpszPath, csidl, fCreate)) { ret = TRUE; break; } } HANDLE hToken = NULL; if (false == ImpersonateLoggedOnUserWrapper(hToken)) { break; } if (S_OK == SHGetFolderPath(NULL, csidl, hToken, SHGFP_TYPE_DEFAULT, lpszPath)) { ret = TRUE; //使用完毕之后通过调用RevertToSelf取消模拟 RevertToSelf(); break; } } while (0); return ret;}
我们的安装包中会创建很多子进程处理不同任务,而由于这些程序之前在编写代码过程中从没有考虑到system权限的问题,因此改动将会比较麻烦。因此我们的思路是:
在安装包被调起后,立即判断当前是否是system权限,如果是system权限,则以普通管理员用户的权限重新把自己调起,同时本身退出。这种方法改动相对就很少了。
话不多说,上代码:
#define TokenLinkedToken 19DWORD 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;}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 (_tcsicmp(procEntry.szExeFile, _T("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_HIDE; ZeroMemory( &pi, sizeof(pi) ); bResult = CreateProcessAsUser( hUserTokenDup, // client's access token NULL, // 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(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;}
【参考资料:
】转载地址:http://okloi.baihongyu.com/