Windows下的服务程序(S程序)都是以SYSTEM权限启动的,通过服务程序启动的程序(N程序)自然也会是SYSTEM权限的,而如果开发N的时候没有考虑到SYSTEM权限这种情况,那么有可能N就无法正常的运行于SYSTEM权限下。
场景:客户需要在服务下运行我的程序,在这样的情况下我的程序崩溃了:(为了方便调试,我给程序添加了崩溃转储功能,把DUMP文件拿回来之后用Windbg调试了一下,发现是由于在SYSTEM权限下通过环境变量获取的一些内容发生变化了,通过部分Windows API获取的内容也变了,还有注册表的部分读写也被重定向了,由于我的程序中的判断不够严格,导致了程序Crash了。
这时候我想到是服务程序在运行我的程序的时候,要先模拟成当前的登录用户,然后通过CreateProcessAsUser来启动我的程序,那样问题很好解决,我甚至不需要做任何修改即可。但是作为一个小小的开发我都没有接触到客户的机会,这其中的沟通成本太高了(可能客户的服务程序也是买来的,连源码都没有,就无从改起了),经过和XX几次交流后还得我自己改。
1. SYSTEM权限引发的问题
1. 针对HKEY_CURRENT_USER的部分注册表写操作被重定向到HKEY_USERS\.DEFAULT下面去了;
2. GetEnvironmentVariable函数获取到的环境变量都是SYSTEM用户的;
3. SHGetSpecialFolderPath函数获取到的许多路径也都是SYSTEM用户的;
4. 通过CreateProcess创建的子进程都是SYSTEM权限的;
比如通过SHGetSpecialFolderPath获取CSIDL_LOCAL_APPDATA的路径为C:\windows\system32\config\systemprofile\appdata\local;通过GetEnvironmentVariable获取TMP的路径为C:\Windows\TEMP等;
2. 判断是否是SYSTEM权限
应该是能够通过权限的API去判断的,不过这里我用了一个简单的方法,直接判断路径是否是正常的:
bool IsSystemPrivilege() { char szPath[MAX_PATH] = {0}; if (SHGetSpecialFolderPath(NULL, szPath, CSIDL_APPDATA, TRUE)) { std::string flag("config\\systemprofile"); std::string path(szPath); if (path.find(flag) != std::string::npos) { return true; } } return false; } |
3. 模拟当前登陆用户
模拟当前登陆用户,可以解决SHGetSpecialFolderPath的问题,另外保存Token,可以通过CreateProcessAsUser来创建登录用户权限的进程。
bool MyImpersonateLoggedOnUser() { HANDLE hToken = NULL; DWORD dwConsoleSessionId = WTSGetActiveConsoleSessionId(); if (WTSQueryUserToken(dwConsoleSessionId, &hToken)) { if (ImpersonateLoggedOnUser(hToken)) { // 保存Token return true; } } return false; } // 使用完毕之后通过调用RevertToSelf取消模拟 |
4. 环境变量
比如说我要获取临时目录的路径,因为环境变量是和进程本身相关的,所以通过GetEnvironmentVariable是不行的。这里先读取注册表HKEY_USERS\.DEFAULT\Environment下的TMP/TEMP的内容,得到:
%USERPROFILE%\AppData\Local\Temp |
然后通过调用GetUserProfileDirectory来获取USERPROFILE的内容,注意要传入上面的Token。
当然还有一些用户相关的环境变量是这里没有的,这时候就要枚举用户的SID,然后读取HKEY_USERS的子键来获取了。
5. 其他问题
这个时候子进程可以正常的跑起来了。但是在我的实际操作中还遇到一点点小问题。我的子进程也会调用SHGetSpecialFolderPath来获取CSIDL_LOCAL_APPDATA的路径,但是这个时候居然失败了,GetLastError返回5。
这个地方还是相当疑惑的,首先,普通的权限调用SHGetSpecialFolderPath来获取CSIDL_LOCAL_APPDATA的路径是OK的;另外通过服务调用CreateProcessAsUser启动的程序,调用SHGetSpecialFolderPath来获取CSIDL_PROGRAM_FILES也是可以的,偏偏获取CSIDL_LOCAL_APPDATA就不行了,这个地方实在是搞不明白了,网上也没找到原因。
好在Windows还提供了一个叫做SHGetFolderPath的API,是可以正常获取路径的,所以我做了一个简单的包装,这样不用改动太多的东西,而且不影响原来的稳定性。
// ============================================================================ // SHGetSpecialFolderPath形式的接口,调用失败时再尝试通过SHGetFolderPath函数实现 // ============================================================================ BOOL WINAPI SHGetSpecialFolderPathWrapper( HWND hwndOwner, LPSTR lpszPath, int nFolder, BOOL fCreate) { if (SHGetSpecialFolderPath(hwndOwner, lpszPath, nFolder, fCreate)) { return TRUE; } if (S_OK == SHGetFolderPath(NULL, nFolder, NULL, SHGFP_TYPE_DEFAULT, lpszPath)) { return TRUE; } return FALSE; } |
MSDN中提到了SHGetSpecialFolderPath is not supported. Instead, use ShGetFolderPath. 不过也提到ShGetFolderPath是Deprecated的,推荐使用SHGetKnownFolderPath。
刚刚从上海听完xKungFoo回来,周末两天加班到深夜,实在是……好在到这里总算是把这些纠结的问题解决了:)
本文地址: 程序人生 >> SYSTEM权限引发的系列问题
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!