0x00 前言

知名恶意软件Poweliks曾使用过的一个后门技术,在注册表启动位置创建一个特殊的注册表键值,通过mshta来执行payload

对于这个特殊的注册表键值,在正常情况下无法对其访问,这其中的原理是什么呢?如何读取、创建以及如何删除呢?本文将要一一介绍

0x01 简介

本文将要介绍以下内容:

·隐藏注册表的原理

·隐藏注册表的实现

·程序编写上需要注意的问题

0x02 原理

注册表键值名称经过特殊构造: 以”\0”作为开头,后面加上任意字符(不能为数字)

对于Windows系统,”\0”(即0x0000)会被识别为字符串的结束符,所以在对该字符串读取的过程中,遇到开头的”\0”,会被解析成结束符,提前截断,导致读取错误

而使用Native API设定注册表,需要使用结构体OBJECT_ATTRIBUTES作为参数, 指定读取的字符串长度

只要长度设定正常,就能够读取正确的字符串,避免这个bug

所以,我们可以通过Native API来创建这个特殊的注册表名

更为重要的是,像regedit.exe和其他对注册表的操作,通常会调用Win32 API,这就导致该注册表无法被读取,也就实现了所谓的”隐藏”

综上,创建方法为: 通过Native API创建一个以”\0”开头的键值

0x03 编写程序实现

通过Native API实现对注册表的操作,可供参考的工程地址:

https://www.codeproject.com/Articles/14508/Registry-Manipulation-Using-NT-Native-APIs

作者Dan Madden,他的代码使用了类的封装

个人倾向于使用最基本的api实现,于是参考他的代码,重新设计

对于Native API,需要的结构如下:

1.获取Native API的地址

注册表操作的相关Native API可从ntdll.dll中获得

关键代码如下:

HINSTANCE hinstStub = GetModuleHandle(_T("ntdll.dll"));
NtOpenKey = (LPNTOPENKEY)GetProcAddress(hinstStub, "NtOpenKey");

2.Native API的重定义和声明

Native API在使用前需要重定义和声明

部分关键代码如下:

typedef NTSTATUS (STDAPICALLTYPE NTOPENKEY)
(
    IN HANDLE               KeyHandle,
    IN ULONG                DesiredAccess,
    IN POBJECT_ATTRIBUTES   ObjectAttributes
);
typedef NTOPENKEY FAR * LPNTOPENKEY;
LPNTOPENKEY                 NtOpenKey;

3.特殊结构体的使用

注册表操作相关Native API会使用到如下结构体,需要定义和声明

·InitializeObjectAttributes
·_STRING
·_UNICODE_STRING
·_OBJECT_ATTRIBUTES
·_KEY_INFORMATION_CLASS
·_KEY_BASIC_INFORMATION
·_KEY_VALUE_PARTIAL_INFORMATION
·_KEY_VALUE_INFORMATION_CLASS·
·RtlInitAnsiString
·RtlAnsiStringToUnicodeString

Dan Madden的工程实现了创建隐藏注册表项(注册表项名称以\0开头),该注册表项下的键值通过正常的Native API实现创建、读取、删除

通过最基本api的实现过程不再赘述,封装好的API源代码可参考文末给出的链接

测试Dan Madden工程包含的功能:

1.创建隐藏注册表项

MyCreateHiddenKey("\\Registry\\Machine\\Software\\testhidden");

使用注册表工具regedit.exe无法打开该键值,如下图

2.在该注册表下创建注册表键值

先获得该注册表项的句柄:

hKey = MyOpenHiddenKey("\\Registry\\Machine\\Software\\testhidden");

创建注册表项下的键值test1并赋值:

MySetValueKey(hKey,"test1","0123456789abcdef",REG_SZ);

读取该注册表项下键值test1的内容:

MyQueryValueKeyString(hKey,"test1");

删除该注册表项下的键值test1:

MyDeleteValueKey(hKey,"test1");

删除注册表项:

MyDeleteKey(hKey);

程序输出如下图,成功对隐藏注册表项下的正常键值进行操作

接下来,对Dan Madden的工程添加新的功能:创建、读取、删除隐藏注册表键值,思路如下:

