14

渗透技巧——"隐藏"注册表的创建

 3 years ago
source link: https://3gstudent.github.io/3gstudent.github.io/%E6%B8%97%E9%80%8F%E6%8A%80%E5%B7%A7-%E9%9A%90%E8%97%8F-%E6%B3%A8%E5%86%8C%E8%A1%A8%E7%9A%84%E5%88%9B%E5%BB%BA/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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无法打开该键值,如下图

Alt text

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

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

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

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

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

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

MyQueryValueKeyString(hKey,"test1");

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

MyDeleteValueKey(hKey,"test1");

删除注册表项:

MyDeleteKey(hKey);

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

Alt text

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

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

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

1.不需要修改的功能

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

2.设置注册表键值

对应源代码中的MySetHiddenValueKey

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

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

变通方法:

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

即len = strlen(buf+2)+2

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

直接打开,如下图

Alt text

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

Alt text

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

Alt text

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

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

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)

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

Alt text

0x05 补充


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

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

0x06 小结


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


LEAVE A REPLY


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK