首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
编程技术
发新帖
2
1
[原创]Internet Explorer 扩展注册项与 COM 实现解析
发表于: 2026-7-23 11:14
2717
[原创]Internet Explorer 扩展注册项与 COM 实现解析
WangWei_CM.
2026-7-23 11:14
2717
# Internet Explorer 扩展注册项与 COM 实现解析 文章所述全部在KswordARK中实现,项目地址<mark class="encrypted">799K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6w2f1%4N6G2M7X3c8p5c8g2k6Q4x3V1k6w2f1%4N6G2M7X3b7`.</mark>  要得到 Internet Explorer(IE)扩展注册项及其关联 COM(Component Object Model,组件对象模型)服务器的静态记录,分为四步: 1. 确定扩展来源、注册表根和 32 位或 64 位视图。 2. 按值型和子键型结构读取扩展记录的原始数据。 3. 验证 CLSID(Class Identifier,COM 类标识符),并保留它来自哪个字段和注册表位置。 4. 查询 COM Classes 注册区,记录 `InprocServer32` 与 `LocalServer32` 的服务器文本。 注册表是 Windows 保存系统和用户配置的分层数据库。COM Classes 注册区保存类标识符与创建该类所需服务器之间的映射。`InprocServer32` 表示进程内 DLL 服务器,`LocalServer32` 表示可单独启动的本地 EXE 服务器。 IE 扩展来源键 -> 值名、值数据或子键名 -> 有效 CLSID -> HKCU 或 HKLM Classes 注册 -> `InprocServer32` 或 `LocalServer32` 默认值 `HKCU` 是 `HKEY_CURRENT_USER`,保存当前登录用户的配置。`HKLM` 是 `HKEY_LOCAL_MACHINE`,保存整台计算机的配置。两个范围都可能提供同一个 CLSID 的注册信息。 这四步得到的是注册表在读取时的静态关系。注册项存在、服务器文件可见和浏览器进程实际加载扩展是不同层次的事实,最后需要分别表述。 ## 一、第一步:确定扩展来源、对象和读取范围 第一步要先确定 IE 从哪里发现扩展,以及每个注册表位置代表什么。COM 是 Windows 用来标识和创建可复用组件的一套规则。IE 历史上提供 BHO(Browser Helper Object,浏览器帮助对象)、Explorer Bar(浏览器停靠栏)、Toolbar(浏览器工具栏)和 URLSearchHook(地址栏搜索处理对象)等扩展点。浏览器先从对应注册表来源取得 CLSID,再通过 COM Classes 注册定位 DLL 或本地 EXE 服务器。 ```text IE 扩展来源键 -> 值名、值数据或子键名提供 CLSID -> 查询 HKCU 或 HKLM Classes 注册 -> InprocServer32 或 LocalServer32 提供服务器文本 -> COM 激活扩展对象 ``` ### 1.1 IE 的扩展点通过注册表发现 COM 类 浏览器不会直接从扩展路径列表加载对象。IE 读取对应注册表位置,得到 CLSID,再通过 COM 激活该 CLSID 对应的对象。 CLSID 是用花括号包围的 GUID(Globally Unique Identifier,全局唯一标识符),例如 `{01234567-89AB-CDEF-0123-456789ABCDEF}`。它用于标识 COM 类,不能凭自身说明扩展名称、服务器路径或浏览器是否已加载它。注册项中出现 CLSID 后,还需要查询 Classes 注册根下的服务器键,才能找到实现对象的 DLL 或本地服务器命令文本。 ```text IE 扩展来源键 -> CLSID -> InprocServer32(进程内 DLL)或 LocalServer32(本地 EXE 服务器) ``` ### 1.2 BHO、Explorer Bar、Toolbar 和 URLSearchHook 的职责不同 扩展种类决定注册入口和接口要求。BHO 是可在浏览器进程中参与导航和文档事件的对象。Explorer Bar 通常提供停靠式侧边栏用户界面。Toolbar 关联浏览器工具栏。URLSearchHook 可参与地址栏输入的搜索处理。服务器定位步骤都遵循 COM 注册规则。 一个 CLSID 可能出现在多个来源键中。这种情况表示同一个 COM 类被不同扩展点引用,记录时应保留每次出现的来源路径、值名或子键名,而不是按 CLSID 折叠成唯一记录。服务器注册同样可能同时存在用户级和机器级、32 位和 64 位不同视图。 ### 1.3 历史扩展配置需要保留版本和宿主边界 IE 扩展机制需要保留历史与宿主边界。现代 Windows 版本上的 IE 可用性、IE 模式和实际宿主进程取决于系统组件、策略和安装状态。注册表中保留的扩展项只证明配置存在,不能单独证明某个浏览器进程当前会调用它。 读取结果可明确写出“扩展来源”“CLSID 注册状态”“服务器文本”和“读取视图”。是否被加载、是否实现目标接口、是否在某个浏览器版本中启用,需要由实际 COM 激活、进程模块或浏览器运行时证据进一步确认。 ### 1.4 用户级和机器级 Classes 数据要保留独立来源 `HKEY_CLASSES_ROOT` 提供合并后的类注册视图,底层可来自 `HKCU\Software\Classes` 或 `HKLM\Software\Classes`。为了看清原始配置,应分别查询这两个根。 `InprocServer32` 的默认值通常是 DLL 路径,DLL 需要与加载它的浏览器进程架构匹配。`LocalServer32` 的默认值通常是可带参数的 EXE 命令文本。读取默认值时保存其原文,不用第一个空格强行截断路径,也不把服务器文本替换为文件签名结论。 ### 1.5 浏览器进程、扩展对象与网页内容处于不同边界 浏览器宿主和扩展对象需要分开。IE 扩展的 COM 服务器可能由浏览器进程在进程内加载,也可能由系统单独启动为本地服务器。BHO 等进程内组件与浏览器共享同一地址空间、线程和崩溃范围。网页 URL、扩展 CLSID 和 DLL 文件路径属于不同身份字段。 网页内容属于浏览器呈现和安全区域处理的对象,COM 扩展注册属于 Windows 类注册对象,DLL 或 EXE 属于文件系统对象。调查时若只得到 CLSID,最多说明存在一个注册的类身份。若要解释页面行为,还需要独立的浏览器进程、导航事件、加载模块或网络记录证据。 完成第一步后,已经有了扩展类型、用户级或机器级范围,以及 32 位和 64 位视图这三类读取边界。接下来要按来源键的真实结构读取原始数据,才能避免把值名、值数据和子键名混成同一种线索。 ## 二、第二步:按值型和子键型结构读取扩展记录 第二步要按来源键保存记录,不能只留下看似有用的字符串。`URLSearchHooks` 与 `Toolbar` 要枚举键中的所有值。CLSID 可能是值名,值数据可能是显示名称、空字符串、二进制标志或另一个 CLSID,值名和值数据不能混为同一字段。 `Browser Helper Objects` 与 `Explorer Bars` 要枚举一级子键。子键名经常是 CLSID,默认值可提供显示名称。默认值缺失不影响通过子键名定位 COM 注册。 读取时分别覆盖 `HKLM`、`HKCU` 的 64 位与 32 位视图。64 位进程的默认注册表视图只覆盖一侧,显式使用 `KEY_WOW64_64KEY` 与 `KEY_WOW64_32KEY` 才能取得两份配置。 ```cpp // 意义:打开注册表子键。 // 返回:ERROR_SUCCESS 表示成功。其它返回值是 Win32 错误码。 // 成功时 phkResult 接收 HKEY,调用方必须关闭它。 LSTATUS RegOpenKeyExW( HKEY hKey, LPCWSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult ); // 意义:关闭打开成功的键句柄。 // 返回:ERROR_SUCCESS 表示成功。 LSTATUS RegCloseKey(HKEY hKey); ``` ```cpp HKEY key64 = nullptr; RegOpenKeyExW(HKEY_LOCAL_MACHINE, sourcePath, 0, KEY_QUERY_VALUE | KEY_WOW64_64KEY, &key64); HKEY key32 = nullptr; RegOpenKeyExW(HKEY_LOCAL_MACHINE, sourcePath, 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &key32); ``` ### 2.1 读取值型扩展来源 值枚举先取得最大值名和数据长度,再按各自单位分配缓冲区。值名长度是 UTF-16 字符数,值数据长度是字节数。注册表并发变化时 `ERROR_MORE_DATA` 表示其中一侧容量需要扩大。 ```cpp // 意义:取得键中的值数、最大值名长度和最大数据长度。 // 返回:ERROR_SUCCESS 表示成功。长度适合作为本次枚举的初始容量。 LSTATUS RegQueryInfoKeyW( HKEY hKey, LPWSTR lpClass, LPDWORD lpcchClass, LPDWORD lpReserved, LPDWORD lpcSubKeys, LPDWORD lpcbMaxSubKeyLen, LPDWORD lpcbMaxClassLen, LPDWORD lpcValues, LPDWORD lpcbMaxValueNameLen, LPDWORD lpcbMaxValueLen, LPDWORD lpcbSecurityDescriptor, PFILETIME lpftLastWriteTime ); // 意义:按索引读取一个值名、类型和原始数据。 // 返回:ERROR_SUCCESS 表示成功。ERROR_NO_MORE_ITEMS 表示结束。 // ERROR_MORE_DATA 表示名称或数据缓冲区不足。 LSTATUS RegEnumValueW( HKEY hKey, DWORD dwIndex, LPWSTR lpValueName, LPDWORD lpcchValueName, LPDWORD lpReserved, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData ); ``` ```cpp std::vector<wchar_t> valueName(maxValueNameChars + 1, L'\0'); std::vector<BYTE> valueData(maxValueDataBytes); DWORD nameChars = static_cast<DWORD>(valueName.size()); DWORD dataBytes = static_cast<DWORD>(valueData.size()); DWORD type = REG_NONE; LSTATUS status = RegEnumValueW(key, index, valueName.data(), &nameChars, nullptr, &type, valueData.empty() ? nullptr : valueData.data(), &dataBytes); ``` 值数据只在类型为 `REG_SZ` 或 `REG_EXPAND_SZ` 且字节数满足 UTF-16 对齐时转换为文本。`Toolbar` 中的 `REG_BINARY` 值仍作为完整记录输出类型和字节数,不能按字符串读取。 ```cpp // 错误示例:只读取字符串类型,二进制类型的 CLSID 值名会被遗漏。 if (type != REG_SZ) continue; ``` ### 2.2 读取 BHO 与 Explorer Bar 子键 子键枚举使用 `RegEnumKeyExW`。某些扩展键只授予子键枚举权限,名称缓冲区从合理初始值开始并在 `ERROR_MORE_DATA` 时扩容,能避免依赖不必要的附加查询权限。 ```cpp // 意义:按索引读取一级子键名。 // 返回:ERROR_SUCCESS 表示成功。ERROR_NO_MORE_ITEMS 表示结束。 // ERROR_MORE_DATA 表示 lpName 的字符容量不足。 LSTATUS RegEnumKeyExW( HKEY hKey, DWORD dwIndex, LPWSTR lpName, LPDWORD lpcchName, LPDWORD lpReserved, LPWSTR lpClass, LPDWORD lpcchClass, PFILETIME lpftLastWriteTime ); ``` 默认值通过空值名读取。默认值存在时保存类型和原始文本。默认值缺失时保留子键 CLSID 与“默认值未设置”状态。 ```cpp DWORD type = REG_NONE; DWORD bytes = 0; LSTATUS status = RegQueryValueExW(childKey, L"", nullptr, &type, nullptr, &bytes); if (status == ERROR_FILE_NOT_FOUND) { // 子键存在,默认值未设置。 } ``` ### 2.3 区分资源、权限和长度单位 这一阶段还要把资源所有权、访问权限和长度单位分开处理。`HKEY` 是已打开注册表键的资源引用,`RegOpenKeyExW` 成功后由得到它的一方调用 `RegCloseKey`。`REGSAM` 是访问掩码,决定这次句柄允许查询值还是枚举子键。`DWORD` 是 32 位无符号整数,经常承载长度或访问位。 `RegEnumValueW` 的 `lpcchValueName` 以 UTF-16 宽字符数表示名称缓冲区容量和实际长度,`lpcbData` 以字节表示数据缓冲区容量和实际长度。UTF-16 是 Windows 宽字符 API 使用的文本编码。返回的文本数据未必包含 NUL 终止符,因此要按 API 返回的确切长度构造字符串。只有 `REG_SZ` 或 `REG_EXPAND_SZ` 且数据字节数可被 `sizeof(wchar_t)` 整除时,才把原始字节解码为文本。 完成第二步后,已经取得每条扩展记录的来源根、路径、注册表视图、值名或子键名、类型和原始数据。接下来要从这些独立字段中确认 CLSID,防止任意显示文本进入 COM Classes 查询。 ## 三、第三步:验证 CLSID 并保留来源线索 ### 3.1 按完整 GUID 结构验证 CLSID 第三步要确认候选文本确实是 COM 类标识符。CLSID 使用 `{8-4-4-4-12}` 结构,共 38 个字符。两侧是花括号,四个连字符处于固定位置,余下位置只能出现十六进制字符。值名或子键名满足该结构时可以作为 CLSID。字符串值数据恰好满足该结构时也可以作为 CLSID。其它文本继续作为显示数据保存。 ```cpp // 作用:判断一个 UTF-16 字符是否属于 GUID 可用的十六进制字符。 // 返回:true 表示 c 是 0-9、a-f 或 A-F。 bool IsHex(const wchar_t c) { return (c >= L'0' && c <= L'9') || (c >= L'a' && c <= L'f') || (c >= L'A' && c <= L'F'); } // 作用:验证 text 是否完整符合带花括号的 CLSID 文本格式。 // 输入:text 是带明确长度的 UTF-16 文本,不要求以 NUL 结尾。 // 返回:true 表示文本可作为 CLSID 候选,false 表示只能保留为原始显示数据。 bool IsClsidText(std::wstring_view text) { if (text.size() != 38 || text.front() != L'{' || text.back() != L'}' || text[9] != L'-' || text[14] != L'-' || text[19] != L'-' || text[24] != L'-') { return false; } for (std::size_t index = 1; index < 37; ++index) { if (index != 9 && index != 14 && index != 19 && index != 24 && !IsHex(text[index])) { return false; } } return true; } ``` ### 3.2 为有效 CLSID 保留来源字段 来源记录不能在格式验证后丢失。每个有效 CLSID 都要同时保存扩展类型、`HKCU` 或 `HKLM` 根、32 位或 64 位视图、来源路径,以及它来自值名、值数据还是子键名。相同 CLSID 出现在多个来源时,代表多个独立引用,不能把它们合并成一条无来源的记录。 完成第三步后,已经得到可追溯的 CLSID 候选,且每个候选仍能回到具体扩展记录。接下来查询 COM Classes 注册,才能把类标识符连接到进程内 DLL 或本地 EXE 服务器文本。 ## 四、第四步:定位 COM 服务器并区分证据范围 ### 4.1 在对应 Classes 根和视图中查询服务器键 第四步要按同一注册表视图查询 CLSID 的 COM Classes 注册。服务器定位分别查询 `HKCU\Software\Classes\CLSID\{CLSID}` 与 `HKLM\Software\Classes\CLSID\{CLSID}`。每个根都在当前 32 位或 64 位视图下读取 `InprocServer32`、`LocalServer32` 的默认值,找到一个服务器后仍要继续读取另一种服务器类型。 ```cpp const std::wstring base = L"Software\\Classes\\CLSID\\" + clsid; const std::wstring inproc = base + L"\\InprocServer32"; const std::wstring local = base + L"\\LocalServer32"; // 对 HKCU 与 HKLM 分别以 KEY_WOW64_64KEY、KEY_WOW64_32KEY 查询两个子键的默认值。 ``` 读取默认值时使用 `RegQueryValueExW`。这个函数先用空数据指针取得所需字节数,再使用同一个值名读回数据。读取期间注册表内容可能变化,第二次调用返回 `ERROR_MORE_DATA` 时要按新的字节数扩大缓冲区后重试。 ```cpp // 作用:读取已打开键中一个值的类型和数据。 // 返回:ERROR_SUCCESS 表示成功,ERROR_FILE_NOT_FOUND 表示指定值不存在, // ERROR_MORE_DATA 表示数据缓冲区容量不足。 // hKey:输入,已打开的键,函数不取得所有权,也不关闭它。 // lpValueName:输入,NUL 结尾 UTF-16 值名。L"" 表示默认值,可为 nullptr 表示默认值。 // lpReserved:输入,必须为 nullptr。 // lpType:输出,可为 nullptr。指向 DWORD,接收 REG_SZ、REG_EXPAND_SZ 等值类型。 // lpData:输出缓冲区,可为 nullptr。nullptr 时只查询所需长度。 // lpcbData:输入和输出,指向字节数。输入为 lpData 容量,输出为实际数据字节数。 LSTATUS RegQueryValueExW( HKEY hKey, LPCWSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData ); ``` ### 4.2 按注册、文件和运行时三个层次输出结论 `InprocServer32` 记录进程内服务器文本,通常指向由浏览器进程加载的 DLL。`LocalServer32` 记录本地服务器命令文本,通常指向可单独启动的 EXE。它们属于 COM 注册信息,原始文本应独立保存。默认值文本不能直接证明文件存在、文件具有有效签名,或 IE 已经加载了这个 COM 类。 输出结论要保持证据范围。Classes 键存在说明 COM 注册数据在读取时存在。服务器默认值能够解码说明得到了候选路径或命令文本。候选文件存在、PE 架构匹配和签名验证成功属于文件层面的结论。浏览器是否在某次会话中加载扩展、组件是否成功创建、页面是否调用接口,需要进程模块、COM 激活或浏览器运行时记录支持。 完整可运行程序在附件 <mark class="encrypted">229K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4j5h3&6Y4N6$3g2A6j5$3#2Q4x3X3g2D9j5h3&6*7L8%4g2#2i4K6u0W2j5$3!0E0i4K6u0r3K9g2p5&6c8g2p5K6P5e0j5H3z5r3#2B7</mark> 
登录后可查看完整内容
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
上传的附件:
014-Internet-Explorer扩展注册项与COM实现解析.zip
(165.40kb,6次下载)
收藏
・
2
点赞
・
1
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
appview
这个讨论对我很有帮助,谢谢!
2026-7-27 14:51
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
0
)
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
WangWei_CM.
30
发帖
1
回帖
40
RANK
关注
私信
他的文章
[原创] 进程模块枚举、映像身份与线程启动地址关联
216
[原创]Windows 进程 CPU、内存、I/O 与调度指标采样
981
[原创]跨位数进程的映像、命令行与环境块读取
535
[原创]进程实例身份、父子关系与会话归属
295
[原创]Windows 进程枚举NtQuerySystemInformation(SystemProcessInformation
637
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
谁下载
×
wowocock
sinker_
Fzzz
mb_eizetlni
mb_lthgjpwj
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部