首页
社区
课程
招聘
[原创]基于钉钉探索针对CEF框架的一些逆向思路
发表于: 2022-8-29 01:30 29481

[原创]基于钉钉探索针对CEF框架的一些逆向思路

2022-8-29 01:30
29481

CEF 是 Chromium Embedded Framework 的简写,这是一个把 Chromium 嵌入其他应用的开源框架
现在市面上有许多桌面软件都使用了CEF框架,比如我们经常使用的钉钉、网易云音乐等等。
我本意是突破钉钉的一些功能限制,结果发现钉钉使用了CEF框架,故开始对CEF框架做了一些浮于表面的探索。由于个人能力有限,如果文章中有什么错误之处,还望大家多多指教

在开始正式开始之前,有必要先观察一下钉钉的安装目录,看看里面有哪些我们感兴趣的文件。
我电脑上的钉钉版本是6.5.30-Release.7289101

通过查看运行中的DingTalk.exe进程的映射文件锁定你电脑上目前运行的钉钉的目录(这个地方会发现有多个同名进程,我们随便选择一个)

有朋友可能要问为什么要通过这种方式确定目录,这其实是因为钉钉的安装目录下面一般都会存在两个版本的文件,一个是当前版本另外一个则是上一个版本。据我观察这两个目录下的文件结构基本一致。

我电脑上的钉钉目前就使用的是current目录。
打开current目录可以发现许多的资源文件和依赖库文件,其中对于本文来说最重要的文件是libcef.dllweb_content.paklibcef.dll是CEF框架的支持库,web_content.pak则是钉钉缓存在本地的html、js、css文件。web_content.pak本质是一个zip压缩文件,我们可以通过解压软件查看里面的内容

那么可以知道这个压缩文件是被加密了,解压的时候会让输入密码,后面会提到怎么获取密码。通过观察文件的名字也大致可以猜出这些文件的作用。
钉钉中使用CEF框架的区域主要在聊天框显示区域。

下面主要介绍三个方面的内容

另外提一嘴,在钉钉的安装目录下面我们还可以发现有cef_LICENSE.txt``duilib_license.txtlicense声明,通过这些声明我们也可以获得一些信息,比如钉钉还使用了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.25104.0.5112.102是CEF和Chromium的版本信息,gd80d467git commithash

我们可以先看看钉钉使用的libcef.dll是什么版本

这里发现一个很坑的点,就是Windows的文件属性显示不全,而且还不能拖开,也不能复制。
不过根据已经显示出来的内容,可以发现钉钉使用的libcef.dll明显不是在官方提供的页面下载的。版本约定和官方的太不一样,git commit是8位的,官方库可是只有7位。
g2e1fb6b,我尝试使用g2e1fb62e1fb6bhashcommit列表中搜索也没有发现,只能猜测钉钉使用的libcef.dll是自己从源码编译的,而且可能对源码做了一些修改吧。
同时我使用91.0.0在下载界面搜索也没有发现相同的版本。后面的版本信息显示不全,得想个办法解决一下子,争取下载一个最接近的版本。其实这里有一个大坑,后面会提到。

其实文件属性的信息是存在于PE中的资源节中的,使用Windows系统提供的API或者自己解析都可以拿到相关信息。不过我是本着能不写代码就不写代码的懒人思想的。

一般这种库或者框架的动态库中都会提供函数查询版本信息,所以我浏览了一下libcef的导出函数
libcef的导出函数中我发现了cef_version_info这个函数,看名字就知道干什么用的了。
该说不说,官方提供了C++版本的文档,为什么不提供一个libcefapi文档呢?反正我是没找到。不过虽然没有文档,还是有源码和大量注释的。

这个函数的定义是这样的

我们再结合下面的信息
从反汇编很明显的看出来这是一个数组下标寻址

从源码得知不同的参数获取不同的信息,那么完整的版本信息存在于一个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/Akernel32.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/WReadFileA/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_tget_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编辑 ,原因:
收藏
免费 21
支持
分享
打赏 + 110.00雪花
打赏次数 2 雪花 + 110.00
 
赞赏  AgileWorker   +10.00 2022/12/08 加我VX25489181
赞赏  Editor   +100.00 2022/09/05 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (32)
雪    币: 8233
活跃值: (2736)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
谢谢,学习了
2022-8-29 05:31
0
雪    币: 3043
活跃值: (6786)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3

像你文中那样没法看清软件版本号可以用7zip之类支持多种压缩格式的解压缩软件打开exe或dll文件, 然后在 .rsrc\1033 路径下有个version.txt文件, 这个文件记录了程序的版本号之类的内容. 文件内容如下:

FILEVERSION    91,0,0,0
PRODUCTVERSION 91,0,0,0
FILEFLAGSMASK  0x17
FILEFLAGS      0x0
FILEOS         VOS_UNKNOWN | VOS__WINDOWS32
FILETYPE       VFT_DLL
FILESUBTYPE    0x0
{
  BLOCK "StringFileInfo"
  {
    BLOCK "040904b0"
    {
      VALUE "FileDescription",   "Chromium Embedded Framework (CEF) Dynamic Link Library"
      VALUE "FileVersion",       "91.0.0-CEF91.39+gd9b4641+chromium-91.0.4472.164"
      VALUE "InternalName",      "libcef"
      VALUE "LegalCopyright",    "Copyright (C) 2022 The Chromium Embedded Framework Authors"
      VALUE "OriginalFilename",  "libcef.dll"
      VALUE "ProductName",       "Chromium Embedded Framework (CEF) Dynamic Link Library"
      VALUE "ProductVersion",    "91.0.0-CEF91.39+gd9b4641+chromium-91.0.4472.164"
    }
  }
  BLOCK "VarFileInfo"
  {
    VALUE "Translation", 0x409, 1200
  }
}

