1.引言
我们知道微信现在不光在手机上很常用,在电脑也是非常常用的,尤其是使用微信联系客户和维护群的人,那这个时候每天都会定期发送一些信息,如果人工操作会很累,所以自动化工具是王道,本节就使用x64dbg让你看看怎么完成发消息。本节完整源码在github:https://github.com/15pb/wechat_tools
2. 网络协议分析模型
首先我们讲一些理论知识,在开发中,网络通信的协议最常见的有tcp,udp,http等,而在通信时使用的模型(这里的模型说的是代码执行方式)也不尽相同,我们在分析微信协议的时候第一要确定微信是什么通信协议,第二要确定使用的是哪种模型。第三再分析微信的私有通信协议完成调用。
2.1 PC微信通信协议的识别
PC端微信使用的协议我们可以通过对每种协议相关的API设置断点来确定是哪个协议。一般PC端客户端程序的通信协议常用的是tcp和udp。
关于tcp,udp的API:
协议 | 函数 |
---|---|
tcp | send |
udp | sendto |
2.2 通信模型的识别
知道了通信协议之后,再说说通信协议的模型。一般我们在开发网络程序时,会采用两种方式来与网络进行通信,第一种是就是直接从UI获取数据然后发送数据,第二种是有单独的工作线程负责发送数据,两种通信模型我把它们分别称为同步模型,异步模型。 这两种模型中异步模型是比较复杂的,为了方便大家理解,我们将两种模型最简单的雏形给大家梳理一下:
① 同步模型
这种模型的优点在于UI操作完就可以立即发送,非常适合我们做逆向分析,通过栈回溯一般都可以找到字符串的踪迹,从而找到发送消息的函数,剩下的就是分析其参数,完成调用即可。一些网络负载不是太大的程序,比如普通软件、2D网络游戏,大多会采用这种方式,实时的获取
② 异步模型
这种模型通常在UI线程中发送数据时会添加到一个队列中,然后在工作线程中不停的从队列中读取数据,然后发送数据。在逆向分析时,我们要做的就是通过在send设置断点,然后可以通过观察send的参数缓冲区,再对缓冲区或是与缓冲区关联的地址设置访问或写入断点,断到写缓冲区,添加队列的地方。之后在去找调用函数。由于这个过程比较复杂,有时需要多次设置断点才可以找到我们想要的信息。
3. 使用x64dbg分析微信模型与定位关键函数
基于上面的理论,我们实际去分析一下微信的通信模型。
3.1 使用x64dbg分析微信网络发包线程
首先我们还是在send设置断点,然后等断下之后观察堆栈窗口中有没有敏感字符串,我们使用PC版微信中的文件传输助手来测试
当send
断下之后,我们查看堆栈窗口中的信息,观察有无我们输入的字符串
实际观察会发现堆栈中没有我们想要的字符串,一般这个时候可以大致确定发送数据的这个线程与UI线程不是一个线程,通信的模型属于异步模型。
3.2 使用x64dbg定位UI线程的队列添加
确定了异步模型,我们下一步做的就是在send
的参数buf上设置硬件写入断点,看看哪里给这个buf写入了信息,找到那个队列信息。
在send
函数断下返回上层的代码中,发现buf的传递经过了好几层,而这几层当中有一个地址中的内容是随着消息的改变而变化的,所以我们可以对这个地址设置硬件写入断点,而不是buf。
我在图中的04EEECA8
地址设置硬件写入断点,然后重新发送消息,看断下之后填充值的代码,然后观察堆栈信息,如果没错的话,这个堆栈应该是UI线程,其中应该能找到我们刚刚发送的字符串。
当我们设置了硬件写入断点,等断点断下时观察堆栈,发现堆栈中并没有刚刚输入的字符串,观察许久发现只有一些和发送消息有关的字符串
这个时候说明我们发送的信息可能被包装起来了,因为程序会将我们的字符串放在结构体中或是某些数据结构中进行传递,只是查看堆栈可能看不到,需要查看堆栈中看起来像地址的值的内容,才可能找到。
3.3 使用x64dbg定位关键函数
我们刚刚在分析堆栈时发现的字符串:”/cgi-bin/micromsg-bin/newsendmsg” 看起来像是发生信息时信息的类型字符串。我们可以使用x64dbg查看模块wechatwin.dll
中的所有和newsendmsg
字符串,在其上所有相关字符串设置断点,然后再次发消息测试。
以上的动图可以看到,我们通过搜索newsendmsg
字符串,定位到一处代码,设置断点后,再次发送消息,可以在堆栈中找到我们发送的内容。只要找到内容,其实离成功就不远了。在堆栈中我们去寻找和内容有关的调用CALL,需要由下而上一一查看调用CALL的参数,分析之后,可以发现有一个CALL的调用参数非常适合我们的需求。其有5个参数,如下图:
上图中的调用call 0x0F7F10C0
应该就是发消息的代码,看起来非常幸运。当我们查看call内部的时候发现有大量的混淆代码,貌似这个函数被VM过了。查看wechatwin.dll
模块的区段果然有有一个VMP的区段。不管怎么样,函数是定位到了,下一步就是调用函数了。
3.4 使用x64dbg分析关键函数的参数
定位到关键函数之后,观察函数的参数,发现有5个参数。
对每一个参数进行清除测试,即调试过程中分别将每一个参数清除,然后运行程序,发现参数4和参数5,可以没有值,而参数1、参数2、参数3必须有值,根据内容可知参数2是微信用户名,参数3是要发送的内容。而参数1看起来并没有什么含义,经过测试发现,其实这是一个传出缓冲区,其中存储的是加密的数据信息,即我们发送信息的加密版本,由此也可知,0x0F7F10C0
函数被VM的理由是其中有加密信息的代码,即微信的私有加密通信协议。
不管怎么样,参数现在基本定了,剩下的就是只要能调用这个函数,其实就可以最初的设想了。
4.使用C++编写测试代码
代码使用的是一个MFC的DLL来完成的,我们只需注入到微信中,执行我们的代码就可以了。我们可以根据参数编写如下代码:
wchar_t* pUser1 = L"filehelper";
wchar_t* pContent1 = L"hello 15pb";
wchar_t* pUser = (wchar_t*)&pUser1; wchar_t* pPass = (wchar_t*)&pContent1; char buf[0x3d4] = { 0 }; // 传出buf _asm { push 1 // 参数5 push 0 // 参数4 push pPass // 参数3:发送的内容 push pUser // 参数2:用户名 lea eax, buf push eax // 参数1:传出buf mov eax, 0x0F7F10C0 call eax }
使用上面代码会发现程序会出现异常,而出现异常的地方中,是在访问字符串指向的结构里,上面的代码微信用户名只是一个字符串指针,其他并没有,而观察原本调用时传递的参数可以发现,这是一个字符串结构体,其除了字符串指针之外还有字符串的长度,缓冲区最大长度等等。所以我们需要将整个结构都定义出来,然后测试,耐心排查直到测试完成。这个过程需要不停的修改我们定义的字符串结构体才行,或者分析代码中所有的引用点反推出字符串的结构。因此需要大量的时间才能最终完成。
这里注意一下,在测试的过程中,总结下来有两个需要注意的问题。第一,参数2和参数3都是二级指针且参数2和参数3是一个字符串结构体而非就一个指针,第二,参数1是传出参数。这三个参数传对,就不会有问题了。
经过测试以及对比内存中字符串周围的信息,最后定义的字符串数据结构如下:
struct WXString {
int num;
wchar_t* pString;
int nLen;
int nMaxLen; int n1; int n2; int n3; int n4; WXString(wchar_t* pStr) { int len = wcslen(pStr); pString = new wchar_t[len + 1]; memset(pString, 0, len * 2 + 2); memcpy(pString, pStr, len * 2); nLen = len; nMaxLen = len + 2; n1 = 0; n2 = 0; n3 = 0; n4 = 0; } ~WXString() { if (pString) delete pString; } };
而最后执行的代码:
WXString szUser(L"filehelper");
WXString szContent(L"hello 15pb"); char* pAddr = (char*)&szContent + 0x14; char* pUser = (char*)&szUser.pString; char* pPass = (char*)&szContent.pString; char buf[0x3d4] = { 0 }; _asm { push 1 push 0 push pPass push pUser lea eax, buf push eax mov eax, 0xF7F10c0 call eax }
5. 测试结果
我们可以使用x64dbg的插件Scylla
完成注入完成,为了测试方便,我们注入之后会显示一个对话框,点击对话框中的按钮测试即可,执行我们上面的关键代码:
6. 总结
在分析微信的发送消息的函数过程中,我们使用x64dbg的功能有软件断点、硬件写入断点、查找字符串、插件注入等等功能,而且在分析时用到了堆栈分析,找数据时会对数据窗口各种切换。总体来说想要从源头一点一点实现本节的功能还是比较复杂的,祝大家好运!