很多时候使用注册表操作是不安全的,因此很多重要的场合是不会使用注册表操作去达到目的的。但不可否认在很多小的地方,使用注册表操作是十分方便而且直接有效的。有些内容的注册表操作时常会涉及到当前用户的操作,在注册表中就体现在了HKEY_CURRENT_USER的子项上(系统通常情况下只具有如下HKEY_CLASSES_ROOT,HKEY_CURRENT_USER,HKEY_LOCAL_MACHINE,HKEY_USERS,HKEY_CURRENT_CONFIG五个子项)。
当我们需要进行注册表操作的应用程序以进程方式运行的时候,是不用担心注册表操作失效的问题的。但是一旦应用程序以服务的方式启动,那么其针对HKEY_CURRENT_USER下的子项操作就会出现问题,基本体现是功能失效,无法操作。这是因为应用程序以服务方式运行时无法针对某一个当前用户操作。当然,如果这个注册表操作在HKEY_LOCAL_MACHINE项下可以完成,并能达到预期目的,我们就没有必要大费周章了。
要使服务运行状态下的应用程序通过注册表操作影响当前用户的状态,就必须在操作之前进行当前用户的虚拟。一般来说我们可以通过如下两个函数就可以实现了:
// 获得Explorer进程的PID
DWORD GetExplorerPID ()
{
HANDLE hProcessSnap = NULL;
DWORD dwRet = 0;
PROCESSENTRY32 pe32 = {0};
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE)
return 0;
pe32.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hProcessSnap, &pe32))
{
DWORD dwPriorityClass;
BOOL bGotModule = FALSE;
MODULEENTRY32 me32 = {0};
do
{
if( _stricmp(pe32.szExeFile, "Explorer.EXE") == 0)
return pe32.th32ProcessID;
}
while (Process32Next(hProcessSnap, &pe32));
}
else
dwRet = 0;
CloseHandle (hProcessSnap);
return (dwRet);
}
// 虚拟当前用户
BOOL ImpersonateCurrentUser()
{ BOOL bRet;
HANDLE ph;
DWORD ExplorerPID = GetExplorerPID();
if(ExplorerPID == 0)
{ return 0; }
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ExplorerPID);
if(!ph)
{ return 0; }
HANDLE token = 0;
bRet = OpenProcessToken(ph, TOKEN_QUERY|TOKEN_DUPLICATE, &token);
if(!bRet)
{ return 0; }
bRet = ImpersonateLoggedOnUser(token);
return bRet;
}
其中的api函数可能需要包含一些头文件,比如tlhelp32.h和windows.h,如何还需要其他的可以自己去msdn上查;到了这一步虚拟当前用户算是成功了。不过ImpersonateCurrentUser这个函数大家可以发现,存在句柄没有释放,当然也不能释放,如果释放,虚拟就没有作用。但是一旦这种虚拟当前用户在轮询中出现,问题就出来了,会使应用程序产生很多的句柄并且拽着不放。
出现这种情况的话,我们可以改进ImpersonateCurrentUser函数,在应用程序进行注册表操作之前先建立一个句柄,传进ImpersonateCurrentUser函数中,当注册表操作完成以后,再释放。注意一点,释放也就是杀掉句柄以后,不要忘记使用RevertToSelf(); 函数返回当前用户
BOOL ImpersonateCurrentUser(HANDLE &htoken)
{
BOOL bRet;
HANDLE ph;
DWORD ExplorerPID = GetExplorerPID();
if(ExplorerPID == 0)
{
return 0;
}
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ExplorerPID);
if(!ph)
{
return 0;
}
bRet = OpenProcessToken(ph, TOKEN_QUERY|TOKEN_DUPLICATE, &htoken);
if(!bRet)
{
return 0;
}
bRet = ImpersonateLoggedOnUser(htoken);
if(!bRet)
{
return 0;
}
CloseHandle(ph);
return bRet;
}
好像到了这一步就没有问题了,其实不然,通过这些一般来说是可以了,但是有时候还是出现问题,注册表操作无效,主要还是应用程序以服务方式运行时找当前用户的问题,也就是说到这一步还是不能确保正确。
要如何做才可以呢?在注册表中有这么一个根项HKEY_USERS,它是用来管理系统所有用户的,它的每一个子项都对应系统的一个用户,这些子项名称可以看作时那些用户的suid号。而HKEY_USERS项是服务可以直接作用的,如果我们能找到当前用户的suid,然后通过修改HKEY_USERS下该suid号下面对应的注册表项来达到本来的目的。因为HKEY_USERS下当前用户suid号对应的子项的注册表项和HKEY_CURRENT_USER是完全相同并实时更新的,改动其中一个就必然改动了另一个,那么解决方法就出来了。
BOOL ImpersonateCurrentUser(std::string &sid)
{
HANDLE handle;
char namebuf[MAX_PATH];
DWORD buflen = MAX_PATH;
char usid[MAX_PATH];
char domain[MAX_PATH];
DWORD usidlen = MAX_PATH;
DWORD udomainsidlen = MAX_PATH;
BOOL ret = UT::Tools::ImpersonateCurrentUser(handle);//首先模拟当前用户身份
if(!ret)
{
return FALSE;
}
ret = GetUserName(namebuf, &buflen);
if(!ret)
return FALSE;
SID_NAME_USE pnu;
ret = LookupAccountName(NULL, namebuf, (SID *)usid, &usidlen, domain, &udomainsidlen, &pnu);
if(!ret)
return FALSE;
char *usidstr;
ret = ConvertSidToStringSidA((SID *)usid, &usidstr);
if(!ret)
return FALSE;
sid = usidstr;
LocalFree(usidstr);
CloseHandle(handle);
RevertToSelf(); //返回原来的用户
return TRUE;
}
通过这个函数我们可以返回当前用户的suid号,再通过操作HKEY_USERS和这个suid号下对应的注册表项就可以达到我们的目的了。其实就相当于来说把原来的注册表操作中的HKEY_CURRENT_USER给替换成HKEY_USERS+suid。 注意,这个函数里要使用到头文件Sddl.h。
还是举个例子来说吧,在进程中操作注册表项HKEY_CURRENT_USER/Software/Kingsoft/Office/WPS/Addins/KSAddins.XDictHelper,我们就需要在服务中使用下面的键值了(HKEY_USERS+suid)/Software/Kingsoft/Office/WPS/Addins/KSAddins.XDictHelper。
通过产品的使用验证,发现以上还有有问题的。问题就在用户上,获取sid号是通过用户获取的对应的id串。但是如果用户获取的不正确呢。产品问题中暴露出来的一个问题。当一个域中某个pc,拥有同当前域用户相同名称的本地账户的时候问题就出现了。比如,域中计算机A使用域账户Test登陆,同时呢,计算机A中也有一个名叫Test的本地账户。如果还是按照上面的做法的话,看起来是没有任何错误的。但是使用起来就会发现HKEY_USERS+SUID是不存在的,为什么呢,经过调试发现,GetUserName的时候是没有错误的,取出来的用户名确实是Test,但是在LookupAccountName的时候就出现错误了。因为根据msdn的说法,其获取suid的顺序是先本地账户查找,如果查找不到就到信任的域中查找。明白了,原来当登陆域用户的时候,LookupAccountName查找suid的时候错误了。那么怎么解决呢。简单,我们在取当前用户的时候取出当前计算机登陆的域。如果可以取出,则在LookupAccountName的时候传入的用户名就不能只是username了,而应该是domainname/username。如果取出域名为空,则继续使用username来取suid了。注意哦,我根据使用的Api函数包含适当的头文件以及lib文件,Netapi32.lib和Lm.h哦 .......下面是更改后的代码:
2010-03-05
BOOL DLLMODULENAME_LIB_API ImpersonateCurrentUser(std::string &sid)
{
HANDLE handle;
char namebuf[MAX_PATH];
DWORD buflen = MAX_PATH;
char usid[MAX_PATH];
char domain[MAX_PATH];
DWORD usidlen = MAX_PATH;
DWORD udomainsidlen = MAX_PATH;
BOOL ret = UT::Tools::ImpersonateCurrentUser(handle);//首先模拟当前用户身份
if(!ret)
{
return FALSE;
}
WKSTA_USER_INFO_1* workstationInfo;
const NET_API_STATUS status = NetWkstaUserGetInfo(NULL,1,(LPBYTE*)&workstationInfo);
if(status == NERR_Success)
{
std::wstring domainname = workstationInfo->wkui1_logon_domain;
std::wstring username = workstationInfo->wkui1_username;
NetApiBufferFree((LPBYTE*)&workstationInfo);
std::string namedomain = UT::Tools::UTStringConvert::ToAString(domainname);
std::string nameuser = UT::Tools::UTStringConvert::ToAString(username);
std::string newName = nameuser;
if(!namedomain.empty())
{
newName = namedomain;
newName += "//";
newName += nameuser;
}
nameuser = newName;
strcpy_s(domain,MAX_PATH,namedomain.c_str());
strcpy_s(namebuf,MAX_PATH,nameuser.c_str());
}
else
{
ret = GetUserName(namebuf, &buflen);
if(!ret)
{
return FALSE;
}
}
SID_NAME_USE pnu;
ret = LookupAccountName(NULL, namebuf, (SID *)usid, &usidlen, domain, &udomainsidlen, &pnu);
if(!ret)
{
return FALSE;
}
char *usidstr;
ret = ConvertSidToStringSidA((SID *)usid, &usidstr);
if(!ret)
{
return FALSE;
}
sid = usidstr;
LocalFree(usidstr);
CloseHandle(handle);
RevertToSelf();
return TRUE;
}