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