Internet Explorer 扩展注册项与 COM 实现解析

Internet Explorer 扩展注册项与 COM 实现解析 Internet Explorer 扩展注册项与 COM 实现解析所有内容均已在KswordARK中实现项目地址https://github.com/KSwordDEV/KSword要得到 Internet ExplorerIE扩展注册项及其关联 COMComponent Object Model组件对象模型服务器的静态记录分为四步确定扩展来源、注册表根和 32 位或 64 位视图。按值型和子键型结构读取扩展记录的原始数据。验证 CLSIDClass IdentifierCOM 类标识符并保留它来自哪个字段和注册表位置。查询 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 历史上提供 BHOBrowser Helper Object浏览器帮助对象、Explorer Bar浏览器停靠栏、Toolbar浏览器工具栏和 URLSearchHook地址栏搜索处理对象等扩展点。浏览器先从对应注册表来源取得 CLSID再通过 COM Classes 注册定位 DLL 或本地 EXE 服务器。IE 扩展来源键 - 值名、值数据或子键名提供 CLSID - 查询 HKCU 或 HKLM Classes 注册 - InprocServer32 或 LocalServer32 提供服务器文本 - COM 激活扩展对象1.1 IE 的扩展点通过注册表发现 COM 类浏览器不会直接从扩展路径列表加载对象。IE 读取对应注册表位置得到 CLSID再通过 COM 激活该 CLSID 对应的对象。CLSID 是用花括号包围的 GUIDGlobally Unique Identifier全局唯一标识符例如{01234567-89AB-CDEF-0123-456789ABCDEF}。它用于标识 COM 类不能凭自身说明扩展名称、服务器路径或浏览器是否已加载它。注册项中出现 CLSID 后还需要查询 Classes 注册根下的服务器键才能找到实现对象的 DLL 或本地服务器命令文本。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才能取得两份配置。// 意义打开注册表子键。// 返回ERROR_SUCCESS 表示成功。其它返回值是 Win32 错误码。// 成功时 phkResult 接收 HKEY调用方必须关闭它。LSTATUSRegOpenKeyExW(HKEY hKey,LPCWSTR lpSubKey,DWORD ulOptions,REGSAM samDesired,PHKEY phkResult);// 意义关闭打开成功的键句柄。// 返回ERROR_SUCCESS 表示成功。LSTATUSRegCloseKey(HKEY hKey);HKEY key64nullptr;RegOpenKeyExW(HKEY_LOCAL_MACHINE,sourcePath,0,KEY_QUERY_VALUE|KEY_WOW64_64KEY,key64);HKEY key32nullptr;RegOpenKeyExW(HKEY_LOCAL_MACHINE,sourcePath,0,KEY_QUERY_VALUE|KEY_WOW64_32KEY,key32);2.1 读取值型扩展来源值枚举先取得最大值名和数据长度再按各自单位分配缓冲区。值名长度是 UTF-16 字符数值数据长度是字节数。注册表并发变化时ERROR_MORE_DATA表示其中一侧容量需要扩大。// 意义取得键中的值数、最大值名长度和最大数据长度。// 返回ERROR_SUCCESS 表示成功。长度适合作为本次枚举的初始容量。LSTATUSRegQueryInfoKeyW(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 表示名称或数据缓冲区不足。LSTATUSRegEnumValueW(HKEY hKey,DWORD dwIndex,LPWSTR lpValueName,LPDWORD lpcchValueName,LPDWORD lpReserved,LPDWORD lpType,LPBYTE lpData,LPDWORD lpcbData);std::vectorwchar_tvalueName(maxValueNameChars1,L\0);std::vectorBYTEvalueData(maxValueDataBytes);DWORD nameCharsstatic_castDWORD(valueName.size());DWORD dataBytesstatic_castDWORD(valueData.size());DWORD typeREG_NONE;LSTATUS statusRegEnumValueW(key,index,valueName.data(),nameChars,nullptr,type,valueData.empty()?nullptr:valueData.data(),dataBytes);值数据只在类型为REG_SZ或REG_EXPAND_SZ且字节数满足 UTF-16 对齐时转换为文本。Toolbar中的REG_BINARY值仍作为完整记录输出类型和字节数不能按字符串读取。// 错误示例只读取字符串类型二进制类型的 CLSID 值名会被遗漏。if(type!REG_SZ)continue;2.2 读取 BHO 与 Explorer Bar 子键子键枚举使用RegEnumKeyExW。某些扩展键只授予子键枚举权限名称缓冲区从合理初始值开始并在ERROR_MORE_DATA时扩容能避免依赖不必要的附加查询权限。// 意义按索引读取一级子键名。// 返回ERROR_SUCCESS 表示成功。ERROR_NO_MORE_ITEMS 表示结束。// ERROR_MORE_DATA 表示 lpName 的字符容量不足。LSTATUSRegEnumKeyExW(HKEY hKey,DWORD dwIndex,LPWSTR lpName,LPDWORD lpcchName,LPDWORD lpReserved,LPWSTR lpClass,LPDWORD lpcchClass,PFILETIME lpftLastWriteTime);默认值通过空值名读取。默认值存在时保存类型和原始文本。默认值缺失时保留子键 CLSID 与“默认值未设置”状态。DWORD typeREG_NONE;DWORD bytes0;LSTATUS statusRegQueryValueExW(childKey,L,nullptr,type,nullptr,bytes);if(statusERROR_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。其它文本继续作为显示数据保存。// 作用判断一个 UTF-16 字符是否属于 GUID 可用的十六进制字符。// 返回true 表示 c 是 0-9、a-f 或 A-F。boolIsHex(constwchar_tc){return(cL0cL9)||(cLacLf)||(cLAcLF);}// 作用验证 text 是否完整符合带花括号的 CLSID 文本格式。// 输入text 是带明确长度的 UTF-16 文本不要求以 NUL 结尾。// 返回true 表示文本可作为 CLSID 候选false 表示只能保留为原始显示数据。boolIsClsidText(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-){returnfalse;}for(std::size_t index1;index37;index){if(index!9index!14index!19index!24!IsHex(text[index])){returnfalse;}}returntrue;}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的默认值找到一个服务器后仍要继续读取另一种服务器类型。conststd::wstring baseLSoftware\\Classes\\CLSID\\clsid;conststd::wstring inprocbaseL\\InprocServer32;conststd::wstring localbaseL\\LocalServer32;// 对 HKCU 与 HKLM 分别以 KEY_WOW64_64KEY、KEY_WOW64_32KEY 查询两个子键的默认值。读取默认值时使用RegQueryValueExW。这个函数先用空数据指针取得所需字节数再使用同一个值名读回数据。读取期间注册表内容可能变化第二次调用返回ERROR_MORE_DATA时要按新的字节数扩大缓冲区后重试。// 作用读取已打开键中一个值的类型和数据。// 返回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 容量输出为实际数据字节数。LSTATUSRegQueryValueExW(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 激活或浏览器运行时记录支持。完整可运行程序在附件https://wangweicm.lanzouu.com/iQ9EQ3y608mj