知名恶意软件Poweliks曾使用过的一个后门技术,在注册表启动位置创建一个特殊的注册表键值,通过mshta来执行payload
对于这个特殊的注册表键值,在正常情况下无法对其访问,这其中的原理是什么呢?如何读取、创建以及如何删除呢?本文将要一一介绍
本文将要介绍以下内容:
·隐藏注册表的原理
·隐藏注册表的实现
·程序编写上需要注意的问题
注册表键值名称经过特殊构造: 以”\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,需要的结构如下:
注册表操作的相关Native API可从ntdll.dll中获得
关键代码如下:
HINSTANCE hinstStub = GetModuleHandle(_T("ntdll.dll"));
NtOpenKey = (LPNTOPENKEY)GetProcAddress(hinstStub, "NtOpenKey");
Native API在使用前需要重定义和声明
部分关键代码如下:
typedef NTSTATUS (STDAPICALLTYPE NTOPENKEY)
(
IN HANDLE KeyHandle,
IN ULONG DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes
);
typedef NTOPENKEY FAR * LPNTOPENKEY;
LPNTOPENKEY NtOpenKey;
注册表操作相关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工程包含的功能:
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”,但在参数传递上需要注意更多问题
创建注册表键、打开注册表键和删除注册表键的功能不需要修改,使用正常的名称即可
对应源代码中的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';
对应源代码中的MyQueryHiddenValueKeyString
参照2,需要注意”\0”的影响
对应源代码中的MyDeleteHiddenValueKey
参照2,需要注意”\0”的影响
实际测试:
创建注册表项test2,创建隐藏注册表键值\0test2,创建正常注册表键值test2
直接打开,如下图
能够正常访问注册表键值test2,但无法访问注册表键值\0test2
如下图
而我们编写的程序能够正常读取,如下图
至此,成功实现对注册表键值的隐藏
以上功能代码已开源,地址如下:
https://github.com/3gstudent/HiddenNtRegistry
可参考Brian Reitz的工程,地址如下:
https://gist.github.com/brianreitz/feb4e14bd45dd2e4394c225b17df5741
具体说明可参考:
实现了在HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run下创建键值\0abcd,内容为mshta javascript:alert(1)
使用我们编写的程序成功读取该键值,如下图
PSReflect-Functions包含多个通过powershell调用API的实例代码,地址如下:
https://github.com/jaredcatkinson/PSReflect-Functions
本文介绍了Poweliks使用过的注册表隐藏技术,分析原理,编写c程序实现功能,测试powershell实现代码
本文为 3gstudent 原创稿件, 授权嘶吼独家发布,如若转载,请联系嘶吼编辑: http://www.4hou.com/penetration/9141.html