首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
编程技术
发新帖
0
0
[原创]IFEO Debugger、VerifierDlls 与 SilentProcessExit 配置
发表于: 2026-7-25 22:14
296
[原创]IFEO Debugger、VerifierDlls 与 SilentProcessExit 配置
WangWei_CM.
2026-7-25 22:14
296
# IFEO Debugger、VerifierDlls 与 SilentProcessExit 配置 本节的目标是读取 Image File Execution Options(IFEO,映像文件执行选项)与 SilentProcessExit(静默进程退出监视)中的 `Debugger`、`VerifierDlls`、`MonitorProcess` 配置,并保留它们来自哪个注册表视图、哪个目标映像和哪个值名。 要完成这项检查,分为四步: 1. 定位 IFEO 与 SilentProcessExit 的六项来源,明确值名和 32 位、64 位注册表视图。 2. 打开每项来源的父键,枚举以目标映像名命名的直接子键。 3. 在每个目标映像子键中,按返回字节数读取指定的注册表值。 4. 验证值类型和 UTF-16 字节边界,解码文本并连同来源信息输出。 ```text 六项注册表来源 -> 目标映像子键 -> 指定值的原始字节 -> 类型验证和文本记录 ``` 这个流程只读取静态配置。读取结果可以说明配置在检查时存在,实际的调试器启动、验证器加载和退出监视触发仍要由进程或事件证据确认。 ## 一、第一步:定位配置来源和对象关系 第一步要确定每项配置的来源。IFEO 与 SilentProcessExit 都按目标映像名组织配置。Image File Execution Options(IFEO,映像文件执行选项)是 Windows 按可执行映像名保存创建相关设置的一组注册表项。SilentProcessExit 是另一组按映像名保存退出监视设置的注册表项。两类键、两个注册表视图和值名共同决定一条读取记录的来源。 ### 1. IFEO 使用可执行映像名作为配置匹配键 IFEO 按目标映像名匹配配置。Image File Execution Options(IFEO)是 Windows 的一组按可执行映像名组织的配置项,根键下的一级子键通常是文件名,例如 `example.exe`。当系统处理同名映像的创建请求时,相关组件可以读取该子键中的调试、验证或兼容性配置。 子键名是目标映像标识,不能单独证明磁盘上存在某个路径相同的文件。路径解析、哈希、签名和进程标识属于另一类信息。读取 IFEO 时应以“来源根键 + 视图 + 目标映像子键 + 值名”构成记录身份。 ```text Image File Execution Options target.exe:目标映像名 Debugger:创建时调试器重定向相关文本 GlobalFlag:全局标志配置 VerifierDlls:应用程序验证器相关模块文本 ``` ### 2. Debugger、验证器与静默退出监视有不同的触发点 不同值需要按进程生命周期解释。`Debugger` 与目标映像的创建处理有关,文本可指向调试器命令。`VerifierDlls` 与 Application Verifier 等验证器配置相关,是否参与还受其它验证器设置和系统条件影响。两者都位于 IFEO 层级,但并不表示相同的加载或启动规则。 `SilentProcessExit` 是独立父键,它也使用目标映像名建立子键。`MonitorProcess` 描述在静默进程退出监视条件满足时可使用的监视进程文本。是否触发还依赖对应的进程退出情形和相关标志配置,单独出现 `MonitorProcess` 值不代表它会在每一次目标程序退出时启动。 ### 3. 全局标志影响条件,监视器文本描述配置 SilentProcessExit 的触发条件与监视端配置需要分开保存。进程相关的全局标志可影响验证器、堆调试或静默退出监视等功能开关。SilentProcessExit 子键中的 `MonitorProcess` 值提供的是监视端配置。两类信息共同存在时,记录才能说明已观察到哪些条件和配置。 本节读取 `Debugger`、`VerifierDlls` 和 `MonitorProcess` 三个文本值。`GlobalFlag`、`ReportingMode`、`DumpFolder` 等字段属于理解触发条件时可能需要结合的其它配置,它们不在本节的读取范围内。三个已读取值仍应分别保存类型、原始数据和缺失状态,避免根据一个字段推导另一个字段的结果。 ### 4. 32 位与 64 位目标映像的配置来源可能不同 32 位与 64 位视图需要完整覆盖。64 位 Windows 对软件注册表的可重定向部分维护 32 位和 64 位视图,32 位和 64 位目标进程可读取不同的 IFEO、验证器或 SilentProcessExit 配置。检查程序自身架构会影响默认视图,因此必须显式请求两个视图。 输出中应写明“IFEO 64 位视图”“IFEO 32 位视图”“SilentProcessExit 64 位视图”等来源标签。某一视图键不存在、子键存在但值缺失、值类型不符合预期、值文本为空,都应是不同状态。 ### 5. 注册表配置不能替代运行时过程证据 静态配置不能当作事件记录。发现 `Debugger` 文本能说明读取时重定向配置存在。发现 `VerifierDlls` 能说明验证器模块文本存在。发现 `MonitorProcess` 能说明监视配置存在。实际创建、验证器加载、退出监视和监视进程启动需要从进程、事件日志或调试证据单独确认。 ### 6. IFEO 键、目标映像和实际文件路径要分开保存 目标映像子键名不能当作磁盘文件身份。IFEO 子键通常使用 `target.exe` 这样的映像名,系统在处理同名创建请求时匹配它。该字符串不包含目录、卷、文件 ID 或签名。不同目录中的同名文件可能触发同一个映像名规则,进程退出后 PID 也可能复用,因此静态配置记录和实际进程记录需要通过创建时间、映像路径、文件身份等独立字段关联。 每条检查结果至少保留父键路径、32/64 位视图、目标映像子键、值名、REG 类型、原始字节长度和读取状态。读取到命令文本后可追加严格按返回长度解码的原文,但不拆分为“程序路径”与“参数”来替代原始数据。这样才能区分“某个 IFEO 配置存在”“当前文件路径可见”和“某次目标进程实际受该配置影响”。 读取时间和当前调用账户也应成为记录字段,因为权限、注册表重定向和并发修改都会影响一次观察的范围。 输出按来源逐条保留,才能在后续采样中可靠比较配置变化。 完成第一步后,已经知道每条记录需要保留父键路径、注册表视图、目标映像名和值名。接下来需要逐项打开这些父键,才能取得实际存在的目标映像子键。 ## 二、第二步:打开来源键并枚举目标映像子键 IFEO 位于 `Image File Execution Options`,每个一级子键通常是目标映像名。`Debugger` 是进程启动时的调试器重定向文本,`VerifierDlls` 是验证器 DLL 配置,两类值按各自的命令规则解释。 SilentProcessExit 位于独立的 `SilentProcessExit` 键。其子键同样以目标映像名组织,`MonitorProcess` 是退出监视条件下的进程文本,与 IFEO 启动重定向分开记录。 ```cpp // 意义:从根键或已打开的父键中打开一个现有子键。 // 返回:ERROR_SUCCESS 表示成功。ERROR_FILE_NOT_FOUND 表示键不存在。 // ERROR_ACCESS_DENIED 表示当前令牌没有 samDesired 所请求的访问权限。 // 成功时 *phkResult 是调用方拥有的 HKEY,必须用 RegCloseKey 恰好关闭一次。 LSTATUS RegOpenKeyExW( HKEY hKey, // 输入:HKEY_LOCAL_MACHINE 等根键或已打开父键。预定义根键不关闭。 LPCWSTR lpSubKey, // 输入:相对 UTF-16 路径,必须 NUL 结尾。nullptr 表示 hKey 本身。 DWORD ulOptions, // 输入:保留参数,必须为 0。 REGSAM samDesired, // 输入:所需权限。本节按操作分为 KEY_ENUMERATE_SUB_KEYS 或 KEY_QUERY_VALUE,并附加 KEY_WOW64_*。 PHKEY phkResult // 输出:非空指针,成功时接收 HKEY。失败时输出无效且不得使用。 ); // 意义:释放调用方拥有的注册表键句柄。 // 返回:ERROR_SUCCESS 表示关闭请求成功。关闭后 hKey 不能重复传给查询 API。 LSTATUS RegCloseKey( HKEY hKey // 输入/释放:RegOpenKeyExW 成功返回的 HKEY,不能是预定义根键。 ); ``` ```cpp const struct { const wchar_t* parent; const wchar_t* valueName; REGSAM view; } sources[] = { { L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options", L"Debugger", KEY_WOW64_64KEY }, { L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options", L"Debugger", KEY_WOW64_32KEY }, { L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options", L"VerifierDlls", KEY_WOW64_64KEY }, { L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options", L"VerifierDlls", KEY_WOW64_32KEY }, { L"Software\\Microsoft\\Windows NT\\CurrentVersion\\SilentProcessExit", L"MonitorProcess", KEY_WOW64_64KEY }, { L"Software\\Microsoft\\Windows NT\\CurrentVersion\\SilentProcessExit", L"MonitorProcess", KEY_WOW64_32KEY }, }; ``` ### 2.1 枚举所有目标映像子键 目标映像名长度应使用动态缓冲区读取。`RegEnumKeyExW` 返回的名称长度单位是 UTF-16 字符数,遇到 `ERROR_MORE_DATA` 后扩大缓冲区再重试。 ```cpp // 意义:按零基索引读取一个直接子键名。 // 返回:ERROR_SUCCESS 表示成功。ERROR_NO_MORE_ITEMS 表示已到达枚举最后。 // ERROR_MORE_DATA 表示 *lpcchName 指定的字符容量不足。 LSTATUS RegEnumKeyExW( HKEY hKey, // 输入:已打开且具有 KEY_ENUMERATE_SUB_KEYS 的父键。不转移所有权。 DWORD dwIndex, // 输入:从零开始的直接子键索引。它会随并发修改变化,不是稳定身份。 LPWSTR lpName, // 输出:接收 NUL 结尾 UTF-16 名称的缓冲区。不可为 nullptr。 LPDWORD lpcchName, // 输入/输出:lpName 容量/实际长度,单位为 wchar_t,实际长度不含 NUL。 LPDWORD lpReserved, // 输入:保留参数,必须为 nullptr。 LPWSTR lpClass, // 输出:可选键类名缓冲区。本节无需,传 nullptr。 LPDWORD lpcchClass, // 输入/输出:lpClass 容量/长度。lpClass 为 nullptr 时传 nullptr。 PFILETIME lpftLastWriteTime // 输出:可选最后写入时间。本节传 nullptr。 ); ``` ```cpp std::vector<wchar_t> imageName(256, L'\0'); DWORD imageNameChars = static_cast<DWORD>(imageName.size()); LSTATUS status = RegEnumKeyExW(parentKey, index, imageName.data(), &imageNameChars, nullptr, nullptr, nullptr, nullptr); if (status == ERROR_SUCCESS) { std::wstring targetImage(imageName.data(), imageNameChars); std::wstring childPath = parentPath + L"\\" + targetImage; } ``` 子键存在而指定值未设置是常见状态。记录仍保留目标映像名和子键路径,避免只展示有配置的少数子键而掩盖完整范围。 ```cpp // 错误示例:只枚举 Debugger 存在的子键。 if (ReadValue(childKey, L"Debugger").empty()) continue; ``` 完成第二步后,每条记录都有了来源标签和目标映像名。下一步需要读取该子键中的指定值,并把“值不存在”“值太大”“访问失败”和“读取成功”分开记录。 ## 三、第三步:按返回字节数读取指定配置值 `Debugger`、`VerifierDlls`、`MonitorProcess` 都通过注册表值读取。先查询数据字节数,再分配缓冲区。并发修改导致 `ERROR_MORE_DATA` 时有限重试。 ```cpp // 意义:读取一个指定值的注册表类型和原始字节。 // 返回:ERROR_SUCCESS 表示成功。ERROR_FILE_NOT_FOUND 表示值未设置。 // ERROR_MORE_DATA 表示 *lpcbData 指定的字节容量不足。 LSTATUS RegQueryValueExW( HKEY hKey, // 输入:已打开且有 KEY_QUERY_VALUE 权限的子键。不转移所有权。 LPCWSTR lpValueName, // 输入:NUL 结尾 UTF-16 值名,例如 Debugger。nullptr/L"" 表示默认值。 LPDWORD lpReserved, // 输入:保留参数,必须为 nullptr。 LPDWORD lpType, // 输出:REG_SZ、REG_EXPAND_SZ 等类型。本节必须检查它。 LPBYTE lpData, // 输出:接收原始字节。第一次只查询长度时传 nullptr。 LPDWORD lpcbData // 输入/输出:lpData 容量/实际长度,单位始终为字节。不可为 nullptr。 ); ``` ```cpp #include <Windows.h> #include <utility> #include <vector> struct RawRegistryValue { DWORD type = REG_NONE; // 成功读取时的 REG_* 类型。 std::vector<BYTE> bytes; // 成功读取时的原始数据,长度单位为字节。 }; // 正确示范:完整读取一个值,并对两次调用间发生的长度变化进行有界重试。 static LSTATUS ReadRawRegistryValue( HKEY key, // 输入:已打开且具有 KEY_QUERY_VALUE 的键。调用方保持关闭责任。 const wchar_t* valueName, // 输入:NUL 结尾 UTF-16 值名,例如 L"Debugger"。 RawRegistryValue& result) // 输出:仅在 ERROR_SUCCESS 时写入类型和全部原始字节。 { for (int attempt = 0; attempt != 3; ++attempt) { DWORD type = REG_NONE; DWORD requiredBytes = 0; LSTATUS status = RegQueryValueExW( key, valueName, nullptr, &type, nullptr, &requiredBytes); if (status != ERROR_SUCCESS) { return status; // ERROR_FILE_NOT_FOUND、ERROR_ACCESS_DENIED 等由调用方按子键记录。 } std::vector<BYTE> bytes(requiredBytes); // requiredBytes 是字节,不能当作 wchar_t 数量。 DWORD actualBytes = requiredBytes; status = RegQueryValueExW( key, valueName, nullptr, &type, bytes.empty() ? nullptr : bytes.data(), &actualBytes); if (status == ERROR_MORE_DATA) { continue; // 配置变长,下一轮重新测量容量。 } if (status != ERROR_SUCCESS) { return status; } bytes.resize(actualBytes); result.type = type; result.bytes = std::move(bytes); return ERROR_SUCCESS; } return ERROR_MORE_DATA; // 连续变化超过重试上限,保留不稳定状态。 } RawRegistryValue debugger; const LSTATUS status = ReadRawRegistryValue(childKey, L"Debugger", debugger); if (status == ERROR_SUCCESS && (debugger.type == REG_SZ || debugger.type == REG_EXPAND_SZ) && debugger.bytes.size() % sizeof(wchar_t) == 0) { // 仅在类型与 UTF-16 字节对齐都成立后,再用返回字节范围构造文本。 } ``` 完成第三步后,已经得到注册表类型和准确的原始字节范围。接下来要先验证类型与字节边界,再把符合条件的数据解释为 UTF-16 文本。 ## 四、第四步:验证类型、解码文本并标注证据范围 第四步要避免把任意注册表数据当作命令文本。`REG_SZ` 是以 UTF-16 字符串保存的普通文本。`REG_EXPAND_SZ` 也是 UTF-16 文本,其中可能含有 `%SystemRoot%` 这类环境变量标记。只有值类型属于这两类,并且返回字节数能被 `sizeof(wchar_t)` 整除时,读取程序才可以按 UTF-16 字符范围构造文本。 ```cpp #include <cstring> #include <string> // 正确示范:value.bytes 的长度来自 RegQueryValueExW 返回的实际字节数。 // 返回 true 表示 output 接收了按精确字节范围构造的文本。返回 false 时调用方保留原始字节,不解释为文本。 static bool DecodeRegistryText(const RawRegistryValue& value, std::wstring& output) { output.clear(); // output 由调用方拥有,先清空上一次的内容,避免失败时保留旧文本。 if (value.type != REG_SZ && value.type != REG_EXPAND_SZ) { return false; // 其它 REG_* 类型没有本节定义的 UTF-16 命令文本语义。 } if (value.bytes.size() % sizeof(wchar_t) != 0) { return false; // UTF-16 单元是两个字节,奇数字节长度表明数据边界无效。 } output.resize(value.bytes.size() / sizeof(wchar_t)); if (!value.bytes.empty()) { std::memcpy(output.data(), value.bytes.data(), value.bytes.size()); } // NUL 只在最后一个 UTF-16 单元时移除。中间出现的 NUL 仍属于原始数据的一部分。 if (!output.empty() && output.back() == L'\0') { output.pop_back(); } return true; } std::wstring text; if (DecodeRegistryText(debugger, text)) { // 此处可连同父键、视图、目标映像名和值名输出 text。 } ``` 下面的写法会忽略类型和实际字节数。二进制数据或没有终止 NUL 的文本会被错误读取,甚至会访问缓冲区外的数据。 ```cpp // 错误示例:bytes 的有效范围由 returnedBytes 决定,不能直接构造依赖 NUL 的字符串。 std::wstring text(reinterpret_cast<const wchar_t*>(debugger.bytes.data())); ``` 字符串仅在 `REG_SZ` 或 `REG_EXPAND_SZ` 且返回字节数符合 UTF-16 对齐时解码。原始文本、目标映像名和值名共同构成记录,三个值具有各自的触发条件和含义。 `Debugger` 描述启动重定向,`VerifierDlls` 描述验证器 DLL 配置,`MonitorProcess` 描述静默退出监视。读取到某个值只证明该配置在读取时存在,实际触发还取决于相应映像的启动、验证器设置或退出条件。 完整可运行程序在附件  <mark class="encrypted">93cK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4j5h3&6Y4N6$3g2A6j5$3#2Q4x3X3g2D9j5h3&6*7L8%4g2#2i4K6u0W2j5$3!0E0i4K6u0r3K9h3!0g2k6o6l9K6P5h3#2G2k6K6k6T1</mark>
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
上传的附件:
019-IFEO调试器验证器与SilentProcessExit配置.zip
(160.33kb,1次下载)
收藏
・
0
点赞
・
0
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
查看更多
赞赏
×
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、移动、智能设备安全研究及逆向工程的开发者社区
谁下载
×
Fzzz
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部