微信 PC 端技术研究(3)-如何找到消息发送接口 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
anhkgg
V2EX    程序员

微信 PC 端技术研究(3)-如何找到消息发送接口

  •  
  •   anhkgg
    anhkgg 2019-02-20 23:32:06 +08:00 2527 次点击
    这是一个创建于 2515 天前的主题,其中的信息可能已经有所发展或是发生改变。

    微信 PC 端技术研究-如何找到消息发送接口
    by anhkgg (公众号:汉客儿
    2019 年 2 月 18 日

    微信 PC 开源项目地址:https://github.com/anhkgg/SuperWeChatPC

    0x0. 前言

    准备工具:Cheat Engine,OllyDbg,IDA。

    前一篇(微信 PC 端技术研究(2)-保存聊天语音)已经说过 CE 是什么,也应用 CE 研究了如何保存微信语音,这篇继续使用 CE 和 OD 来研究一下微信的消息发送接口。

    思路大概是这样:在消息框中输入内容之后,通过 CE 找到内容地址,然后通过内存断点来找到发送该数据的相关代码,从而找到消息发送接口。

    0x2. 分析过程

    查找关键数据地址

    在输入框输入一个比较特别的文字内容(避免搜索时太多内存选项)后,使用 CE 搜索该内容地址。

    由于已经知道确切的消息内容,很容易就能通过 CE 的 Exact value->String 来找到内容地址,修改内容多次筛选,最后留下两个结果(详细操作见上一篇文章)。

    img

    通过 CE 修改一下内存的内容,微信输入框中内容同步改变,说明这个内存地址就是输入框中内容地址,最终确认地址是2A1E1A8

    接着在点击发送按钮之后,输入框内容会被清空,所以第一想法就是对内存地址下内存写入断点,可以找到发送过程中清空内容的代码。

    打开 OD,挂载到 WeChat.exe 进程,在右下角数据窗口 Ctrl+G 输入2A1E1A8,然后右键选择断点->内存写入断点。

    img

    F9 让 OD 跑起来,然后点击微信发送按钮,没想到意外发生了,输入框内容清空了,但是断点却没有触发。

    怎么回事?断点弄错?地址找错?暂时没有答案。

    用 CE 多次重复前面的操作,地址依然是这个地址,断点就是不触发。

    通过 OD 查看到,在输入框清空后,2A1E1A8的内容确实没有变化,和发送前一样,并且在重新输入新的内容之后,该内存内容同步更新。

    所以结论就是输入框内容地址确实是2A1E1A8,但是清空输入框并不是清空该内存内容,猜测编辑框可能通过控制字符串长短来控制显示的,清空输入框内容就是设置字符串长度为 0。

    找到输入框类

    清空输入框没有进展了,那怎么办呢?

    尝试去找了其他数据,比如发送按钮的发送(S),发送按钮的提示内容不能发送空白内容等等,数据地址也可以很快找到,但是和我们的分析目标偏的太远了。

    几番折腾后,作罢。

    转念一想,清空不行,发送总的读取输入框中内容吧,那换成内存访问断点尝试一下。

    依然是在右下角数据窗口 Ctrl+G 输入2A1E1A8,然后右键选择断点->内存访问断点。

    img

    完成后回到微信界面,没想到直接断下了,我还没点发送按钮呢。根据以前的经验,下意识就觉得是界面刷新显示文字触发了断点,这可能会影响分析,根本没办法通过发送按钮来触发内存访问断点。

    img

    一般解决方法有:

    1. 条件断点。也就是屏蔽掉刷新界面触发的断点,但是好像内存断点不支持条件断点啊,要么通过脚本来完成,好麻烦。
    2. 找其他切入点。废话,清空那边的路都断了,死心吧。
    3. 其他我不知道的...

    放弃了一般的解决方法,我决定看看本次断点究竟干嘛了。

    注意到断点的位置不是微信模块WeChatWin.dll中,而是在msftedit.dll,很少见的一个模块。根据目录可以看到是微软系统的一个模块,名字中的edit也可以看出这应该是一个编辑框相关的模块。

    可执行模块, 条目 20 基址=6F050000 大小=00094000 (606208.) 入口=6F05D53D msftedit.<ModuleEntryPoint> 名称=msftedit (系统) 文件版本=5.41.21.2510 路径=C:\Windows\System32\msftedit.dll 

    好像和我们的分析目标很贴近嘛,在 OD 中数据窗口右键断点->删除内存断点,然后按下 Alt+F9 回到用户模块领空,也就是跳过系统模块的代码,直接回到微信的模块代码中,省过对系统代码的分析。

    看到返回到6E20CCC2这个地址,上一行代码就是调用msftedit.dll的函数,我们对其下一个断点,鼠标点击到6E20CCBF这行代码,按下F2下一个int 3断点,然后F9跳过本次分析。

    img

    OD 继续断下,此次直接断在了6E20CCBF这个位置,可以看到call调用了msftedit.6F05AD69,这是个什么函数呢?

    img

    既然msftedit.dll是微软模块,那么肯定是有符号的嘛,嘿嘿。

    这里可以直接在 OD 中加载符号来分析,使用方法是:

    1.在 WingDbg 目录下拷贝 dbgeng.dll ,dbghelp.dll ,srcsrv.dll ,symbolcheck.dll ,symsrv.dll ,symsrv.yes ,一共 6 个文件至 OD 目录下。 2.打开 OD,设置符号路径。调试--->选择符号路径。 3.设置 StrongOD 的插件选项。选择加载符号。 原文: https://blog.csdn.net/sr0ad/article/details/8253311 

    但是只支持本地符号,也就是得自己下载了模块对应符号到本地,OD 设置符号文件路径后,才能正常使用,有点麻烦。

    我这时候一般就会使用 IDA 了,因为它会自己在线下载模块对应的符号,很方便。

    用 IDA 打开msftedit.dll,等待些许时间,IDA 下载符号,解析等等完成后,我们去找到msftedit.6F05AD69对应的函数究竟是个什么东西。

    但是这里msftedit.6F05AD69的模块基址是6F050000,而 IDA 解析使用的是默认基址0x6FCD0000,要么修改 IDA 解析基址为6F050000,等待 IDA 重新解析,要么通过偏移计算对应地址。

    再解析等太久,直接计算吧,所以要安利我写的一个小工具(偏移计算工具),能够快速计算地址,具体使用见相关文章。

    img

    再 IDA 中按下 g,输入6fcdad69,找到msftedit.6F05AD69对应函数为CTxtEdit::OnTxInPlaceActivate

    img

    很明显通过名字OnTxInPlaceActivate可以看出是编辑框中文字激活状态(显示)下就会触发该函数,这不是重点。

    重点看CTxtEdit,不言而喻,这就是msftedit.dll中实现的编辑框的类。

    如果写过MFC相关代码,应该很快就能想到CTxtEdit肯定还有其他读内容、写内容的函数,叫做GetXXX或者SetXXX

    在 IDA 的函数列表中翻看一下,果然很快就找到了CTxtEdit::GetTextExCTxtEdit::SetText

    img

    但到底这两个函数是不是编辑框读写内容的函数呢,我们对这两个函数下断点试试,通过工具算到在 ID 调试中这两个函数的相应地址为6f0684376f056d37

    img

    在 OD 的底部命令窗口输入bp 6f068437bp 6f056d37,删掉之前CTxtEdit::OnTxInPlaceActivate的断点,然后F9跑起来。

    img

    回到微信界面,这次能够正常显示了,点击发送按钮。OD 触发断点,断在了6f068437也就是CTxtEdit::GetTextEx上,很明显这是发送函数在读取输入框内容。

    img

    回溯找到发送函数

    此时的调用堆栈是这样的:

    调用堆栈 地址 堆栈 函数过程 / 参数 调用来自 结构 0026E280 6F06842D msftedit.6F068437 msftedit.6F068428 0026E3FC //CTxtEdit:GetTextEx 0026E400 6E20D239 包含 msftedit.6F06842D WeChatWi.6E20D233 0026E3FC 0026E43C 6DBD38EB 包含 WeChatWi.6E20D239 WeChatWi.6DBD38E8 0026E438 //TxtEdit_GetText 0026E5AC 6DC15B65 ? WeChatWi.6DBD3860 WeChatWi.6DC15B60 0026E5A8 //sendBtn_GetText 0026E60C 6DC15DEE WeChatWi.6DC15B10 WeChatWi.6DC15DE9 0026E608 //sendbtn_click 0026E618 6E20BFB8 WeChatWi.6E20BEF4 WeChatWi.6E20BFB3 0026E614 0026E62C 6E20362E WeChatWi.6E20BF90 WeChatWi.6E203629 0026E628 0026E6CC 6E203589 WeChatWi.6E2035A7 WeChatWi.6E203584 0026E6C8 0026E820 6DC53695 ? WeChatWi.6E20352E WeChatWi.6DC53690 0026E81C 

    img

    在 OD 中回溯调用堆栈跟踪返回到WeChatWi.6E20D239,看到右侧堆栈窗口已经获取到输入框中内容,证明前面的分析没有问题。

    img

    再次回溯两层到WeChatWi.6DC15B60,可以看到堆栈中的参数依然是获取到的输入框内容。

    [0026E5E4] = 0828C070 [0828C070 + 4] = 0828CAF0 => a12bcAAAAA 

    img

    此时函数首地址是WeChatWi.6DC15B10,进入到IDA中对应函数100d5b10(你要问我为什么此时进入 IDA 查看?我只好说其实这个步骤花费了很多时间,一边 OD 调试,一边 IDA 辅助确认等等,过程并没有这么顺利,篇幅原因省略),然后按下 x 回到上层函数,看到如下代码:

    img

    看到click很明显可以看出这就是发送按钮的响应函数了(相关知识可以了解duilib编程,微信界面是duilib实现的)。

    到目前找到了发送消息的函数,但还并不是消息发送接口,这还只是界面的操作函数,具体发送消息接口应该在该函数内部被调用。

    有技巧找到发送接口

    先粗略地在 OD 中跟一遍WeChatWi.6DC15B10的代码逻辑,函数很多,没法很快确认哪个函数是消息发送接口。

    截取部分代码感受一下,大概 11 个函数。根据 OD 跟的逻辑大概是sendBtn_GetText_10093860->sub_100DD340->sub_100C50C0->sub_10094100->sub_100DD9D0->sub_100C4450->sub_10323DF0->sub_100DE120

    if ( sendBtn_GetText_10093860(a1->unk_560, (int)&savedregs, a2, a3, msg) <= 0 )// 这里是获取 msg { // x //省略一大段逻辑 } if ( msg[0] != msg[1] ) { // x //省略一大段逻辑 } if ( sub_100DD340() ) { // x //省略一大段逻辑 sub_1047C070(&v34, v23); sub_100DB8C0((int)a1_, v34, v35, (int)v36, v37, (int)v38, v39, v40, (int)v41, msg_); } if ( sub_100C50C0((_DWORD *)(a1_->unk_558 + 2528), (int)msg, (int)v43) ) { sub_10094100((_DWORD *)a1_->unk_560);// sub_100DD9D0(msg); sub_100C4450((_DWORD *)(a1_->unk_558 + 2528), (_msg *)msg);// v31 = sub_10323DF0(); sub_100DE120(v31, (int)a1_, (int)sub_100D6C40, 0, v40, (int)v41, msg_);// retn 18 v12 = 1; } else { //省略一大段逻辑 sub_10108D60(v30, *(&a1_->unk_558 + 1), v33, (int)v34, v35, v36, (int)v37, v38, v39, v40, v41); } 

    通常通过调试每个函数的参数、返回结果等基本可以猜测到函数功能,然后来找到消息发送接口。

    但这里我偷懒了,因为参数结构复杂,一时半会没法找到关键点,有点晕了。

    所以我通过排除法来一一筛选函数,最多 11 次左右就能找到消息发送接口。举个例子,如果sub_100DD340是消息发送接口,在我手工屏蔽其功能之后,消息肯定发不出去了,那么我就可以通过看到的结果(是否发送成功)来确认sub_100DD340是不是要找到的消息发送接口。

    具体屏蔽方法:

    1. 通过 IDA 或 OD 进入sub_100DD340函数内部,找到函数结尾,找到 retn xx 类似代码
    2. 用 OD 在sub_100DD340函数开始修改汇编代码为 retn xx,双击输入 retn xx 即可

    这样sub_100DD340函数直接在入口就返回了,功能没有了,也保证了函数调用时的栈平衡。

    img

    在确认sub_100DD340并没有影响消息发送之后,通过右键撤销选择处修改恢复修改的内容。

    如此重复筛选其他的函数,最终确认sub_100C4450为发送消息函数。代码接口如下:

    sub_100C4450((_DWORD *)(a1_->unk_558 + 2528), (_msg *)msg);// 

    msg是发送内容,a1_->unk_558 + 2528)是当前聊天窗口的好友信息,包括wxid和名字之类的信息。

    img

    但作为接口依然不够简洁,需要构造好友信息,比较复杂,所以继续深入sub_100C4450内部,看看是否能够找到最简单的接口,比如:

    sendmsg(wxid, msg); //传入发给谁,发什么即可 

    sub_100C4450内部依然很复杂,使用和前面同样的方式,先大致跟一遍执行流程,然后通过排除法逐个筛选。

    if ( !sub_100C43D0(msg_.buf, msg_.len, msg_.maxlen, wxid_) )// 是不是全是特殊字符\r\n\t 等,是返回 1,不是返回 0 { sub_1007D390(); msg_packet = sub_102DA4A0((int)wxid, (int)&v67, msg__, &unk, 1);// 数据打包,发送 sub_100494E0(msg_packet_, (size_t)msg_packet);// sub_1004B550(&v67); // v11 = sub_102478D0(); v12 = sub_10402C10((int)v11); v89 = (void **)v13; if ( sub_10402C10((int)msg_packet_) != v12 || v14 != v89 ) { if ( sub_100C6770(this_) ) // { sub_1004BBF0((int *)&msgpacket);// sub_10056940((int *)&msgpacket, (size_t)msg_packet_);// sub_100C56D0(this___, (size_t)&msgpacket, 1); sub_10081210((LPVOID *)&msgpacket); v16 = sub_100C0EC0(); sub_10247250((int **)v16, (int)path); } } } if ( (signed int)(msg->msgend - (unsigned int)msg->msg) / 0x24 != 1 ) v9 = sub_10323DF0(); sub_10324E70(v9, msg_.len, msg_.maxlen, (int)wxid_, (int)path); sub_100ADA10(&msg___); 

    这一次筛选屏蔽的方法换一种,直接在某个函数执行完成之后,通过jmp跳到sub_100C4450结尾,如果某次消息发送成功,最后执行的函数就是我们要的接口。

    img

    很幸运,这次在第三个函数就找到了消息发送函数sub_102DA4A0,看看它的参数:

    sub_102DA4A0((int)wxid, (int)&v67, msg__, &unk, 1); sub_102DA4A0@<eax>(int wxid@<edx>, int a2@<ecx>, wxstring *msg, _DWORD *a4, int a5) 

    下图是调试中看到的数据,确认接口没有问题。至于其他两个参数,经过分析是用于接收输出的,没有实际作用,在此不赘述。

    img

    如此分析消息发送接口的工作完成,找到了和预期基本一致的接口函数。

    0x3. 总结

    篇幅好像有点长了,最后做一下此次分析的总结:

    1. ce 找到编辑框中的内容内存
    2. 发送后,编辑框内容删除,写断点无效,神奇,猜测通过设置长度控制显示
    3. 改为内存访问断点,进入界面就会断下,徘徊几次后,决定分析,没想到找到了关键点 CTxtEdit::OnTxInPlaceActivate
    4. 知道编辑框使用了 msftedit.dll 的 CTxtEdit 的类,用 ida 找到符号
    5. 查询类似 getvalue 的接口,找到 SetText、GetTextEx 等,对这两个函数下断点
    6. 果然断下,回溯找到了发送的消息响应函数
    7. 详细分析响应函数,多次通过 retn、jmp 排除,找到真正发送消息函数,最后分析出接口函数

    此次分析中 CE 找到地址是第一步非常关键的点,直接就进入了函数调用堆栈内部,对此次分析作用非常明显。

    再就是在发送消息响应函数内部,逐个分析找到消息发送接口函数中,通过修改指令来屏蔽函数功能来确认函数功能,比每个函数去分析参数猜测确认功能来的更快,效果更明显。

    调试工具非常重要,动( OD )静态( IDA )分析结合能够提高分析速度。

    OD 适合分析函数参数、解析数据结构、确认函数功能,IDA 适合分析函数逻辑、整体函数结构、代码框架等等,各有优势。

    最后,再次安利一下开源项目https://github.com/anhkgg/SuperWeChatPC,此次分析的发送消息接口也会在后续合入到项目中,欢迎starPR

    相关文章:

    1. 微信 PC 端技术研究(2)-保存聊天语音

    2. 偏移计算工具

    hugedeffing
        1
    hugedeffing  
       2019-02-21 10:44:06 +08:00
    看雪上看到过,是本人嘛?还是转载?
    anhkgg
        2
    anhkgg  
    OP
       2019-02-21 11:02:57 +08:00
    @hugedeffing 看 id
    KINGOD
        3
    KINGOD  
       2019-02-21 14:10:39 +08:00
    所有图片都挂了。
    “此图片来自微信公众平台 未经许允许不可引用”
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2583 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 01:57 PVG 09:57 LAX 17:57 JFK 20:57
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86