吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 8681|回复: 29
收起左侧

[原创] PCXX逆向:发送与接收消息的分析与代码实现

  [复制链接]
鬼手56 发表于 2019-6-22 16:52
本帖最后由 鬼手56 于 2020-5-22 10:30 编辑

定位微信的消息接收函数

我们先来定位一下消息接收函数,这对我们后面分析消息发送函数会有所帮助

定位消息接收函数的相关思路

与接收消息函数最直接相关的东西肯定是消息本身,所以消息本身的内容就是我们的切入点。我们可以首先找到存放消息内容的地址,然后对地址下断,通过栈回溯最终定位到接收消息的函数

定位消息内容的地址

首先用另外一个微信给自己发一条消息,在不点开消息的状态下用CE搜索消息内容

1560921587933.png

然后再发送一条消息

1560921650555.png

此时有效结果只剩下3个,把这三个地址加入到下方地址栏,右键->更改记录->类型

1560921772858.png

将显示范围调大

1560921796891.png

其中有一个是最原始的未经处理的消息,也是显示的最全的那一条,剩下的两条是经过处理的。我们需要中间那个未经任何处理的消息

 定位接收消息函数的地址

既然消息内容的地址找到了,那么接下来就通过这个内容来找到接收消息的函数

1560921921648.png

在OD中找到这个地址,下内存写入断点。为什么是写入不是访问?因为这个是最原始的的消息,要想对这条消息进行处理就必须改写当前的这条消息,所以在这个位置下内存写入断点,当客户端对这条原始消息进行处理时,断点就会断下。

此时,再次发送一条消息,程序断下,删除内存写入断点,这个时候堆栈的返回地址里面一定有一个函数是用来接收消息的。我们点击K查看调用堆栈。

1560922776546.png

经过排查确认是这个call,大家可以根据我图中的函数特征直接找到这个call。我们在这个函数下断点,让程序再次断下,分析附近的代码。

分析接收消息函数

好友消息

1560923899627.png

此时我们点击查看堆栈中esp寄存器的值,数据窗口跟随

1560923923543.png

此时[esp+0x40]的位置是发送者的微信ID,[esp+0x68]的位置是消息内容(通过这个call我们还可以拿到文件助手的ID是filehelper,这对后面分析消息发送会有用,大家可以去试验一下)
1560923951137.png

[esp+0x114]的位置是0,[esp+0x128]的位置是一串未知数据。

1560923988035.png

群消息

然后我们再发送一条群消息,看看有什么区别
1560924027886.png

此时[esp+0x40]的位置是群ID,[esp+0x68]的位置是消息内容

1560924063833.png

[esp+0x114]的地址不再是零,而是消息发送者的ID,[esp+0x128]的位置依旧是一串未知数据。大家可以用同样的方式分析处图片和表情在内存中的表现形式。

那么我们只要记录下这个call的地址+偏移,然后写一个dll注入到微信进程空间中,HOOK这个函数,就能拦截所有的消息,并显示到我们的程序中。

总结

总结一下思路,寻找切入点->找地址->下断->栈回溯分析。就是这么简单粗暴

代码实现

注入之后接收消息的代码如下:

void RecieveMsg()
{
        wstring receivedMessage = L"";
        BOOL isFriendMsg = FALSE;
        //[[esp]]
        //信息块位置
        DWORD** msgAddress = (DWORD * *)r_esp;

        //消息类型[[esp]]+0x30
        //[01文字] [03图片] [31转账XML信息] [22语音消息] [02B视频信息]
        //感谢:.順唭_自嘫ɑ、unravel提供类型消息。
        DWORD msgType = *((DWORD*)(**msgAddress + 0x30));
        receivedMessage.append(L"消息类型:");
        switch (msgType)
        {
        case 0x01:
                receivedMessage.append(L"文字 ");
                break;
        case 0x03:
                receivedMessage.append(L"图片 ");
                break;

        case 0x22:
                receivedMessage.append(L"语音 ");
                break;
        case 0x25:
                receivedMessage.append(L"好友确认 ");
                break;
        case 0x28:
                receivedMessage.append(L"POSSIBLEFRIEND_MSG ");
                break;
        case 0x2A:
                receivedMessage.append(L"名片 ");
                break;
        case 0x2B:
                receivedMessage.append(L"视频 ");
                break;
        case 0x2F:
                //石头剪刀布
                receivedMessage.append(L"表情 ");
                break;
        case 0x30:
                receivedMessage.append(L"位置 ");
                break;
        case 0x31:
                //共享实时位置
                //文件
                //转账
                //链接
                receivedMessage.append(L"共享实时位置、文件、转账、链接 ");
                break;
        case 0x32:
                receivedMessage.append(L"VOIPMSG ");
                break;
        case 0x33:
                receivedMessage.append(L"微信初始化 ");
                break;
        case 0x34:
                receivedMessage.append(L"VOIPNOTIFY ");
                break;
        case 0x35:
                receivedMessage.append(L"VOIPINVITE ");
                break;
        case 0x3E:
                receivedMessage.append(L"小视频 ");
                break;
        case 0x270F:
                receivedMessage.append(L"SYSNOTICE ");
                break;
        case 0x2710:
                //系统消息
                //红包
                receivedMessage.append(L"红包、系统消息 ");
                break;
        case 0x2712:
                receivedMessage.append(L"撤回消息 ");
                break;
        default:
                break;
        }
        receivedMessage.append(L"\r\n");

        //dc [[[esp]] + 0x114]
        //判断是群消息还是好友消息
        //相关信息
        wstring msgSource2 = L"<msgsource />\n";
        wstring msgSource = L"";
        msgSource.append(GetMsgByAddress(**msgAddress + 0x168));

        if (msgSource.length() <= msgSource2.length())
        {
                receivedMessage.append(L"收到好友消息:\r\n");
                isFriendMsg = TRUE;
        }
        else
        {
                receivedMessage.append(L"收到群消息:\r\n");
                isFriendMsg = FALSE;
        }

        //好友消息
        if (isFriendMsg == TRUE)
        {
                receivedMessage.append(L"好友wxid:\r\n")
                        .append(GetMsgByAddress(**msgAddress + 0x40))
                        .append(L"\r\n\r\n");
        }
        else
        {
                receivedMessage.append(L"群号:\r\n")
                        .append(GetMsgByAddress(**msgAddress + 0x40))
                        .append(L"\r\n\r\n");

                receivedMessage.append(L"消息发送者:\r\n")
                        .append(GetMsgByAddress(**msgAddress + 0x114))
                        .append(L"\r\n\r\n");

                receivedMessage.append(L"相关信息:\r\n");
                receivedMessage += msgSource;
                receivedMessage.append(L"\r\n\r\n");
        }

        receivedMessage.append(L"消息内容:\r\n")
                .append(GetMsgByAddress(**msgAddress + 0x68))
                .append(L"\r\n\r\n");

        //文本框输出信息
        USES_CONVERSION;
        SetWindowText(GetDlgItem(hWinDlg, IDC_MSG), W2A(receivedMessage.c_str()));
}

定位微信的消息发送函数

定位消息发送函数的相关思路

一个发消息的函数,至少需要两个参数。第一个是发送给谁,第二个是发送的内容,所以我们可以从参数入手,然后通过栈回溯的方式找到发送消息的call。

至于突破口我们可以从发送的消息内容和消息的接收者的微信ID入手,比如文件传输助手的微信ID是filehelper,这个可以在接收消息的call中拿到。以这个微信ID为突破口会比从文本来追溯方便。

过滤当前聊天窗口的微信ID

首先将当前聊天窗口设置为文件传输助手,搜索filehelper

1560860709235.png

除了文件传输助手,我们还知道个人的微信ID都是以wxid_开头的,所以将窗口切换到微信好友,搜索wxid_

1560861132332.png

接着我们选中所有地址,加入到下方地址栏

1560861170832.png

然后选择全部地址右键->更改记录->类型

1560861249387.png

将长度修改为50以显示更多的内容

1560862191419.png

此时你会看到微信好友的ID,记录下这个ID,待会有用

1560861347090.png

再将窗口切回文件助手,下方地址栏的ID会发生变化,将数值不是filehelper的全部剔除掉。剩下的地址中的某一个是当前窗口的微信ID,它会随着你当前微信窗口ID进行变换。

定位当前聊天窗口的ID

这个当前聊天窗口的ID到底有什么作用呢?我们来测试一下

1560862402602.png

选中所有地址,右键->更改记录->数值,将当前聊天窗口的ID改为filehelper,然后在当前好友的聊天窗口发送一条消息,你会发现此时消息发到了文件传输助手

当前聊天窗口的ID是谁 谁就会接收到这条消息,利用这个特性我们来找出那个唯一的当前窗口ID

1560862699359.png

选中一半地址,将其更改为filehelper,然后在当前窗口发送消息。如果消息发给了filehelper,那么选中的地址里面就有真正的当前聊天窗口的ID。重复这个步骤,可以找到真正的当前窗口ID

定位发送消息的函数

1560863633880.png

接着载入OD,在找到的当前窗口ID的地址中下一个内存访问断点。为什么是内存访问断点而不是内存写入呢?因为当前微信窗口的ID肯定会被发送消息的当作参数传入到堆栈中,所以必定会访问这个ID,而不是写入ID。

给好友发送一条消息,点击发送,内存访问断点断下。

1560863743347.png

此时eax指向当前窗口ID,接着删除内存访问断点。点击K查看调用堆栈,在堆栈的返回地址中逐个排查每一个函数,这个函数必须有两个以上的参数,其中一个参数是消息内容,另外一个参数是消息ID

1560864547959.png

经过排查,可以在调用堆栈的第二层找到一个疑似发消息的call。在这个地方下断点,让程序断下,分析附近代码

分析发送消息的函数

普通消息

1560864995458.png

此时edx指向微信ID,[edx+4]保存的是微信ID的长度

1560865007453.png

ebx指向消息内容,[ebx+4]保存的是消息内容的长度。那么这个很有可能就是我们要找的发送消息的call。

找到了发送消息的函数,那么怎么验证呢?利用微信ID。将edx指向的微信ID的地址和我们之前在CE中找到的当前窗口的微信ID对比,你会发现两个地址是一样的。
1560865779326.png

改变这个地址的微信ID和内容,就能直接改变消息的接收者和内容,这个刚才我们已经实验过了。再结合这个函数传入的参数有当前消息的内容,就可以确定这个call就是微信发送消息的函数。

艾特某人消息

除了以普通文本的方式发送消息以外,还可以以艾特某人的方式发送消息。那么当发送的消息是艾特某人的时候,这个函数和发送普通文本消息有什么区别呢?区别就在于eax寄存器的值

先发送一条普通消息程序断下

1560941310593.png

此时eax的值为0,然后再发送一条艾特某人的消息

1560941381852.png

此时eax是有值的,数据窗口跟随,看看这个14704C40的地址保存的是什么内容

1560941432589.png

里面的被艾特的人的微信ID,普通消息与艾特消息的区别就在于eax是否保存了被艾特人的微信ID。大家可以用同样的方式分析处图片和表情在内存中的表现形式。

接下来我们只要记录下当前发送消息函数的地址+偏移,就能写一个dll注入到微信进程空间中,直接调用发送消息的函数,就能实现用自己写的程序给任何人发送消息。

总结

总结一下思路,寻找切入点->找地址->下断->栈回溯分析。跟接收消息的步骤是一致的。找call的关键在于你能不能找到一个好的切入点,并且利用切入点与call之间的关系。

代码实现

调用发送消息的函数代码如下:

void SendTextMessage(wchar_t* wxid, wchar_t* msg)
{
        //拿到发送消息的call的地址
        DWORD dwSendCallAddr = GetWeChatWinAddr() + 0x2EB4E0;

        //微信ID/群ID
        wxMsg id = {0};
        id.pMsg = wxid;
        id.msgLen = wcslen(wxid);
        id.buffLen = wcslen(wxid)*2;

        //消息内容
        wxMsg text = { 0 };
        text.pMsg = msg;
        text.msgLen = wcslen(msg);
        text.buffLen = wcslen(msg)*2;

        //取出微信ID和消息的地址
        char* pWxid = (char*)&id.pMsg;
        char* pWxmsg = (char*)&text.pMsg;

        char buff[0x81C] = { 0 };
        //调用微信发送消息call
        __asm {
                mov edx, pWxid;
                push 1;
                mov eax, 0;
                push eax;
                mov ebx, pWxmsg;
                push ebx;
                lea ecx, buff;
                call dwSendCallAddr;
                add esp, 0xC;
        }
}

最终效果

发送消息

1561192523457.jpg

接收消息

1561192658037.png





免费评分

参与人数 27威望 +1 吾爱币 +38 热心值 +27 收起 理由
liudong4 + 1 + 1 准备放弃研究了,你又发了!我再学习一下&amp;amp;#128514;看看能不能领悟了
Hmily + 1 + 7 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
黑色芝麻 + 1 + 1 太强了
vipwp + 1 + 1 用心讨论,共获提升!
想比你有钱 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
红颜世家、 + 1 + 1 谢谢@Thanks!
sangong + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
枫叶下的蟋蟀 + 1 + 1 谢谢@Thanks!
ki11in9 + 1 + 1 谢谢@Thanks!
marlborogolo + 1 + 1 谢谢@Thanks!
anvx + 1 + 1 我很赞同!
Eaysuild.xean + 2 + 1 我很赞同!
cndmad + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
wmsuper + 3 + 1 谢谢@Thanks!
qaz003 + 1 + 1 清新脱俗~~~
吃泡面加不起蛋 + 1 + 1 谢谢@Thanks!
qsws3344 + 1 + 1 谢谢@Thanks!
独行风云 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
bjcar + 1 + 1 谢谢@Thanks!
hui135135 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
nj001 + 1 + 1 热心回复!
笙若 + 1 + 1 谢谢@Thanks!
asq56747277 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
何故 + 3 + 1 我很赞同!
小母牛倒立 + 1 + 1 用心讨论,共获提升!
Takitooru + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
旺仔大馒头 + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

by商人 发表于 2019-6-24 13:08
楼主,我尝试把你这个方法套入到某腾xQx,已找到接受消息call,但是在堆栈esp数据窗口跟随没有找到可用信息,应该怎么处理啊?还有问一下,OD中的地址是动态的吗?怎么转换成基址呢
Bachelor硕 发表于 2021-1-23 09:27
大神  我现在用最新版xx  按照上面方法一直找不到发送消息的call   例如上面内存断点  现在ebx显示id
旺仔大馒头 发表于 2019-6-22 16:55
豪~豪 发表于 2019-6-22 17:07
大牛啊啊啊  啊啊 啊啊啊!
zeroro 发表于 2019-6-22 17:33
支持~~~~~
酷鸟 发表于 2019-6-22 17:41
编辑标题 微信换成XX  比较好
Joduska 发表于 2019-6-22 18:58
DAGE!
gunxsword 发表于 2019-6-22 19:05
感谢分享,有了这样的东西,就可以做到很多事情了
newpowersky 发表于 2019-6-22 20:53
太牛B了。。。没有关注 错。
newpowersky 发表于 2019-6-22 20:56
封个DLL,放在C#下来用用。这个C不会写啊。。。。。。
xiaoy 发表于 2019-6-22 21:19
这东西内存没处理好啊,发图片内存就跟着涨,涨到一定程度就崩了...和Q比起来还差远了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-27 00:46

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表