对于注册表项的隐藏,在注册表项的名称首位填”\0”即可

对应注册表键值的隐藏,原理上也是在键值的名称首位填”\0”,但在参数传递上需要注意更多问题

1.不需要修改的功能

创建注册表键、打开注册表键和删除注册表键的功能不需要修改,使用正常的名称即可

2.设置注册表键值

对应源代码中的MySetHiddenValueKey

传入参数使用char型数组,,用来定义注册表键值名称,内容为”\0abcd”

由于”\0”的存在,所以无法直接使用strlen计算数组长度

变通方法:

计算从偏移1开始的数组长度,最终再加1

即len = strlen(buf+1)+1

Native API NtSetValueKey用来设定键值,定义如下:

typedef NTSTATUS (STDAPICALLTYPE NTSETVALUEKEY)
(
    IN HANDLE           KeyHandle,
    IN PUNICODE_STRING  ValueName,
    IN ULONG            TitleIndex,         /* optional */
    IN ULONG            Type,
    IN PVOID            Data,
    IN ULONG            DataSize
);

第二个参数指定键值名称,需要使用结构体UNICODE_STRING

正常情况下,我们需要先使用RtlInitAnsiString将传入的buf数组转换成结构体ANSI_STRING,再使用RtlAnsiStringToUnicodeString将其转换成结构体UNICODE_STRING,作为参数

由于”\0”的存在,无法使用RtlAnsiStringToUnicodeString

所以,我们需要自己实现结构体ANSI_STRING向结构体UNICODE_STRING的转换

ANSI向UNICODE的转换,在长度计算上,乘以2即可

数组内容上,奇数位赋值,偶数为填0x00

当然,我们需要一个中转数组TempBuff实现数组内容的转换

关键代码如下:

ValueName.Length = asName.Length*2;
ValueName.MaximumLength = asName.MaximumLength*2;
char *TempBuff;
TempBuff = (char*)malloc(ValueName.Length);
for(int i=0;i<asName.Length;i++)
{
    TempBuff[i*2] = asName.Buffer[i];
    TempBuff[i*2+1] = 0x00;
}
ValueName.Buffer = (WCHAR *)TempBuff;

第四个参数,指定键值内容,需要将传入的char数组转换为WCHAR

关键代码:

WCHAR wszValue[1024];
unsigned int n ;
for (n=0; n<strlen(csData); n++) {
    wszValue[n] = (WCHAR)csData[n];
}
wszValue[n++] = L'\0';

3.读取注册表键值

对应源代码中的MyQueryHiddenValueKeyString

参照2,需要注意”\0”的影响

4、删除注册表键值

对应源代码中的MyDeleteHiddenValueKey

参照2,需要注意”\0”的影响

实际测试:

创建注册表项test2,创建隐藏注册表键值\0test2,创建正常注册表键值test2

直接打开,如下图

能够正常访问注册表键值test2,但无法访问注册表键值\0test2

如下图

而我们编写的程序能够正常读取,如下图

至此,成功实现对注册表键值的隐藏

以上功能代码已开源,地址如下:

https://github.com/3gstudent/HiddenNtRegistry

0x04 powershell实现

可参考Brian Reitz的工程,地址如下:

https://gist.github.com/brianreitz/feb4e14bd45dd2e4394c225b17df5741

具体说明可参考:

https://posts.specterops.io/hiding-registry-keys-with-psreflect-b18ec5ac8353?source=collection_archive———2—————-

实现了在HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run下创建键值\0abcd,内容为mshta javascript:alert(1)

使用我们编写的程序成功读取该键值,如下图

0x05 补充

PSReflect-Functions包含多个通过powershell调用API的实例代码,地址如下:

https://github.com/jaredcatkinson/PSReflect-Functions

0x06 小结

本文介绍了Poweliks使用过的注册表隐藏技术,分析原理,编写c程序实现功能,测试powershell实现代码

本文为 3gstudent 原创稿件, 授权嘶吼独家发布,如若转载,请联系嘶吼编辑: http://www.4hou.com/penetration/9141.html

源链接

Hacking more

...