CEF 是 Chromium Embedded Framework 的简写,这是一个把 Chromium 嵌入其他应用的开源框架。
现在市面上有许多桌面软件都使用了CEF框架,比如我们经常使用的钉钉、网易云音乐等等。
我本意是突破钉钉的一些功能限制,结果发现钉钉使用了CEF框架,故开始对CEF框架做了一些浮于表面的探索。由于个人能力有限,如果文章中有什么错误之处,还望大家多多指教。
在开始正式开始之前,有必要先观察一下钉钉的安装目录,看看里面有哪些我们感兴趣的文件。
我电脑上的钉钉版本是6.5.30-Release.7289101
通过查看运行中的DingTalk.exe进程的映射文件锁定你电脑上目前运行的钉钉的目录(这个地方会发现有多个同名进程,我们随便选择一个)
有朋友可能要问为什么要通过这种方式确定目录,这其实是因为钉钉的安装目录下面一般都会存在两个版本的文件,一个是当前版本另外一个则是上一个版本。据我观察这两个目录下的文件结构基本一致。
我电脑上的钉钉目前就使用的是current
目录。
打开current
目录可以发现许多的资源文件和依赖库文件,其中对于本文来说最重要的文件是libcef.dll
和web_content.pak
。libcef.dll
是CEF框架的支持库,web_content.pak
则是钉钉缓存在本地的html、js、css
文件。web_content.pak
本质是一个zip
压缩文件,我们可以通过解压软件查看里面的内容
那么可以知道这个压缩文件是被加密了,解压的时候会让输入密码,后面会提到怎么获取密码。通过观察文件的名字也大致可以猜出这些文件的作用。
钉钉中使用CEF框架的区域主要在聊天框显示区域。
下面主要介绍三个方面的内容
另外提一嘴,在钉钉的安装目录下面我们还可以发现有cef_LICENSE.txt``duilib_license.txt
等license
声明,通过这些声明我们也可以获得一些信息,比如钉钉还使用了duilib
界面库。
既然钉钉使用了CEF
框架,那么学会简单的使用CEF
框架,了解相关的API
会使我们事半功倍。
根据官方库的指引,我们前往https://cef-builds.spotifycdn.com/index.html下载框架。
官方实现了C语言版本的CEF框架以及C++版本的CEF框架,其中C++版本的框架是基于C语言版本的二次封装。而我们需要的libcef.dll
就是C版本的框架。
在此处下载的文件包含了已经编译好的libcef.dll
,无需我们从源码编译libcef
库。
实质上从源码编译libcef
库并不容易,因为其中涉及到编译chromium,我猜这也是为什么官方会提供各种平台各种版本的库的原因吧。
在下载时我们需要先了解CEF的版本编号格式
格式解释如下
以cef_binary_104.4.25+gd80d467+chromium-104.0.5112.102_windows32.tar.bz2
为例,其中
104.4.25
和104.0.5112.102
是CEF和Chromium的版本信息,gd80d467
是git commit
的hash
我们可以先看看钉钉使用的libcef.dll
是什么版本
这里发现一个很坑的点,就是Windows的文件属性显示不全,而且还不能拖开,也不能复制。
不过根据已经显示出来的内容,可以发现钉钉使用的libcef.dll
明显不是在官方提供的页面下载的。版本约定和官方的太不一样,git commit
是8位的,官方库可是只有7位。
g2e1fb6b
,我尝试使用g2e1fb6
、2e1fb6b
等hash
在commit
列表中搜索也没有发现,只能猜测钉钉使用的libcef.dll
是自己从源码编译的,而且可能对源码做了一些修改吧。
同时我使用91.0.0
在下载界面搜索也没有发现相同的版本。后面的版本信息显示不全,得想个办法解决一下子,争取下载一个最接近的版本。其实这里有一个大坑,后面会提到。
其实文件属性的信息是存在于PE中的资源节中的,使用Windows系统提供的API或者自己解析都可以拿到相关信息。不过我是本着能不写代码就不写代码的懒人思想的。
一般这种库或者框架的动态库中都会提供函数查询版本信息,所以我浏览了一下libcef
的导出函数
在libcef
的导出函数中我发现了cef_version_info
这个函数,看名字就知道干什么用的了。
该说不说,官方提供了C++版本的文档,为什么不提供一个libcef
的api
文档呢?反正我是没找到。不过虽然没有文档,还是有源码和大量注释的。
这个函数的定义是这样的
我们再结合下面的信息
从反汇编很明显的看出来这是一个数组下标寻址
从源码得知不同的参数获取不同的信息,那么完整的版本信息存在于一个32字节的数组中
在内存窗口转到数组内存
我们缺少的是最后Chromium的版本信息,那么就是最后四个int。那么简单的拼接,得到
5B.0.1178.A4
转成10进制 91.0.4472.164
。
搜索发现只有一个版本满足要求,那么就用这个好了,下载Standard Distribution,这个里面的文件是完整的,包含了框架代码和示例代码。
后面突然想起使用解析PE的格式的一些工具,也能很方便的查看资源信息。
我用CFF试了一下
将下载后的文件解压,使用cmake
生成vs
工程。然后使用vs编译。
这个时候编译成功了,当然可能会在编译的时候遇到一些错误或者警告,按照提示解决即可。
那么环境准备好了,我们需要去学习一些CEF框架的基础知识了,直接看示例代码或者直接看框架源码都不是那么容易的,可以先在网上找前辈取点经。
掘金小册-CEF 桌面软件开发实战
知乎专栏-CEF
最终的目标是实现钉钉聊天窗口的防撤回功能,基于这个目标,一步步的解决一些遇到的问题。
CEF可以从本地或者网络加载资源,一般来说桌面应用程序会将大部分需要用到的文件缓存在本地。
所以第一步就是需要找到资源文件的位置,这个不同的软件可能使用的资源文件的名称不太一样,存放的位置也不太一样。比如钉钉是放在安装目录下的,但是网易云音乐就没有放在安装目录下。
在钉钉登录页面附加DingTalk.exe
选择没有命令行参数的附加
选择这两个函数下断点
cef_stream_reader_create_for_data
cef_stream_reader_create_for_file
这两个函数是CEF提供的两个操作文件数据的函数,返回值都是cef_stream_reader_t
结构体。
区别在于cef_stream_reader_create_for_file
的参数是文件路径
cef_stream_reader_create_for_data
的参数是内存地址和大小,即内存中的文件数据。
这两个函数的声明和相关的结构体如下:
断点下好之后,直接登录。
钉钉中没有使用cef_stream_reader_create_for_data
函数,使用的是cef_stream_reader_create_for_file
。
命中断点,观察参数
/local_res/common_res.pak
/web_content.pak
/local_res/common_res.pak
文件中的内容
/web_content.pak
文件中的内容
到这就已经确定了资源文件的路径了。
不过需要注意的一点是,如果程序使用了cef_stream_reader_create_for_data
函数,那我们就不能从参数直接得到路径了。这个时候需要配合下面的方法使用。
直接在kernel32.dll.CreateFileW/A
和kernel32.dll.ReadFileW/A
下断点,观察函数的参数,如果觉得这样比较废手的话,可以使用行为监控软件比如微软的ProcessMonitor
,设置好过滤选项之后监控程序的文件操作。
如果资源文件被加密了,怎么解密文件。
思路其实很简单,程序运行时肯定会在某个时机解密数据,我们在相关API处下断点,逆向分析即可得到密码。
钉钉的资源文件是zip压缩加密,得到密码的方式有两个方向。
cef_zip_directory
写数据到zip文件
cef_zip_reader_create
从zip文件读取数据
函数声明和相关结构体声明
需要特别关注的是cef_zip_reader_t
中的open_file
成员
参数中带有password
,那我们在这个函数下断点就可以得到密码了。
具体步骤如下
在钉钉登录页面附加程序,cef_stream_reader_create_for_file
函数下断点。
登录钉钉,在函数cef_stream_reader_create_for_file
参数是web_content.pak
路径的时候记住返回值,并给cef_zip_reader_create
下断点,程序继续运行。
cef_zip_reader_create
断点名命中,检查参数是否是上面记住的返回值
如果没问题断到则先让程序回到返回处,得到cef_zip_reader_t*
返回值0x25CF2940
。
在内存中按地址查看0x25CF2940
根据open_file在结构体中的偏移我们直接就可以找到函数地址,我直接数了一下偏移是0x30
,下标第12项,直接下断点,运行程序等待断点命中。
然后断点确实命中了,第二个参数就是密码。这里就不截图了,感兴趣的可以自己去试一下。
如果程序没有使用CEF框架提供的函数解密,那么上面说的方法就不行了。这种时候只能使用老办法,在CreateFileA/W
和ReadFileA/W
下断点,调试程序。
用这种方式也能得到密码,好奇的同学可以去试一下,可以在栈中发现密码。
最后提一嘴,这个密码钉钉是怎么计算出来的。我只能说这个算法是MD5,可以利用IDA分析安装目录下的MainFrame.dll
结合算法识别插件。不过我没有逆,有大哥逆过,感谢大哥,手动at大哥0xC5。
可以解密资源之后,我们就可以分析Js文件了。想让修改生效,有两种方式
直接替换文件非常简单,但是有个问题。这个方式不太稳定,据我观察钉钉会不定期的更新资源文件(这个更新不是指钉钉的升级),更新之后还得重新替换。
第二种方式的话,其实也不难。我们可以hook cef_zip_reader_t
结构体中的read_file
函数,并配合get_file_name
函数实现在内存中修改。
不过内存替换我也没有去尝试,这里只提供一种思路。
改代码不是什么难事,难的是找到关键点。如果能开启Chromium本身的动态调试功能,那对于分析人员来说简直是如虎添翼。
在 cef_browser_host_t
结构体中有一个show_dev_tools
成员,可以用来开启调试窗口。
cef_browser_host_t
对象可以通过cef_browser_t
的get_host
拿到。
get_host ``show_dev_tools
声明
cef_browser_t
声明,cef_browser_host_t
声明比较大,就不放上来了,可以自己去看头文件(include/capi/cef_browser_capi.h)。
我们通过注入DLL,HOOK CEF的事件处理回调函数,使用回调函数的struct _cef_browser_t* browser
参数,从而调用到show_dev_tools
。
以按键事件为例
(代码来自将js代码注入到第三方CEF应用程序的一点浅见 的评论区风铃i大佬的评论,我做了一些修改)
这个有个需要注意的点,非常重要(还记得我上面说的大坑嘛)。我使用的库的版本和钉钉的不一致,那么上面代码中使用的结构体声明可能在不同版本会有不同。这意味着我们编译出来的DLL中结构体的偏移和钉钉中也可能不一致。
注意上面的第43行代码,调用show_dev_tools
在我实际测试中,show_dev_tools
的偏移和钉钉中就不一致。当时也是找了很久原因,一开始也没往这方面想,还以为是参数没传对,或者有什么对抗存在。最后在调试的时候和官方例子做了对比,才发现调用的函数都不是show_dev_tools
!
所以我最后改了一下43行的代码,show_dev_tools
偏移差了4个字节,用close_dev_tools
刚好对上。
在聊天框中F12,最后终于是开启成功。
最后还要说一点就是DLL注入的时机,我选择的是程序在登录框界面的时候。这个时候libcef.dll
已经加载,cef_browser_host_create_browser
函数也没被调用。
刀已经准备好了,可以试试刀锋了。
首先考虑消息撤回的时候大概发生了什么。
用户A点击撤回->触发Js点击事件->向服务器发送网络请求->服务器处理请求,向各个客户端发送消息
用户B收到撤回的请求->Js处理请求,最后修改页面元素
向服务器发送请求这里有两种可能,一种是直接在Js中发送请求,另一种是Js代码和C++代码通信C++来发这个请求。钉钉使用的是后者,因为在撤回的时候调试窗口的Network页面没有发现有网络请求。
所以防撤回的实现点有很多种,我这里主要尝试在Js层做防撤回。
设置好断点
撤回时断点命中,调用链出来了。阅读代码看看什么地方修改比较合适。
找了一圈,发现最顶层的调用处做消息过滤比较合适
修改代码如下,成功防撤回
这里调试的时候还会遇到一个问题--Js文件太大,调试窗口格式化代码的时候卡死了。
解决方法很简单,我们把在web_content.pak
中找到代码文件把该文件先格式化了,不用调试的时候去格式化,这样调试就不会因为格式化的原因卡死了。
CEF框架是一个开源的框架,而且钉钉也没有加入诸如反调试之内的对抗手段,研究起来比较容易,遇到的一些问题基本都解决了。最大的坑就在于库的版本问题,但是通过调试也能发现端倪。
最后可以思考一些防御的手段,比如:
可以进行的相关研究还有很多,无聊的时候玩玩也挺好,毕竟CEF框架的使用还是挺普遍的。
int
cef_version_info(
int
entry);
int
cef_version_info(
int
entry);
/
/
/
/
/
Structure used to read data
from
a stream. The functions of this structure
/
/
may be called on
any
thread.
/
/
/
typedef struct _cef_stream_reader_t {
/
/
/
/
/
Base structure.
/
/
/
cef_base_ref_counted_t base;
/
/
/
/
/
Read raw binary data.
/
/
/
size_t(CEF_CALLBACK
*
read)(struct _cef_stream_reader_t
*
self
,
void
*
ptr,
size_t size,
size_t n);
/
/
/
/
/
Seek to the specified offset position. |whence| may be
any
one of SEEK_CUR,
/
/
SEEK_END
or
SEEK_SET. Returns zero on success
and
non
-
zero on failure.
/
/
/
int
(CEF_CALLBACK
*
seek)(struct _cef_stream_reader_t
*
self
,
int64 offset,
int
whence);
/
/
/
/
/
Return the current offset position.
/
/
/
int64(CEF_CALLBACK
*
tell)(struct _cef_stream_reader_t
*
self
);
/
/
/
/
/
Return non
-
zero
if
at end of
file
.
/
/
/
int
(CEF_CALLBACK
*
eof)(struct _cef_stream_reader_t
*
self
);
/
/
/
/
/
Returns true (
1
)
if
this reader performs work like accessing the
file
/
/
system which may block. Used as a hint
for
determining the thread to access
/
/
the reader
from
.
/
/
/
int
(CEF_CALLBACK
*
may_block)(struct _cef_stream_reader_t
*
self
);
} cef_stream_reader_t;
/
/
/
/
/
Create a new cef_stream_reader_t
object
from
a
file
.
/
/
/
CEF_EXPORT cef_stream_reader_t
*
cef_stream_reader_create_for_file(
const cef_string_t
*
fileName);
/
/
/
/
/
Create a new cef_stream_reader_t
object
from
data.
/
/
/
CEF_EXPORT cef_stream_reader_t
*
cef_stream_reader_create_for_data(
void
*
data,
size_t size);
/
/
/
/
/
Structure used to read data
from
a stream. The functions of this structure
/
/
may be called on
any
thread.
/
/
/
typedef struct _cef_stream_reader_t {
/
/
/
/
/
Base structure.
/
/
/
cef_base_ref_counted_t base;
/
/
/
/
/
Read raw binary data.
/
/
/
size_t(CEF_CALLBACK
*
read)(struct _cef_stream_reader_t
*
self
,
void
*
ptr,
size_t size,
size_t n);
/
/
/
/
/
Seek to the specified offset position. |whence| may be
any
one of SEEK_CUR,
/
/
SEEK_END
or
SEEK_SET. Returns zero on success
and
non
-
zero on failure.
/
/
/
int
(CEF_CALLBACK
*
seek)(struct _cef_stream_reader_t
*
self
,
int64 offset,
int
whence);
/
/
/
/
/
Return the current offset position.
/
/
/
int64(CEF_CALLBACK
*
tell)(struct _cef_stream_reader_t
*
self
);
/
/
/
/
/
Return non
-
zero
if
at end of
file
.
/
/
/
int
(CEF_CALLBACK
*
eof)(struct _cef_stream_reader_t
*
self
);
/
/
/
/
/
Returns true (
1
)
if
this reader performs work like accessing the
file
/
/
system which may block. Used as a hint
for
determining the thread to access
/
/
the reader
from
.
/
/
/
int
(CEF_CALLBACK
*
may_block)(struct _cef_stream_reader_t
*
self
);
} cef_stream_reader_t;
/
/
/
/
/
Create a new cef_stream_reader_t
object
from
a
file
.
/
/
/
CEF_EXPORT cef_stream_reader_t
*
cef_stream_reader_create_for_file(
const cef_string_t
*
fileName);
/
/
/
/
/
Create a new cef_stream_reader_t
object
from
data.
/
/
/
CEF_EXPORT cef_stream_reader_t
*
cef_stream_reader_create_for_data(
void
*
data,
size_t size);
/
/
/
/
/
All
ref
-
counted framework structures must include this structure first.
/
/
/
typedef struct _cef_base_ref_counted_t {
/
/
/
/
/
Size of the data structure.
/
/
/
size_t size;
/
/
/
/
/
Called to increment the reference count
for
the
object
. Should be called
/
/
for
every new copy of a pointer to a given
object
.
/
/
/
void(CEF_CALLBACK
*
add_ref)(struct _cef_base_ref_counted_t
*
self
);
/
/
/
/
/
Called to decrement the reference count
for
the
object
. If the reference
/
/
count falls to
0
the
object
should
self
-
delete. Returns true (
1
)
if
the
/
/
resulting reference count
is
0.
/
/
/
int
(CEF_CALLBACK
*
release)(struct _cef_base_ref_counted_t
*
self
);
/
/
/
/
/
Returns true (
1
)
if
the current reference count
is
1.
/
/
/
int
(CEF_CALLBACK
*
has_one_ref)(struct _cef_base_ref_counted_t
*
self
);
/
/
/
/
/
Returns true (
1
)
if
the current reference count
is
at least
1.
/
/
/
int
(CEF_CALLBACK
*
has_at_least_one_ref)(struct _cef_base_ref_counted_t
*
self
);
} cef_base_ref_counted_t;
/
/
/
/
/
Structure that supports the reading of
zip
archives via the zlib unzip API.
/
/
The functions of this structure should only be called on the thread that
/
/
creates the
object
.
/
/
/
typedef struct _cef_zip_reader_t {
/
/
/
/
/
Base structure.
/
/
/
cef_base_ref_counted_t base;
/
/
/
/
/
Moves the cursor to the first
file
in
the archive. Returns true (
1
)
if
the
/
/
cursor position was
set
successfully.
/
/
/
int
(CEF_CALLBACK
*
move_to_first_file)(struct _cef_zip_reader_t
*
self
);
/
/
/
/
/
Moves the cursor to the
next
file
in
the archive. Returns true (
1
)
if
the
/
/
cursor position was
set
successfully.
/
/
/
int
(CEF_CALLBACK
*
move_to_next_file)(struct _cef_zip_reader_t
*
self
);
/
/
/
/
/
Moves the cursor to the specified
file
in
the archive. If |caseSensitive|
/
/
is
true (
1
) then the search will be case sensitive. Returns true (
1
)
if
the
/
/
cursor position was
set
successfully.
/
/
/
int
(CEF_CALLBACK
*
move_to_file)(struct _cef_zip_reader_t
*
self
,
const cef_string_t
*
fileName,
int
caseSensitive);
/
/
/
/
/
Closes the archive. This should be called directly to ensure that cleanup
/
/
occurs on the correct thread.
/
/
/
int
(CEF_CALLBACK
*
close)(struct _cef_zip_reader_t
*
self
);
/
/
The below functions act on the
file
at the current cursor position.
/
/
/
/
/
Returns the name of the
file
.
/
/
/
/
/
The resulting string must be freed by calling cef_string_userfree_free().
cef_string_userfree_t(CEF_CALLBACK
*
get_file_name)(
struct _cef_zip_reader_t
*
self
);
/
/
/
/
/
Returns the uncompressed size of the
file
.
/
/
/
int64(CEF_CALLBACK
*
get_file_size)(struct _cef_zip_reader_t
*
self
);
/
/
/
/
/
Returns the last modified timestamp
for
the
file
.
/
/
/
cef_basetime_t(CEF_CALLBACK
*
get_file_last_modified)(
struct _cef_zip_reader_t
*
self
);
/
/
/
/
/
Opens the
file
for
reading of uncompressed data. A read password may
/
/
optionally be specified.
/
/
/
int
(CEF_CALLBACK
*
open_file)(struct _cef_zip_reader_t
*
self
,
const cef_string_t
*
password);
/
/
/
/
/
Closes the
file
.
/
/
/
int
(CEF_CALLBACK
*
close_file)(struct _cef_zip_reader_t
*
self
);
/
/
/
/
/
Read uncompressed
file
contents into the specified
buffer
. Returns <
0
if
/
/
an error occurred,
0
if
at the end of
file
,
or
the number of bytes read.
/
/
/
int
(CEF_CALLBACK
*
read_file)(struct _cef_zip_reader_t
*
self
,
void
*
buffer
,
size_t bufferSize);
/
/
/
/
/
Returns the current offset
in
the uncompressed
file
contents.
/
/
/
int64(CEF_CALLBACK
*
tell)(struct _cef_zip_reader_t
*
self
);
/
/
/
/
/
Returns true (
1
)
if
at end of the
file
contents.
/
/
/
int
(CEF_CALLBACK
*
eof)(struct _cef_zip_reader_t
*
self
);
} cef_zip_reader_t;
/
/
/
/
/
Writes the contents of |src_dir| into a
zip
archive at |dest_file|. If
/
/
|include_hidden_files|
is
true (
1
) files starting with
"."
will be included.
/
/
Returns true (
1
) on success. Calling this function on the browser process UI
/
/
or
IO threads
is
not
allowed.
/
/
/
CEF_EXPORT
int
cef_zip_directory(const cef_string_t
*
src_dir,
const cef_string_t
*
dest_file,
int
include_hidden_files);
/
/
/
/
/
Create a new cef_zip_reader_t
object
. The returned
object
's functions can
/
/
only be called
from
the thread that created the
object
.
/
/
/
CEF_EXPORT cef_zip_reader_t
*
cef_zip_reader_create(
struct _cef_stream_reader_t
*
stream);
/
/
/
/
/
All
ref
-
counted framework structures must include this structure first.
/
/
/
typedef struct _cef_base_ref_counted_t {
/
/
/
/
/
Size of the data structure.
/
/
/
size_t size;
/
/
/
/
/
Called to increment the reference count
for
the
object
. Should be called
/
/
for
every new copy of a pointer to a given
object
.
/
/
/
void(CEF_CALLBACK
*
add_ref)(struct _cef_base_ref_counted_t
*
self
);
/
/
/
/
/
Called to decrement the reference count
for
the
object
. If the reference
/
/
count falls to
0
the
object
should
self
-
delete. Returns true (
1
)
if
the
/
/
resulting reference count
is
0.
/
/
/
int
(CEF_CALLBACK
*
release)(struct _cef_base_ref_counted_t
*
self
);
/
/
/
/
/
Returns true (
1
)
if
the current reference count
is
1.
/
/
/
int
(CEF_CALLBACK
*
has_one_ref)(struct _cef_base_ref_counted_t
*
self
);
/
/
/
/
/
Returns true (
1
)
if
the current reference count
is
at least
1.
/
/
/
int
(CEF_CALLBACK
*
has_at_least_one_ref)(struct _cef_base_ref_counted_t
*
self
);
} cef_base_ref_counted_t;
/
/
/
/
/
Structure that supports the reading of
zip
archives via the zlib unzip API.
/
/
The functions of this structure should only be called on the thread that
/
/
creates the
object
.
/
/
/
typedef struct _cef_zip_reader_t {
/
/
/
/
/
Base structure.
/
/
/
cef_base_ref_counted_t base;
/
/
/
/
/
Moves the cursor to the first
file
in
the archive. Returns true (
1
)
if
the
/
/
cursor position was
set
successfully.
/
/
/
int
(CEF_CALLBACK
*
move_to_first_file)(struct _cef_zip_reader_t
*
self
);
/
/
/
/
/
Moves the cursor to the
next
file
in
the archive. Returns true (
1
)
if
the
/
/
cursor position was
set
successfully.
/
/
/
int
(CEF_CALLBACK
*
move_to_next_file)(struct _cef_zip_reader_t
*
self
);
/
/
/
/
/
Moves the cursor to the specified
file
in
the archive. If |caseSensitive|
/
/
is
true (
1
) then the search will be case sensitive. Returns true (
1
)
if
the
/
/
cursor position was
set
successfully.
/
/
/
int
(CEF_CALLBACK
*
move_to_file)(struct _cef_zip_reader_t
*
self
,
const cef_string_t
*
fileName,
int
caseSensitive);
/
/
/
/
/
Closes the archive. This should be called directly to ensure that cleanup
/
/
occurs on the correct thread.
/
/
/
int
(CEF_CALLBACK
*
close)(struct _cef_zip_reader_t
*
self
);
/
/
The below functions act on the
file
at the current cursor position.
/
/
/
/
/
Returns the name of the
file
.
/
/
/
/
/
The resulting string must be freed by calling cef_string_userfree_free().
cef_string_userfree_t(CEF_CALLBACK
*
get_file_name)(
struct _cef_zip_reader_t
*
self
);
/
/
/
/
/
Returns the uncompressed size of the
file
.
/
/
/
int64(CEF_CALLBACK
*
get_file_size)(struct _cef_zip_reader_t
*
self
);
/
/
/
/
/
Returns the last modified timestamp
for
the
file
.
/
/
/
cef_basetime_t(CEF_CALLBACK
*
get_file_last_modified)(
struct _cef_zip_reader_t
*
self
);
/
/
/
/
/
Opens the
file
for
reading of uncompressed data. A read password may
/
/
optionally be specified.
/
/
/
int
(CEF_CALLBACK
*
open_file)(struct _cef_zip_reader_t
*
self
,
const cef_string_t
*
password);
/
/
/
/
/
Closes the
file
.
/
/
/
int
(CEF_CALLBACK
*
close_file)(struct _cef_zip_reader_t
*
self
);
/
/
/
/
/
Read uncompressed
file
contents into the specified
buffer
. Returns <
0
if
/
/
an error occurred,
0
if
at the end of
file
,
or
the number of bytes read.
/
/
/
int
(CEF_CALLBACK
*
read_file)(struct _cef_zip_reader_t
*
self
,
void
*
buffer
,
size_t bufferSize);
/
/
/
/
/
Returns the current offset
in
the uncompressed
file
contents.
/
/
/
int64(CEF_CALLBACK
*
tell)(struct _cef_zip_reader_t
*
self
);
/
/
/
/
/
Returns true (
1
)
if
at end of the
file
contents.
/
/
/
int
(CEF_CALLBACK
*
eof)(struct _cef_zip_reader_t
*
self
);
} cef_zip_reader_t;
/
/
/
/
/
Writes the contents of |src_dir| into a
zip
archive at |dest_file|. If
/
/
|include_hidden_files|
is
true (
1
) files starting with
"."
will be included.
/
/
Returns true (
1
) on success. Calling this function on the browser process UI
/
/
or
IO threads
is
not
allowed.
/
/
/
CEF_EXPORT
int
cef_zip_directory(const cef_string_t
*
src_dir,
const cef_string_t
*
dest_file,
int
include_hidden_files);
/
/
/
/
/
Create a new cef_zip_reader_t
object
. The returned
object
's functions can
/
/
only be called
from
the thread that created the
object
.
/
/
/
CEF_EXPORT cef_zip_reader_t
*
cef_zip_reader_create(
struct _cef_stream_reader_t
*
stream);
/
/
/
/
/
Opens the
file
for
reading of uncompressed data. A read password may
/
/
optionally be specified.
/
/
/
int
(CEF_CALLBACK
*
open_file)(struct _cef_zip_reader_t
*
self
,
const cef_string_t
*
password);
/
/
/
/
/
Opens the
file
for
reading of uncompressed data. A read password may
/
/
optionally be specified.
/
/
/
int
(CEF_CALLBACK
*
open_file)(struct _cef_zip_reader_t
*
self
,
const cef_string_t
*
password);
int
CEF_CALLBACK hook_read_file(
struct _cef_zip_reader_t
*
self
,
void
*
buffer
,
size_t bufferSize) {
/
/
调用原始的read_file
int
result
=
old_read_file(
self
,
buffer
, bufferSize);
/
/
获取文件名
cef_string_userfree_t ptr_file_name
=
get_file_name(
self
);
/
/
对比文件名
if
(strcmp(ptr_file_name
-
>
str
,
"xxxx"
)
=
=
0
) {
/
/
如果文件名满足要求,则可以考虑遍历
buffer
修改关键点
}
}
int
CEF_CALLBACK hook_read_file(
struct _cef_zip_reader_t
*
self
,
void
*
buffer
,
size_t bufferSize) {
/
/
调用原始的read_file
int
result
=
old_read_file(
self
,
buffer
, bufferSize);
/
/
获取文件名
cef_string_userfree_t ptr_file_name
=
get_file_name(
self
);
/
/
对比文件名
if
(strcmp(ptr_file_name
-
>
str
,
"xxxx"
)
=
=
0
) {
/
/
如果文件名满足要求,则可以考虑遍历
buffer
修改关键点
}
}
/
/
/
/
/
Returns the browser host
object
. This function can only be called
in
the
/
/
browser process.
/
/
/
struct _cef_browser_host_t
*
CEF_CALLBACK get_host(
struct _cef_browser_t
*
self
);
/
/
/
/
/
Open
developer tools (DevTools)
in
its own browser. The DevTools browser
/
/
will remain associated with this browser. If the DevTools browser
is
/
/
already
open
then it will be focused,
in
which case the |windowInfo|,
/
/
|client|
and
|settings| parameters will be ignored. If |inspect_element_at|
/
/
is
non
-
NULL then the element at the specified (x,y) location will be
/
/
inspected. The |windowInfo| parameter will be ignored
if
this browser
is
/
/
wrapped
in
a cef_browser_view_t.
/
/
/
void CEF_CALLBACK show_dev_tools(
struct _cef_browser_host_t
*
self
,
const struct _cef_window_info_t
*
windowInfo,
struct _cef_client_t
*
client,
const struct _cef_browser_settings_t
*
settings,
const cef_point_t
*
inspect_element_at);
/
/
/
/
/
Returns the browser host
object
. This function can only be called
in
the
/
/
browser process.
/
/
/
struct _cef_browser_host_t
*
CEF_CALLBACK get_host(
struct _cef_browser_t
*
self
);
/
/
/
/
/
Open
developer tools (DevTools)
in
its own browser. The DevTools browser
/
/
will remain associated with this browser. If the DevTools browser
is
/
/
already
open
then it will be focused,
in
which case the |windowInfo|,
/
/
|client|
and
|settings| parameters will be ignored. If |inspect_element_at|
/
/
is
non
-
NULL then the element at the specified (x,y) location will be
/
/
inspected. The |windowInfo| parameter will be ignored
if
this browser
is
/
/
wrapped
in
a cef_browser_view_t.
/
/
/
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2022-8-29 01:31
被Learn Life编辑
,原因: