这篇文章收录于《取证实录》第四季中。
注册表隐藏技术
通常用于恶意软件、后门程序或攻击者企图在系统中保持隐蔽,绕过安全检查和防御系统。
常见的隐藏技术有:使用非法字符隐藏注册表项(如PlugX、TDL4/Alureon(Rootkit))、利用默认键值(空字符串)隐藏(如Adwind RAT)、使用深度嵌套的注册表项(如Zlob (DNSChanger)、Rovnix (Rootkit))、重命名标准注册表项(如TrickBot、Dridex)、注册表反射技术(如Zeus(Zbot))、通过注册表权限进行隐藏(如Carberp)、使用模糊键名或随机化键值(如Qakbot、Ursnif)、滥用镜像注册表键(如Rovnix)、注册表软链接(如Necurs(Rootkit)、Max++(Rootkit))、滥用组策略(GPO)注册表项等。
这里,我们通过研究源代码来了解其中的两项技术,通过代码探究它背后的细节,达到知已知彼。
一、 创建Unicode空字节
空字节,就是“0x0000
”,就是“\0\0
”。对于Windows系统,””
(0x0000)会被识别为字符串的结束符,所以在使用Regedit对该字符串读取的过程中,遇到开头的””
,会被解析成结束符,提前截断,导致读取错误。
以SharpHide开源工具为例,“一个很好的后门持久性技巧,用来混淆DFIR调查。使用NtSetValueKey本机API创建隐藏(以空结尾)注册表项。这是通过在UNICODE_STRING键值名称前面添加一个空字节来实现的”。
程序运行后,显示有三个功能:在Run下创建一个键值:“keyvalue=bla.exe”或带参数的“keyvalue=bla.exe argument=arg1 arg2”,以及delete该键。
当程序以sharphide action=create keyvalue="c:\windows\temp\bla.exe"运行时创建成功了键名为“Software\”+键值为“c:\windows\temp\bla.exe”的注册表项。
用regedit却看不到它的内容并报错,阻止查看;分别对应着HKCU和HKLM的一般用户和管理员权限,均在\Software\Mircosoft\Windows\CurrentVersion\Run。
1.1 源码部分:
UIntPtr regKeyHandle = UIntPtr.Zero;string runKeyPath = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run";string runKeyPathTrick = "\0\0SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run";uint Status = 0xc0000000;uint STATUS_SUCCESS = 0x00000000;if (IsSystem || IsElevated){Console.WriteLine("\n[+] SharpHide running as elevated user:\r\n Using HKLM\\{0}", runKeyPath);Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, runKeyPath, 0, KEY_SET_VALUE, out regKeyHandle);}else{Console.WriteLine("\n[+] SharpHide running as normal user:\r\n Using HKCU\\{0}", runKeyPath);Status = RegOpenKeyEx(HKEY_CURRENT_USER, runKeyPath, 0, KEY_SET_VALUE, out regKeyHandle);}UNICODE_STRING ValueName = new UNICODE_STRING(runKeyPathTrick){Length = 2 * 11,MaximumLength = 0};IntPtr ValueNamePtr = StructureToPtr(ValueName);UNICODE_STRING ValueData;if (arguments.ContainsKey("arguments")){ValueData = new UNICODE_STRING("\"" + arguments["keyvalue"] + "\" " + arguments["arguments"]);}else{ValueData = new UNICODE_STRING("\"" + arguments["keyvalue"] + "\"");}Status =NtSetValueKey(regKeyHandle, ValueNamePtr, 0, RegistryKeyType.REG_SZ, ValueData.buffer, ValueData.MaximumLength);
注意一
:runKeyPathTrick = “\0\0SOFTWARE\”,这里有个\0\0,经过unicode后就成为了“0x0000”,这在windows里是个截断字符,后面的内容就丢弃,成为了隐藏部分,不显示。但它的值“c:\windows\temp\bla.exe”还是存在的,写了一个显示窗体的bla.exe放在temp目录下,重启后,这个bla运行了,说明这个程序确实在注册表中的启动项了。
注意二
:NtSetValueKey是个WIndows Native API,对注册表进行创建、修改和删除等操作;这些操作,单纯的使用 Regedit 查询是查询不到的。
1.2 Native API
1、以Nt开头的是Native API,而不是Windows API,两者有差别。
-
如果使用 Native API 则导入 ntdll.dll;
-
如果使用 Win32 API 则导入 advapi32.dll;
2、实现的根本原因:
在Win32 API中,以 NULL结尾的字符串被解释为 ANSI(8位)或宽字符(16位)字符串。在 Native API中,以 NULL结尾的字符串被解释为 Unicode(16位)字符串。尽管平时这个区别并不重要,但是却带来了一个有趣的情况,即当使用 Native API来构造特别的名称时,不能使用Win32 API来对其进行查询。这是因为作为计数的Unicode字符串的名称可以包含NULL 字符(0),例如“key”,这个Unicode字符串长度为4,但是在使用 Win32 API来进行查询,这是因为在Win32 API中,“key”字符串的长度为3,不满足查询条件。
Regedit看不到的原因是因为Regedit使用的是Win32 API;用户模式调用本机系统服务是通过ntdll.dll来实现的。表面上,Win32 函数为编程人员提供了大量的API接口来实现功能,但这些Win32 函数只不过是一个API接口的容器而已,它将Native API包装起来,通过系统服务来实现真正的功能,也就是ntdll.dll是系统调用接口在用户模式下一个外壳。
1.3 工具验证
既然用regedit看不了,
1、那我们用autoruns呢?截图看下:
结果是:发现不了。
2、用wke导出hive文件,截图看下:
用regedit explorer查看,
结果是:发现了空字节。
3、在注册表中直接查询;如果知道名字,用RegScanner查看,
工具读取的是hive文件,所以可以读取到,看下图:
二、无文件的注册表存储二进制
上面的隐藏技术是针对ValueName做的处理,下面介绍的Fileless Malware技术是针对ValueData的内容进行处理。
无文件的注册表存储二进制数据
是一种更加隐蔽和持久的恶意软件存储方式,通常不在文件系统中存储任何可疑的文件,而是将恶意的二进制数据或代码隐藏在Windows注册表中。这种技术旨在绕过传统的文件检测机制,因为它避免了通过磁盘上的文件进行存储和加载。这类技术可以显著提高恶意软件的隐蔽性和持久性,以下是其工作原理、常见的实现方式及防御策略。
该技术在查看键值时,也会像第一种一样提示错误,但是除了指定的可见字符外,会将其他内容进行隐藏;与第一种技术一致,该内容无法导出。
char decoy[] = "(value not set)"; ....void writeHiddenBuf(char *buf, DWORD buflen, const char *decoy, char *keyName, const char* valueName){ HKEY hkResult = NULL; BYTE *buf2 = (BYTE*)malloc(buflen + strlen(decoy) + 1); strcpy((char*)buf2, decoy); buf2[strlen(decoy)] = 0; memcpy(buf2 + strlen(decoy) + 1, buf, buflen); if (!RegOpenKeyExA(HKEY_CURRENT_USER, keyName, 0, KEY_SET_VALUE, &hkResult)) { printf("Key opened!n"); LSTATUS lStatus = RegSetValueExA(hkResult, valueName, 0, REG_SZ, (const BYTE *)buf2, buflen + strlen(decoy) + 1);printf("lStatus == %dn", lStatus); RegCloseKey(hkResult); } free(buf2);}
这段代码的主要功能是在Windows注册表中存储数据,并使用一种简单的混淆方法来隐藏实际数据。分析如下:
1、定义 char decoy[] = "(value not set)";
该字符数组定义了一个伪装值(decoy),即在实际数据前插入的字符串,用于混淆存储在注册表中的数据。
2、writeHiddenBuf 函数参数:
buf
: 实际数据的指针,用户希望隐藏存储的数据。
buflen
: buf 数据的长度。
decoy
: 一个伪装值,数据存储前附加的字符串。
keyName
: 注册表键的名称,用于定位存储数据的位置。
valueName
: 注册表值的名称,用于将数据与键关联。
3、BYTE buf2 = (BYTE)malloc(buflen + strlen(decoy) + 1);
分配内存来存储伪装值和实际数据。总大小为伪装值的长度、实际数据长度以及一个额外的字节(+1)来存储字符串的终止符。
4、strcpy((char*)buf2, decoy);
将伪装值复制到新分配的 buf2 内存中。
5、buf2[strlen(decoy)] = 0;
在伪装字符串的末尾添加一个空字符,以确保 buf2 在伪装值部分是一个有效的字符串。
6、memcpy(buf2 + strlen(decoy) + 1, buf, buflen);
将实际数据存储在伪装值后面。此步骤将实际数据放置在 buf2 中,但在存储前用伪装值隐藏。
7、打开注册表键:
RegOpenKeyExA(HKEY_CURRENT_USER, keyName, 0, KEY_SET_VALUE, &hkResult) 打开指定的注册表键 keyName。如果成功,注册表键将会打开。
8、写入隐藏数据到注册表:
使用 RegSetValueExA 函数将混淆后的数据写入注册表。这个函数将 buf2 的数据作为值写入指定的 valueName 位置。
9、关闭键和释放内存:
在数据写入后,关闭注册表键,并释放之前分配的 buf2 内存。
总结:
该函数的作用是将用户的数据(buf)与一个伪装字符串(decoy)一起存储到Windows注册表的指定键值位置。这样,实际的数据被隐藏在伪装字符串之后,可以用于混淆分析者或恶意检测工具。
这种技术常用于隐藏恶意软件数据。
-
将decoy设置成 (value not set);
-
然后将我们利用Fileless Malware 处理过的buffer放在(value not set)后面;
-
可知,Regedit会自动截断,达到隐藏的效果;
只要RegSetValueExA传递的decoy字符串的长度+隐藏缓冲区的长度,它将把整个缓冲区写入注册表,达到隐藏效果。
三、检测和防御注册表隐藏技术的工具和方法
注册表隐藏技术发起于21世纪,至今仍在推陈出新,面对这种情况,安全研究人员也在不断地研究发展检测和防御技术和工具方法进行对抗。
3.1 检测与防御策略
1、注册表监控
使用实时注册表监控工具(如 Sysmon 和 Windows Auditing),监控对注册表的写入操作,特别是常见的持久化路径(如 Run 键)。
定期审计系统关键注册表项,查找异常的二进制数据或可疑的Base64编码字符串。
2、内存监控和分析
使用内存分析工具(如 Volatility 或 Rekall)定期检查系统内存中的可疑行为,特别是检测无文件恶意软件的加载和执行。
配合 Endpoint Detection and Response (EDR) 工具,监控内存中的代码注入和执行情况。
3、阻止脚本语言滥用
限制PowerShell、WMI和JavaScript的执行权限,启用 PowerShell Constrained Language Mode 或 AppLocker 来阻止未经授权的脚本运行。
配置PowerShell脚本的日志记录(启用PowerShell的全面审计功能)以监控和分析脚本执行行为。
4、加密检测
检查注册表中含有大量Base64编码字符串或可疑二进制数据的项。这些项可能是恶意代码存储的证据。
使用机器学习或基于行为的检测工具来识别通过注册表加载的恶意二进制数据。
5、行为监控
通过行为分析工具监控进程中的异常行为,如从注册表读取大量二进制数据后立即执行、内存中加载未签名代码或脚本执行等行为。
3.2 部分检测工具
Sysinternals Autoruns
:该工具可以检测启动项,包括注册表中的隐藏启动项。
RegRipper
:这是一个强大的注册表取证工具,可以分析注册表中的可疑行为。
Process Monitor (Procmon)
:可以监控实时注册表访问,帮助发现隐藏的注册表操作。
Powershell 脚本
:可以通过脚本遍历注册表中的所有项,确保未遗漏任何隐藏项。
原创 MicroPest MicroPest 2025年01月12日 15:00 安徽