selph
selph
Published on 2024-08-01 / 265 Visits
0
1

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

#re

前景回顾

发送图片消息延续上次发送文本消息的分析(参考资料[0]),从日志中得到的字符串关键词SendMessageMgr,搜索找到疑似发送图片的字符串:

.rdata:0000000184EFB930	0000001D	C	SendMessageMgr::sendImageMsg

交叉引用发现,全部来自同一个函数的调用:

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"

这个函数十有八九就是发送图片的了

日志分析

抓一下发送图片时候的调试信息:(这里是筛出来的和发送图片相关的几个)

(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

刚好和函数中的日志匹配,任取一条函数中的日志:

  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);

再次确认了这个函数就是用来发送图片的

伪代码分析

接下来对这个函数继续分析,开始的第一部分如下:

  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[0] = *(_OWORD *)xmmword_184DED0B0;
    v243[0] = *(_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*)

继续往下看:

  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的值

再往下就是创建缩略图,发送图片这些操作了,中间有好多没有调试信息的部分,有兴趣可以自行分析,这块内容本文不做重点

向上追一层,看看参数是什么:

__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传入了呗

参数一模一样的的传递

继续向上看:

        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个,很面熟啊,附近发现上次见过的类似的文本消息发送函数的调用:

            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 中获取即可,该函数的内容:

__int64 sub_181B4F500()
{
  __int64 result; // rax
  __int64 v1; // rbx
  void *v2; // [rsp+40h] [rbp+8h]

  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 的其他使用:

        sub_1820D84F0(v12 + 80, (__int64)v215, v12 + 8, (__int64)&Block);
        ChatMsg_destruct(v215);

说明 v215 是之前见过的 ChatMsg 结构体(之前的分析见参考资料[0]),大小是0x450

函数和参数都找齐了,可以去写代码了

踩坑:a5是结构体

写代码发现参数都是对的,但是就是报错,有异常访问,在排查过程中,突然意识到一个点,这个参数a5,可能不是一个指向1的指针,而是个结构体!!!

结合上下文代码,以及调试正常情况,ida给变量的命名,都表明这很可能是结构体

  • 结合上下文代码,进入函数后,发现有对a5成员的访问(最大访问到 0x48 偏移):v9 = *(_QWORD *)(a5 + 0x48);
  • ida给变量命名是,连号意味着是内存上也连着的
  • 每次正常情况下,v185+8的地方都是个可读地址

再次看伪代码,这里的连续赋值的变量刚好10个,刚好对应到内部函数对a5最大访问到0x48偏移处,所以可以认为这就是结构体的初始化

        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的指针

        ChatMsg_construct_2((__int64)chatmsg_obj);

内部是构造函数,在之前文章内见过的(参考资料[0]),大小为0x450的结构体

到此才是真正的参数找齐了!

代码实现(核心代码)

#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;
}

效果展示

执行代码,自动发送图片出去:

image

参考资料


Comment