跳到主要内容

使用 js 代码注入 CEF

CEF 应用典型特征

文件目录特征很明显

cef.pak
cef_100_percent.pak
cef_200_percent.pak
libcef.dll

既然 CEF 功能核心全部都在 libcef.dll 里面,那么自然首选也是从这个 dll 入手

网上简单搜索 CEF 的教程后发现,CEF 的框架在基础 libcef.dll 的导出函数的基础上,增加了一层 c++ 类的实现,实现了 c2cpp 接口的对接

观察官方给出的demo发现,创建浏览器对象,最终会调用 libcef.dll!cef_browser_host_create_browser 来创建出浏览器

而一旦得到浏览器对象,我们就可以根据 CEF 框架中的头文件,非常方便的执行 js 代码了

所以很自然想到把 libcef.dll!cef_browser_host_create_browser 给 hook 掉,然后从中间偷出 cef_browser_t* 对象

int cef_browser_host_create_browser(
const cef_window_info_t* windowInfo,
struct _cef_client_t* client,
const cef_string_t* url,
struct _cef_browser_settings_t* settings,
struct _cef_request_context_t* request_context);

然而新的问题马上出现,cef_browser_host_create_browser 是使用异步的方法创建浏览器,因此 cef_browser_t* 对象只会在创建成功的回调函数中才能得到,于是我准备设法从 _cef_client_t* client 这个参数中得到回调地址,然后 inline hook 掉回调函数(直接改回调地址失败了,估计是地址不可写)

很不幸的是,虽然获取回调地址很顺利,但是不知什么原因,回调函数始终没有得到调用,因此此路不通,只能另辟蹊径

此时完全没有头绪,根本不知道该如何下手了,毕竟 cef_browser_t* 得不到,想执行 js 代码根本不可能,无聊之余打开了 ApiMonitor,想看看能否从应用的 libcef.dll 的调用情况中得到一点启发

ApiMonitor 监视自定义 dll 的调用情况也非常方便,设置监视的函数为 libcef.dll 中的全部函数

然后启动应用,由于浏览器内核特性,应用自然也是多进程的,一个个查看后,其中一个进程调用的 api 引起我的兴趣

cef_v8context_get_current_context 这个函数的调用瞬间将思路打开 看到 V8,那这个函数传参或者返回值多半和 Javascript 分不开了,再去 CEF 的源码里找找参数或者返回值的含义

///
// Returns the current (top) context object in the V8 context stack.
///
CEF_EXPORT cef_v8context_t* cef_v8context_get_current_context();

惊喜的发现居然没有参数,直接反汇当前 V8 引擎上下文中最前的 context

由于 browser 是与 V8js 引擎绑定的,所以也可以通过返回值 cef_v8context_t* 得到 browser 对象了

cef_browser_t* browser = NULL;
cef_v8context_t* my_cef_v8context_get_current_context() {
cef_v8context_t* js_context = ori_cef_v8context_get_current_context();
Print("my_cef_v8context_get_current_context js_context: %X!!\n", js_context);
browser = js_context->get_browser(js_context);
//Print("[!!!] browser = %X\n", browser);
_cef_frame_t* frame = browser->get_main_frame(browser);
//Print("[!!!] frame = %X\n", frame);
CefString script = payload;
CefString url = xorstr("app/wd").crypt_get();
frame->execute_java_script(frame, script.GetStruct(), url.GetStruct(), 0);
return js_context;
}

接下来的事也就水到渠成了,按照网上教程的方法,得到 browser 过后一切变得简单明了起来

而且写完后我发现这样的写法还有个好处,就是可以考虑到页面刷新的情况,因为浏览器一旦页面刷新,重启 js 虚拟环境,那么浏览器又会主动调取 cef_v8context_get_current_context,因此只要我们 hook 这个函数过后,所有的页面,不管是否刷新,我们都能够注入自己想要的js代码

别的思路

直接 hook onkey event 打开 DevTools, 跟浏览器 F12 一样多方便,下面的脚本用于拦截和修改CEF的默认键盘事件处理函数。

这段代码通过钩子技术拦截键盘事件,特别是用于处理开发者工具的调用(如 F12 键),从而允许程序在 CEF 浏览器中自定义键盘行为。

// 定义一个指向_cef_client_t结构体的指针,并初始化为NULL
struct _cef_client_t* cef_client = NULL;

// 定义指针变量,用于保存原始的浏览器创建函数、键盘处理获取函数和键盘事件处理函数
PVOID o_cef_browser_host_create_browser = NULL;
PVOID o_cef_get_keyboard_handler = NULL;
PVOID o_cef_on_key_event = NULL;

// 定义一个键盘事件的钩子函数,用于拦截键盘事件
int CEF_CALLBACK hook_cef_on_key_event(struct _cef_keyboard_handler_t* self,
struct _cef_browser_t* browser,
const struct _cef_key_event_t* event,
cef_event_handle_t os_event) {
// 当键盘释放F12键时触发
if (event->type == KEYEVENT_KEYUP && event->windows_key_code == 123) {
DbgLog::Log(TEXT("[CEF] press: F12"));
// 获取浏览器的host对象
auto cef_browser_host = browser->get_host(browser);
CefWindowInfo windowInfo{};
CefBrowserSettings settings{};
CefPoint point{};
// 设置并显示开发者工具
windowInfo.SetAsPopup(NULL, "DevTools");
cef_browser_host->show_dev_tools(cef_browser_host, &windowInfo, cef_client, &settings, &point);
}
// 调用原始的键盘事件处理函数
return reinterpret_cast<decltype(&hook_cef_on_key_event)>(o_cef_on_key_event)
(self, browser, event, os_event);
}

// 定义一个获取键盘处理器的钩子函数
struct _cef_keyboard_handler_t* CEF_CALLBACK hook_cef_get_keyboard_handler(struct _cef_client_t* self) {
// 获取原始的键盘处理器对象
auto keyboard_handler = reinterpret_cast<decltype(&hook_cef_get_keyboard_handler)>(o_cef_get_keyboard_handler)(self);
if (keyboard_handler) {
cef_client = self;
o_cef_on_key_event = keyboard_handler->on_key_event;
// 替换原键盘事件处理函数为自定义的钩子函数
keyboard_handler->on_key_event = hook_cef_on_key_event;
}
return keyboard_handler;
}

// 定义一个浏览器创建的钩子函数
int hook_cef_browser_host_create_browser(
const cef_window_info_t* windowInfo,
struct _cef_client_t* client,
const cef_string_t* url,
const struct _cef_browser_settings_t* settings,
struct _cef_request_context_t* request_context){
// 记录原键盘处理器获取函数,然后替换为自定义的钩子函数
o_cef_get_keyboard_handler = client->get_keyboard_handler;
client->get_keyboard_handler = hook_cef_get_keyboard_handler;
// 调用原始的浏览器创建函数
return reinterpret_cast<decltype(&hook_cef_browser_host_create_browser)>(o_cef_browser_host_create_browser)
(windowInfo, client, url, settings, request_context);
}

References