JieKeH 发表于 2019-5-17 10:31

PCXX逆向 --- 带自定义提示的PCXX防撤回

本帖最后由 Kido 于 2019-6-6 18:09 编辑

#                                                            带自定义提示的防撤回

## 1.前言

最近太忙了,一直没有时间更新这个文章.今天做点好玩的东西

在第一篇[分析获取登录二维码的数据](https://www.52pojie.cn/thread-924687-1-1.html)中,我们分析了登录过程中二维码的显示逻辑,现在我们分析下怎么实现带自定义提示的消息防撤回,实现的效果图就是如下所示

![撤回显示](https://ae01.alicdn.com/kf/HTB12.LwUwHqK1RjSZFE763GMXXas.png)

可以看到这人一直在辱骂我,哈哈哈



**PC微信核心模块:wechatwin.dll (2.6.7.57版本)**

相关工具:

- windbg 或者 OD,x64dbg (动态调试工具)
- (https://www.52pojie.cn/thread-675251-1-1.html) (静态分析)
- vs2015(开发hook功能模块)



## 2.分析

大家知道ios/android/mac中都实现了防撤回,这些平台上的分析也是有相似之处,就是搜索"Revoke"的关键字进行分析,我们可以借鉴他们的思路,尝试从关键字"Revoke"入手,我们先脑中想一下整个撤回逻辑的实现

- 对方在一个终端上发起一个撤回消息,服务端收到这个通知,下发消息给我方的所有终端
- 我方所有的终端收到了服务端发来的撤回消息通知
- 我方所有的终端实现撤回消息逻辑

可以看出,如果我们要实现防撤回逻辑,需要在第二步的网络数据层或者第三步的撤回逻辑实现层做点手脚,我们先搜索下关键字"Revoke"吧

!(https://ae01.alicdn.com/kf/HTB1kt6zUwHqK1RjSZFP763wapXa4.png)

可以看到Revoke相当之多了, 初步可以认为上面标注的消息中可能存在我们需要查找的逻辑,通过调试确认,能发现 `On RevokeMsg svrId : %d`这个就是服务端发来的撤回消息通知,看下下图中相关的逻辑

!(https://ae01.alicdn.com/kf/HTB1RwzyUCzqK1RjSZFH7623CpXaU.png)

可以看出来服务端对于撤回消息的定义是10002,上图中还标注了sub_10252f80这个函数,下面解释下为什么要标注这个函数

看看上图中对sub_10483FE0的汇编调用

```asm
sub   esp, 14h            
mov   ecx, esp;               
push    0
push    dword ptr
call    sub_10483FE0
```

再看下sub_10483FE0函数的汇编

```asm
push    ebp
mov   ebp, esp
sub   esp, 8
push    esi
mov   esi, ecx
mov   ecx,
push    edi
mov   dword ptr , 0
mov   dword ptr , 0
mov   dword ptr , 0
mov   dword ptr , 0
mov   dword ptr , 0
```

综合起来看,可以看出来

```asm
sub   esp, 14h            
mov   ecx, esp;
```

```asm
mov   esi, ecx
mov   ecx,
push    edi
mov   dword ptr , 0
mov   dword ptr , 0
mov   dword ptr , 0
mov   dword ptr , 0
mov   dword ptr , 0
```

其实就是有两个含义

- ecx此时是一个类的指针,这个类的内存布局大小是0x14

- 类每个成员是4字节大小,共有5个成员

而且从sub_10483FE0函数中可以发现这是一个MultiByteToWideChar的操作,可以推断下这个类其实有点类似Wstring,那么对于下面的汇编函数来说,此时ecx是wstring, dword ptr 是string(UTF-8)

```asm
sub   esp, 14h            
mov   ecx, esp;               
push    0
push    dword ptr
call    sub_10483FE0
```

那将断点断在在call    sub_10483FE0上, 先记录下此时的ecx的值,等执行完call    sub_10483FE0,再dd刚才保存的ecx的值,下面用xxxxx打码了一些个人信息

```asm
0:000:x86> dd 00efcb20-14
00efcb0c13f0e7f0 000000d6 00000100 00000000
00efcb1c00000000 a6d44d71 00efee84 00efe484
00efcb2c00000000 00000000 00000000 00000000
00efcb3c00000000 00000000 00000000 00000000
00efcb4c00000000 00000000 00000000 00000000
00efcb5c00000000 00000000 00000000 00000000
00efcb6c00000000 00000000 00000000 00000000
00efcb7c00000000 00000000 00000000 00000000
0:000:x86> du 13f0e7f0
13f0e7f0"<sysmsg type="revokemsg"><revoke"
13f0e830"msg><session>wxid_xxxxxxxxxxxxx"
13f0e870"</session><msgid>1675514177</msg"
13f0e8b0"id><newmsgid>2468247753633590716"
13f0e8f0"</newmsgid><replacemsg><![CDATA["
13f0e930""xxxxxxx" 撤回了一条消息]]></replacemsg"
13f0e970"></revokemsg></sysmsg>"

```

可以看到拿到了服务端的消息数据,注意其中的 **xxxx 撤回了一条消息** 这个和微信界面显示是一致的,那么可以知道微信其实提取了replacemsg里面的消息显示给界面

ida中搜索"replacemsg"这个字符串,可以发现sub_10257570中是唯一处理replacemsg的函数,并且可以找到一条sub_10252F80 -> sub_10257570 的调用链条,现在可以知道,sub_10252F80这个函数其实就是第三步的撤回逻辑实现层,下图是sub_10252F80的逻辑,一步步的跟下去,可以知道sub_10253250这个函数是逻辑实现层

![逻辑实现层](https://ae01.alicdn.com/kf/HTB1EKh_UNTpK1RjSZFM762G_VXa8.png)



下图是sub_10253250函数中的逻辑,经过调试后能发现,如果v69为0的时候,界面仍然会显示出来 **xxx撤回了一条消息** , 但是实际上这条消息并没有被撤回,所以我们的思路很简单,Patch这块的代码,让它一直走else里面的逻辑即可

![](https://ae01.alicdn.com/kf/HTB11GuaUQzoK1RjSZFl761i4VXai.png)





## 3.深入挖掘

到第二步其实已经就可以了,但是其实还是有一个问题的,你会发现,上面在阻止别人撤回的同时也阻止了自己的撤回,更糟糕的是,当自己发起撤回的时候,界面会弹出来两次 **你撤回了一条消息** ,一开始其实还好了,自己用这个插件的时候倒是能接受,后来把这个插件给好友使用后,被一直吐槽,说体验很差,还有就是希望加上自定义的撤回提醒,吐槽的久了以后,那就进一步研究下,当自己发起撤回的时候,就不拦截这个撤回了

注意看下sub_10253250函数中下图的代码段, 其中v46是个结构体,sub_1004E320初始化这个结构,sub_1025A390是对这个结构体的赋值,v50是结构体中的成员,可以看到当v50为10000的时候,sub_10253250是什么事情也不做就退出了,这个结构体值得我们的关注,它里面的成员对sub_10253250的逻辑有很大的影响,调试观察下这个结构体的数据吧

![](https://ae01.alicdn.com/kf/HTB1G1qdUH2pK1RjSZFs761NlXXaA.png)

将断点断在sub_1025A390执行后,直接能打印出来此时v46中的值,然后先让别人撤回一条消息,再自己撤回一条消息,可以看到两次v46内容的差异性

![撤回对比](https://ae01.alicdn.com/kf/HTB1AbqqUNjaK1RjSZFA762dLFXaP.png)

为啥使用这两个数据的作为差异比较呢,当自己撤回的时候,微信还提供重新编辑的功能,但是当别人撤回的时候,微信就直接撤回了,也就是说,微信其实是保存了是自己撤回还是别人撤回这个变量作为区分的

再看下sub_10253250函数中下图的代码段

![](https://ae01.alicdn.com/kf/HTB1UX.knDZmx1VjSZFG761x2XXaf.png)



查看sub_102541D0 -> sub_102542A0 的调用,在sub_102542A0中可以看出当a1为1的时候,会提供撤回编辑的能力,这个a1是 *(_DWORD *)(v7 + 52) != 0 赋值的 v7就是v46,也就是上图中的差异的位置

![](https://ae01.alicdn.com/kf/HTB1YZilUHrpK1RjSZTE763WAVXav.png)

至此就找到了判断是别人撤回还是自己撤回的标志,解决了第一个问题,那下面还要解决第二个问题,那就是撤回消息的定制

既然sub_102542A0函数中提供了撤回编辑的能力,并且可以在sub_102542A0中看到下面的函数片段

![](https://ae01.alicdn.com/kf/HTB1tKQQUHrpK1RjSZTE763WAVXa6.png)

对应的汇编操作

- ```asm
.text:102544A1 85 C0                                       test    eax, eax
.text:102544A3 74 06                                       jz      short loc_102544AB
.text:102544A5 66 83 38 00                                 cmp   word ptr , 0
.text:102544A9 75 05                                       jnz   short loc_102544B0
```


可以看出来这个是字符串拼接,a5就是需要被拼接的字符串.调试看下这个数据

```
Breakpoint 1 hit
WeChatWin!IMVQQEngine::`default constructor closure'+0x1a7121:
69a844a1 85c0            test    eax,eax
0:000:x86> du eax
0c073b08""xxx" 撤回了一条消息"

```

也就是说对a5进行修改就可以了,a5是sub_102542A0的参数,那么在sub_102541D0中看下a5的赋值

![](https://ae01.alicdn.com/kf/HTB1LMANUNTpK1RjSZFK7612wXXas.png)

好了,只需要修改lpmem就行了.



## 4.工程实现

现在我们知道只需要hook住sub_102541D0,然后判断下撤回的来源

1. 如果是别人撤回的,Patch掉sub_10253250的v69的逻辑,并且修改撤回的字符串消息为我们自定义消息
2. 如果是自己的撤回,恢复sub_10253250的v69的逻辑



下图中**SigPatternRevoke2_6_7_40**是sub_102541D0函数头开始的特征码

![](https://ae01.alicdn.com/kf/HTB1srekV3HqK1RjSZFE763GMXXaO.png)

下图是代{过}{滤}理函数的实现,注意修饰符**__declspec(naked)** 就是告诉编译器按照我们写的汇编代码生成代码,不要偷偷的再加入一些多余的汇编代码



![](https://ae01.alicdn.com/kf/HTB1df5GV4naK1RjSZFB763W7VXac.png)



解释下 上图 中间那段汇编push寄存器的来源, 首先是Push edx, 从下图中可以看到sub_102541D0中判断自己还是别人撤回的标志位是,而esi来源于edx,故代{过}{滤}理函数需要的参数是edx寄存器



![](https://ae01.alicdn.com/kf/HTB179WyXfvi21VjSZK9761AEpXag.png)





![](https://ae01.alicdn.com/kf/HTB1r2miV9zqK1RjSZFH7623CpXa6.png)

从上图中可以看到要修改的字符串是的位置,而ebp又是

```asm
pushebp
mov   ebp, esp
```

也就是说当断点断在 push ebp这个位置的时候,此时的就是字符串了, 而看下代码中的代{过}{滤}理函数

```asm
__asm
      {
                PUSHAD;
                sub esp, 20;
      }
```

其中 pushad会使得 esp-0x20(压了8个寄存器), 然后又 sub esp, 20(0x14), 所以代{过}{滤}理函数 esp + 0x14 + 0x20 + 4 的位置就是字符串了



剩下的**RevokeMsg2_6_7_40**函数就是比较简单了

1.需要注意下这个字符串使用的内存不是crt的堆,而是进程堆就可以了. 至于为啥这样,这里就当留个作业了,提示下sub_10484190函数内有答案
2.   **PatchMemoryUCHAR**中修改的就是sub_10253250的v69的逻辑



具体的工程实现就在上篇文章上增加而来,还是放到百度云盘了

[网盘链接](https://pan.baidu.com/s/18S8rNF-BkXeL0aoK6MCtPQ)提取码: 3964

重要提示:

[*]如果对代码工程感兴趣的,可以下载网盘的文件
[*]如果只是对二进制的bin文件有兴趣的,直接下载论坛的附件,解压后将version.dll放到微信的安装目录下就可以



如果二进制的bin文件替换后没有效果的,可以做下面的排查

[*]文件替换后,要重启微信才生效。
[*]目前支持的微信版本是 2.6.7.40 2.6.7.48 2.6.7.57,其余的微信版本是不支持的


做了上述的排查后还是没有生效的,可以查看下dbgview的log信息.回帖的时候可以截图或者上传log信息
如果发现微信异常或者功能异常的,也可截图回帖,感谢


2019/06/04 :增加2.6.8.51版本的支持


Brainor 发表于 2019-9-3 19:32

本帖最后由 Brainor 于 2020-4-2 21:26 编辑

补一下更新
适用于`2.6.8.68`.
前面的版本可以使用的**可能**有`2.6.7.40`, `2.6.7.48`, `2.6.7.57`, `2.6.8.51`.
除了`2.6.8.68`, 这些版本的我都没试过, 都是在楼主代码中已经存在的~

另外, 有些版本可能我的电脑没有更新到, 那这个插件就不会对这些版本有用, 我相信大家都懂的哈~

---
update:
19.9.17: 适用于`2.7.1.54`
19.9.23: 适用于`2.7.1.65`
19.10.12: 适用于`2.7.1.74`
20.1.7: 适用于`2.8.0.106`
20.1.8: 适用于`2.8.0.111`
20.4.2: 适用于`2.9.0.63`

jiazhiyuan0325 发表于 2019-5-17 11:04

可以做出来一个吗啊?

honda160 发表于 2019-5-17 11:29

JieKeH 发表于 2019-5-17 11:27
你的版本是对的嘛

2.6.7.57呀,百度盘上那3个全部覆盖到微信根目录了,微信也重启过了

XTo 发表于 2019-5-17 10:48

看看学习学习

kenny216 发表于 2019-5-17 10:56

楼主,下载下来的东西怎么用?能否做个程序。

zhaoladen 发表于 2019-5-17 10:59

学习学习

格鲁特 发表于 2019-5-17 11:00

没有傻瓜一键的那种吗?

小阿花 发表于 2019-5-17 11:06

看上去好有意思,很厉害的样子{:1_893:}

unixcs 发表于 2019-5-17 11:16

好东西!!!

tankyle 发表于 2019-5-17 11:19

把version放进安装目录就可以了吗
但是测试了一下撤回的消息还是撤回了啊

zpzb 发表于 2019-5-17 11:23

比较详细的 说明,多谢分享
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: PCXX逆向 --- 带自定义提示的PCXX防撤回