搜一搜网上的文章,很多以前的大佬都是通过hook掉 updateWithOnConflict函数,当type=1000时,判断出撤回信息的id,然后重新插入数据库,并修改“xx撤回一条信息”为“xx撤回信息失败”。
猜想这个过程可能分为三步
1.接收到某某发出的一条信息
2.接收到某某撤回了一条信息,删除或者替换这条信息
3.插入文字“某某撤回了一条信息文字”
通过monitor方法回溯或者直接hook函数 updateWithOnConflict作为突破口,因为发送消息肯定要与数据库交互,而处理数据库信息很有可能用到这个函数,手动狗头
我们先正常发一条消息,查看hook结果
函数原型updateWithOnConflict(String table, ContentValues values, String whereClause, String[] whereArgs, int conflictAlgorithm)
向测试手机发送haha,拼接数据库语句为
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
堆栈信息如下,这个下面分析撤回的堆栈信息时要用
下面我们撤回这条信息,发现updateWithOnConflict这个函数被调用了3次,也就是说一次撤回包含着3次数据库更新
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,放过来对比一下,content从haha变成"baba" 撤回了一条消息
a2:msgType=10000 flag=1579779746000 digestUser= digest="baba" 撤回了一条消息
isSend=0 hasTrunc=1 unReadCount=0 conversationTime=1579779746000 content="baba" 撤回了一条消息 username=wxid_dajiadebaba status=3
a22: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
第一句update message这个表,还附带 msgId,这个 msgId应该就是待撤回的msg的id,猜测这一句的功能就是通过 msgId删除msg,替换为"baba" 撤回了一条消息。
第二句就是更新界面了。
第三句,字面意思就是更新 rconversation未读信息的计数,跟防撤回关系不大。
其实到了这里,我们已经可以通过判断 msgType是否=1000判断是否需要撤回信息,
如果要撤回,先得到要撤回的msgId,调用相关函数重新插入数据库就能实现防撤回了,
类似的文章如下:
简书大佬的防撤回4c1K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2B7K9h3q4F1M7$3S2#2i4K6u0W2j5$3!0E0i4K6u0r3M7q4)9J5c8X3k6T1x3e0k6W2j5e0N6T1x3U0S2T1k6R3`.`.
很明显,updateWithOnConflict必定有上层函数直接判断是否撤回,如果判断撤回,调用更下面的逻辑才会到updateWithOnConflict和delete类似函数完成撤回功能的删除和更新。如果我们hook上层函数,可以直接屏蔽掉撤回功能,而不需要先通过判断
msgType是否=1000判断是否需要撤回信息,如果要撤回,得到要撤回的msgId,再调用相关函数重新插入数据库
于是,我们再一次分析函数流程,撤回haha这句消息,打印updateWithOnConflict堆栈如下
这里先点出重点函数吧,后面再具体分析流程,懒得看同学的就看到这里结束吧,
关键函数在com.tencent.mm.model.f类里面,为什么关注这个model呢,因为正常接受信息没有走这个类,还有com.tencent.mm.plugin.messenger.foundation类,应该在更上层,
类比django的mvc框架,猜想model也是封装了和数据库交互的上层,
1.com.tencent.mm.model.f.a(SourceFile:145) m34535a;
2.com.tencent.mm.model.f.a(SourceFile:352) mo7343a;
函数mo7343a调用了m34535a,这里我们直接看mo7343a,南极公司对jadx做了小动作,导致反编译失败,具体原因估计还是变量太多,但我们还有gjden
大佬的神器gda,这是真正的大佬,给大佬打个广告4b4K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6h3N6V1j5g2)9J5k6i4N6A6K9$3W2Q4x3@1p5&6x3o6V1H3i4K6u0r3j5X3I4G2k6#2)9#2k6X3I4A6M7%4b7H3i4K6u0W2M7r3S2H3i4@1g2r3i4@1u0o6i4K6S2o6i4@1f1^5i4K6V1&6i4@1u0p5i4@1f1%4i4K6R3@1i4@1t1$3i4@1f1@1i4@1t1&6i4K6W2r3i4@1f1@1i4@1t1^5i4K6S2p5i4@1f1%4i4@1q4q4i4K6V1%4i4@1f1#2i4@1q4q4i4K6S2o6i4@1f1%4i4@1u0q4i4K6S2q4i4@1g2r3i4@1u0o6i4K6S2o6i4@1f1@1i4@1t1^5i4K6R3H3i4@1f1#2i4@1p5@1i4@1p5%4i4@1f1#2i4@1p5H3i4K6R3$3i4@1f1%4i4K6W2m8i4K6R3@1i4@1f1#2i4@1q4r3i4K6R3@1i4@1f1#2i4@1q4p5i4K6V1^5i4@1f1#2i4K6V1&6i4@1p5^5i4@1f1#2i4K6S2r3i4K6V1^5i4@1f1&6i4K6R3%4i4K6S2r3i4@1g2r3i4@1u0o6i4K6S2o6i4@1f1@1i4@1u0p5i4K6R3$3i4@1f1$3i4K6V1^5i4@1q4r3i4@1f1^5i4K6R3J5i4@1q4r3i4@1f1#2i4@1q4q4i4K6W2m8i4@1f1$3i4@1q4r3i4K6V1@1i4@1f1%4i4K6W2o6i4K6S2n7M7$3#2S2L8r3W2Q4c8e0g2Q4b7e0g2Q4b7V1c8Q4c8e0g2Q4b7e0c8Q4z5f1q4Q4c8e0c8Q4b7V1q4Q4z5o6k6Q4c8f1k6Q4b7V1y4Q4z5p5y4Q4c8e0k6Q4z5o6S2Q4z5e0q4Q4c8e0c8Q4b7V1u0Q4b7f1y4Q4c8e0k6Q4z5p5q4Q4z5p5q4Y4k6r3q4Q4c8e0g2Q4z5p5k6Q4z5p5c8Q4c8e0N6Q4b7V1y4Q4z5e0k6Q4c8e0S2Q4b7f1k6Q4z5e0q4Q4c8e0N6Q4z5f1q4Q4z5o6c8Q4c8e0c8Q4b7V1u0Q4b7e0y4Q4c8e0N6Q4b7e0m8Q4z5o6q4Q4c8e0N6Q4b7U0u0Q4z5e0S2Q4c8e0S2Q4b7V1k6Q4z5o6N6Q4c8e0k6Q4z5f1c8Q4b7e0g2Q4c8e0N6Q4z5f1y4Q4z5p5t1`.
我们一眼就看到了这一句p0.equals("revokemsg"),revokemsg不就是撤回消息吗,很明显,p0=revokemsg时,跳转进入撤回的逻辑,
p1是一个Map,v5 = p1.get(".sysmsg.revokemsg.newmsgid"); v6 = p1.get(".sysmsg.revokemsg.replacemsg");存储着msgid和replacemsg,
而这句话实现了具体功能 f.a(c.ajB().ak(p1.get(".sysmsg.revokemsg.session"),
bo.getLong(v5, 0)), p2, v6, "MicroMsg.BigBallSysCmdMsgConsumer");
我们直接hook这个函数,当p0=revokemsg时直接返回就可以屏蔽掉撤回了;粗略一看,这个函数还控制着添加联系人,更新包等功能,大家有兴趣可以具体跟一下。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2021-3-7 12:35
被挤蹭菌衣编辑
,原因: