? ? ? ? 最近在工作中碰到了,某个进程在SYSTEM权限下无法读取到HKEY_CURRENT_USER注册表的问题。所以写一下文章记录下排查和处理的过程。
????????环境比较奇怪,是WIN7下的USER相关权限的账号才会出现(但最终发现WIN10下也会出现)。
????????于是用VMBOX弄了WIN7的虚拟机进行调试,在进程读取注册表前的代码使用MESSAGEBOX作人为的断点。当进程运行到相关位置时,出现弹窗。然后使用VS的REMOTEDEBUGGER进行远程调试。发现注册表路径没什么问题, 但是的确读取不到相关信息。RegQueryValueEx函数返回错误码ERROR_FILE_NOT_FOUND。
? ? ? ? 这就很奇怪了,明明注册表就存在。怀疑是SYSTEM权限的问题,于是google了下相关关键字。看到stackoverflow的相同问题下有人推荐用LoadUserProfile试试。windows - My C++ application running under SYSTEM is unable to create new values or keys in the HKEY_CURRENT_USER registry how can I make it write these values? - Stack Overflow
? ? ? ? 然后根据信息,推测,HKEY_CURRENT_USER应该跟账号有关。也就是说,当进程在SYSTEM权限下时,读取到的HKEY_CURRENT_USER应该是SYSTEM的,而不是当前账号的。面向高级用户的 Windows 注册表 - Windows Server | Microsoft Learn
????????那么处理方式就变为,如何让SYSTEM权限的进程,读取到当前账号下的HKEY_CURRENT_USER了。在网上找了下,发现了两个处理方法
????????我是选择了方法二去试试,下面也是方法二示例的代码。方法一理论上是可以的,但我没有去试。
#include <iostream>
#include <memory>
#include <Windows.h>
#include <WtsApi32.h>
#include <UserEnv.h>
#include <tchar.h>
#pragma comment(lib, "Wtsapi32.lib")
#pragma comment(lib, "Userenv.lib")
int main()
{
HANDLE hToken = NULL;
bool bImpersonated = false;
do
{
if (WTSQueryUserToken(WTSGetActiveConsoleSessionId(), &hToken) == FALSE)
{
std::cerr << "WTSQueryUserToken fail, GetLastError " << GetLastError() << "\n";
break;
}
if (ImpersonateLoggedOnUser(hToken) == FALSE)
{
std::cerr << "ImpersonateLoggedOnUser fail, GetLastError " << GetLastError() << "\n";
break;
}
bImpersonated = true;
DWORD nameLen = MAX_PATH;
TCHAR userName[MAX_PATH + 1] = { 0 };
if (GetUserName(userName, &nameLen) == FALSE)
{
std::cerr << "GetUserName fail, GetLastError " << GetLastError() << "\n";
break;
}
PROFILEINFO profileInfo;
memset(&profileInfo, 0, sizeof(PROFILEINFO));
profileInfo.dwSize = sizeof(PROFILEINFO);
profileInfo.lpUserName = userName;
profileInfo.dwFlags = PI_NOUI;
if (LoadUserProfile(hToken, &profileInfo) == FALSE)
{
std::cerr << "LoadUserProfile fail, GetLastError " << GetLastError() << "\n";
break;
}
// 这里读取注册表
HKEY hKey = NULL;
if (RegOpenKey(reinterpret_cast<HKEY>(profileInfo.hProfile),
_T("SOFTWARE\\Baidu\\BaiduYunGuanjia"),
&hKey) == ERROR_SUCCESS)
{
DWORD dataLen = 0;
LPCTSTR regName = _T("installDir");
if (RegQueryValueEx(hKey, regName, NULL, NULL, NULL, &dataLen) == ERROR_SUCCESS)
{
auto dataBuf = std::make_unique<BYTE[]>(dataLen + 1);
if (RegQueryValueEx(hKey, regName, NULL, NULL, dataBuf.get(), &dataLen) == ERROR_SUCCESS)
{
std::wcout << reinterpret_cast<LPCWSTR>(dataBuf.get()) << L"\n";
}
}
RegCloseKey(hKey);
hKey = NULL;
}
UnloadUserProfile(hToken, profileInfo.hProfile);
} while (false);
if (bImpersonated)
{
RevertToSelf();
bImpersonated = false;
}
if (hToken != NULL)
{
CloseHandle(hToken);
hToken = NULL;
}
return 0;
}
? ? ? ? 当然我最终还是没有使用上面的相关代码处理,虽然实测过可以。
????????获取注册表的代码的作用是判断注册表的值是否可以执行后续的代码,那么,将相关的代码移动到提权前处理也行,比上面的处理方式要简单点。所以最后我是移动代码逻辑来处理的。