首页
论坛
专栏
课程

[原创]分析wx防撤回原理

2020-2-15 08:44 1404

[原创]分析wx防撤回原理

2020-2-15 08:44
1404

菜鸟原先在52发了,再发一遍和大家一起学习。

搜一搜网上的文章,很多以前的大佬都是通过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:中间有一些类和函数我没有仔细去看,可能会有些错误,但大致流程应该没啥问题,今天就开始上班了,时间比较急,中间整理的有些乱,大家见谅吧,干活去喽!














看雪线上公开课第01期《安全编码之SQL注入》,周日下午14:00,提前报名免费参与!

最新回复 (4)
mb_xghoecki 2020-2-15 10:25
2
0
感谢分享
未来永劫 2020-2-15 16:15
3
0
感谢分享
mb_eyarabkt 6天前
4
0
感谢分享
mb_angsudft 4天前
5
0
感谢感谢
游客
登录 | 注册 方可回帖
返回