首页
社区
课程
招聘
分析wx防撤回原理
2020-2-15 08:44 9413

分析wx防撤回原理

2020-2-15 08:44
9413

搜一搜网上的文章,很多以前的大佬都是通过hook掉 updateWithOnConflict函数,当type=1000时,判断出撤回信息的id,然后重新插入数据库,并修改“xx撤回一条信息”为“xx撤回信息失败”。

猜想这个过程可能分为三步

1.接收到某某发出的一条信息

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

3.插入文字“某某撤回了一条信息文字”


通过monitor方法回溯或者直接hook函数 updateWithOnConflict作为突破口,因为发送消息肯定要与数据库交互,而处理数据库信息很有可能用到这个函数,手动狗头

 
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(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



堆栈信息如下,这个下面分析撤回的堆栈信息时要用

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次数据库更新


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,调用相关函数重新插入数据库就能实现防撤回了,

类似的文章如下:

简书大佬的防撤回https://www.jianshu.com/p/fb16ea7b28bf

很明显,updateWithOnConflict必定有上层函数直接判断是否撤回,如果判断撤回,调用更下面的逻辑才会到updateWithOnConflict和delete类似函数完成撤回功能的删除和更新。如果我们hook上层函数,可以直接屏蔽掉撤回功能,而不需要先通过判断 msgType是否=1000判断是否需要撤回信息,如果要撤回,得到要撤回的msgId,再调用相关函数重新插入数据库

于是,我们再一次分析函数流程,撤回haha这句消息,打印updateWithOnConflict堆栈如下


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类,应该在更上层,

类比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,这是真正的大佬,给大佬打个广告http://www.gda.wiki:9090/blog_list0.php,虽然也不算完美,一大堆的寄存器变量,但是肯定比看smali好多了,我们把gda反编译的代码粘过来看

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是一个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时直接返回就可以屏蔽掉撤回了;粗略一看,这个函数还控制着添加联系人,更新包等功能,大家有兴趣可以具体跟一下。



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,我们仍然是倒着看

简单观察堆栈,第一步这4个update是必经之路,最底层是 updateWithOnConflict,上面都是这个update函数的层层封装,这里的关键点就是数据库名,也就是update的到底是哪个数据库,

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.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是获得路径的,这里贴一下数据库的打开与解密吧,与本文无关,权当延伸,很清晰的打开与解密流程

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
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 属性初始化在public C1520a Boo = new C1520a();于是我们跳转到类 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这个类里面的数据库赋值,至此,我们就搞清楚了数据库实例如何获得,贴一个函数大家简单看看:

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.a,storage这个类表示存储,这个a方法有很多重载,简单hook一下判断出a方法签名(JLcom/tencent/mm/storage/bi;)V,所以函数为mo10677a

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),这个函数,在这里hook会稍微复杂一点,主要是搞清楚参数流程,

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分为sysmsg和appmsg,这里我们主要看一下参数map的传递,撤回好像跑的是appmsg;

C27985cr crVar = aVar.foR;//1

String a = C2976aa.m5550a(crVar.yCk);//2 这应该就是个tostring类似功能的函数

int indexOf = a.indexOf("<sysmsg");//3

String substring = a.substring(indexOf);//4 居然是暴力切割字符串

Map J = C9020br.m13742J(substring, "sysmsg");//5

组合一下,Map J = 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存储着这个app是newsapp或readerapp或blogapp,还有外层的一个fucking判断:this fucking msg from mac weixin ,someone send msg to newsapp at mac weixin ,givp up,跟一下应该可以找到如何判断msg是mac系统的,顺便过掉

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这个类,其他没啥好说的

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,很简单,自己看看吧

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,仍然是注意一下参数传递,其实每次参数传递和变换,就是把外层协议层层剥离的过程,每一次消耗一些参数进行判断或其他动作,大家应该很容易看明白

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,明显是判断超时了怎么办,在上面就是一些系统调用了,到此为止了

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;
             }
         }
     }


最后,简单总结一下:

1.从com.tencent.mm.sdk.g.c.c$1$1.run开始 ,进入系统调用,开启loop消息循环,通过厂商自己重写的dispatchMessage和handleMessage处理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,有的直接继承参数,有的剥离外层在继承

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") 两个键值。这里也很适合hook获得这个map修改 newmsgid和 replacemsg完成防撤回

5.进入本文的关键函数com.tencent.mm.model.f.a(SourceFile:145)m34535a和com.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(Native Method)
com.tencent.wcdb.database.SQLiteDatabase.update(SourceFile:1726) ,更新数据库,完成功能



ps:中间有一些类和函数我没有仔细去看,可能会有些错误,但大致流程应该没啥问题,今天就开始上班了,时间比较急,中间整理的有些乱,大家见谅吧,干活去喽!













[培训]科锐软件逆向50期预科班报名即将截止,速来!!! 50期正式班报名火爆招生中!!!

最后于 2021-3-7 12:35 被挤蹭菌衣编辑 ,原因:
收藏
免费 1
打赏
分享
最新回复 (6)
雪    币: 2510
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_xghoecki 2020-2-15 10:25
2
0
感谢分享
雪    币: 483
活跃值: (682)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
RainFog 2020-2-15 16:15
3
0
感谢分享
雪    币: 6
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_eyarabkt 2020-2-17 11:37
4
0
感谢分享
雪    币: 1274
活跃值: (162)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
乌鸦卫队 2020-2-18 20:45
5
0
感谢感谢
雪    币: 3859
活跃值: (3823)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
cxbcxb 2020-3-1 20:00
6
0
谢谢啦!
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_izaexbcc 2020-3-6 12:05
7
0
感谢分享
游客
登录 | 注册 方可回帖
返回