逆向调用QQ截图NT与WeChatOCR

已逆出64位的QQ Mojo Ipc通信过程, 请点击下方链接下载QQImpl项目, 此项目将QQ的Mojo Ipc和WeChatOCR独立了出来, 可以在任何项目中使用:

https://github.com/EEEEhex/QQImpl


采用QQImpl的成品在下方链接下载:

链接:QQScreenShotNT-Plus(https://pan.baidu.com/s/1v5M6EGO6aDN6uiBwEEIGSw?pwd=ctjs)

提取码:ctjs


QQ出Electron版了, 将截图独立出来了(应该是个过渡, 之后感觉会采用Electron重写), 并且使用的OCR改成了TencentOCR(低版本的WeChatOCR)


本文将重点介绍如何逆向调用WeChatOCR


0x00 逆向调用QQ截图NT


0x01 前期调研


先把QQ运行起来, 看看QQ NT的截图是什么情况的:

逆向调用QQ截图NT与WeChatOCR

注意到 有一个–pcqq-platform-channel-handle的命令行参数, 并且存在一个有mojo字样的命名管道。

那就搜索一下, 可以在这篇文章(https://zhuanlan.zhihu.com/p/512820043)里发现, mojo是Chrome实现的一套IPC机制, 并且想要简单使用的话, mojo IPC有一套Invitation机制。

Invitation简单来说就是创建子进程, 并给它传命令行–mojo-platform-channel-handle=xxx。

显然QQ就是从这个基础上改了一个命令行参数, 本质就是采用Mojo IPC进行通信。

那就开逆! (一开始分析的是QQNT 64位版本的, 后面为了方便分析用的是9.9.0.14619 x86版本的, 可能地址会变化)

0x02 分析QQScreenshot.exe


先从被调用端分析, 直接分析QQ太大了, 先小看一下main函数:

逆向调用QQ截图NT与WeChatOCR

这个main函数跟我以前逆的结果差不多哇, 总的来说就是启动QQ截图的环境, 图上画框的函数是启动IPC的, 进去看看:

逆向调用QQ截图NT与WeChatOCR

还是COM的形式, 这里碍于篇幅不再介绍child ipc的过程, 直接看parent ipc的过程。
(其实可以发现parent-ipc-core-x86.dll和child-ipc-core-x86.dll是同一个代码编译的DLL)

既然QQScreenshot是通过加载child-ipc-core-x86.dll再通过DllGetClassObject获取COM对象,
那QQ也很有可能是通过加载parent-ipc-core再动态获取COM对象实现的
那就动调一下QQ.exe, 搜索一下这种情况的代码:

DllGetClassObject = GetProcAddress(LibraryA, "DllGetClassObject");


0x03 分析parent-ipc-core-x86.dll


定位到DllGetClassObject字符串:

逆向调用QQ截图NT与WeChatOCR

可以看到和QQScreenshot.exe里初始化IPC的过程一模一样, 从parent-ipc-core-x86.dll中获取了一个COM对象。

看一下这个COM对象的实现了哪些方法:

逆向调用QQ截图NT与WeChatOCR

前三个函数是实现的IUnknown接口不用管, 其他的方法都可以通过字符串猜出来, 只要确定他们的参数类型就好了, 这里碍于篇幅省略掉(动调能直接看出来)
只介绍一下pCOM->LaunchChildProcess(…)方法:

0x04 LaunchChildProcess方法

通过动调可以发现LaunchChildProcess方法的第5个参数是用来设置接收IPC MSG函数的

在LaunchChildProcess方法里, 会调用base::Thread::task_runner新起一个线程来创建截图子进程↓

逆向调用QQ截图NT与WeChatOCR

怎么确定的这些函数呢?因为新版QQ本身就是一个Chrome嘛, 直接通过字符串去搜Chrome的源代码(https://source.chromium.org/chromium)就OK了。

下面进LaunchQQScreenShot函数看看:

逆向调用QQ截图NT与WeChatOCR

整个函数完全就是参考文章[1]里面介绍的Invitation机制。

这个函数其实是LaunchChildProcessInternal, 是LaunchChildProcess的内部函数,用来创建子进程:


pIMojoIpc->LaunchChildProcess(wchar_t *path, (char*)[] cmdlines, DWORD64 cmdlines_num, LPVOID callback, LPVOID lpClass);//最后一个参数是callback的第一个参数 是一个类指针


而LaunchChildProcess的最后一个参数是接收IPC消息回调函数的第一个参数。


之后就是仿写一遍就能调用了, 详细信息还是请见附件代码:

逆向调用QQ截图NT与WeChatOCR


0x10 逆向调用WeChatOCR

QQ采用的TencetnOCR就是低版本的WeChatOCR, 他们也是采用Mojo IPC进行通信


0x11 前期调研


通过这篇文章(https://keyou.github.io/blog/2020/01/03/Chromium-Mojo&IPC/)可以知道, 在Mojo中要想调用其他服务要通过Services Manager的转发, 也就是说QQScreenshot.exe是ServiceA, TencentOCR.exe是Service B, QQ.exe则是Service Manager(我猜的, 对不对也无伤大雅):
逆向调用QQ截图NT与WeChatOCR

那想要分析OCR调用过程就不得不用IDA分析一下wrapper.node了(QQ的一个DLL, 主要逻辑都在这个DLL里)

0x12 定位OCR调用函数


通过搜索字符串可以发现很多个感兴趣的:

逆向调用QQ截图NT与WeChatOCR

直接进DoOCRTask函数看看:

在DoOCRTask函数里进行了OCR的初始化以及OCR任务的发送, 即OCRdoInit和SendOCRTask函数。

逆向调用QQ截图NT与WeChatOCR

那我们需要分析一下是怎么实现的, 毕竟过程是QQScreenshot把IPC消息发给了我们的程序, 我们的程序要去调用OCR。

0x13 分析OCRdoInit函数


首先初始化了TencentOCR.exe和mmmojo.dll的路径, 并很明显赋值给了一个类的成员变量:

逆向调用QQ截图NT与WeChatOCR

之后就是进行了Mojo环境的初始化:在这里设置了命令行参数与回调函数。

逆向调用QQ截图NT与WeChatOCR

其中(*v11+4)这个函数是这样的:

逆向调用QQ截图NT与WeChatOCR

就是获取mmmojo.dll的导出函数并保存起来供后面调用
在SetMMMojoEnvironmentCallbacks函数里设置了接收IPC消息的函数这个后面分析。

0x14 分析SendOCRTask函数


在SendOCRTask函数里调用了很多方法, 一开始确实看蒙了, 但是通过后面分析可以发现其实逻辑很简单。

逆向调用QQ截图NT与WeChatOCR

函数一开始初始化了一些IPbMsg, CPbMsg什么的类, 一开始确实分析不出来这是啥玩意儿, 难不成为了给OCR发个IPC消息还得自己去实现这么多类? 那工作量可太大了(但其实不用, 通过后面的分析就会豁然开朗)。

逆向调用QQ截图NT与WeChatOCR

之后SendOCRTask函数调用CreateMMMojoWriteInfo创建了一个write_info这个write_info具体是啥现在也是不清楚, 然后拿到了WriteInfoRequest, 并调用memmove往里写东西, 最后调用了SendMMMojoWriteInfo发送出去, 那我们看看往里写了啥:

逆向调用QQ截图NT与WeChatOCR

wow 龙头出来了! 发现图片路径了, 但问题是前八个字节是啥?
先不管了 既然发送函数分析不出来那就先去看接收IPC MSG的函数。

0x15 分析接收IPC MSG的函数


通过分析SetMMMojoEnvironmentCallbacks的参数, 可以发现, 它设置的回调函数是一个虚表里的函数:

逆向调用QQ截图NT与WeChatOCR

看看OnReadPush:

逆向调用QQ截图NT与WeChatOCR

在这个函数里发现了protobuf的字样! (具体在哪发现的我忘了,反正确实有)
我们直接大胆猜测writeinfo、readinfo全是protobuf! 把数据扣下来测试一下(用protobuf-inspector这个工具):

逆向调用QQ截图NT与WeChatOCR

成功了! 同理, readinfo里都是OCR结果的数据包括识别的坐标、UTF8文字、识别率啥的。

0x16 分析protobuf格式


数据格式其实很容易看出来, 大体都能猜出来。
比如发送的格式:

message OcrRequest {
int32 unknow = 1; //必定为0
int32 task_id = 2;

message PicPaths {
repeated string pic_path = 1;
}

PicPaths pic_path = 3;
}

接收OCR结果的数据格式:

message OcrResponse {
int32 type = 1; //第一次运行OCR会有push一次type1, 正常OCR结束type0
int32 task_id = 2;
int32 err_code = 3;

message OcrResult {
message ResultPos { //四个角的坐标 左上 右上 右下 左下
message PosXY {
float x = 1;
float y = 2;
}

repeated PosXY pos = 1;
}

message SingleResult { //SingleResult是一行结果 OneResult是单字的
ResultPos single_pos = 1;
bytes single_str_utf8 = 2; //UTF8格式的字符串
float single_rate = 3; //单行的识别率

message OneResult {
ResultPos one_pos = 1;
bytes one_str_utf8 = 2;
}

repeated OneResult one_result = 4;
float lx = 5; //识别矩形的左上和右下的坐标? 可能是
float ly = 6;
float rx = 7;
float ry = 8;
int32 unknown_0 = 9; //未知
ResultPos unknown_pos = 10; //未知
}

repeated SingleResult single_result = 1; //repeated 每行的结果
int32 unknown_1 = 2;
int32 unknown_2 = 3;
}

OcrResult ocr_result = 4;
}

编译一下google的protobuf库, 再拿protoc生成一下头文件和.cc就能用了
WeChatOCR同理, 调用方法是一样的。

成品展示


逆向调用QQ截图NT与WeChatOCR

结束语


其实有很多细节没有写出来, 比如QQ设置的接收IPC消息的函数的分析, 比如SendOCRTask第一次调用其实是在OnRemoteConnect回调函数里新起了一个线程异步调用的, 再比如OCR初始化的时候需要SetMMMojoEnvirenmentInitParam来设置–usr-lib,就是mmmojo_64.dll所在的目录。

具体的细节都可以去看附件的代码, 文章中没写出来的代码里都有哦(只能使用Release x86配置编译)。

参考文章


[1]Mojo IPC 设计与Invitation机制实现
(https://zhuanlan.zhihu.com/p/512820043)

[2]Chrome Code Search
(https://source.chromium.org/chromium)

[3]Chromium Mojo & IPC
(https://keyou.github.io/blog/2020/01/03/Chromium-Mojo&IPC/)



逆向调用QQ截图NT与WeChatOCR


看雪ID:0xEEEE

https://bbs.kanxue.com/user-home-901761.htm

*本文为看雪论坛精华文章,由 0xEEEE 原创,转载请注明来自看雪社区

逆向调用QQ截图NT与WeChatOCR

# 往期推荐

1、在 Windows下搭建LLVM 使用环境

2、深入学习smali语法

3、安卓加固脱壳分享

4、Flutter 逆向初探

5、一个简单实践理解栈空间转移

6、记一次某盾手游加固的脱壳与修复


逆向调用QQ截图NT与WeChatOCR


逆向调用QQ截图NT与WeChatOCR

球分享

逆向调用QQ截图NT与WeChatOCR

球点赞

逆向调用QQ截图NT与WeChatOCR

球在看

原文始发于微信公众号(看雪学苑):逆向调用QQ截图NT与WeChatOCR

版权声明:admin 发表于 2023年9月10日 下午6:00。
转载请注明:逆向调用QQ截图NT与WeChatOCR | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...