首页
社区
课程
招聘
[原创]Tauri 2 / WebView2 桌面应用运行时分析:从 COM 回调到 ICoreWebView2 捕获
发表于: 1天前 116

[原创]Tauri 2 / WebView2 桌面应用运行时分析:从 COM 回调到 ICoreWebView2 捕获

1天前
116

最近研究了一下 iShellPro。

这是一个基于 Tauri 2 + WebView2 的 Windows 程序,最后实现了免登录、纯离线解锁全部本地功能。

一开始按传统思路去逆 Rust Native 层,但很快发现一个问题:

对于 Tauri 这类 Hybrid Desktop Application,大量真正的业务逻辑其实运行在 WebView 的 JavaScript 环境中。

与其在几十 MB 的 Rust Release 二进制里追业务函数,不如换个目标:

直接拿到宿主创建的 ICoreWebView2

最终实现的核心链路如下:

Proxy DLL
    ↓
进入目标进程
    ↓
Hook WebView2 Environment 创建
    ↓
ICoreWebView2Environment
    ↓
Hook Controller 创建
    ↓
ICoreWebView2Controller
    ↓
get_CoreWebView2()
    ↓
ICoreWebView2
    ↓
切回 WebView 所在线程
    ↓
AddScriptToExecuteOnDocumentCreated
    ↓
JavaScript Runtime

本文主要记录实现过程中几个比较关键的点。


0x01 为什么直接盯 WebView2

Tauri 2 在 Windows 上的结构大致可以理解成:

Rust / Tauri
    ↓
WRY
    ↓
WebView2
    ↓
HTML / JavaScript

如果目标逻辑最终由前端消费,那么持续追踪 Rust Native 层并不一定是效率最高的路线。

WebView2 本身有一套比较清晰的生命周期:

Create Environment
    ↓
ICoreWebView2Environment
    ↓
Create Controller
    ↓
ICoreWebView2Controller
    ↓
get_CoreWebView2()
    ↓
ICoreWebView2

所以思路也很直接:

不去关心 Tauri 内部究竟怎么保存 WebView,而是在 WebView2 生命周期中把对象截出来。


0x02 捕获 ICoreWebView2Environment

第一步 Hook:

CreateCoreWebView2EnvironmentWithOptions

WebView2 的 Environment 创建是异步的,最终结果通过 COM Completion Handler 返回:

handler->Invoke(
    HRESULT,
    ICoreWebView2Environment*
);

这里没有直接修改宿主原本的 Handler,而是在外面套一层 Proxy:

HRESULT EnvironmentCompletedProxy::Invoke(
    HRESULT error,
    ICoreWebView2Environment* environment)
{
    if (SUCCEEDED(error) && environment)
        InstallEnvironmentHooks(environment);

    return original_->Invoke(
        error,
        environment
    );
}

这样做有两个目的:

  1. 捕获 ICoreWebView2Environment
  2. 不破坏宿主原来的初始化流程

COM Proxy 中的:

QueryInterface
AddRef
Release

也必须正确实现。

否则即使 Hook 本身没有问题,也很容易因为引用计数或对象生命周期出现随机崩溃。


0x03 从 Environment 截获 Controller 创建

拿到:

ICoreWebView2Environment*

之后,就可以继续从 COM vtable 中截获 Controller 创建函数。

项目里处理了几条常见路径:

CreateCoreWebView2Controller
CreateCoreWebView2CompositionController
CreateCoreWebView2ControllerWithOptions
CreateCoreWebView2CompositionControllerWithOptions

这里不能只盯着最基础的 CreateCoreWebView2Controller

不同的 WebView2 Runtime、框架版本以及初始化方式,可能会走不同的 Environment Interface。

所以我的处理方式是:

先通过 QueryInterface 探测高版本 Environment Interface,再对实际存在的创建入口分别安装 Hook。

这样比单押某一个接口稳定得多。


0x04 捕获 ICoreWebView2

Controller 的创建同样通过 Completion Handler 完成。

因此继续使用 Proxy:

HRESULT ControllerCompletedProxy::Invoke(
    HRESULT error,
    ICoreWebView2Controller* controller)
{
    ICoreWebView2* webview = nullptr;

    if (SUCCEEDED(error) && controller)
        controller->get_CoreWebView2(&webview);

    auto hr = original_->Invoke(
        error,
        controller
    );

    if (webview) {
        SaveWebView(webview);
        webview->Release();
    }

    return hr;
}

到这里,真正需要的对象就拿到了:

ICoreWebView2*

理论上接下来已经可以通过 WebView2 API 与页面交互。

但这里有一个比 Hook 本身更容易踩的坑。


0x05 最大的坑:COM STA 线程

一开始拿到 ICoreWebView2* 后,我直接尝试:

webview->ExecuteScript(...)

然后很快遇到了:

RPC_E_WRONG_THREAD

原因是 WebView2 基于 COM STA。

简单理解:

WebView 在线程 A 创建
        ↓
相关 WebView2 API
通常也必须在线程 A 调用

所以不能在一个工作线程里保存 ICoreWebView2*,然后想什么时候调就什么时候调。

最后采用的方案是:

在 WebView 所在线程创建一个 Message-Only Window,把它作为 Dispatcher。

