微信逆向:发送图片消息分析
## 前言
上次有同学评论说想看发送图片的分析,这不就来了嘛,本人技术水平有限,有啥问题欢迎指出,没啥问题欢迎留言互动(
之前的分析文章:
1. [微信逆向:hook 强制输出调试信息 - 『软件调试区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn](https://www.52pojie.cn/thread-1940040-1-1.html)
2. [微信逆向:定位功能调用(以文本消息发送为例) - 『软件调试区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn](https://www.52pojie.cn/thread-1940500-1-1.html)
3. [微信逆向:防多开检测原理和绕过 - 『软件调试区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn](https://www.52pojie.cn/thread-1944151-1-1.html)
4. [微信逆向:防撤回消息分析 - 『软件调试区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn](https://www.52pojie.cn/thread-1947110-1-1.html)
## 前景回顾
发送图片消息延续上次发送文本消息的分析(参考资料),从日志中得到的字符串关键词SendMessageMgr,搜索找到疑似发送图片的字符串:
```c
.rdata:0000000184EFB930 0000001D C SendMessageMgr::sendImageMsg
```
交叉引用发现,全部来自同一个函数的调用:
```c
Direction Type Address Text
Up o SendMessageMgr__sendImageMsg+14F lea r9, aSendmessagemgr_8; "SendMessageMgr::sendImageMsg"
Up o SendMessageMgr__sendImageMsg+254 lea r9, aSendmessagemgr_8; "SendMessageMgr::sendImageMsg"
Up o SendMessageMgr__sendImageMsg+CFF lea r9, aSendmessagemgr_8; "SendMessageMgr::sendImageMsg"
Up o SendMessageMgr__sendImageMsg+DF4 lea r9, aSendmessagemgr_8; "SendMessageMgr::sendImageMsg"
Up o SendMessageMgr__sendImageMsg+F5F lea r9, aSendmessagemgr_8; "SendMessageMgr::sendImageMsg"
Up o SendMessageMgr__sendImageMsg+10C4 lea r9, aSendmessagemgr_8; "SendMessageMgr::sendImageMsg"
Up o SendMessageMgr__sendImageMsg+1328 lea r9, aSendmessagemgr_8; "SendMessageMgr::sendImageMsg"
Up o SendMessageMgr__sendImageMsg+1636 lea r9, aSendmessagemgr_8; "SendMessageMgr::sendImageMsg"
Up o SendMessageMgr__sendImageMsg+195E lea r9, aSendmessagemgr_8; "SendMessageMgr::sendImageMsg"
```
这个函数十有八九就是发送图片的了
## 日志分析
抓一下发送图片时候的调试信息:(这里是筛出来的和发送图片相关的几个)
```c
(2024-7-10:14:11:48:318 02532)-i/SendMessageMgr: sendImageMsg. mmbuf.size:18286
(2024-7-10:14:11:48:362 02532)-i/SendMessageMgr:send image progress(183 of 18288). msgId=360289069701268057
(2024-7-10:14:11:48:919 02532)-i/SendMessageMgr:send image ok. msgId=360289069701268057
```
刚好和函数中的日志匹配,任取一条函数中的日志:
```c
log_message(
2,
(__int64)"D:\\Tools\\agent\\workspace\\MicroMsgWindowsV3911\\MicroMsgWin\\02_manager\\SendMessageMgr.cpp",
717,
(__int64)"SendMessageMgr::sendImageMsg",
"SendMessageMgr",
" sendImageMsg. mmbuf.size:%d",
&v310,
&v309,
&v308,
&v313,
&v312,
&v311);
```
再次确认了这个函数就是用来发送图片的
## 伪代码分析
接下来对这个函数继续分析,开始的第一部分如下:
```c
v249 = a4;
v7 = a2;
*(_QWORD *)v280 = a2;
v276 = a1;
v257 = (char *)a2;
v8 = a5;
if ( !*(_BYTE *)(sub_181B50D00() + 2040) )
goto LABEL_7;
sub_181BE0050();
if ( (*(_BYTE *)(sub_181B50D00() + 1272) & 1) != 0 )
{
sub_181BE0050();
sub_182615020(v340, a3);
sub_1824CA8B0(v10, v9);
}
if ( !(unsigned __int8)sub_1825E3A70(a4) )
{
v274 = *(_OWORD *)xmmword_184DED0B0;
v243 = *(_OWORD *)xmmword_184DED0B0;
v244 = *(_OWORD *)xmmword_184DED0B0;
*(_OWORD *)v271 = *(_OWORD *)xmmword_184DED0B0;
*(_OWORD *)v268 = *(_OWORD *)xmmword_184DED0B0;
v261 = *(_OWORD *)xmmword_184DED0B0;
log_message(
4,
(__int64)"D:\\Tools\\agent\\workspace\\MicroMsgWindowsV3911\\MicroMsgWin\\02_manager\\SendMessageMgr.cpp",
710,
(__int64)"SendMessageMgr::sendImageMsg",
"SendMessageMgr",
" File not found.",
&v261,
(__int128 *)v268,
(__int128 *)v271,
&v244,
v243,
v274);
v14 = sub_181B4A130(v13, v12);
v15 = 17;
LABEL_6:
sub_182223DF0(v14, 95, v15, 1, 1);
LABEL_7:
sub_181B59670(v7);
return v7;
}
```
这里的调试信息表示,文件未找到,进入log_message的条件是sub_1825E3A70(a4),把关键功能放在if里已经不是第一次见了,这里直接猜测这个函数是类似打开图片的操作,而这里的a4是参数,刚好又是个指针类型(void*)
继续往下看:
```c
LOBYTE(v11) = 1;
v16 = sub_1825E89F0(a4, v11);
v258 = (void *)v16;
LOBYTE(v306) = 0;
*((_QWORD *)&v306 + 1) = v16;
v311 = *(_OWORD *)xmmword_184DED0B0;
v312 = *(_OWORD *)xmmword_184DED0B0;
v313 = *(_OWORD *)xmmword_184DED0B0;
v308 = *(_OWORD *)xmmword_184DED0B0;
v309 = *(_OWORD *)xmmword_184DED0B0;
v310 = v306;
log_message(
2,
(__int64)"D:\\Tools\\agent\\workspace\\MicroMsgWindowsV3911\\MicroMsgWin\\02_manager\\SendMessageMgr.cpp",
717,
(__int64)"SendMessageMgr::sendImageMsg",
"SendMessageMgr",
" sendImageMsg. mmbuf.size:%d",
&v310,
&v309,
&v308,
&v313,
&v312,
&v311);
if ( !v16 )
{
v14 = sub_181B4A130(v18, v17);
v15 = 18;
goto LABEL_6;
}
if ( (unsigned int)(v16 - 1) > 0x77FF )
{
if ( (unsigned int)(v16 - 30721) > 0x117FF )
{
if ( (unsigned int)(v16 - 102401) > 0x257FF )
{
if ( (int)v16 <= 256000 )
goto LABEL_19;
v19 = sub_181B4A130(v18, v17);
v20 = 53;
}
else
{
v19 = sub_181B4A130(v18, v17);
v20 = 52;
}
}
else
{
v19 = sub_181B4A130(v18, v17);
v20 = 51;
}
}
else
{
v19 = sub_181B4A130(v18, v17);
v20 = 50;
}
```
这里的功能应该是sub_1825E89F0,参数又是a4,调试信息是输出图片缓冲区大小,那这个函数多半是获取图片大小的,如果获取失败跳转到LABEL_6,然后就是对于不同大小的图片分别进行处理,并设置v20的值
再往下就是创建缩略图,发送图片这些操作了,中间有好多没有调试信息的部分,有兴趣可以自行分析,这块内容本文不做重点
向上追一层,看看参数是什么:
```c
__int64 __fastcall sub_1822C13B0(int a1, __int64 a2, int a3, __int64 a4, __int64 a5)
{
...
v18 = *(_DWORD *)a5;
...
SendMessageMgr::sendImageMsg(a1, a2, a3, a4, (__int64)&v18);
if ( Block )
{
free(Block);
Block = 0i64;
}
v23 = 0i64;
if ( v24 )
free(v24);
}
else
{
sub_181B59670(a2);
}
return a2;
}
```
这里v18是a5解引用,参数传递的又是v18的地址,那就是直接把a5传入了呗
参数一模一样的的传递
继续向上看:
```c
ChatMsg_construct_2((__int64)chatmsg_obj);
sub_1824EC670(chatmsg_obj, v12 + 40);
sub_18260DBF0(&v182, &qword_18590DA90);
sub_18260DBF0(&v180, &qword_18590DA90);
HIDWORD(v145) = v9 | 6;
v185 = 1;
v186 = 0i64;
v187 = 0;
v188 = 0i64;
v189 = 0i64;
v191 = 0i64;
v192 = 0;
v190 = 0i64;
v193 = &v180;
v194 = &v182;
if ( v180 )
{
free(v180);
v180 = 0i64;
v181 = 0;
}
if ( v182 )
{
free(v182);
v182 = 0i64;
v183 = 0;
}
v186 = chatmsg_obj;
v48 = SendMessageMgr_Constructor();
sendImageMsg_2(v48, (__int64)v215, (int)&Block, v12 + 8, (__int64)&v185);// 发送图片
```
参数5个,很面熟啊,附近发现上次见过的类似的文本消息发送函数的调用:
```c
SendMessageMgr::sendMsg(
(__int64)chatmsg_obj,
(__int64)&Block,
v12 + 8,
v12 + 80,
1,
1,
*(_DWORD *)(v12 + 4),
0i64);
ChatMsg_destruct(chatmsg_obj);
goto LABEL_82;
}
```
看来是找对地方了
* a1 是个结构体
* a2 是个缓冲区
* a3 是个 wxid 的引用
* a4 是个图片路径的引用
* a5 是 1(如果这么想的话,就踩坑了!下面介绍)
a1 从 sub_181B4F500 中获取即可,该函数的内容:
```c
__int64 sub_181B4F500()
{
__int64 result; // rax
__int64 v1; // rbx
void *v2; //
result = qword_1859374D0;
if ( !qword_1859374D0 )
{
EnterCriticalSection(&stru_185941F38);
v1 = qword_1859374D0;
if ( !qword_1859374D0 )
{
v2 = operator new(0x150ui64);
qword_1859374D0 = SendMessageMgr((__int64)v2);
sub_182614D30(sub_181B4FF00);
v1 = qword_1859374D0;
}
LeaveCriticalSection(&stru_185941F38);
return v1;
}
return result;
}
```
返回了个SendMessageMgr的对象,中间调用的那个是构造函数
a2 的参数来自 v215,在上面发现 v215 的其他使用:
```c
sub_1820D84F0(v12 + 80, (__int64)v215, v12 + 8, (__int64)&Block);
ChatMsg_destruct(v215);
```
说明 v215 是之前见过的 ChatMsg 结构体(之前的分析见参考资料),大小是0x450
函数和参数都找齐了,可以去写代码了
### 踩坑:a5是结构体
写代码发现参数都是对的,但是就是报错,有异常访问,在排查过程中,突然意识到一个点,这个参数a5,可能不是一个指向1的指针,而是个结构体!!!
结合上下文代码,以及调试正常情况,ida给变量的命名,都表明这很可能是结构体
* 结合上下文代码,进入函数后,发现有对a5成员的访问(最大访问到 0x48 偏移):`v9 = *(_QWORD *)(a5 + 0x48);`
* ida给变量命名是,连号意味着是内存上也连着的
* 每次正常情况下,v185+8的地方都是个可读地址
再次看伪代码,这里的连续赋值的变量刚好10个,刚好对应到内部函数对a5最大访问到0x48偏移处,所以可以认为这就是结构体的初始化
```c
v185 = 1;
v186 = 0i64;
v187 = 0;
v188 = 0i64;
v189 = 0i64;
v191 = 0i64;
v192 = 0;
v190 = 0i64;
v193 = &v180;
v194 = &v182;
if ( v180 )
{
free(v180);
v180 = 0i64;
v181 = 0;
}
if ( v182 )
{
free(v182);
v182 = 0i64;
v183 = 0;
}
v186 = chatmsg_obj;
```
这里的v186来自一个构造函数,其他的不是1就是0或者指向0的指针
```c
ChatMsg_construct_2((__int64)chatmsg_obj);
```
内部是构造函数,在之前文章内见过的(参考资料),大小为0x450的结构体
到此才是真正的参数找齐了!
## 代码实现(核心代码)
```c
#include "pch.h"
#include "selSendImageMsg.h"
_ChatMsg_construct fChatMsg_construct;
_SendMessageMgr_Constructor fSendMessageMgr_Constructor;
_SendImageMsg fSendImageMsg;
struct ImageMsg {
__int64 v185; // 1
__int64 v186; // ChatObj
__int64 v187;
__int64 v188;
__int64 v189;
__int64 v191;
__int64 v192;
__int64 v190;
__int64* v193; // ptr -> 0
__int64* v194; // ptr -> 0
};
__int64 sendImageMsg(wchar_t* target_wxid, wchar_t* msgPath) {
//fSendMessageMgr_Constructor = (_SendMessageMgr_Constructor)AOBScan_SundayEx(FUNCTION_SendMessageMgr_Constructor);
//fSendImageMsg = (_SendImageMsg)AOBScan_SundayEx(FUNCTION_SendImageMsg);
fChatMsg_construct = (_ChatMsg_construct)((__int64)LoadLibraryA("wechatwin.dll") + FUNCTION_ChatMsg_Constructor_o);
fSendMessageMgr_Constructor = (_SendMessageMgr_Constructor)((__int64)LoadLibraryA("wechatwin.dll") + FUNCTION_SendMessageMgr_Constructor_o);
fSendImageMsg = (_SendImageMsg)((__int64)LoadLibraryA("wechatwin.dll") + FUNCTION_SendImageMsg_o);
__int64 a1 = fSendMessageMgr_Constructor();
__int64 a2 = (__int64)calloc(1, 0x1000);
WxString a3 = { 0 };
a3.str = target_wxid;
a3.len = wcslen(a3.str);
a3.len2 = wcslen(a3.str);
WxString a4 = { 0 };
a4.str = msgPath;
a4.len = wcslen(a4.str);
a4.len2 = wcslen(a4.str);
char* chatObj = (char*)calloc(1, 0x450);
fChatMsg_construct((__int64)chatObj);
__int64 v180 = 0;
__int64 v182 = 0;
ImageMsg a5 = { 0 };
a5.v185 = 1;
a5.v186 = (__int64)chatObj;
a5.v193 = &v180;
a5.v194 = &v182;
fSendImageMsg(a1, a2, (__int64) &a3, (__int64) &a4, (__int64)&a5);
return 0;
}
```
## 效果展示
执行代码,自动发送图片出去:
## 参考资料
* [微信逆向:定位功能调用(以文本消息发送为例) - 我可是会飞的啊 (kn0sky.com)](https://www.kn0sky.com/?p=427fec77-5896-4f5f-80f4-12e05617d011)
* [原文链接] [微信逆向:发送图片消息分析 - 我可是会飞的啊 (kn0sky.com)](https://www.kn0sky.com/?p=0abac048-c887-4500-ab09-b75c4a83718c)
牛人,学习一下,点赞 楼主可以逆向一下消息接收吗,有消息来的时候先执行了 SyncMgr::doAddMsg 然后会调用 SyncMgr::ConvertAddMsgToChatMsg 这个函数
我HOOK了这个函数,它有3个参数,第二个参数应该是解析消息结果的结构体指针,分析了一下大概结构体是
+38 -> 消息类型 1 = 消息 51 = xml 通知47 = xml 表情包
+48 ->消息来源
+50 ->文本长度
+54 ->总来源长度
+88 ->消息地址
+90 ->文本长度
+94 ->总消息长度
虽然这样已经可以获取到wxid 和 消息内容了,但是在私聊和群聊的时候,都不能判断是自己发的消息同步过来的,还是其他人发的,请教一下楼主是如何hook消息来源通知的 大神永远就是大神 大佬的世界对小白来说是天书 小白一脸懵,啥都没看懂{:1_907:} 强!!! 我啥时候能这样啊 学会了😘 牛!!! 大神牛!!!