吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

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

[Android 原创] 某聊天软件撤回流程的分析与防撤回实现

  [复制链接]
L剑仙 发表于 2020-2-3 10:26
本帖最后由 L剑仙 于 2021-5-31 12:13 编辑

严重声明:本文仅供学习交流使用,严禁随意转载或者用于其他用途,所产生的一切后果与本人无关。
菜鸟继https://www.52pojie.cn/thread-1097114-1-1.html简单分析筛子流程之后,在同大家一起学习一下撤回的一步步实现。如果大家支持,希望学习的人够多,抢红包、转账、甚至整个协议在java和so层的组装和剥离菜鸟都可以写文章共同学习。
搜一搜网上的文章,很多以前的大佬都是通过hookupdateWithOnConflict函数,当type=1000时,判断出撤回信息的id,然后重新插入数据库,并修改“xx撤回一条信息”为“xx撤回信息失败”。猜想这个过程可能分为三步:
1.接收到某某发出的一条信息

2.接收到某某撤回了一条信息,删除或者替换这条信息

3.插入文字“某某撤回了一条信息通过monitor方法回溯或者直接hook函数updateWithOnConflict作为突破口,因为发送消息肯定要与数据库交互,而处理数据库信息很有可能用到这个函数,手动狗头。
[JavaScript] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var sql = Java.use('com.tencent.wcdb.database.SQLiteDatabase');
// int updateWithOnConflict(String str, ContentValues contentValues, String str2, String[] strArr, int i)
sql.updateWithOnConflict.implementation=function(a1,a2,a3,a4,a5)
{  
  console.log("hook update start");
  console.log("a1:"+a1);
  console.log("a2:"+a2);
  console.log("a3:"+a3);
  console.log("a4:"+a4);
  console.log("a5:"+a5);
  console.log("rtn:"+this.updateWithOnConflict(a1,a2,a3,a4,a5));
  //  var threadef = Java.use('java.lang.Thread');
  //  var threadinstance = threadef.$new();
  //  var stack = threadinstance.currentThread().getStackTrace();
  //  function Where(stack){
  //   for(var i = 0; i < stack.length; ++i){
  //     console.log(stack[i].toString());
  //   }
  // }
  //  console.log("Full call stack:" + Where(stack));
 
    return this.updateWithOnConflict(a1,a2,a3,a4,a5)
}


我们先正常发一条消息,查看hook结果函数原型updateWithOnConflict(Stringtable, ContentValues values, String whereClause, String[] whereArgs,int conflictAlgorithm)
向测试手机发送haha,拼接数据库语句为
[Asm] 纯文本查看 复制代码
1
2
3
4
5
6
7
8
update rconversation set  msgType=1 flag=1579779746000 content=haha digestUser= digest=haha lastSeq=707367243 msgCount=33 isSend=0 hasTrunc=1 unReadCount=1 conversationTime=1579779746000 username=wxid_dajiadebaba status=3 where  username=wxid_dajiadebaba
 
hook update start
a1:rconversation
a2:msgType=1 flag=1579779746000 content=haha digestUser= digest=haha lastSeq=707367243 msgCount=33 isSend=0 hasTrunc=1 unReadCount=1 conversationTime=1579779746000 username=wxid_dajiadebaba status=3
a3:username=?
a4:wxid_dajiadebaba
a5:0


