危险漫步博客
有时候,正是那些意想不到之人,成就了无人能成之事。
文章1443 浏览13839468

Windows7注册表之SAM文件取证分析

Windows注册表包含大量的键值,而且这些键值可能被用户修改过,犯罪嫌疑犯也可能借用它隐藏一些隐私,如用户密码。故,一些注册表键值在许多取证分析中都要被调查,它们并无必要对应一个给定领域,但是它们和大量计算机调查相关。这些键值包括基本的系统信息(谁使用了该系统,安装了什么应用程序)和关键系统领域的更详细信息(安装了哪些硬件,装载了哪些驱动器)等,如运行过的程序、访问过的网站、编辑过的文档等。这些信息可以为取证分析提供一定的帮助,而且容易获取,因为都是明文显示即通过注册表API函数RegOpenKeyEx、RegEnumKeyEx、RegQueryValueEx即可获取。读者若对这些关键值路径感兴趣,可参考《InitialCaseAnalysisusingWindowsRegistryinComputerForensics》。

本文重点论述注册表中隐藏的关键信息即用户ID和密码。

若系统事先设有密码,则用户输入密码进行系统之前,windows会对此明文密码采用MD4算法进行散列,然后将散列值与系统中保存的HASH值进行比对。这些MD4散列值即存放在安全账户管理器SAM文件内。尽管SAM文件在最高权限下都无法访问,但所有的信息都存放在注册表内,故完全可以在SYSTEM权限下,通过调用注册表API函数获取到这些信息。本文是在参考VincentRoch在codeproject上发表的文章“RetrievetheWindows7PasswordHashontheFly”、DustinHurlbut的文章“ForensicDeterminationofaUser’sLogon的基础上并结合自己的理解分析完成的,探讨windows下SAM文件用户密码的破解分析。

1.SAM结构分析

SAM结构中包含两内hash分别为:LanManhash和NThash,然而从Vista版本开始,因为LMhash的安全性略低于后者,故已经基本上不再使用了。在注册表中用户信息都存放在HKLM\SAM\SAM\Domains\Account\users\userRID下,其下的子键即每个账号的SID相对标志符。每个账户下包含两个子项,F和V。V值包含了用户名、以及散列后的用户密码值等。

它实际上可以看作一个微型文件系统,包含多个指针即指向一个可变长度的结构体。每个指针结构为一个12字节值,例如第2个12字节结构体,前4字节即用户数据起始相对偏移值(需要加上偏移量0xCC),中间4字节表示用户名的长度,后4字节保留未用。如图1所示,0xBC+0xCC=188,即偏移188处即为用户名。

图1.SAM文件中Adminitrator用户的SID结构

而且,图1中的12字节结构体中的第二个4字节包含特殊含义,即0xBC000000—ADMINISTRATIVEUSER0xD4000000USER—ONLYPRIVILEGELEVEL即受限制的用户权限0xB0000000—GUESTACCOUNT

“F”键值中保存的是一些登录记录,如上次登录时间、错误登录次数等。从上面的分析,可知用户密码保存在SAM文件中,虽然系统默认下config文件受保护,但用户信息全部存在在注册表内,故这里我们可以在SCM(服务控制管理器)内注册一个系统服务实现SAM文件取证。

2.创建新服务实现SAM文件取证

当一个服务控制程序请求开启一个新的服务时,SCM开启服务的同时,向控制调度器发送一个开始请求。控制调度器为服务创建一个新的线程来运行ServiceMain函数。代码如下:


intAPIENTRYWinMain(HINSTANCEhProg,HINSTANCEhPrevInstance,LPSTRlpCmdLine,
intnCmdShow){
/*SERVICE_TABLE_ENTRY结构类型的数组,设置调用进程所提供的GetSAM服务的入口函数及字符串名*/
SERVICE_TABLE_ENTRYDispatchTable[]=
{
{GetSAM,(LPSERVICE_MAIN_FUNCTION)GetSAMServiceMain},
{NULL,NULL}
};
//连接程序主线程到服务控制管理程序
StartServiceCtrlDispatcher(DispatchTable);
}
DWORDWINAPIHandlerFunction(DWORDdwControl,DWORDdwEventType,LPVOID
lpEventData,LPVOIDlpContext){
SERVICE_STATUSServiceStatus;
if(dwControl==SERVICE_CONTROL_SHUTDOWN||
dwControl==SERVICE_CONTROL_STOP){
ServiceStatus.dwServiceType=SERVICE_WIN32_OWN_PROCESS;
ServiceStatus.dwCurrentState=SERVICE_STOPPED;
SetServiceStatus(hServiceStatus,ServiceStatus);
}
return(0);
}
voidGetSAMServiceMain(DWORDdwArgc,LPTSTR*lpszArgv){
SERVICE_STATUSServiceStatus;
//注册一个处理控制请求的HandlerFunction函数
hServiceStatus=RegisterServiceCtrlHandlerEx(GetSAM,
(LPHANDLER_FUNCTION_EX)HandlerFunction,NULL);
ZeroMemory(ServiceStatus,sizeof(SERVICE_STATUS));
ServiceStatus.dwServiceType=SERVICE_WIN32_OWN_PROCESS;
ServiceStatus.dwCurrentState=SERVICE_RUNNING;
ServiceStatus.dwControlsAccepted=SERVICE_ACCEPT_SHUTDOWN|
SERVICE_ACCEPT_STOP;
//调用SetServiceStatus函数来报告当前状态为RUNNING态
SetServiceStatus(hServiceStatus,ServiceStatus);
//后台服务程序开始分析SAM文件
RegistryEnumerateSAM();
}


通过自建的后台服务,我们具备了分析SAM文件的权限。但密码存放在用户对应的SID的V键下却是以一种密文形式存放。Windows2000版本之后,V键值内容似乎可结合用户ID下的某个键值作为key输入到DES算法中解密。事情变得却是比较诡秘,微软通过HKLM\SYSTEM\CurrentControlSet\Control\Lsa\{JD,Skew1,GBG,Data}四个键值的CLASS值,结合一个置换表换位得来。获取syskey后,仍然存在两大障碍需要解决。首先我们需要计算syskey的散列值,即使用syskey和F键的前0x10字节,与特殊的字符串

!@#$%^*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@%,0123456789012345678901234567890123456789做MD5运算,再与F键的后0x20字节做RC4运算得到一个解密密钥dkey即解密SAM中加密数据到散列态。当需要解密SAM中加密数据到散列时,需要将此dkey与当前用户的SID、散列类型名NTPASSWORD做MD5运算获取针对此用户密码散列的会话密钥skey。利用skey作为RC4算法的解密密钥对散列进行处理,然后将用户SID扩展为14字节做DES切分,生成DESECB,再对RC4处理后的散列进行分开2次DES解密即可得到密码散列。整个处理流程十分诡异。最后将用户SID值作为salt,可确保不同用户的相同散列在SAM中存放不一样。

3.核心代码分析

在获取到每个HKLM\SAM\SAM\Domains\Account\users\UserID之后,我们需要对每个用户的SAM进行分析。首先读取SAM文件中的V键值对应的内容,其包含了Unicode形式表示的用户名、字符串长度以及NThash的加密版本。


EnumerateRegValues(hSAM,V,VBlock,VBlockLen);//获取NThash的密文长度
dwNTHashLen=VBlock[0xAC]+0x100*VBlock[0xAD]-4;//获取用户名长度
dwUserNameLen=VBlock[0x10]+0x100*VBlock[0x1a];
for(i=0;idwNTHashLen;i++){//获取NThash密文
nthash[i]=VBlock[VBlock[0xa8]+VBlock[0xa9]*0x100+4+0xcc+i];
}
for(i=0;idwUserNameLen/2;i++){//获取用户名
UserNameA[i]=VBlock[VBlock[0xc]+VBlock[0xd]*0x100+0xcc+2*i];
}


前面我们也讲到了如何获取Syskey值,它由四个不同的键码值信息组成,每个信息均存放在键值的class属性内。


#defineREGISTRY_LSAJDSYSTEM\\CurrentControlSet\\Control\\Lsa\\JD
#defineREGISTRY_LSASKEWSYSTEM\\CurrentControlSet\\Control\\Lsa\\Skew1
#defineREGISTRY_LSAGBGSYSTEM\\CurrentControlSet\\Control\\Lsa\\GBG
#defineREGISTRY_LSADATASYSTEM\\CurrentControlSet\\Control\\Lsa\\Data//获取这些键值对应的class属性内容
dwError=RegQueryInfoKey(hKey,Class,dwcClass,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
for(i=0;i4;i++){
temp[0]=Class[2*i];
temp[1]=Class[2*i+1];//将ASCII转换为16进制数
RegistryKeyValue[i]=strtol(temp,stop,16);
}
for(i=0;i4;i++){
/*依次从JD,Skew1,GBG,Data开始,分别获取bootkey[0-3],bootkey[4-7],
bootkey[8-11],bootkey[12-15]值*/
bootkey[i]=RegistryKeyValue[i];
}
//对bootkey进行P置换得到syskey
for(i=0;i16;i++)syskey[i]=bootkey[p[i]];


至此,我们已经获取到了syskey值。下面就是繁琐的解密过程了,代码就不在给出了。

按着前面分析的解密流程,即可实现。

4.小结

Windows注册表容纳了所有应用程序和计算机系统的全部配置信息、系统和应用程序的初始化信息、应用程序和文档文件的关联关系、硬件设备的说明、状态和属性以及各种状态信息和数据。对于计算机取证分析员而言,注册表的分析是至关重要一环。