CreateWindowExW(
    0,
    className,
    L"",
    0,
    0, 0, 0, 0,
    HWND_MESSAGE,
    nullptr,
    module,
    nullptr
);

其他线程只负责:

PostMessageW(...)

真正涉及 WebView2 的操作统一回到窗口所属线程:

Worker Thread
    ↓
PostMessage
    ↓
WebView UI Thread
    ↓
ICoreWebView2 API

包括部分 COM 对象的 Release(),也最好回到原线程完成。

这个问题在单窗口、短时间测试时可能并不明显,但到了:

多窗口
Reload
窗口关闭
WebView 重建

之后,对稳定性的影响会非常明显。

整个项目里,我认为这一点比单纯“拿到 ICoreWebView2”更值得注意。


0x06 补齐创建入口,以及为什么必须足够早

仅仅 Hook:

WebView2Loader.dll

并不一定能覆盖所有情况。

实际运行中,框架还可能动态解析内部入口:

CreateWebViewEnvironmentWithOptionsInternal

因此项目同时 Hook 了:

GetProcAddress

当目标程序解析该内部函数时,再补装对应 Hook。

也就是说,Environment 的捕获实际上覆盖了两类入口:

Public WebView2Loader
        │
        ├──────────────┐
        ↓              ↓
 Public Hook      Internal Runtime
                       ↓
                  GetProcAddress
                       ↓
                  Internal Hook
        └───────┬──────┘
                ↓
       Environment Proxy
                ↓
        Controller Hook
                ↓
          ICoreWebView2

但这里还有一个前提:

这些 Hook 必须在 WebView2 Environment 创建之前安装完成。

所以 Proxy DLL 真正重要的并不是“代理哪个 DLL”,而是:

哪个可利用 DLL 能在目标进程启动阶段足够早地被加载。

实际观察模块加载顺序后,我排除了几类不适合作为入口的 DLL:

KnownDLLs
COM 方式加载的 DLL
由其他模块间接加载的 DLL

最后选择了剩余候选中加载时机最早的 dwmapi.dll

原因很简单。

如果发生:

WebView2 Environment 已创建
        ↓
Proxy DLL 才进入进程

那么即使后面的 Hook 实现完全正确,也已经错过最关键的生命周期节点。

代理 DLL 本身,只需要保证原有导出正常转发,不影响宿主启动即可。

真正的核心仍然是后面的 WebView2 Capture。


0x07 为什么用 AddScriptToExecuteOnDocumentCreated

拿到 ICoreWebView2 后,可以直接:

ExecuteScript(...)

ExecuteScript 更适合页面已经运行之后临时执行 JavaScript。

如果目标是观察页面初始化阶段,比如:

fetch
localStorage
全局对象
框架初始化

更合适的是:

AddScriptToExecuteOnDocumentCreated

它的执行时机更早,大致可以理解成:

Global Object Created
        ↓
Injected Script
        ↓
HTML Parse
        ↓
Page JavaScript

0x08 从 Target Patch 到 Framework Instrumentation

如果只是针对单一版本做修改,传统思路通常是:

找到业务判断
    ↓
修改分支
    ↓
结束

问题是这种方式往往高度依赖:

版本
Offset
具体编译结果

而这次做的事情不太一样。

Hook 的目标主要是:

WebView2 生命周期
COM Interface
Framework Runtime

关注点从:

Target Specific Patch

变成了:

Framework Level Instrumentation

因此 WebView2 Capture 这一层并不完全依赖 iShellPro 本身。

对于其他使用 WebView2 的程序,例如:

Tauri
WPF WebView2
WinForms WebView2
WinUI WebView2

同样具有一定复用价值。

这也是这次分析里我觉得最有意思的地方。


0x09 总结

整个项目真正想解决的问题其实只有一个:

如何稳定地拿到宿主程序创建的 ICoreWebView2,并在正确的 STA 线程上,在页面脚本之前进入 JavaScript Runtime。

围绕这个目标,最终串起来的知识点包括:

DLL Proxy / API Forwarding
Inline Hook
COM / vtable
Reference Counting
WebView2 Lifecycle
STA
Windows Message Pump
JavaScript Runtime

对于 Tauri / WebView2 这类 Hybrid Desktop Application,我觉得比较值得记住的并不是某个具体业务字段,而是一个分析思路:

不一定非要从业务代码本身下手,也可以从承载业务代码的 Framework Runtime 和生命周期下手。

目前代码中,我认为最值得单独抽象出来的也是 WebView2 Capture 这一层。

以后遇到类似目标,基本可以沿着:

Environment
    ↓
Controller
    ↓
ICoreWebView2

继续往下分析。

仓库:

89cK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6D9N6%4c8%4x3e0t1K6y4o6f1$3i4K6u0r3K9g2y4Z5k6h3I4D9f1s2u0G2i4K6u0V1d9r3q4U0K9H3`.`.

如有错误,欢迎各位师傅指正。


传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!

收藏
免费 1
打赏
分享
最新回复 (1)
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
Tauri 2看来也是太冷门了。。。通用的crack方法都没人感兴趣
49分钟前
0
游客
登录 | 注册 方可回帖
返回