kn0sky 发表于 2024-8-1 16:06

微信逆向:发送图片消息分析



## 前言

上次有同学评论说想看发送图片的分析,这不就来了嘛,本人技术水平有限,有啥问题欢迎指出,没啥问题欢迎留言互动(

之前的分析文章:

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)

sunzhw 发表于 2024-8-1 19:37

牛人,学习一下,点赞

zzylyzs 发表于 2024-8-9 01:14

楼主可以逆向一下消息接收吗,有消息来的时候先执行了 SyncMgr::doAddMsg 然后会调用 SyncMgr::ConvertAddMsgToChatMsg 这个函数
我HOOK了这个函数,它有3个参数,第二个参数应该是解析消息结果的结构体指针,分析了一下大概结构体是
+38 -> 消息类型 1 = 消息 51 = xml 通知47 = xml 表情包

+48 ->消息来源
+50 ->文本长度
+54 ->总来源长度
+88 ->消息地址
+90 ->文本长度
+94 ->总消息长度

虽然这样已经可以获取到wxid 和 消息内容了,但是在私聊和群聊的时候,都不能判断是自己发的消息同步过来的,还是其他人发的,请教一下楼主是如何hook消息来源通知的

gqdsc 发表于 2024-8-1 16:27

大神永远就是大神

LODGE 发表于 2024-8-1 16:33

大佬的世界对小白来说是天书

556699 发表于 2024-8-1 16:41

小白一脸懵,啥都没看懂{:1_907:}

Clangy 发表于 2024-8-1 17:02

强!!!

happy1every1day 发表于 2024-8-1 17:17

我啥时候能这样啊

DancingRain 发表于 2024-8-1 17:35

学会了&#128536;

phr 发表于 2024-8-1 18:27

牛!!!

csvip 发表于 2024-8-1 19:00

大神牛!!!
页: [1] 2 3 4 5 6 7 8
查看完整版本: 微信逆向:发送图片消息分析