注册表操作
0x01 简介
注册表是windows操作系统、硬件设备以及客户应用程序得以正常运行和保存设置的核心“数据库”,也可以说是一个非常巨大的树状分层结构的数据库系统。
注册表记录了用户安装在计算机上的软件和每个程序的相互关联信息,它包括了计算机的硬件配置,包括自动配置的即插即用的设备和已有的各种设备说明、状态属性以及各种状态信息和数据。利用一个功能强大的注册表数据库来统一集中地管理系统硬件设施、软件配置等信息,从而方便了管理,增强了系统的稳定性。
(详细可参考:http://blog.sina.com.cn/s/blog_4d41e2690100q33v.html)
0x02 相关API
(1)ZwOpenKey() 打开注册表键
1 2 3 4 5 6 7 | NTSTATUS ZwOpenKey( _Out_ PHANDLE KeyHandle, _In_ ACCESS_MASK DesiredAccess, _In_ POBJECT_ATTRIBUTES ObjectAttributes, //OBJECT_ATTRIBUTES结构的指针 ); |
参数:
KeyHandle [out] 指向一个HANDLE 变量的指针,此变量用于接收key的句柄。
DesiredAccess [in] 指定一个ACCESS_MASK,以决定请求访问对象的方式。可以是中所有权限的任何组合:
KEY_QUERY_VALUE:读取键下的值。
KEY_SET_VALUE:设置键下的值。
KEY_CREATE_SUB_KEY:生成子键。
KEY_ENUMERATE_SUB_KEYS:枚举子键。
也可以用KEY_READ来做为通用的读权限组合。这是一个组合宏。此外对应的有KEY_WRITE。如需获得全部的权限,可以使用KEY_ALL_ACCESS
ObjectAttributes [in] 指向OBJECT_ATTRIBUTES 结构体的指针,它指定了对象名和其他属性。使用InitializeObjectAttributes 初始化这个结构。若调用者不是在一个系统线程的上下文中运行,必须在InitializeObjectAttributes时设置OBJ_KERNEL_HANDLE属性。
返回值:
ZwOpenKey 返回STATUS_SUCCESS 若打开了指定的键,否则返回一个如下的错误码:
TATUS_INVALID_HANDLE
STATUS_ACCESS_DENIED
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | UNICODE_STRING ustrRegString; OBJECT_ATTRIBUTES obj_attrib; RtlInitUnicodeString(&ustrRegString,L "\\Registry\\Machine\\SOFTWARE\\ODBC\\ODBC.INI" ); InitializeObjectAttributes(&obj_attrib, &ustrRegString, OBJ_CASE_INSENSITIVE, NULL, NULL); // 打开注册表 status = ZwOpenKey(&hRegister, KEY_ALL_ACCESS, &obj_attrib); if (NT_SUCCESS(status)) { KdPrint(( "[Test] ZwOpenKey %wZ Success!" , ustrRegString)); } |
(2)ZwQueryValueKey() 读注册表
1 2 3 4 5 6 7 8 | NTSTATUS ZwQueryValueKey( _In_ HANDLE KeyHandle, _In_ PUNICODE_STRING ValueName, _In_ KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, _Out_opt_ PVOID KeyValueInformation, _In_ ULONG Length, _Out_ PULONG ResultLength ); |
参数:
KeyHandle:这是用ZwCreateKey或者ZwOpenKey所打开的一个注册表键句柄。
ValueName:要读取的值的名字。
KeyValueInformationClass:本次查询所需要查询的信息类型。这有如下的三种可能。
①KeyValueBasicInformation:获得基础信息,包含值名和类型。
②KeyValueFullInformation:获得完整信息。包含值名、类型和值的数据。
③KeyValuePartialInformation:获得局部信息。包含类型和值数据
使用KeyValuePartialInformation最常见。当采用KeyValuePartialInformation的时候,一个类型为KEY_VALUE_PARTIAL_INFORMATION的结构将被返回到参数KeyValueInformation所指向的内存中
1 2 3 4 5 6 | typedef struct _KEY_VALUE_PARTIAL_INFORMATION { ULONG TitleIndex; ULONG Type; ULONG DataLength; UCHAR Data[1]; } KEY_VALUE_PARTIAL_INFORMATION, *PKEY_VALUE_PARTIAL_INFORMATION; |
Length:用户传入的输出空间KeyValueInformation的长度。
ResultLength:返回实际需要的长度。
返回值:
如果说实际需要的长度比Length要大,那么返回STATUS_BUFFER_OVERFLOW或者是STATUS_BUFFER_TOO_SMALL。如果成功读出了全部数据,那么返回STATUS_SUCCESS。其他的情况,返回一个错误码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | UNICODE_STRING KeyName = RTL_CONSTANT_STRING(L”SystemRoot”); // 用来试探大小的key_infor KEY_VALUE_PARTIAL_INFORMATION KeyInfor; // 最后实际用到的key_infor指针。内存分配在堆中 PKEY_VALUE_PARTIAL_INFORMATION ActualKeyInfor; ULONG Length; …… // 前面已经打开了句柄my_key,下面如此来读取值: status = ZwQueryValueKey( KeyHandle, &KeyName, KeyValuePartialInformation, &KeyInfor, sizeof (KEY_VALUE_PARTIAL_INFORMATION), &Length); if (!NT_SUCCESS(status) && status != STATUS_BUFFER_OVERFLOW && status != STATUS_BUFFER_TOO_SMALL) { // 错误处理 … } // 如果没失败,那么分配足够的空间,再次读取 ActualKeyInfor = (PKEY_VALUE_PARTIAL_INFORMATION) ExAllocatePoolWithTag(NonpagedPool, ac_length, MEM_TAG); if (ac_key_infor == NULL) { stauts = STATUS_INSUFFICIENT_RESOURCES; // 错误处理 … } status = ZwQueryValueKey( KeyHandle, &KeyName, KeyValuePartialInformation, ActualKeyInfor, Length, &Length); // 如果status为STATUS_SUCCESS,则要读取的数据已经 // 在ac_key_infor->Data中。应转换为UNICODE_STRING …… |
(3)ZwSetValueKey() 写注册表
1 2 3 4 5 6 7 8 | NTSTATUS ZwSetValueKey( _In_ HANDLE KeyHandle, _In_ PUNICODE_STRING ValueName, _In_opt_ ULONG TitleIndex, _In_ ULONG Type, _In_opt_ PVOID Data, _In_ ULONG DataSize ); |
参数:
TileIndex参数填入0。KeyHandle、ValueName、Type这三个参数和ZwQueryValueKey中对应的参数相同。
Data是要写入的数据的开始地址,而DataSize是要写入的数据的长度。
如果该Value已经存在,那么其值会被这次写入覆盖。如果不存在,则会新建一个。
0x03 注册表注入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | // RegInject.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <Windows.h> #include <iostream> using namespace std; wstring GetExeDirectory(); wstring GetParent( const std::wstring& FullPath); int main() { LONG ReturnValue = 0; HKEY hKey; WCHAR RegPath[] = L "SOFTWARE\\Microsoft\\Windows\ NT\\CurrentVersion\\Windows" ; const wchar_t * DllName = L "Dll.dll" ; wstring InjectFileFullPath; InjectFileFullPath = GetExeDirectory() + L "\\" + DllName; RegEnableReflectionKey(HKEY_LOCAL_MACHINE); //打开键值 ReturnValue = RegOpenKeyEx( HKEY_LOCAL_MACHINE, RegPath, 0, KEY_ALL_ACCESS, &hKey); if (ReturnValue != ERROR_SUCCESS) { return 0; } //查询键值 DWORD dwReadType; DWORD dwReadCount; WCHAR szReadBuff[1000] = { 0 }; ReturnValue = RegQueryValueEx(hKey, L "AppInit_DLLs" , NULL, &dwReadType, ( BYTE *)&szReadBuff, &dwReadCount); if (ReturnValue != ERROR_SUCCESS) { return 0; } //是否dll名称已经在内容中 wstring strCmpBuff(szReadBuff); //strCmpBuff = szReadBuff; int a = strCmpBuff.find(InjectFileFullPath); if (strCmpBuff.find(InjectFileFullPath)) { MessageBox(NULL, TEXT( "RegInject Had Been Succeed" ), TEXT( "It's Done Before" ), MB_OK); return 0; } //有字符串就加入空格 if (wcscmp(szReadBuff, L " " ) != 0) { wcscat_s(szReadBuff, L " " ); } wcscat_s(szReadBuff, InjectFileFullPath.c_str()); //把dll路径设置到注册表中 ReturnValue = RegSetValueEx(hKey, L "AppInit_DLLs" , 0, REG_SZ, (CONST BYTE *)szReadBuff, (_tcslen(szReadBuff) + 1) * sizeof ( TCHAR )); DWORD v1 = 0; ReturnValue = RegSetValueEx(hKey, L "LoadAppInit_DLLs" , 0, REG_DWORD, (CONST BYTE *)&v1, sizeof ( DWORD )); if (ReturnValue != ERROR_SUCCESS) { MessageBox(NULL, TEXT( "RegInject Fail" ), TEXT( "NO NO NO" ), MB_OK); RegCloseKey(hKey); } else { MessageBox(NULL, TEXT( "RegInject Succeed" ), TEXT( "LaLaLa" ), MB_OK); } getchar (); return 0; } wstring GetExeDirectory() { wchar_t ProcessFullPath[MAX_PATH] = { 0 }; DWORD ProcessFullPathLength = ARRAYSIZE(ProcessFullPath); GetModuleFileName(NULL, ProcessFullPath, ProcessFullPathLength); return GetParent(ProcessFullPath); } wstring GetParent( const std::wstring& FullPath) { if (FullPath.empty()) { return FullPath; } auto v1 = FullPath.rfind(L "\\" ); if (v1 == FullPath.npos) { v1 = FullPath.rfind(L '/' ); } if (v1 != FullPath.npos) { return FullPath.substr(0, v1); } else { return FullPath; } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