吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 10129|回复: 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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
 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] 纯文本查看 复制代码
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, 2024-5-9 00:19

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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