要做注册表沙箱,就必须要了解部分注册表知识。而注册表的知识很多,本文主要讲述如何在win32系统是上识别注册表映射的。
在我的xp 32bit系统上,Win+R regedit之后打开注册表管理器。我们可以看到如下主键:HKEY_CLASSES_ROOT、HKEY_CURRENT_USER、HKEY_LOCAL_MACHINE、HKEY_USERS和HKEY_CURRENT_CONFIG。如果关注过注册表的同学可能发现过一个现象:修改HKEY_CURRENT_USER下某键项值为A,搜索A,可以搜索到1~3个结果,不仅值相同,其项的父键名等都一样。这种被“同步”的功能是不是很有意思。其实这个现象是因为HKEY_CURRENT_USER键是HKEY_USERS下某键的映射。同样的HKEY_CLASSES_ROOT和HKEY_CURRENT_CONFIG是HKEY_LOCAL_MACHINE下某键的映射。
如果Hook过NtOpenKey的同学可能发现过一个现象,我们参数中的注册表路径往往是\Registry\User\……或者\Registry\Machine\……的形式,而没有见过其他形式的路径。\Registry\User对应于HKEY_USERS,\Registry\Machine对应于HKEY_LOCAL_MACHINE。HKEY_CLASSES_ROOT 和HKEY_CURRENT_CONFIG对应的注册表也是很固定的,分别是\Registry\Machine\SOFTWARE\Classes和\Registry\Machine\CurrentControlSet\Hardware Profiles\Current。最捉摸不定的是HKEY_CURRENT_USER的真实路径,我在网上找了一种方法,该方法仅适用于win32系统,我验证过,该方法在win64系统上是不正确的。下面我用程序描述这种思路:
1 枚举所有ProfileList键下子键
BOOL CConvertRegPath::GetSIDOnWin32( ATL::CString & cstrSid )
{
BOOL bSuc = FALSE;
// 通过枚举HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList
// 下所有子键,确定哪个是SID
do {
HKEY hKey = NULL;
if ( ERROR_SUCCESS != RegOpenKey( HKEY_LOCAL_MACHINE, ProfileList, &hKey ) ) {
break;
}
WCHAR wszKey[MaxKeyName] = {0};
DWORD nIndex = 0;
DWORD dwLen = MaxKeyName;
ATL::CString cstrKeyPath;
ATL::CString cstrTmpSid;
while ( ERROR_SUCCESS == RegEnumKey( hKey, nIndex++, wszKey, dwLen ) ) {
cstrSid = wszKey;
// 拼接完整的注册表DOS路径
cstrKeyPath = ProfileList;
cstrKeyPath += L"\\";
cstrKeyPath += cstrSid;
cstrKeyPath += L"\\";
2 判断SID是否为当前用户的SID
if ( IsSidKey( cstrKeyPath ) && cstrSid.GetLength() > CurrentUserSidMinLength ) {
bSuc = TRUE;
break;
}
cstrSid.Empty();
dwLen = MaxKeyName;
wmemset( wszKey, 0, MaxKeyName );
}
其中IsSidKey函数的实现如下
BOOL CConvertRegPath::IsSidKey( const ATL::CString & cstrKeyPath )
{
// 该函数通过判断项RefCount值是否大于0来判断该项名是否是SID值
BOOL bSidKey = FALSE;
do {
DWORD dwRefCount = 0;
DWORD dwLength = sizeof(DWORD);
LONG lRes = RegQueryValueEx( HKEY_LOCAL_MACHINE, cstrKeyPath.GetString(), NULL, NULL, (LPBYTE)&dwRefCount, &dwLength );
// 检查项值是否大于0
if ( dwRefCount > 0 ){
bSidKey = TRUE;
}
} while (0);
return bSidKey;
}
该函数就是判断诸如HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\S-1-5-21-73586283-2049760794-839522115-1003键下项RefCount的值是否大于0(一般为1)。其实符合这样的键可能不止一个,比如本机上HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\S-1-5-18键的RefCount就是1。于是就又要加个判断,就是键名长度要大于一定的长度(我定义为20)。
2012-6-11 追加
今天看了别人转载SUDAMI的一篇关于获取SID的方法,个人觉得那个方法比以上经验之谈要靠谱,故贴出他的代码,也没找到他博客的地址,就不列出他博文地址了。
int GetUserName ()
{
HANDLE hProcess = GetCurrentProcess();
if(!hProcess) {
return 0;
}
HANDLE hToken;
if( !OpenProcessToken(hProcess, TOKEN_QUERY, &hToken) || !hToken ){
CloseHandle(hProcess);
return 0;
}
DWORD dwTemp = 0;
char tagTokenInfoBuf[256] = {0};
PTOKEN_USER tagTokenInfo = (PTOKEN_USER)tagTokenInfoBuf;
if( !GetTokenInformation( hToken, TokenUser, tagTokenInfoBuf, sizeof(tagTokenInfoBuf), &dwTemp ) ) {
CloseHandle(hToken);
CloseHandle(hProcess);
return 0;
}
typedef BOOL (WINAPI* PtrConvertSidToStringSid)(
PSID Sid,
LPTSTR* StringSid );
PtrConvertSidToStringSid dwPtr = (PtrConvertSidToStringSid)GetProcAddress(
GetModuleHandle(L"Advapi32.dll"), "ConvertSidToStringSidA" );
LPTSTR MySid = NULL;
dwPtr( tagTokenInfo->User.Sid, (LPTSTR*)&MySid );
printf("sudami's PC Name:\n%s\n", MySid);
getchar ();
LocalFree( (HLOCAL)MySid );
CloseHandle(hToken);
CloseHandle(hProcess);
return 0;
}
在内核里有个函数RtlFormatCurrentUserKeyPath也可以获得SID,在Ntdll中也可以导出这个函数。我做了下实验,发现在Ring3不能直接使用该函数获取SID,因为会报错
错误原因应该很明显了,这个函数内部应该要访问系统空间地址(0x7FFFFFFF以上)上的地址,于是就C0000005了。