首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
编程技术
发新帖
0
1
[原创]Windows Run 与 RunOnce 注册表启动项的枚举
发表于: 2026-7-15 22:34
982
[原创]Windows Run 与 RunOnce 注册表启动项的枚举
WangWei_CM.
2026-7-15 22:34
982
# Windows Run 与 RunOnce 注册表启动项的安全枚举 `Run` 与 `RunOnce` 是登录阶段常见的注册表位置。安全读取的顺序是:确定根键和视图,使用只读权限打开键,按 API 返回的长度分配缓冲区,按值类型解释原始字节,最后再解释 RunOnce 的值名前缀。 ## 一、先确定要读取的位置 ### 1. 根键决定配置范围 读取范围的第一个问题是区分当前用户与本机配置。常见位置如下: ```text HKCU\Software\Microsoft\Windows\CurrentVersion\Run HKCU\Software\Microsoft\Windows\CurrentVersion\RunOnce HKLM\Software\Microsoft\Windows\CurrentVersion\Run HKLM\Software\Microsoft\Windows\CurrentVersion\RunOnce ``` `HKCU` 是 `HKEY_CURRENT_USER` 的缩写,表示当前登录用户的注册表配置。`HKLM` 是 `HKEY_LOCAL_MACHINE` 的缩写,表示本机配置;同名值分别位于 HKCU 与 HKLM 时,属于两条独立记录。 条目身份的第二个问题是不能只保存值名或命令文本。读取结果需要同时保留根键、完整子键路径、32 位或 64 位视图和值名;命令文本属于该记录的数据,不能单独充当位置。 RunOnce 的第三个问题是值存在时间不能表示执行结果。Run 值通常会保留在键中,RunOnce 值涉及删除时机;某个值消失或仍然存在,都不足以单独说明对应目标是否已成功启动或完成。 ### 2. 机器级软件键需要明确注册表视图 视图选择的第一个问题是进程位数会改变默认读取范围。64 位 Windows 对部分 `HKLM\Software` 键维护 32 位和 64 位视图;64 位调用方默认读取 64 位视图,32 位调用方默认读取 32 位视图。 访问掩码的第二个问题是让读取范围固定。需要检查机器级 Run 或 RunOnce 时,使用 `KEY_WOW64_64KEY` 请求 64 位视图,使用 `KEY_WOW64_32KEY` 请求 32 位视图。两次读取分别记录视图名称,才能区分同一路径文本下的不同数据。 用户键的第三个问题是不能手工把 `Wow6432Node` 拼进路径。这个名称是部分注册表重定向呈现出来的结果,适用范围由 Windows 对具体键的规则决定。用户键应使用标准路径,再根据目标键选择是否指定视图。 ## 二、用只读权限打开一个启动键 ### 3. 打开键时先声明函数含义和返回规则 打开现有注册表键的宽字符 API 会返回一个新的 `HKEY`。返回值类型是 `LSTATUS`:`ERROR_SUCCESS` 表示成功,其它值已经是 Win32 错误码,能够直接转换为错误文本。 ```cpp // 意义:打开 hKey 下的 lpSubKey,并按 samDesired 请求指定权限和注册表视图。 // 返回:ERROR_SUCCESS 成功;其它 LSTATUS 表示键不存在、权限不足等错误。 LSTATUS RegOpenKeyExW( HKEY hKey, // 输入:预定义根键或已打开的父键。 LPCWSTR lpSubKey, // 输入:相对 hKey 的子键路径;不能为未终止的字节缓冲区。 DWORD ulOptions, // 输入:保留,必须为 0。 REGSAM samDesired, // 输入:访问权限与 KEY_WOW64_* 视图标志的组合。 PHKEY phkResult // 输出:成功时得到的新 HKEY;调用方负责关闭。 ); // 意义:关闭 RegOpenKeyExW 成功返回的 HKEY。 // 返回:ERROR_SUCCESS 成功;关闭预定义根键没有读取价值,也不应当作普通子键句柄管理。 LSTATUS RegCloseKey( HKEY hKey // 输入:先前成功打开且尚未关闭的键句柄。 ); ``` 正确打开的第一个步骤是只请求枚举所需权限。`KEY_QUERY_VALUE` 允许读取值;对 HKLM 显式加入一个视图标志,成功后在同一作用域关闭返回的句柄。 ```cpp const wchar_t* const runPath = L"Software\\Microsoft\\Windows\\CurrentVersion\\Run"; HKEY openedKey = nullptr; const REGSAM access = KEY_QUERY_VALUE | KEY_WOW64_64KEY; const LSTATUS openStatus = RegOpenKeyExW( HKEY_LOCAL_MACHINE, // 从本机配置根键开始。 runPath, // 使用标准相对路径。 0, // 保留参数固定为 0。 access, // 只读权限加 64 位视图。 &openedKey); // 接收新句柄的地址。 if (openStatus != ERROR_SUCCESS) { // openStatus 是 API 直接返回的错误码,应按它处理错误。 return; } // 此处读取 openedKey 中的值。 RegCloseKey(openedKey); // 成功打开的句柄在完成读取后关闭。 ``` 错误用法的第二个问题是把默认视图误当成完整范围。下面的调用在 32 位调用方中默认打开 32 位视图,无法用于固定读取 64 位视图。 ```cpp // 错误:缺少 KEY_WOW64_64KEY,读取范围随调用方位数变化。 RegOpenKeyExW(HKEY_LOCAL_MACHINE, runPath, 0, KEY_QUERY_VALUE, &openedKey); ``` ## 三、先查询容量,再枚举值 ### 4. 查询键信息时区分字符数与字节数 查询键信息的宽字符 API 会返回统计信息和各类名称、数据的最大长度。返回 `ERROR_SUCCESS` 时,最长值名的单位是宽字符个数,最长值数据的单位是字节;两者不能互换。 ```cpp // 意义:查询一个已打开键的子键数、值数、名称最大长度和数据最大长度。 // 返回:ERROR_SUCCESS 成功;其它 LSTATUS 表示句柄无效、权限不足等错误。 LSTATUS RegQueryInfoKeyW( HKEY hKey, // 输入:已成功打开的注册表键。 LPWSTR lpClass, // 输出,可选:键类名缓冲区。 LPDWORD lpcchClass, // 输入/输出,可选:类名缓冲区字符容量与实际长度。 LPDWORD lpReserved, // 保留:必须为 nullptr。 LPDWORD lpcSubKeys, // 输出,可选:直接子键数量。 LPDWORD lpcMaxSubKeyLen, // 输出,可选:最长直接子键名的字符数,不含 NUL。 LPDWORD lpcMaxClassLen, // 输出,可选:最长子键类名的字符数。 LPDWORD lpcValues, // 输出,可选:直接值数量。 LPDWORD lpcMaxValueNameLen, // 输出,可选:最长值名的字符数,不含 NUL。 LPDWORD lpcMaxValueLen, // 输出,可选:最长值数据的字节数。 LPDWORD lpcbSecurityDescriptor,// 输出,可选:安全描述符字节数。 PFILETIME lpftLastWriteTime // 输出,可选:最后写入时间。 ); ``` 正确分配的第一个步骤是为值名额外预留一个 NUL,为值数据按字节预留空间。这里使用 `std::vector<wchar_t>` 接收名称,使用 `std::vector<BYTE>` 接收原始数据。 ```cpp DWORD valueCount = 0; DWORD maxValueNameChars = 0; DWORD maxValueDataBytes = 0; const LSTATUS queryStatus = RegQueryInfoKeyW( openedKey, nullptr, nullptr, nullptr, // 不读取类名和保留字段。 nullptr, nullptr, nullptr, // 不读取子键统计信息。 &valueCount, // 接收当前直接值数量。 &maxValueNameChars, // 接收最长值名的字符数。 &maxValueDataBytes, // 接收最长值数据的字节数。 nullptr, nullptr); // 不读取安全描述符和时间。 if (queryStatus != ERROR_SUCCESS) { return; } std::vector<wchar_t> valueName(maxValueNameChars + 1, L'\0'); std::vector<BYTE> valueData(maxValueDataBytes == 0 ? 1 : maxValueDataBytes); ``` 错误分配的第二个问题是把字节数直接作为宽字符数量。下面的写法会把数据容量误读为字符数量,并隐藏名称缓冲区缺少终止符空间的问题。 ```cpp // 错误:maxValueDataBytes 的单位是字节,不能用于 wchar_t 元素数量。 std::vector<wchar_t> valueData(maxValueDataBytes); // 错误:maxValueNameChars 不包含 NUL,最大长度值名会没有终止符位置。 std::vector<wchar_t> valueName(maxValueNameChars); ``` ### 5. 枚举一个值时必须重新提供容量 按索引读取当前键直接值的宽字符 API 会修改两个长度参数。返回 `ERROR_SUCCESS` 时,值名长度变为实际字符数,数据长度变为实际字节数;下一次调用前必须重新填回缓冲区容量。 ```cpp // 意义:读取 hKey 中索引为 dwIndex 的一个直接值。 // 返回:ERROR_SUCCESS 成功;ERROR_NO_MORE_ITEMS 表示枚举结束;ERROR_MORE_DATA 表示缓冲区不足。 LSTATUS RegEnumValueW( HKEY hKey, // 输入:已打开且具有 KEY_QUERY_VALUE 权限的键。 DWORD dwIndex, // 输入:当前值索引。 LPWSTR lpValueName, // 输出:值名缓冲区,可为 nullptr。 LPDWORD lpcchValueName, // 输入/输出:值名字符容量与实际长度,不含 NUL。 LPDWORD lpReserved, // 保留:必须为 nullptr。 LPDWORD lpType, // 输出:REG_SZ、REG_DWORD 等注册表类型。 LPBYTE lpData, // 输出:原始值数据缓冲区,可为 nullptr。 LPDWORD lpcbData // 输入/输出:数据字节容量与实际长度。 ); ``` 正确枚举的第一个步骤是在每次调用前恢复两个容量变量。成功后按 API 返回的实际长度裁剪缓冲区,避免把预分配的尾部零字节误当作注册表数据。 ```cpp for (DWORD index = 0;; ++index) { LSTATUS enumStatus = ERROR_MORE_DATA; DWORD valueType = REG_NONE; // 最多重试三次;每次重试都重新设置输入容量。 for (int attempt = 0; attempt < 3 && enumStatus == ERROR_MORE_DATA; ++attempt) { valueName.assign(maxValueNameChars + 1, L'\0'); valueData.assign(maxValueDataBytes == 0 ? 1 : maxValueDataBytes, 0); DWORD valueNameChars = static_cast<DWORD>(valueName.size()); DWORD valueDataBytes = static_cast<DWORD>(valueData.size()); enumStatus = RegEnumValueW( openedKey, index, valueName.data(), &valueNameChars, nullptr, &valueType, valueData.data(), &valueDataBytes); if (enumStatus == ERROR_SUCCESS) { valueName.resize(valueNameChars); // 字符数,不包含 NUL。 valueData.resize(valueDataBytes); // 字节数。 break; } if (enumStatus == ERROR_MORE_DATA) { // 注册表可能刚刚变化;刷新上限后重试同一个 index。 const LSTATUS refreshStatus = RegQueryInfoKeyW( openedKey, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &valueCount, &maxValueNameChars, &maxValueDataBytes, nullptr, nullptr); if (refreshStatus != ERROR_SUCCESS) { enumStatus = refreshStatus; } } } if (enumStatus == ERROR_NO_MORE_ITEMS) { break; // 正常结束。 } if (enumStatus != ERROR_SUCCESS) { continue; // 三次重试后仍失败,继续后续索引。 } // 此处开始按 valueType 解释 valueData。 } ``` 错误用法的第二个问题是复用上一次的实际长度。第一次读取短值后,下面的下一次调用只给 API 一个很小的容量,长值会更容易得到 `ERROR_MORE_DATA`。 ```cpp // 错误:valueNameChars、valueDataBytes 已经被上一次调用改成实际长度。 RegEnumValueW(openedKey, index, valueName.data(), &valueNameChars, nullptr, &valueType, valueData.data(), &valueDataBytes); ``` ## 四、先按类型和字节边界读取值数据 ### 6. 文本类型需要先检查 UTF-16 单元边界 类型处理的第一个步骤是只把 `REG_SZ` 与 `REG_EXPAND_SZ` 当作单个 UTF-16 文本。`REG_SZ` 保存普通文本,`REG_EXPAND_SZ` 保存含环境变量标记的文本;`REG_DWORD`、`REG_QWORD`、`REG_BINARY` 和 `REG_MULTI_SZ` 具有不同的数据布局。 `REG_EXPAND_SZ` 的内容可以是下面这种原始文本。`%SystemRoot%` 是环境变量标记,当前系统常把它展开为 Windows 目录;原始文本与展开后的文本都应保留。 ```text 原始 REG_EXPAND_SZ:%SystemRoot%\System32\SecurityHealthSystray.exe 当前环境展开后: C:\Windows\System32\SecurityHealthSystray.exe ``` 正确解码的第二个步骤是先验证字节数,再按实际长度复制。注册表 API 返回的数据长度以字节表示,Windows 的 `wchar_t` 占两个字节;奇数字节长度不能形成完整 UTF-16 单元。 ```cpp // 意义:将已经读取到的 REG_SZ 或 REG_EXPAND_SZ 原始字节转成宽字符串。 // 返回:true 表示类型和 UTF-16 长度有效;false 表示应保留原始类型与字节数,不显示文本。 bool DecodeRegistryText( DWORD valueType, // 输入:RegEnumValueW 返回的注册表类型。 const std::vector<BYTE>& bytes, // 输入:RegEnumValueW 返回的实际字节,不依赖 NUL。 std::wstring& textOut // 输出:成功时得到不含一个尾部 NUL 的文本。 ) { if (valueType != REG_SZ && valueType != REG_EXPAND_SZ) { return false; // 其它类型不能按单个命令文本解释。 } if (bytes.size() % sizeof(wchar_t) != 0) { return false; // 奇数字节长度会截断一个 UTF-16 单元。 } textOut.resize(bytes.size() / sizeof(wchar_t)); if (!bytes.empty()) { std::memcpy(textOut.data(), bytes.data(), bytes.size()); } if (!textOut.empty() && textOut.back() == L'\0') { textOut.pop_back(); // 只移除真实存在的一个结尾终止符。 } return true; } ``` 错误解码的第三个问题是把原始字节强制转换为 NUL 终止字符串。`wcslen` 会一直查找终止符,未终止的注册表数据会让读取越过 `bytes` 的分配边界。 ```cpp // 错误:bytes 的长度来自 API,不能假定 data() 后面存在 L'\0'。 const wchar_t* text = reinterpret_cast<const wchar_t*>(bytes.data()); std::wstring copied(text, wcslen(text)); ``` 数值数据的第四个步骤是验证固定长度。`REG_DWORD` 必须恰好有四个字节,`REG_QWORD` 必须恰好有八个字节;长度不匹配时,只能把它报告为类型与长度异常,不能把部分字节当成完整数值。 二进制数据的第五个步骤是保留类型。`REG_BINARY` 即使恰好包含可显示文字,也应保留其二进制类型、字节长度和有限十六进制摘要;文本外观不能改变注册表的原始语义。 ### 7. 显示文本时需要把 UTF-16 转成 UTF-8 宽字符到多字节的转换 API 按指定代码页输出文本。返回值是写入或所需的字节数,返回 0 表示转换失败;`CP_UTF8` 选择 UTF-8 输出,`WC_ERR_INVALID_CHARS` 要求遇到无效 UTF-16 时失败。 ```cpp // 意义:将 UTF-16 宽字符转换为 UTF-8 字节。 // 返回:非零表示写入或所需的 UTF-8 字节数;0 表示转换失败。 int WideCharToMultiByte( UINT CodePage, // 输入:目标代码页;CP_UTF8 表示 UTF-8。 DWORD dwFlags, // 输入:WC_ERR_INVALID_CHARS 用于拒绝无效 UTF-16。 LPCWCH lpWideCharStr, // 输入:宽字符起始地址。 int cchWideChar, // 输入:宽字符数量;传显式长度时不包含 NUL。 LPSTR lpMultiByteStr, // 输出:UTF-8 缓冲区;首次查询容量时为 nullptr。 int cbMultiByte, // 输入:输出缓冲区的字节容量;首次查询时为 0。 LPCCH lpDefaultChar, // 输入:UTF-8 时必须为 nullptr。 LPBOOL lpUsedDefaultChar // 输出:UTF-8 时必须为 nullptr。 ); ``` 正确转换的第一个步骤是调用两次。第一次只查询 UTF-8 所需字节数,第二次使用同样的显式 UTF-16 长度写入精确容量;传入长度不是 `-1` 时,结果不包含额外 NUL。 ```cpp std::string ToUtf8(const std::wstring_view text) { if (text.empty()) { return {}; } const int neededBytes = WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, text.data(), static_cast<int>(text.size()), nullptr, 0, nullptr, nullptr); if (neededBytes <= 0) { return {}; // 调用方把空结果作为转换失败处理。 } std::string result(static_cast<std::size_t>(neededBytes), '\0'); const int writtenBytes = WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, text.data(), static_cast<int>(text.size()), result.data(), neededBytes, nullptr, nullptr); return writtenBytes == neededBytes ? result : std::string(); } ``` 错误转换的第二个问题是把 UTF-16 字符数量当成 UTF-8 字节容量。中文和其它非 ASCII 字符通常需要多个 UTF-8 字节,下面的容量会不足。 ```cpp // 错误:text.size() 是 UTF-16 字符数,不能直接用作 UTF-8 字节容量。 std::string utf8(text.size(), '\0'); WideCharToMultiByte(CP_UTF8, 0, text.data(), static_cast<int>(text.size()), utf8.data(), static_cast<int>(utf8.size()), nullptr, nullptr); ``` ## 五、把环境展开与命令行解析分开 ### 8. 环境展开使用当前进程的环境块 环境变量展开的宽字符 API 用当前进程环境变量替换文本中的 `%名称%` 标记。返回值是包含结尾 NUL 在内所需或实际字符数,返回 0 表示失败;展开结果反映当前环境,原始 `REG_EXPAND_SZ` 文本仍是注册表事实。 ```cpp // 意义:将 source 中的 %VARIABLE% 替换为当前进程环境变量的值。 // 返回:非零表示所需或写入字符数,数值包含结尾 NUL;0 表示失败。 DWORD ExpandEnvironmentStringsW( LPCWSTR lpSrc, // 输入:以 NUL 结束的源文本。 LPWSTR lpDst, // 输出:目标缓冲区;查询容量时为 nullptr。 DWORD nSize // 输入:lpDst 的 wchar_t 容量;查询容量时为 0。 ); ``` 正确展开的第一个步骤是保留原始文本,并先查询输出容量。环境展开 API 需要 NUL 终止输入,因此内部含 NUL 的注册表文本不能直接交给它;成功后移除 API 写入的一个结尾 NUL。 ```cpp bool ExpandCurrentEnvironment( const std::wstring_view source, std::wstring& expandedOut) { if (source.find(L'\0') != std::wstring_view::npos) { return false; // c_str() 会截断内嵌 NUL 后的原始数据。 } const std::wstring sourceText(source); const DWORD requiredChars = ExpandEnvironmentStringsW( sourceText.c_str(), nullptr, 0); if (requiredChars == 0) { return false; } std::wstring buffer(requiredChars, L'\0'); if (ExpandEnvironmentStringsW(sourceText.c_str(), buffer.data(), requiredChars) == 0) { return false; } buffer.resize(requiredChars - 1); // requiredChars 包含 API 写入的 NUL。 expandedOut = std::move(buffer); return true; } ``` 错误展开的第二个问题是提前减去终止符容量。下面的缓冲区比 API 要求少一个宽字符,无法容纳完整展开结果和结尾 NUL。 ```cpp // 错误:requiredChars 已经包含 NUL,不能在调用前减一。 std::wstring buffer(requiredChars - 1, L'\0'); ExpandEnvironmentStringsW(sourceText.c_str(), buffer.data(), requiredChars - 1); ``` 命令行边界的第三个步骤是保留完整文本。`"C:\\Program Files\\Agent\\agent.exe" --background` 的第一个空格位于带引号的路径内部;`rundll32.exe`、`cmd.exe /c` 和脚本解释器还具有不同参数规则,因此不能按第一个空格切出所谓的唯一镜像路径。 ## 六、解释 RunOnce 值名前缀 ### 9. 前缀只说明 Windows 对该值的处理条件 `!` 与 `*` 的第一个作用是改变 RunOnce 值的处理条件。值名以 `!` 开头时,删除时机延后到命令行被系统处理之后;以 `*` 开头时,该项允许在安全模式下被处理;两个字符可以连续出现,例如 `!*Repair`。 正确解析的第二个步骤是从值名开头连续读取控制字符,同时保留原始值名。去掉前缀后的文本只用于显示名称,原始名称仍然是注册表中真正保存的名称。 ```cpp struct RunOnceNameInfo { std::wstring_view displayName; // 去除开头控制字符后的名称视图。 bool deferDeletion = false; // 是否出现 !。 bool allowSafeMode = false; // 是否出现 *。 }; RunOnceNameInfo ParseRunOnceName(const std::wstring_view originalName) { RunOnceNameInfo info{}; std::size_t offset = 0; while (offset < originalName.size()) { if (originalName[offset] == L'!') { info.deferDeletion = true; ++offset; continue; } if (originalName[offset] == L'*') { info.allowSafeMode = true; ++offset; continue; } break; // 首个普通字符开始显示名称。 } info.displayName = originalName.substr(offset); return info; } ``` 错误解析的第三个问题是只识别一个固定前缀顺序。下面的判断会遗漏 `*!Repair`,也无法保留多个连续控制字符的语义。 ```cpp // 错误:只处理第一个字符,组合前缀无法完整识别。 if (!originalName.empty() && originalName[0] == L'!') { // 只记录 !,忽略紧随其后的 *。 } ``` 执行结果的第四个问题是前缀不能证明成功。`!` 改变的是删除时机,值名带有 `!` 或 `*` 只能说明观察到的配置;是否创建进程、目标是否完成仍要用进程创建记录、事件日志或其它独立证据确认。 ## 七、让资源释放和错误状态保持准确 ### 10. 句柄所有权需要覆盖所有返回路径 资源管理的第一个步骤是让一个对象独占 `RegOpenKeyExW` 成功返回的句柄。析构函数调用前面声明的 `RegCloseKey`,无论读取成功、容量查询失败或中途返回,键句柄都会释放。 ```cpp class RegKey final { public: explicit RegKey(HKEY key) : key_(key) {} ~RegKey() { if (key_ != nullptr) { RegCloseKey(key_); // 只关闭打开得到的子键句柄。 } } RegKey(const RegKey&) = delete; // 禁止两个对象关闭同一个 HKEY。 RegKey& operator=(const RegKey&) = delete; HKEY get() const { return key_; } // 借出句柄,不转移关闭责任。 private: HKEY key_ = nullptr; }; ``` 错误状态的第二个步骤是直接保存注册表 API 的 `LSTATUS`。`RegOpenKeyExW`、`RegQueryInfoKeyW` 和 `RegEnumValueW` 已经通过返回值给出错误码;在其它调用之后再读取线程错误状态,可能得到无关的旧值。 错误释放的第三个问题是遗漏早退分支。下面的直接 `return` 让 `openedKey` 没有机会关闭;读取大量键时,这类泄漏会累积系统句柄。 ```cpp // 错误:QuerySomething 失败时,openedKey 没有调用 RegCloseKey。 if (QuerySomething(openedKey) != ERROR_SUCCESS) { return; } ``` 并发变化的第四个步骤是限制重试次数。`ERROR_MORE_DATA` 表示查询到容量后数据又增长,重新查询上限并最多重试同一个索引几次即可;持续失败时记录根键、视图、索引和返回码,再继续其它位置,避免枚举停在一个不断变化的值上。 完整可运行程序在附件。 
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
上传的附件:
001-Windows-Run与RunOnce注册表启动项的安全枚举.zip
(163.40kb,4次下载)
收藏
・
0
点赞
・
1
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
wx_晨梦
感谢你的贡献,论坛因你而更加精彩!
2026-7-16 10:25
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
0
)
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
WangWei_CM.
30
发帖
1
回帖
40
RANK
关注
私信
他的文章
[原创] 进程模块枚举、映像身份与线程启动地址关联
215
[原创]Windows 进程 CPU、内存、I/O 与调度指标采样
981
[原创]跨位数进程的映像、命令行与环境块读取
535
[原创]进程实例身份、父子关系与会话归属
293
[原创]Windows 进程枚举NtQuerySystemInformation(SystemProcessInformation
635
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
谁下载
×
huangyalei
Fzzz
jyotidwi
mb_lthgjpwj
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部