堆栈信息如下,这个下面分析撤回的堆栈信息时要用
[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
com.tencent.wcdb.database.SQLiteDatabase.updateWithOnConflict(Native Method)
com.tencent.wcdb.database.SQLiteDatabase.update(SourceFile:1726)
com.tencent.mm.cf.f.update(SourceFile:774)
com.tencent.mm.cf.h.update(SourceFile:601)
com.tencent.mm.storage.al.a(SourceFile:1193)
com.tencent.mm.storage.al.b(SourceFile:25178)
com.tencent.mm.storage.bj$1.fL(SourceFile:326)
com.tencent.mm.sdk.e.l.dQs(SourceFile:158)
com.tencent.mm.sdk.e.l.unlock(SourceFile:49)
com.tencent.mm.storage.bj.Ve(SourceFile:409)
com.tencent.mm.plugin.messenger.foundation.f.cO(SourceFile:141)
com.tencent.mm.plugin.zero.c.cO(SourceFile:70)
com.tencent.mm.modelmulti.o$a$1.onTimerExpired(SourceFile:821)
com.tencent.mm.sdk.platformtools.ap.handleMessage(SourceFile:69)
com.tencent.mm.sdk.platformtools.am.handleMessage(SourceFile:185)
com.tencent.mm.sdk.platformtools.am.dispatchMessage(SourceFile:140)
android.os.Looper.loop(Looper.java:164)
android.os.HandlerThread.run(HandlerThread.java:65)
com.tencent.mm.sdk.g.c.c$1$1.run(SourceFile:39)


下面我们撤回这条信息,发现updateWithOnConflict这个函数被调用了3次,也就是说一次撤回包含着3次数据库更新。
[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
hook update start
a1:message
a2:msgId=38 type=10000 content="baba" 撤回了一条消息
a3:msgId=?
a4:38
a5:0
hook update start
a1:rconversation
a2:msgType=10000 flag=1579779746000 digestUser= digest="baba" 撤回了一条消息 isSend=0 hasTrunc=1 unReadCount=0 conversationTime=1579779746000 content="baba"  撤回了一条消息 username=wxid_dajiadebaba status=3
对比a2:msgType=1 flag=1579779746000 content=haha digestUser= digest=haha lastSeq=707367243 msgCount=33 isSend=0 hasTrunc=1 unReadCount=1 conversationTime=1579779746000 username=wxid_dajiadebaba status=3
a3:username=?
a4:wxid_dajiadebaba
a5:0
hook update start
a1:rconversation
a2:UnReadInvite=0 atCount=0
a3:username= ?
a4:wxid_dajiadebaba
a5:0
 
 
拼接数据库语句为
update message set :msgId=38 type=10000 content="baba" 撤回了一条消息 where msgId=38
update rconversation set msgType=10000 flag=1579779746000 digestUser= digest="baba" 撤回了一条消息 isSend=0 hasTrunc=1 unReadCount=0 conversationTime=1579779746000 content="baba"  撤回了一条消息 username=wxid_dajiadebaba status=3 where msgId=38 where username=wxid_dajiadebaba
update rconversation set UnReadInvite=0 atCount=0 where username=wxid_dajiadebaba


这里msgType=10000很重要,我们刚才成功发送的msgType=1,放过来对比一下,contenthaha变成"baba" 撤回了一条消息a2:msgType=10000flag=1579779746000 digestUser= digest="baba" 撤回了一条消息isSend=0hasTrunc=1 unReadCount=0 conversationTime=1579779746000content="baba"  撤回了一条消息username=wxid_dajiadebabastatus=3a22:msgType=1flag=1579779746000 content=haha digestUser= digest=hahalastSeq=707367243 msgCount=33 isSend=0hasTrunc=1 unReadCount=1 conversationTime=1579779746000username=wxid_dajiadebaba status=3
第一句updatemessage这个表,还附带msgId,这个msgId应该就是待撤回的msgid,猜测这一句的功能就是通过msgId删除msg,替换为"baba"撤回了一条消息。第二句就是更新界面了。第三句,字面意思就是更新rconversation未读信息的计数,跟防撤回关系不大。其实到了这里,我们已经可以通过判断msgType是否=1000判断是否需要撤回信息,如果要撤回,先得到要撤回的msgId,调用相关函数重新插入数据库就能实现防撤回了,类似的文章如下:简书大佬的防撤回https://www.jianshu.com/p/fb16ea7b28bf。如果本文到此为止,我想大家的臭鸡蛋烂菜叶就要砸上来了,本菜鸟还是决定更深入一点,很明显,updateWithOnConflict必定有上层函数直接判断是否撤回,如果判断撤回,调用更下面的逻辑才会到updateWithOnConflictdelete类似函数完成撤回功能的删除和更新。如果我们hook上层函数,可以直接屏蔽掉撤回功能,而不需要先通过判断msgType是否=1000判断是否需要撤回信息,如果要撤回,得到要撤回的msgId,再调用相关函数重新插入数据库。

于是,我们再一次分析函数流程,撤回haha这句消息,打印updateWithOnConflict堆栈如下:
[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
com.tencent.wcdb.database.SQLiteDatabase.updateWithOnConflict(Native Method)
com.tencent.wcdb.database.SQLiteDatabase.update(SourceFile:1726)
com.tencent.mm.cf.f.update(SourceFile:774)
com.tencent.mm.cf.h.update(SourceFile:601)
com.tencent.mm.storage.bj.a(SourceFile:2311)   mo40526a
com.tencent.mm.model.f.a(SourceFile:145)  
com.tencent.mm.model.f.a(SourceFile:352)  mo7343a
com.tencent.mm.model.cc.b(SourceFile:258)
com.tencent.mm.r.b.b(SourceFile:40)
com.tencent.mm.plugin.messenger.foundation.c.a(SourceFile:165)
com.tencent.mm.plugin.messenger.foundation.c.a(SourceFile:1059)
com.tencent.mm.plugin.messenger.foundation.f.a(SourceFile:118)
com.tencent.mm.plugin.zero.c.a(SourceFile:57)                                           处理协议
com.tencent.mm.modelmulti.o$a$1.onTimerExpired(SourceFile:806)
com.tencent.mm.sdk.platformtools.ap.handleMessage(SourceFile:69)
com.tencent.mm.sdk.platformtools.am.handleMessage(SourceFile:185)
com.tencent.mm.sdk.platformtools.am.dispatchMessage(SourceFile:140)
android.os.Looper.loop(Looper.java:164)
android.os.HandlerThread.run(HandlerThread.java:65)
com.tencent.mm.sdk.g.c.c$1$1.run(SourceFile:39)     这之上应该与撤回功能有关               
Full call stack:undefined
com.tencent.wcdb.database.SQLiteDatabase.updateWithOnConflict(Native Method)
com.tencent.wcdb.database.SQLiteDatabase.update(SourceFile:1726)
com.tencent.mm.cf.f.update(SourceFile:774)
com.tencent.mm.cf.h.update(SourceFile:601)
com.tencent.mm.storage.al.a(SourceFile:1193)
com.tencent.mm.storage.al.b(SourceFile:25178)
com.tencent.mm.storage.bj$1.fL(SourceFile:326)
com.tencent.mm.sdk.e.l.dQs(SourceFile:158)
com.tencent.mm.sdk.e.l.unlock(SourceFile:49)
com.tencent.mm.storage.bj.Ve(SourceFile:409)
com.tencent.mm.plugin.messenger.foundation.f.cO(SourceFile:141)
com.tencent.mm.plugin.zero.c.cO(SourceFile:70)
com.tencent.mm.modelmulti.o$a$1.onTimerExpired(SourceFile:821)
com.tencent.mm.sdk.platformtools.ap.handleMessage(SourceFile:69)
com.tencent.mm.sdk.platformtools.am.handleMessage(SourceFile:185)
com.tencent.mm.sdk.platformtools.am.dispatchMessage(SourceFile:140)
android.os.Looper.loop(Looper.java:164)
android.os.HandlerThread.run(HandlerThread.java:65)
com.tencent.mm.sdk.g.c.c$1$1.run(SourceFile:39)                这之上是正常接收消息的流程,可以与前面正常发消息调用的堆栈对比
Full call stack:undefined
dalvik.system.VMStack.getThreadStackTrace(Native Method)
java.lang.Thread.getStackTrace(Thread.java:1538)
com.tencent.wcdb.database.SQLiteDatabase.updateWithOnConflict(Native Method)
com.tencent.wcdb.database.SQLiteDatabase.update(SourceFile:1726)
com.tencent.mm.cf.f.update(SourceFile:774)
com.tencent.mm.cf.h.update(SourceFile:601)
com.tencent.mm.storage.al.avm(SourceFile:42015)
com.tencent.mm.ui.chatting.c.c.efg(SourceFile:846)
com.tencent.mm.ui.chatting.c.aa.axB(SourceFile:479)
com.tencent.mm.ui.chatting.ChattingUIFragment$5.run(SourceFile:887)
com.tencent.mm.sdk.platformtools.aq.run(SourceFile:164)
android.os.Handler.handleCallback(Handler.java:790)
android.os.Handler.dispatchMessage(Handler.java:99)
com.tencent.mm.sdk.platformtools.am.dispatchMessage(SourceFile:129)
android.os.Looper.loop(Looper.java:164)
android.os.HandlerThread.run(HandlerThread.java:65)
com.tencent.mm.sdk.g.c.c$1$1.run(SourceFile:39)     这之上处理ui界面相关操作,其storage.al.avm函数生成了update的ContentValues的值,值得看一看


这里先点出重点函数吧,后面再具体分析流程,懒得看同学的就看到这里结束吧,关键函数在com.tencent.mm.model.f类里面,为什么关注这个model呢,因为正常接受信息没有走这个类,还有com.tencent.mm.plugin.messenger.foundation类,应该在更上层,类比djangomvc框架,猜想model也是封装了和数据库交互的上层,1.com.tencent.mm.model.f.a(SourceFile:145)  m34535a2.com.tencent.mm.model.f.a(SourceFile:352) mo7343a函数mo7343a调用了m34535a,这里我们直接看mo7343a,南极公司对jadx做了小动作,导致反编译失败,具体原因估计还是变量太多,但我们还有gjden大佬的神器gda,这是真正的大佬,给大佬打个广告http://www.gda.wiki:9090/blog_list0.php,虽然也不算完美,一大堆的寄存器变量,但是肯定比看smali好多了,我们把gda反编译的代码粘过来看
[Java] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
public final e$b f.a(String p0,Map p1,e$a p2)        //mo7343a
{
    e$b v4;
    e v4_1;
    e v4_2;
    String v5;
    LinkedList v6;
    String v7;
    boolean v8;
    c v10;
    Object[] v12;
    Iterator v8_1;
    bi v6_1;
    Object[] v11_1;
    ak v9_1;
    e$b v8_2;
    String v8_3;
    String v4_3;
    cj v8_4;
    String v14;
    byte[] v9_2;
    long v6_2;
    long v16;
    aq v4_4;
    String v6_3;
    int v17;
    e v4_5;
    bbp v5_1;
    Object[] v8_5;
    SharedPreferences v10_2;
    String[] v11_2;
    String[] v8_6;
    Integer v4_6;
    Object[] v7_1;
    byte[] v6_4;
    byte[] v7_2;
    byte[] v8_7;
    PByteArray v10_3;
    int v13;
    int v14_1;
    String v4_7;
    String v9_3;
    Boolean v5_2;
    Object[] v6_5;
    a v7_3;
    Object[] v8_8;
    Object[] v9_4;
    Object[] v10_4;
    boolean v11_3;
    Object[] v0;
    in v18;
    b v19;
    a v20;
    String[] v18_1;
    ArrayList v19_1;
    String v20_1;
    String[] v6_6;
    boolean v6_7;
    bkn v5_3;
    c v6_8;
    ak v7_4;
    z v6_9;
    ac$a v8_9;
    Object[] v4_8;
    int v4_9;
    Object v5_6;
    String v6_10;
    Object v7_5;
    nx v10_5;
    e$b v9_5;
    AppMethodBeat.i(16267);
    cr v15 = p2.foR;
    String v11 = aa.a(v15.yCk);
    e$b v9 = null;
    if (p0 && (v4 = this.geB.get(p0))) {
        v4 = v4.a(p0, p1, p2);
        AppMethodBeat.o(16267);
        return v4;
    }else if(p0 && p0.equals("addcontact")){
        v15.yCk = aa.wH(p1.get(".sysmsg.addcontact.content"));
        v15.oXG = 1;
        v4_1 = e$d.bG(Integer.valueOf(1));
        if (!v4_1) {
            v9 = 0;
        }else {
            v9_5 = v4_1.b(p2);
        }
    }
    if (p0 && p0.equals("dynacfg")) {
        g.Vi().a(v11, p1, false);
        g.Vj();
        if (c.UT() == 2) {
            h.syT.kvStat(10879, "");
        }
        ab.d("MicroMsg.BigBallSysCmdMsgConsumer", "Mute_Room_Disable:" + Integero.getInt(g.Vi().getValue("MuteRoomDisable"), 0)));
        if (aa.dOE()) {
            f.akg();
        }
    }
    if (p0 && p0.equals("dynacfg_split")) {
        g.Vi().a(v11, p1, true);
        if (aa.dOE()) {
            f.akg();
        }
    }
    if (p0 && p0.equals("banner")) {
        v4_2 = p1.get(".sysmsg.mainframebanner.$type");
        v5 = p1.get(".sysmsg.mainframebanner.showtype");
        v6 = p1.get(".sysmsg.mainframebanner.data");
        if (v4_2 && (v4_2.length() > 0)) {
            bh.alG().a(new bg(bo.getInt(v4_2, 0), bo.getInt(v5, 0), v6));
        }
        v5 = p1.get(".sysmsg.friendrecommand.touser");
        if (p1.get(".sysmsg.friendrecommand.fromuser") && v5) {
            az.alz().ajX().a(v5, true, null);
        }
        v4_2 = p1.get(".sysmsg.banner.securitybanner.chatname");
        v5 = p1.get(".sysmsg.banner.securitybanner.wording");
        v6 = p1.get(".sysmsg.banner.securitybanner.linkname");
        v7 = p1.get(".sysmsg.banner.securitybanner.linksrc");
        v8 = p1.get(".sysmsg.banner.securitybanner.showtype");
        if (!bo.isNullOrNil(v4_2) && !bo.isNullOrNil(v8)) {
            v10 = null;
            v8 = (v8.equals("1"))? true : v10;
            v12 = new String[3];
            v12[0]=v5;
            v12[1]=v6;
            v12[2]=v7;
            az.alz().ajY().a(v4_2, v8, v12);
        }
        az.alz().ajZ().o(p1);
    }
    if (!bo.isNullOrNil(p0) && p0.equals("midinfo")) {
        v4_2 = p1.get(".sysmsg.midinfo.json_buffer");
        v5 = p1.get(".sysmsg.midinfo.time_interval");
        v8_1 = new Object[3];
        v8_1[0]=v5;
        v8_1[1]=v4_2;
        v8_1[2]=v11;
        ab.i("MicroMsg.BigBallSysCmdMsgConsumer", "QueryMid time[%s] json[%s]  [%s] ", v8_1);
        v5 = bo.getInt(v5, 0);
        if ((((long)v5-0x00015180) > 0) && (((long)v5-0x000d2f00) < 0)) {
            az.alz();
            c.aaH().set(0x00051001, Long.valueOf((bo.azo()+(long)v5)));
        }
        if (!bo.isNullOrNil(v4_2)) {
            d.aaU(v4_2);
        }
    }
    if (p0 && p0.equals("revokemsg")) {//这句p0.equals("revokemsg"让我的小心脏狠狠跳了一下
        ab.i("MicroMsg.BigBallSysCmdMsgConsumer", "mm hit MM_DATA_SYSCMD_NEWXML_SUBTYPE_REVOKE");
        v5 = p1.get(".sysmsg.revokemsg.newmsgid");
        v6 = p1.get(".sysmsg.revokemsg.replacemsg");
        v9 = new Object[2];
        v9[0]=v5;
        v9[1]=v6;
        ab.i("MicroMsg.BigBallSysCmdMsgConsumer", "ashutest::[oneliang][xml parse] ,msgId:%s,replaceMsg:%s ", v9);
        az.alz();
        f.a(c.ajB().ak(p1.get(".sysmsg.revokemsg.session"), bo.getLong(v5, 0)), p2, v6, "MicroMsg.BigBallSysCmdMsgConsumer");
        AppMethodBeat.o(16267);
        return null;
    }else if(p0 && p0.equals("clouddelmsg")){
        ab.i("MicroMsg.BigBallSysCmdMsgConsumer", "mm hit MM_DATA_SYSCMD_NEWXML_CLOUD_DEL_MSG");
        v4_2 = p1.get(".sysmsg.clouddelmsg.delcommand");
        v5 = p1.get(".sysmsg.clouddelmsg.msgid");
        v6 = p1.get(".sysmsg.clouddelmsg.fromuser");
        v7 = v11.indexOf("<msg>");
        v8_1 = v11.indexOf("</msg>");
        v7 = (v7 == -1 || v8_1 == -1)? "" : be.bp(br.J(v11.substring(v7, (v8_1+6)), "msg"));
        v10_1 = new Object[4];
        v10_1[0]=v4_2;
        v10_1[1]=v5;
        v10_1[2]=v6;
        v10_1[3]=v7;
        ab.i("MicroMsg.BigBallSysCmdMsgConsumer", "[hakon][clouddelmsg], delcommand:%s, msgid:%s, fromuser:%s, sysmsgcontent:%s", v10_1);
        az.alz();
        if (!(v6 = c.ajB().gf(v6, v5)) || (v6.size() <= 0)) {
            ab.e("MicroMsg.BigBallSysCmdMsgConsumer", "get null by getByBizClientMsgId");
            AppMethodBeat.o(16267);
            return null;
        }else {
            v8_1 = v6.iterator();
            while (v8_1.hasNext()) {
                v6_1 = v8_1.next();
                if (!v6_1) {
                    ab.e("MicroMsg.BigBallSysCmdMsgConsumer", "[hakon][clouddelmsg], msgInfo == null");
                }else if((v6_1.field_msgSvrId < 0)){
                    v11_1 = new Object[2];
                    v11_1[0]=Long.valueOf(v6_1.field_msgId);
                    v11_1[1]=Long.valueOf(v6_1.field_msgSvrId);
                    ab.e("MicroMsg.BigBallSysCmdMsgConsumer", "[hakon][clouddelmsg], invalid msgInfo.msgId = %s, srvId = %s", v11_1);
                }else {
                    v11_1 = new Object[2];
                    v11_1[0]=Long.valueOf(v6_1.field_msgId);
                    v11_1[1]=Long.valueOf(v6_1.field_msgSvrId);
                    ab.i("MicroMsg.BigBallSysCmdMsgConsumer", "[hakon][clouddelmsg], msgInfo.msgId = %s, srvId = %s", v11_1);
                    v9 = bo.getInt(v4_2, 0);
                    if (v9 == 1) {
                        az.alz();
                        c.ajB().au(v6_1.field_talker, v6_1.field_msgSvrId);
                    }else if(v9 == 2 && v6_1.dSS()){
                        v6_1.setContent(v7);
                        bi.a(v6_1, p2);
                        az.alz();
                        c.ajB().b(v6_1.field_msgSvrId, v6_1);
                        az.alz();
                        v9_1 = c.ajE().avk(v6_1.field_talker);
                        if (v9_1 && (v9_1.field_unReadCount > 0)) {
                            az.alz();
                            if (v9_1.field_unReadCount >= c.ajB().Z(v6_1)) {
                                v9_1.iS((v9_1.field_unReadCount-1));
                                az.alz();
                                c.ajE().a(v9_1, v9_1.field_username);
                            }
                        }
                    }
                    v9 = new pu();
                    v9.dlv.cUH = v6_1.field_msgId;
                    v9.dlv.dlw = v7;
                    v9.dlv.djW = v6_1;
                    a.AGO.l(v9);
                }
            }
            AppMethodBeat.o(16267);
            return null;
        }
    }else if(p0 && p0.equals("updatepackage")){
        v4_2 = e$d.bG(Integer.valueOf(0x90000011));
        v8_2 = (!v4_2)? null : v4_2.b(p2);
    }else {
        v8 = v9;
    }


我们一眼就看到了这一句p0.equals("revokemsg")revokemsg不就是撤回消息吗,很明显,p0=revokemsg时,跳转进入撤回的逻辑,p1是一个Mapv5= p1.get(".sysmsg.revokemsg.newmsgid");v6 = p1.get(".sysmsg.revokemsg.replacemsg");存储着msgidreplacemsg而这句话实现了具体功能f.a(c.ajB().ak(p1.get(".sysmsg.revokemsg.session"),bo.getLong(v5,0)),p2, v6, "MicroMsg.BigBallSysCmdMsgConsumer");
我们直接hook这个函数,当p0=revokemsg时直接返回就可以屏蔽掉撤回了;粗略一看,这个函数还控制着添加联系人,更新包等功能,大家有兴趣可以具体跟一下。
[JavaScript] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
var modlef = Java.use('com.tencent.mm.model.f');
modlef.a.overload('java.lang.String', 'java.util.Map', 'com.tencent.mm.aj.e$a').implementation=function(a1,a2,a3)
{   console.log("hook a start");
    console.log("a1:"+a1);
    console.log("a2:" + JSON.stringify(a2));
    console.log("a3:"+a3);
    if(a1=="revokemsg")return null
 
 //  var threadef = Java.use('java.lang.Thread');
  //  var threadinstance = threadef.$new();
  //  var stack = threadinstance.currentThread().getStackTrace();
  //  function Where(stack){
  //   for(var i = 0; i < stack.length; ++i){
  //     console.log(stack[i].toString());
  //   }
  // }
  //  console.log("Full call stack:" + Where(stack));
     
 
    return this.a(a1,a2,a3)
}


下面,我们就来一步一步看一下撤回的实现,从系统调用一直到updateWithOnConflict,我们仍然是倒着看简单观察堆栈,第一步这4update是必经之路,最底层是updateWithOnConflict,上面都是这个update函数的层层封装,这里的关键点就是数据库名,也就是update的到底是哪个数据库,
[Asm] 纯文本查看 复制代码
1
2
3
4
com.tencent.wcdb.database.SQLiteDatabase.updateWithOnConflict(Native Method)
com.tencent.wcdb.database.SQLiteDatabase.update(SourceFile:1726)
com.tencent.mm.cf.f.update(SourceFile:774)
com.tencent.mm.cf.h.update(SourceFile:601)


2个sqlitedatabase的封装实现我们就不看了,熟悉数据库编程的同学早就用滥了,直接看com.tencent.mm.cf.f.update
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
com.tencent.mm.cf.f.update
public final int update(String str, ContentValues contentValues, String str2, String[] strArr) {
    AppMethodBeat.m3378i(59081);
    SQLiteDatabase sQLiteDatabase = this.BnR != null ? this.BnR : this.BnS;//数据库实例是当前类的 BnR或 BnS属性
    if (isMainThread()) {
        BnX.mo6448a(sQLiteDatabase, 32769, str);
    }
    int update = sQLiteDatabase.update(str, contentValues, str2, strArr);
    AppMethodBeat.m3379o(59081);
    return update;
}
数据库实例BnR赋值语句在m4070F函数中:fVar.BnR= SQLiteDatabase.openDatabase(str3, bytes, sQLiteCipherSpec, null,i, fVar);数据库实例BnS赋值语句在m4074cs函数中:fVar.BnS= SQLiteDatabase.openDatabase(str, null,i, fVar);2个函数简单看,对于数据库赋值而言就是一个有加密一个没加密,我们在这里hook可以得到sQLiteCipherSpec对象实例,分析它的加密流程,有意思的函数还有一个getPath是获得路径的,这里贴一下数据库的打开与解密吧,与本文无关,权当延伸,很清晰的打开与解密流程
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public static C1534f m4070F(String str, String str2, boolean z) {
    String str3;
    byte[] bytes;
    SQLiteCipherSpec sQLiteCipherSpec;
    AppMethodBeat.m3378i(59074);
    try {
        C9958c cVar = new C9958c(str + "-vfslog");
        C9958c cVar2 = new C9958c(str + "-vfslo1");
        if (cVar.exists() && cVar.length() > 256) {
            cVar.delete();
        }
        if (cVar2.exists() && cVar2.length() > 256) {
            cVar2.delete();
        }
    } catch (Throwable th) {
        C8953ab.printErrStackTrace("MicroMsg.MMDataBase", th, "", new Object[0]);
    }
    C1534f fVar = new C1534f();
    int i = 268435456;
    if (C9015bo.isNullOrNil(str)) {
        str3 = SQLiteDatabaseConfiguration.MEMORY_DB_PATH;
        fVar.BnY = true;
    } else {
        str3 = str;
    }
    if (C9015bo.isNullOrNil(str2)) {
        sQLiteCipherSpec = null;
        bytes = null;
    } else {
        bytes = str2.getBytes();
        sQLiteCipherSpec = mnM;
    }
    if (z && C1531b.BnM) {
        i = 805306368;
    } else if (!C1531b.BnM) {
        C9961f.deleteFile(str + "-shm");
    }
    try {
        fVar.BnR = SQLiteDatabase.openDatabase(str3, bytes, sQLiteCipherSpec, null, i, fVar);
        fVar.BnR.setTraceCallback(fVar);
        if (dXc()) {
            fVar.BnR.setCheckpointCallback(BnW);
            C8953ab.m13556i("MicroMsg.MMDataBase", "Enable async checkpointer for DB: " + fVar.getPath());
        }
        if (C9030f.AHl.ass("ENABLE_STETHO")) {
            BnT.put(fVar.getPath(), fVar.BnR);
        }
        if (fVar.BnR == null) {
            AppMethodBeat.m3379o(59074);
            return null;
        }
        AppMethodBeat.m3379o(59074);
        return fVar;
    } catch (SQLiteException e) {
        C6339e.sxI.mo11124f("DBCantOpen", "DB (" + new C9958c(str3).getName() + ") can't open: " + C9015bo.m13722k(e), null);
        AppMethodBeat.m3379o(59074);
        throw e;
    }
}

下面上层的com.tencent.mm.cf.h.update
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public final int update(String str, ContentValues contentValues, String str2, String[] strArr) {
    int i;
    AppMethodBeat.m3378i(59123);
    if (!isOpen()) {
        C8953ab.m13553e(this.TAG, "DB IS CLOSED ! {%s}", C9015bo.dPZ());
        AppMethodBeat.m3379o(59123);
        return -2;
    }
    boolean z = WXHardCoderJNI.hcDBEnable;
    int i2 = WXHardCoderJNI.hcDBDelayWrite;
    int i3 = WXHardCoderJNI.hcDBCPU;
    int i4 = WXHardCoderJNI.hcDBIO;
    if (WXHardCoderJNI.hcDBThr) {
        i = C2700g.abc().dPe();
    } else {
        i = 0;
    }
    int startPerformance = WXHardCoderJNI.startPerformance(z, i2, i3, i4, i, WXHardCoderJNI.hcDBTimeout, 501, WXHardCoderJNI.hcDBActionWrite, this.TAG);
    C1532c.begin();
    try {
        int update = this.BnH.update(str, contentValues, str2, strArr);//这一句调用了上面的update
        C1532c.m4059a(str, null, this.kLT);
        WXHardCoderJNI.stopPerformance(WXHardCoderJNI.hcDBEnable, startPerformance);
        AppMethodBeat.m3379o(59123);
        return update;
    } catch (Exception e) {
        C6339e.sxI.idkeyStat(181, 11, 1, false);
        C8953ab.m13552e(this.TAG, "update Error :" + e.getMessage());
        C1532c.m4060m(e);
        WXHardCoderJNI.stopPerformance(WXHardCoderJNI.hcDBEnable, startPerformance);
        AppMethodBeat.m3379o(59123);
        return -1;
    } catch (Throwable th) {
        WXHardCoderJNI.stopPerformance(WXHardCoderJNI.hcDBEnable, startPerformance);
        AppMethodBeat.m3379o(59123);
        throw th;
    }
}
这个函数封装了上一个update,简单看一下他有很多WXHardCoderJNI的调用,而这个类里面有很多native函数,这应该是鹅厂同行写的一个控制优化相关的类,与本文无关,它的数据库实例是this.BnH,他赋值在this.BnH= this.Boo.BnH;Boo属性初始化在publicC1520aBoo = newC1520a();于是我们跳转到类C1520a(com.tencent.mm.cf.a),这个类里面有很多sql语句,其中包含解密初始化等,一些初始化函数中有这样的语句this.BnH= C1534f.m4070F(str, this.aBL,z);this.BnH= C1534f.m4074cs(str,z);,这不就是调用了com.tencent.mm.cf.h这个类里面的数据库赋值,至此,我们就搞清楚了数据库实例如何获得,贴一个函数大家简单看看:
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
private boolean m4020a(String str, long j, boolean z, String str2) {
    int i;
    AppMethodBeat.m3378i(59029);
    if (this.BnH != null) {
        AssertionError assertionError = new AssertionError();
        AppMethodBeat.m3379o(59029);
        throw assertionError;
    }
    this.isNew = !C9961f.m15607dA(str);
    boolean z2 = false;
    Iterator it = dWT().iterator();
    while (true) {
        if (!it.hasNext()) {
            break;
        }
        String str3 = (String) it.next();
        this.aBL = C1220g.m3432D((str3 + j).getBytes()).substring(0, 7);
//这里像不像拼接7位解密密码?str3应该是imei,j是uin, m3432D是md5,搞过解密数据库的你懂得
        try {
            this.BnH = C1534f.m4070F(str, this.aBL, z);//str是数据库名,aBL是解密密码,简单跟一下就能搞明白
            m4019a(this.BnH);
            if (!C1615q.m4230cl(true).equals(str3)) {// m4230cl获取当前imei对比是否改变
                C8953ab.m13556i("MicroMsg.DBInit", "IMEI changed detected: ".concat(String.valueOf(str3)));// str3明显是imei
                C1609l.m4192SN().set(258, str3);
                C6339e.sxI.idkeyStat(181, 5, 1, false);
            }
            AppMethodBeat.m3379o(59029);
            return true;
        } catch (SQLiteException e) {
            if (!(e instanceof SQLiteDatabaseCorruptException)) {
                z2 = false;
                break;
            }
            z2 = true;
        }
    }
    if (z2) {
        if (!z) {
            i = 42;
        } else if (C1534f.dXc()) {
            i = 43;
        } else {
            i = 41;
        }
        C6339e.sxI.idkeyStat(181, (long) i, 1, true);
        C1534f.awG(str);
        if (str.endsWith("EnMicroMsg.db")) {
            C1534f.awG(C2700g.aaZ().fxL + "dbback/EnMicroMsg.db");
        }
        try {
            this.aBL = C1220g.m3432D((C1615q.m4230cl(true) + j).getBytes()).substring(0, 7);// m4230cl函数获取imei
            this.BnH = C1534f.m4070F(str, this.aBL, z);
            m4019a(this.BnH);
            this.isNew = true;
            C6339e.sxI.idkeyStat(181, 6, 1, false);
            AppMethodBeat.m3379o(59029);
            return true;
        } catch (SQLiteException e2) {
            C6339e.sxI.idkeyStat(181, 7, 1, false);
        }
    } else {
        if (str2 != null && str2.length() > 0) {
            this.isNew = !C9961f.m15607dA(str2);
            try {
                this.BnH = C1534f.m4070F(str2, this.aBL, z);
                m4019a(this.BnH);
                C6339e.sxI.idkeyStat(181, 6, 1, false);
                AppMethodBeat.m3379o(59029);
                return true;
            } catch (SQLiteException e3) {
                C6339e.sxI.idkeyStat(181, 7, 1, false);
            }
        }
        if (this.BnH != null) {
            this.BnH.close();
            this.BnH = null;
        }
        this.aBL = null;
        AppMethodBeat.m3379o(59029);
        return false;
    }
}

上面是com.tencent.mm.storage.bj.astorage这个类肯定和存储有关,这个a方法有很多重载,简单hook一下判断出a方法签名(JLcom/tencent/mm/storage/bi;)V,所以函数为mo10677a
[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
public final void mo10677a(long j, C9124bi biVar) {//j就是 msgId, biVar存储了contentValues
    AppMethodBeat.m3378i(1389);
    if (biVar.dUZ()) {
        String avS = avS(biVar.dVU);
        if (C21678w.m34652oO(avS)) {
            C8953ab.m13551d("MicroMsg.MsgInfoStorage", "msgCluster = %s", avS);
            biVar.mo6820kh("notifymessage");
        }
    }
    m46253au(biVar);
    if (this.ght.update(m46255ow(j), biVar.convertTo(), "msgId=?", new String[]{String.valueOf(j)}) != 0) {//这一句调用上面的update
        doNotify();
        mo10682a(new C5973c(biVar.field_talker, "update", biVar));//这一句还取了 talker,猜想功能是替换为xx撤回了一条消息的上层函数,懒得跟了
        AppMethodBeat.m3379o(1389);
        return;
    }
    C6339e.sxI.idkeyStat(111, 244, 1, false);
    AppMethodBeat.m3379o(1389);
}

在往上就是我开头提到的关键函数com.tencent.mm.model.f.a(SourceFile:145),com.tencent.mm.model.f.a(SourceFile:352) mo7343a组合了,不记得的同学翻上去看看


在上面是com.tencent.mm.model.cc.b(Lcom/tencent/mm/aj/e$a;)Lcom/tencent/mm/aj/e$b;mo5953b),这个函数:
[Java] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
public final C1268b mo5953b(C1267a aVar) {
    Map map;
    String str;
    List<C5988p> list;
    AppMethodBeat.m3378i(59939);
    C27985cr crVar = aVar.foR;//1
    switch (crVar.oXG) {
        case 10001:
            m5274a(C2976aa.m5550a(crVar.yCi), aVar, false);
            C6339e.sxI.kvStat(10395, String.valueOf(crVar.rCB));
            AppMethodBeat.m3379o(59939);
            return null;
        case 10002:
            String a = C2976aa.m5550a(crVar.yCk);//2
            if (C9015bo.isNullOrNil(a)) {
                C8953ab.m13552e("MicroMsg.SysCmdMsgExtension", "null msg content");
                AppMethodBeat.m3379o(59939);
                return null;
            }
            if (a.startsWith("~SEMI_XML~")) {
                Map asT = C9008be.asT(a);
                if (asT == null) {
                    C8953ab.m13553e("MicroMsg.SysCmdMsgExtension", "SemiXml values is null, msgContent %s", a);
                    AppMethodBeat.m3379o(59939);
                    return null;
                }
                map = asT;
                str = "brand_service"; //
            } else {
                int indexOf = a.indexOf("<sysmsg");//3
                if (indexOf != -1) {
                    String substring = a.substring(indexOf);//4
                    C8953ab.m13551d("MicroMsg.SysCmdMsgExtension", "oneliang, msg content:%s,sub content:%s", a, substring);
                    Map J = C9020br.m13742J(substring, "sysmsg");//5
                    if (J == null) {
                        C8953ab.m13553e("MicroMsg.SysCmdMsgExtension", "XmlParser values is null, msgContent %s", a);
                        AppMethodBeat.m3379o(59939);
                        return null;
                    }
                    map = J;
                    str = (String) J.get(".sysmsg.$type");
                } else {
                    int indexOf2 = a.indexOf("<appmsg");
                    if (indexOf2 != -1) {
                        C8953ab.m13556i("MicroMsg.SysCmdMsgExtension", "msgContent start with <appmsg");
                        String substring2 = a.substring(indexOf2);
                        C8953ab.m13551d("MicroMsg.SysCmdMsgExtension", "oneliang, msg content:%s,sub content:%s", a, substring2);
                        Map J2 = C9020br.m13742J(substring2, "appmsg");
                        if (J2 == null) {
                            C8953ab.m13553e("MicroMsg.SysCmdMsgExtension", "XmlParser values is null, msgContent %s", a);
                            AppMethodBeat.m3379o(59939);
                            return null;
                        }
                        map = J2;
                        str = (String) J2.get(".appmsg.title");
                    } else {
                        C8953ab.m13552e("MicroMsg.SysCmdMsgExtension", "msgContent not start with <sysmsg or <appmsg");
                        AppMethodBeat.m3379o(59939);
                        return null;
                    }
                }
            }
            C8953ab.m13551d("MicroMsg.SysCmdMsgExtension", "recieve a syscmd_newxml %s subType %s", a, str);
            if (str != null) {
                m5274a(str, aVar, true);
                synchronized (this.ghU) {
                    try {
                        list = (List) this.ghU.get(str);
                    } finally {
                        while (true) {
                            AppMethodBeat.m3379o(59939);
                            break;
                        }
                    }
                }
                if (list == null || list.isEmpty()) {
                    C8953ab.m13560w("MicroMsg.SysCmdMsgExtension", "listener list is empty, return now");
                } else {
                    C8953ab.m13557i("MicroMsg.SysCmdMsgExtension", "listener list size is %d", Integer.valueOf(list.size()));
                    synchronized (list) {
                        try {
                            for (C5988p onNewXmlReceived : list) {
                                onNewXmlReceived.onNewXmlReceived(str, map, aVar);
                            }
                        } catch (Throwable th) {
                            AppMethodBeat.m3379o(59939);
                            throw th;
                        }
                    }
                }
                C5987o oVar = (C5987o) this.ghV.get(str);
                if (oVar != null) {
                    return oVar.mo7343a(str, map, aVar);//这一句调用上面mo7343a,str="revokemsg",map 存储"sysmsg.revokemsg.newmsgid"和"sysmsg.revokemsg.replacemsg"两个键值
                }
                C8953ab.m13553e("MicroMsg.SysCmdMsgExtension", "no NewXmlConsumer to consume cmd %s!!", str);
            }
            AppMethodBeat.m3379o(59939);
            return null;
        default:
            C8953ab.m13561w("MicroMsg.SysCmdMsgExtension", "cmdAM msgType is %d, ignore, return now", Integer.valueOf(crVar.oXG));
            AppMethodBeat.m3379o(59939);
            return null;
    }
}


容易看出msg分为sysmsgappmsg,这里我们主要看一下参数map的传递,撤回好像跑的是appmsgC27985crcrVar = aVar.foR;//1Stringa = C2976aa.m5550a(crVar.yCk);//2  m5550a应该就是个tostring类似功能的函数intindexOf= a.indexOf("<sysmsg");//3Stringsubstring = a.substring(indexOf);//4居然是暴力切割字符串MapJ = C9020br.m13742J(substring, "sysmsg");//5组合一下,MapJ = C9020br.m13742J( C2976aa.m5550a(aVar.foR .yCk).substring(C2976aa.m5550a(aVar.foR .yCk).indexOf("<sysmsg")),"sysmsg");哈哈,是不是挺复杂的,其实我就是想说明,通过上层函数参数aVar的数据结构完全可以定位下层函数的map参数,只不过表达式有点复杂而已。

再往上进入到plugin.messenger.foundation这个大类里面,扫一眼发现,上层函数的参数已经和protocal相关了。com.tencent.mm.plugin.messenger.foundation.c.a(Lcom/tencent/mm/aj/e$a;Lcom/tencent/mm/plugin/messenger/foundation/a/v;)Lcom/tencent/mm/aj/e$b;= m38177a
com.tencent.mm.plugin.messenger.foundation.c.a(Lcom/tencent/mm/protocal/protobuf/vg;[BZLcom/tencent/mm/plugin/messenger/foundation/a/v;)V= mo33997a


这个函数没啥好说的直接传参到下一层的 mo5953b,值得注意的是aVar.foR.yCi存储着这个appnewsappreaderappblogapp,还有有个有意思的是外层的一个fucking判断:thisfucking msg from mac weixin ,someone send msg to newsapp at macweixin ,givp up,跟一下应该可以找到如何判断msgmac系统的,顺便过掉。
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public static C1268b m38177a(C1267a aVar, C24153v vVar) {
    AppMethodBeat.m3378i(TXLiteAVCode.EVT_RTMP_PUSH_PUBLISH_START);
    C27985cr crVar = aVar.foR;
    if (10008 == C2977ae.hiZ && C2977ae.hja != 0) {
        C8953ab.m13557i("MicroMsg.MessageSyncExtension", "dkmsgid  set svrmsgid %d -> %d", Long.valueOf(crVar.rCB), Integer.valueOf(C2977ae.hja));
        crVar.rCB = (long) C2977ae.hja;
        C2977ae.hja = 0;
    }
    if (((C5985k) C2700g.m5022ac(C5985k.class)).ciy().mo10747kF(crVar.rCB)) {
        C8953ab.m13556i("MicroMsg.MessageSyncExtension", "ignore, because reSync the deleted msg perhaps the IDC has change has swtiched");
        AppMethodBeat.m3379o(TXLiteAVCode.EVT_RTMP_PUSH_PUBLISH_START);
        return null;
    }
    String a = C2976aa.m5550a(crVar.yCi);
    String a2 = C2976aa.m5550a(crVar.yCj);
    if (!a.equals(C2837u.akn()) || !a2.equals("newsapp") || crVar.oXG == 51) {
        C8953ab.m13557i("MicroMsg.MessageSyncExtension", "dkAddMsg from:%s to:%s id:[%d,%d,%d] status:%d type:%d time:[%d %s] diff:%d imgstatus:%d imgbuf:%d src:%d push:%d content:%s", a, a2, Long.valueOf(crVar.rCB), Integer.valueOf(crVar.rCz), Integer.valueOf(crVar.yCp), Integer.valueOf(crVar.kVf), Integer.valueOf(crVar.oXG), Integer.valueOf(crVar.CreateTime), C9015bo.m13727nW((long) crVar.CreateTime), Long.valueOf(C9015bo.azo() - ((long) crVar.CreateTime)), Integer.valueOf(crVar.yCl), Integer.valueOf(C2976aa.m5553a(crVar.yCm, new byte[0]).length), Integer.valueOf(C9015bo.nullAsNil(crVar.yCn).length()), Integer.valueOf(C9015bo.nullAsNil(crVar.yCo).length()), C9015bo.atw(C2976aa.m5551a(crVar.yCk, "")));
        C8953ab.m13557i("MicroMsg.MessageSyncExtension", "parseMsgSource  has been Deprecated  by dk. at 20151218 [%s] %s ", crVar.yCn, "");
        C24154w.m38170h(crVar);
        if (a.equals("readerapp")) {
            crVar.yCi = C2976aa.m5556wH("newsapp");
            crVar.oXG = 12399999;
        }
        if ((a.equals("blogapp") || a.equals("newsapp")) && crVar.oXG != 10002) {
            crVar.oXG = 12399999;
        }
        if (crVar.oXG == 52) {
            crVar.oXG = 1000052;
        }
        if (crVar.oXG == 53) {
            crVar.oXG = 1000053;
        }
        C21628bi.m34464c(aVar);
        boolean z = false;
        C1268b bVar = null;
        C1264e bG = C1266d.m3510bG(Integer.valueOf(crVar.oXG));
        if (bG == null) {
            bG = C1266d.m3510bG(a);
        }
        if (bG != null) {
            bVar = bG.mo5953b(aVar);//这一句调用上面函数mo5953b,传入aVar
            C9124bi biVar = bVar == null ? null : bVar.cRE;
            if (biVar == null) {
                C8953ab.m13561w("MicroMsg.MessageSyncExtension", "summerbadcr extension declared but skipped msg, type=%d, svrId=%d, MsgSeq=%d, createTime=%d, addMsgInfo=%s", Integer.valueOf(crVar.oXG), Long.valueOf(crVar.rCB), Integer.valueOf(crVar.yCp), Integer.valueOf(crVar.CreateTime), aVar);
            } else if (!m38176UL(a)) {
                C8953ab.m13550d("MicroMsg.MessageSyncExtension", " msg , id =" + biVar.field_msgId + "  " + vVar);
                if (biVar.field_msgId > 0 && vVar != null && bVar.gnh) {
                    vVar.mo10881a(biVar, crVar);
                }
            }
            z = true;
        }
        C24154w.m38168b(5, crVar);
        if (!z) {
            C8953ab.m13555f("MicroMsg.MessageSyncExtension", "unknown add msg request, type=%d. drop now !!!", Integer.valueOf(crVar.oXG));
        }
        AppMethodBeat.m3379o(TXLiteAVCode.EVT_RTMP_PUSH_PUBLISH_START);
        return bVar;
    }
    C8953ab.m13561w("MicroMsg.MessageSyncExtension", "msgid:%d type:%d this fucking msg from mac weixin ,someone send msg to newsapp at mac weixin ,givp up.", Long.valueOf(crVar.rCB), Integer.valueOf(crVar.oXG));
    AppMethodBeat.m3379o(TXLiteAVCode.EVT_RTMP_PUSH_PUBLISH_START);
    return null;
}

这里主要还是关注一下参数的传递,关键是C27985cr这个类,其他没啥好说的
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public final void mo33997a(C28418vg vgVar, byte[] bArr, boolean z, C24153v vVar) {
             //(Lcom/tencent/mm/protocal/protobuf/vg;[BZLcom/tencent/mm/plugin/messenger/foundation/a/v;)V
    int i = 0;
    AppMethodBeat.m3378i(TXLiteAVCode.EVT_CAMERA_CLOSE);
    switch (vgVar.zbt) {
        case 5:
            C27985cr crVar = (C27985cr) new C27985cr().parseFrom(bArr);//1
            if (crVar != null) {
                C1267a aVar = new C1267a(crVar, false, false, false);//2
                m38177a(aVar, vVar);//这句调用了上面m38177a,aVar继承自bArr, C27985cr crVar = (C27985cr) new C27985cr().parseFrom(bArr);C1267a aVar = new C1267a(crVar, false, false, false);
                // 而这个C27985cr就是com.tencent.p130mm.protocal.protobuf,我们已经触摸到协议层了,有兴趣可以去看C27985cr的数据结构,反复hook就能搞明白
                if (!aVar.gnc) {
                    C21637bj.m34487a(C2976aa.m5550a(crVar.yCi), crVar.rCB, ((long) crVar.CreateTime) * 1000, crVar.oXG);
                }
            }
            AppMethodBeat.m3379o(TXLiteAVCode.EVT_CAMERA_CLOSE);
            return;
        case 8:
            C28483ya yaVar = (C28483ya) new C28483ya().parseFrom(bArr);
            String a = C2976aa.m5550a(yaVar.zdL);
            int i2 = yaVar.zdO;
            Cursor di = ((C5985k) C2700g.m5022ac(C5985k.class)).ciy().mo10730di(a, i2);
            if (di.moveToFirst()) {
                while (!di.isAfterLast()) {
                    C9124bi biVar = new C9124bi();
                    biVar.convertFrom(di);
                    C21628bi.m34468m(biVar);
                    di.moveToNext();
                }
            }
            di.close();
            ((C5985k) C2700g.m5022ac(C5985k.class)).ciy().mo10729dh(a, i2);
            AppMethodBeat.m3379o(TXLiteAVCode.EVT_CAMERA_CLOSE);
            return;
        case 9:
            C28490yh yhVar = (C28490yh) new C28490yh().parseFrom(bArr);
            LinkedList<Integer> linkedList = yhVar.zdR;
            while (true) {
                int i3 = i;
                if (i3 >= linkedList.size()) {
                    break;
                } else {
                    C21628bi.m34484y(C2976aa.m5550a(yhVar.zdL), (long) ((Integer) linkedList.get(i3)).intValue());
                    i = i3 + 1;
                }
            }
    }
    AppMethodBeat.m3379o(TXLiteAVCode.EVT_CAMERA_CLOSE);
}


在往上是com.tencent.mm.plugin.messenger.foundation.f.a(Lcom/tencent/mm/protocal/protobuf/vg;[BZ)V= mo36123a,很简单,自己看看吧
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
public final void mo36123a(C28418vg vgVar, byte[] bArr, boolean z) {
    AppMethodBeat.m3378i(1062);
    C24149s By = C24150a.m38158By(vgVar.zbt);
    if (By != null) {
        try {
            By.mo33997a(vgVar, bArr, z, this.qvv);//这一句调用前面mo33997a,无脑传参,没啥意思
            AppMethodBeat.m3379o(1062);
        } catch (IOException e) {
            C8953ab.m13552e("MicroMsg.SyncDoCmdExtensions", "docmd: parse protobuf error, " + e.getMessage());
            RuntimeException runtimeException = new RuntimeException("docmd: parse protobuf error");
            AppMethodBeat.m3379o(1062);
            throw runtimeException;
        }
    } else {
        C8953ab.m13561w("MicroMsg.SyncDoCmdExtensions", "SyncDoCmdExtension for cmd id [%s] is null.", Integer.valueOf(vgVar.zbt));
        AppMethodBeat.m3379o(1062);
    }
}



在上com.tencent.mm.plugin.zero.c.a(Lcom/tencent/mm/protocal/protobuf/vg;Z)Z=mo40148a,仍然是注意一下参数传递,其实每次参数传递和变换,就是把外层协议层层剥离的过程,每一次消耗一些参数进行判断或其他动作,大家应该很容易看明白。
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public final boolean mo40148a(C28418vg vgVar, boolean z) {
    AppMethodBeat.m3378i(58774);
    if (!C2700g.aaU()) {
        C8953ab.m13552e("MicroMsg.SyncDoCmdDelegate", "account storage disabled, discard all commands");
        AppMethodBeat.m3379o(58774);
        return false;
    }
    long azp = C9015bo.azp();
    byte[] a = C2976aa.m5552a(vgVar.zbu);//1
    C8953ab.m13557i("MicroMsg.SyncDoCmdDelegate", "doCmd %d cmdid:%d buf:%d thr:[%d]", Long.valueOf(azp), Integer.valueOf(vgVar.zbt), Integer.valueOf(C9015bo.m13690co(a)), Long.valueOf(Thread.currentThread().getId()));
    if (C9015bo.m13689cn(a)) {
        C8953ab.m13552e("MicroMsg.SyncDoCmdDelegate", "docmd: no protobuf found.");
        AppMethodBeat.m3379o(58774);
        return false;
    }
    try {
        if (this.xUP != null) {
            this.xUP.mo36123a(vgVar, a, z);//这句调用mo36123a,byte[] a = C2976aa.m5552a(vgVar.zbu);
        }
        C8953ab.m13557i("MicroMsg.SyncDoCmdDelegate", "doCmd FIN %d cmdid:%d Time:%d", Long.valueOf(azp), Integer.valueOf(vgVar.zbt), Long.valueOf(C9015bo.m13715hn(azp)));
        AppMethodBeat.m3379o(58774);
        return true;
    } catch (Exception e) {
        C8953ab.printErrStackTrace("MicroMsg.SyncDoCmdDelegate", e, "", new Object[0]);
        AppMethodBeat.m3379o(58774);
        return false;
    }
}


在往上com.tencent.mm.modelmulti.o$a$1.onTimerExpired,明显是判断是否超时了,超了怎么处理,在上面就是一些系统调用了,到此为止了
[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public final boolean onTimerExpired() {
    AppMethodBeat.m3378i(58399);
    if (C2700g.aaU() && !C2586a.aaa()) {
        C2700g.aba();
        if (C2700g.aaZ() != null) {
            C2700g.aba();
            if (C2700g.aaZ().aaH() != null) {
                LinkedList<C28418vg> linkedList = C21737a.this.gDu.yUs.kUI;
                C27641c cVar = new C27641c();
                cVar.mo40149cN(C21737a.this.gDw);
                long azp = C9015bo.azp();
                while (C21737a.this.gCe < linkedList.size()) {
                    linkedList.size();
                    if (!cVar.mo40148a((C28418vg) linkedList.get(C21737a.this.gCe), false)) {
                        C6339e.sxI.idkeyStat(99, 46, 1, false);
                    }
                    C21737a.this.gCe++;
                    long hn = C9015bo.m13715hn(azp);
                    C8953ab.m13557i("MicroMsg.SyncService", "processResp %s time:%s size:%s index:%s", C21737a.this.gDw, Long.valueOf(hn), Integer.valueOf(linkedList.size()), Integer.valueOf(C21737a.this.gCe - 1));
                    if (hn >= 500) {
                        break;
                    }
                }
                cVar.mo40150cO(C21737a.this.gDw);
                if (C21737a.this.gCe < linkedList.size()) {
                    C8953ab.m13557i("MicroMsg.SyncService", "processResp %s time:%s size:%s index:%s Shold Continue.", C21737a.this.gDw, Long.valueOf(C9015bo.m13715hn(azp)), Integer.valueOf(linkedList.size()), Integer.valueOf(C21737a.this.gCe - 1));
                    AppMethodBeat.m3379o(58399);
                    return true;
                }
                C21733o.m34801a(C21737a.this.gDt, C21737a.this.gDu, C21737a.this.gDw);
                C21737a.this.gDv.mo34011no(linkedList.size());
                AppMethodBeat.m3379o(58399);
                return false;
            }
        }
[/size][/size][/font]
[font=新宋体][size=4][size=9pt]    }


最后,简单总结一下:

1.com.tencent.mm.sdk.g.c.c$1$1.run开始,进入系统调用,开启loop消息循环,通过厂商自己重写的dispatchMessagehandleMessage处理message

2.进入com.tencent.mm.modelmulti.o$a$1.onTimerExpired,判断是否超时,记得超时了好像撤回的功能就没有了,这个大家自己跟吧
3.com.tencent.mm.r.b.b(SourceFile:40)com.tencent.mm.plugin.messenger.foundation.c.a(SourceFile:165)
com.tencent.mm.plugin.messenger.foundation.c.a(SourceFile:1059)
com.tencent.mm.plugin.messenger.foundation.f.a(SourceFile:118)

都是处理上层传来的的protobuf,有的直接继承参数,有的剥离外层后继承,protocal相关函数大家有时间慢慢分析

4.函数com.tencent.mm.model.f.a(SourceFile:145)com.tencent.mm.model.f.a(SourceFile:352)通过上层函数参数aVar的数据结构生成下层函数的map参数,这个参数很关键,保存了(".sysmsg.revokemsg.newmsgid")(".sysmsg.revokemsg.replacemsg")两个键值。
5.进入本文的关键函数com.tencent.mm.model.f.a(SourceFile:145)m34535acom.tencent.mm.model.f.a(SourceFile:352)mo7343a;函数mo7343a调用了m34535a直接hook这个函数,当p0=revokemsg时直接返回就可以屏蔽掉撤回了
6.进入数据库封装的上层com.tencent.mm.cf.f.update(SourceFile:774)com.tencent.mm.cf.h.update(SourceFile:601),这里主要注意sqlite实例的获得,分为加密和不加密,中间还有一些优化的功能。

7.进入最终更新数据库的com.tencent.wcdb.database.SQLiteDatabase.updateWithOnConflict(NativeMethod)com.tencent.wcdb.database.SQLiteDatabase.update(SourceFile:1726),更新数据库,完成功能


ps:中间有一些类和函数我没有仔细去看,可能会有些错误,但大致流程应该没啥问题,今天就开始上班了,时间比较急,中间格式整理的有些乱,大家见谅吧,干活去喽!
ps2:大家觉得不错的话,给个热心再走吧,我真的很想升到2级

免费评分

参与人数 10吾爱币 +9 热心值 +10 收起 理由
风之隼人 + 1 + 1 谢谢@Thanks!
sdjxyk + 1 + 1 虽然看不懂 但是感觉很厉害的样子
Luke01 + 1 用心讨论,共获提升!
wmsuper + 3 + 1 谢谢@Thanks!
笙若 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
乐观的阿斯顿 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
生有涯知无涯 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
子晗。 + 1 + 1 用心讨论,共获提升!
cskz008 + 1 + 1 带佬
vLove0 + 1 + 1 谢谢@Thanks!

查看全部评分

本帖被以下淘专辑推荐:

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

 楼主| L剑仙 发表于 2020-2-3 20:31
本帖最后由 L剑仙 于 2020-2-3 20:46 编辑

Noi_x 发表于 2020-2-3 18:48
等一个手机反撤回的教程,有可能吗

关键函数都找到了,兄弟你没仔细看吧,直接hook或者修改smali就行了啊,很简单啊,建议从头看一遍
反编译找到.com.tencent.mm.model.f.a函数,找到这段,直接return null,就屏蔽了防撤回

[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
if (p0 && p0.equals("revokemsg")) {//这句p0.equals("revokemsg"让我的小心脏狠狠跳了一下
       ab.i("MicroMsg.BigBallSysCmdMsgConsumer", "mm hit MM_DATA_SYSCMD_NEWXML_SUBTYPE_REVOKE");
       v5 = p1.get(".sysmsg.revokemsg.newmsgid");
       v6 = p1.get(".sysmsg.revokemsg.replacemsg");
       v9 = new Object[2];
       v9[0]=v5;
       v9[1]=v6;
       ab.i("MicroMsg.BigBallSysCmdMsgConsumer", "ashutest::[oneliang][xml parse] ,msgId:%s,replaceMsg:%s ", v9);
       az.alz();
       f.a(c.ajB().ak(p1.get(".sysmsg.revokemsg.session"), bo.getLong(v5, 0)), p2, v6, "MicroMsg.BigBallSysCmdMsgConsumer");
       AppMethodBeat.o(16267);
       return null;


或者用frida,xposed等hook ,hook代码如下
[Asm] 纯文本查看 复制代码
1
2
3
4
5
6
7
8
9
var modlef = Java.use('com.tencent.mm.model.f');
modlef.a.overload('java.lang.String', 'java.util.Map', 'com.tencent.mm.aj.e$a').implementation=function(a1,a2,a3)
{   console.log("hook a start");
    console.log("a1:"+a1);
    console.log("a2:" + JSON.stringify(a2));
    console.log("a3:"+a3);
    if(a1=="revokemsg")return null
    return this.a(a1,a2,a3)
}

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
幼儿园最皮de + 1 + 1 我很赞同!

查看全部评分

 楼主| L剑仙 发表于 2020-2-3 10:27
菜鸟确实不咋会改格式啊,换行又没了好多,字体也大小不一样
DemoCc10 发表于 2020-2-3 10:40
逆劫古修 发表于 2020-2-3 10:58
谢谢分享,我也来学习学习
popuui123 发表于 2020-2-3 11:31
感谢分享,学习一下
lqr712 发表于 2020-2-3 11:41
看着有点困难,自身基础差唉。
nang 发表于 2020-2-3 12:54
之前也研究过千牛工作台的,有大神也有分享过 ,但比微信的撤回差太多了。
critics 发表于 2020-2-3 13:59
厉害,看不懂
天空の幻像 发表于 2020-2-3 15:22
大佬厉害了
q510 发表于 2020-2-3 16:01
感谢分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2025-4-24 23:27

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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