最后于 2022-8-29 09:57 被微启宇编辑 ,原因:
2022-8-29 09:40
1
雪    币: 2781
活跃值: (2379)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
4
用7z试了一下确实可以,感谢,受教了。
2022-8-29 10:06
0
雪    币: 478
活跃值: (2838)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
大佬牛逼。
2022-8-29 16:03
0
雪    币: 1115
活跃值: (1259)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
6

https://cef-builds.spotifycdn.com/index.html#windows32:74



可以在这里下载到所有cef小版本(lib+头文件),最好和目标程序用的版本一样


比如上面LXL用的74版本

最后于 2022-8-30 11:41 被风铃i编辑 ,原因:
2022-8-30 11:40
0
雪    币: 2781
活跃值: (2379)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
7
风铃i https://cef-builds.spotifycdn.com/index.html#windows32:74可以在这里下载到所有cef小版本(lib+头文件),最好和目标程序用的版本一样比如上面 ...
文中有提到下载,但是钉钉的库版本不是91嘛。大佬是怎么确定是74版本的阿。
2022-8-30 19:53
0
雪    币: 1115
活跃值: (1259)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
8
Learn Life 文中有提到下载,但是钉钉的库版本不是91嘛。大佬是怎么确定是74版本的阿。

举了个例子,lol大厅是用的chrome74,下载链接的可以直接用

最后于 2022-8-30 23:56 被风铃i编辑 ,原因:
2022-8-30 22:15
0
雪    币: 2397
活跃值: (2305)
能力值: (RANK:400 )
在线值:
发帖
回帖
粉丝
9
厉害了,学习学习。
我也写过一个钉钉防撤回的工具,思路还不太一样。
开源代码:https://github.com/mohuihui/DingTalk_Assistant
2022-8-31 09:48
0
雪    币: 1755
活跃值: (1680)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
以前找的点都是找远程调试端口打开,现在又学到了,谢谢大佬分享。
2022-8-31 11:54
0
雪    币: 2781
活跃值: (2379)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
11
莫灰灰 厉害了,学习学习。 我也写过一个钉钉防撤回的工具,思路还不太一样。 开源代码:https://github.com/mohuihui/DingTalk_Assistant
大佬是从C++层改了关键跳,位置在MainFrame.dll中。强!
2022-8-31 13:18
0
雪    币: 2781
活跃值: (2379)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
12
远程调试端口  能细谈一下吗
2022-8-31 13:19
0
雪    币: 2781
活跃值: (2379)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
13
hanhaochi 以前找的点都是找远程调试端口打开,现在又学到了,谢谢大佬分享。
远程调试端口  能细谈一下吗
2022-8-31 13:20
0
雪    币: 1755
活跃值: (1680)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14

chrome源码

下面图在目标软件中分析的。


2022-8-31 16:53
0
雪    币: 986
活跃值: (1693)
能力值: ( LV6,RANK:97 )
在线值:
发帖
回帖
粉丝
15
牛牛牛!想请教下楼主,CEF框架还运用在哪些有名的别的应用软件上呀
2022-8-31 21:04
0
雪    币: 2781
活跃值: (2379)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
16
橘喵Cat 牛牛牛!想请教下楼主,CEF框架还运用在哪些有名的别的应用软件上呀
网易云音乐有的,网易有道好像也有,官方有个列表https://en.wikipedia.org/wiki/Chromium_Embedded_Framework#Applications_using_CEF
2022-8-31 22:36
0
雪    币: 2781
活跃值: (2379)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
17
hanhaochi chrome源码下面图在目标软件中分析的。
学习了,值得研究!
2022-8-31 22:37
0
雪    币: 1372
活跃值: (5383)
能力值: ( LV13,RANK:240 )
在线值:
发帖
回帖
粉丝
18
莫灰灰 厉害了,学习学习。 我也写过一个钉钉防撤回的工具,思路还不太一样。 开源代码:https://github.com/mohuihui/DingTalk_Assistant
好久没看到MFC的工程了。一时间竟有点亲切感
2022-9-1 09:40
0
雪    币: 2397
活跃值: (2305)
能力值: (RANK:400 )
在线值:
发帖
回帖
粉丝
19
IamHuskar 好久没看到MFC的工程了。一时间竟有点亲切感
现在懂MFC的,说明有点年级了。心塞,哈哈哈哈。
2022-9-1 09:50
0
雪    币: 250
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
20
 91.0.4472.164  这个版本已经下不到了,楼主可以把源码压缩包发我下么。
2022-9-2 15:56
0
雪    币: 2781
活跃值: (2379)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
21
wx_Fruits Basket 91.0.4472.164 这个版本已经下不到了,楼主可以把源码压缩包发我下么。

点击 Show more builds

2022-9-3 17:12
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
22
MFC都忘完了啊
2022-9-3 20:25
0
雪    币: 1440
活跃值: (1401)
能力值: ( LV3,RANK:23 )
在线值:
发帖
回帖
粉丝
23

2022-9-8 09:56
0
雪    币: 3848
活跃值: (4417)
能力值: (RANK:215 )
在线值:
发帖
回帖
粉丝
24
感谢分享,连同另外一个帖子学习了
2022-9-19 19:57
0
雪    币: 475
活跃值: (157)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
感谢分享
2022-9-27 15:02
0
游客
登录 | 注册 方可回帖
返回
//