前面介紹了PC端hook關鍵函數得到xxxx客戶端消息,也導出了sqlite數據庫;事實上,xxxx官方的答復是xxxx不會存用戶的消息,所有的消息就只能存客戶端的sqlite數據庫,紅包消息也不例外;那么通過sqlite數據是不是可以判斷當前的消息是不是紅包了?com.tencent.wcdb.database.SQLiteDatabase下面有個insert方法,hook這個方法看看能不能得到紅包消息,js代碼如下:
function luckyMsgParse() { Java.perform(function () { var sqLite = Java.use('com.tencent.wcdb.database.SQLiteDatabase'); var Set = Java.use("java.util.Set"); var ContentValues = Java.use("android.content.ContentValues"); sqLite.insert.implementation = function(arg1,arg2,arg3){ var retval = this.insert(arg1,arg2,arg3);//先執行以前的方法,確保邏輯不會出錯 console.log("[insert]:"+arg1+"---"+arg2);//先打印前面兩個string參數 var values = Java.cast(arg3,ContentValues); var sets = Java.cast(values.keySet(),Set); var array = sets.toArray().toString().split(","); for(var i = 0;i<array.length;i++){ console.log("[insert]: key="+array[i] +"-----value:"+values.get(array[i])); } if (arg3.getAsInteger('type') == 436207665 && arg3.getAsInteger('status') == 3){ //setTimeout(luckyMoneyGet, 5000); //luckyMoneyGet(); } return retval; } }) }
得到如下消息:上述的insert方法果然也包括紅包消息;
[KIW AL10::com.tencent.mm]-> [insert]:LuckyMoneyDetailOpenRecord---send_id [insert]: key=send_id-----value:1000039801202107046167708300133 [insert]: key=open_count-----value:0 [insert]:message---msgId [insert]: key=bizClientMsgId-----value: [insert]: key=reserved-----value:~SEMI_XML~ [insert]: key=msgId-----value:57 [insert]: key=msgSvrId-----value:5838206542265258358 [insert]: key=talker-----value:wahaha [insert]: key=content-----value:<msg> <appmsg appid="" sdkver=""> <des><![CDATA[我給你發了一個紅包,趕緊去拆!]]></des> <url><![CDATA[https://wxapp.tenpay.com/mmpayhb/wxhb_personalreceive?showwxpaytitle=1&msgtype=1&channelid=1&sendid=1000039801202107046167708300133&ver=6&sign=062075120c92e1 0baca28b1ea93763d3ff7b3e7522ccbef75bccdbd4e457e1027ae9a3b36830c43fac0ec1c2fa95d47b563a79d725fb768d124f1ae22d5bd1ffce83b72177b8697224bdd85cee87bed5]]></url> <type><![CDATA[2001]]></type> <title><![CDATA[微信紅包]]></title> <thumburl><![CDATA[https://wx.gtimg.com/hongbao/1800/hb.png]]></thumburl> <wcpayinfo> <templateid><![CDATA[7a2a165d31da7fce6dd77e05c300028a]]></templateid> <url><![CDATA[https://wxapp.tenpay.com/mmpayhb/wxhb_personalreceive?showwxpaytitle=1&msgtype=1&channelid=1&sendid=1000039801202107046167708300133&ver=6&sign=062075 120c92e10baca28b1ea93763d3ff7b3e7522ccbef75bccdbd4e457e1027ae9a3b36830c43fac0ec1c2fa95d47b563a79d725fb768d124f1ae22d5bd1ffce83b72177b8697224bdd85cee87bed5]]></url> <iconurl><![CDATA[https://wx.gtimg.com/hongbao/1800/hb.png]]></iconurl> <receivertitle><![CDATA[恭喜發財,大吉大利]]></receivertitle> <sendertitle><![CDATA[恭喜發財,大吉大利]]></sendertitle> <scenetext><![CDATA[微信紅包]]></scenetext> <senderdes><![CDATA[查看紅包]]></senderdes> <receiverdes><![CDATA[領取紅包]]></receiverdes> <nativeurl><![CDATA[wxpay://c2cbizmessagehandler/hongbao/receivehongbao?msgtype=1&channelid=1&sendid=1000039801202107046167708300133&sendusername=wahaha&ver =6&sign=062075120c92e10baca28b1ea93763d3ff7b3e7522ccbef75bccdbd4e457e1027ae9a3b36830c43fac0ec1c2fa95d47b563a79d725fb768d124f1ae22d5bd1ffce83b72177b8697224bdd85cee87bed5]]></nativeurl> <sceneid><![CDATA[1002]]></sceneid> <innertype><![CDATA[0]]></innertype> <paymsgid><![CDATA[1000039801202107046167708300133]]></paymsgid> <scenetext>微信紅包</scenetext> <locallogoicon><![CDATA[c2c_hongbao_icon_cn]]></locallogoicon> <invalidtime><![CDATA[1625457178]]></invalidtime> <broaden /> </wcpayinfo> </appmsg> <fromusername><![CDATA[wahaha]]></fromusername> </msg> [insert]: key=flag-----value:0 [insert]: key=status-----value:3 [insert]: key=msgSeq-----value:736015725 [insert]: key=imgPath-----value:THUMBNAIL_DIRPATH://th_44a88a61f9d4fcd328322568fe1d597a [insert]: key=createTime-----value:1625370778000 [insert]: key=lvbuffer-----value:[B@be30b5 [insert]: key=isSend-----value:0 [insert]: key=type-----value:436207665 [insert]: key=bizChatId-----value:-1 [insert]: key=talkerId-----value:35 [insert]:AppMessage---msgId [insert]: key=appId-----value: [insert]: key=source-----value:null [insert]: key=description-----value:我給你發了一個紅包,趕緊去拆! [insert]: key=title-----value:微信紅包 [insert]: key=xml-----value:null [insert]: key=msgId-----value:57 [insert]: key=type-----value:2001 [KIW AL10::com.tencent.mm]-> [KIW AL10::com.tencent.mm]-> exit
既然這里能探測到紅包了,下一步直接調用onClick方法是不是就達到自動搶紅包的目的了?剛開始我就是這樣想的(后面才發現我想的太簡單了)!;
這里通過adb shell dumpsys activity top| grep ACTIVITY命令找紅包界面的top activity,發現有個類com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI很可疑:從名稱上看是接受紅包的UI界面(這里專門標注了NotHook,估計是專門防hook的,后面詳細說這個!);這個類里面有10多個內部匿名類,每個類都有onClick方法,到底那個onClick才是紅包“開”的那個onClick了?我也不知道,挨個去hook這些onClick太耗時了;仔細分析每個onClick,發現每個函數都調用了com.tencent.mm.hellhoundlib.a.a.a方法。這里暫時不分析該方法的功能;既然每個onClick都調用,說明很重要,那就用objection hook這個函數試試。點擊紅包的“開”后,結果如下:
com.tencent.mm on (HONOR: 6.0.1) [usb] # (agent) [672845] Called com.tencent.mm.hellhoundlib.a.a.a(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.Object, [Ljava.lang.Object;) (agent) [672845] Backtrace: com.tencent.mm.hellhoundlib.a.a.a(Native Method) com.tencent.mm.hellhoundlib.a.a(SourceFile:450) com.tencent.mm.hellhoundlib.a.a.b(SourceFile:527) com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI$15.onClick(SourceFile) android.view.View.performClick(View.java:5264) android.view.View$PerformClick.run(View.java:21297) android.os.Handler.handleCallback(Handler.java:743) android.os.Handler.dispatchMessage(Handler.java:95) android.os.Looper.loop(Looper.java:150) android.app.ActivityThread.main(ActivityThread.java:5621) java.lang.reflect.Method.invoke(Native Method) com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:794) com.android.internal.os.ZygoteInit.main(ZygoteInit.java:684) (agent) [672845] Arguments com.tencent.mm.hellhoundlib.a.a.a(com/tencent/mm/plugin/luckymoney/ui/LuckyMoneyNotHookReceiveUI$9, android/view/View$OnClickListener, onClick, (Landroid/view/View;)V, com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI$15@629f4b0, android.widget.Button{88aff29 VFED..C.. ...P.... 256,892-526,1162 #7f091f3f app:id/f4f}) (agent) [672845] Return Value: (none) (agent) [672845] Called com.tencent.mm.hellhoundlib.a.a.a(java.lang.Object, java.lang.String, java.lang.String, java.lang.String, java.lang.String) (agent) [672845] Backtrace: com.tencent.mm.hellhoundlib.a.a.a(Native Method) com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI$15.onClick(SourceFile:908) android.view.View.performClick(View.java:5264) android.view.View$PerformClick.run(View.java:21297) android.os.Handler.handleCallback(Handler.java:743) android.os.Handler.dispatchMessage(Handler.java:95) android.os.Looper.loop(Looper.java:150) android.app.ActivityThread.main(ActivityThread.java:5621) java.lang.reflect.Method.invoke(Native Method) com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:794) com.android.internal.os.ZygoteInit.main(ZygoteInit.java:684) (agent) [672845] Arguments com.tencent.mm.hellhoundlib.a.a.a(com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI$15@629f4b0, com/tencent/mm/plugin/luckymoney/ui/LuckyMoneyNotHookReceiveUI$9, android/view/View$OnClickListener, onClick, (Landroid/view/View;)V) (agent) [672845] Called com.tencent.mm.hellhoundlib.a.a.a(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.Object, java.lang.Object) (agent) [672845] Backtrace: com.tencent.mm.hellhoundlib.a.a.a(Native Method) com.tencent.mm.hellhoundlib.a.a(SourceFile:464) com.tencent.mm.hellhoundlib.a.a.a(SourceFile:538) com.tencent.mm.hellhoundlib.a.a.a(Native Method) com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI$15.onClick(SourceFile:908) android.view.View.performClick(View.java:5264) android.view.View$PerformClick.run(View.java:21297) android.os.Handler.handleCallback(Handler.java:743) android.os.Handler.dispatchMessage(Handler.java:95) android.os.Looper.loop(Looper.java:150) android.app.ActivityThread.main(ActivityThread.java:5621) java.lang.reflect.Method.invoke(Native Method) com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:794) com.android.internal.os.ZygoteInit.main(ZygoteInit.java:684)
從日志很容易發現:LuckyMoneyNotHookReceiveUI$15.onClick反復被調用好幾次,有重大作案嫌疑!那就直接調用這個方法唄!代碼如下:
Java.perform(function () { Java.choose("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI$15", { onMatch: function (instance) { console.log("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI$15.onClick before invoke onclick2!"); var view = Java.use('android.view.View').$new(); instance.onClick(view); console.log("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI$15.onClick after invoke onclick2!"); }, onComplete: function () { console.log("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI$15.onClick cpmplete invoke onclick2!");//這里打印了,說明沒匹配上,和下面的類找不到是一樣的 } }); })
結果傻眼了:com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI$15.onClick cpmplete invoke onclick2! 這個打印了出來!說明$15這個匿名類根本就沒找到!好吧,可能是方法沒用對,換個姿勢繼續嘗試,這次這樣寫hook代碼:通過classloader來遍歷LuckyMoneyNotHookReceiveUI$15這個類,如下:
Java.perform(function () { Java.enumerateClassLoadersSync().forEach(function (classloader) { try { var receive = classloader.findClass("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI$15"); var view = Java.use('android.view.View').$new(); console.log("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI$15.onClick before invoke onclick3!"); receive.onClick(view); console.log("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI$15.onClick after invoke onclick!3"); } catch (e) { console.log("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI$15.onClick exception:"+e);//類找不到 } }) })
結果更是傻眼,直接打印了如下的日志:這次更直接,爆出找不到類的錯誤!想了半天,原因可能是:我是從sqlite數據庫的insert函數開始hook的,而objection hook的時候是手動點擊紅包的結果,所以sqlite中insert消息時這個類可能確實還沒加載到內存,所以classloader都找不到!即使用了setTimeout(luckyMoneyGet, 15000)讓點擊紅包的函數延遲加載還是報找不到類的錯誤; 這么來看直接簡單粗暴的點擊onClick方法大概率是行不通的!這個類名里面的NotHook名稱看還真不是白取的┭┮﹏┭┮!
com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI$15.onClick before invoke onclick3! com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI$15.onClick exception:TypeError: not a function com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI$15.onClick exception:Error: java.lang.ClassNotFoundException: com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI$15 com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI$15.onClick exception:Error: java.lang.ClassNotFoundException: Didn't find class "com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI$15" on path: DexPathList[[zip file "/system/app/webview/webview.apk"],nativeLibraryDirectories=[/system/app/webview/lib/x86, /system/app/webview/webview.apk!/lib/x86, /system/lib, /vendor/lib, /system/lib, /vendor/lib]]
沒辦法,只能老老實實繼續逆向onClick方法,而不是偷懶式地簡單粗暴調用!下面這是LuckyMoneyNotHookReceiveUI$15.onClick的代碼:
public final void onClick(View view) { AppMethodBeat.i(65741); b bVar = new b(); bVar.bm(view); com.tencent.mm.hellhoundlib.a.a.b("com/tencent/mm/plugin/luckymoney/ui/LuckyMoneyNotHookReceiveUI$9", "android/view/View$OnClickListener", "onClick", "(Landroid/view/View;)V", this, bVar.axR()); g.af(com.tencent.mm.pluginsdk.wallet.b.class).he(10, 3); com.tencent.mm.plugin.report.service.h.CyF.a(11701, new Object[]{5, Integer.valueOf(LuckyMoneyNotHookReceiveUI.awY(LuckyMoneyNotHookReceiveUI.this.yXY.egY)), Integer.valueOf(LuckyMoneyNotHookReceiveUI.m5701m(LuckyMoneyNotHookReceiveUI.this)), 0, 2}); Log.i("MicroMsg.LuckyMoneyNotHookReceiveUI", "open btn click!"); LuckyMoneyNotHookReceiveUI.this.yRl.setClickable(false); m.vli.a(s.bh.UTO, s.bg.UTG, true); LuckyMoneyNotHookReceiveUI.this.aDF(""); com.tencent.mm.hellhoundlib.a.a.a(this, "com/tencent/mm/plugin/luckymoney/ui/LuckyMoneyNotHookReceiveUI$9", "android/view/View$OnClickListener", "onClick", "(Landroid/view/View;)V"); AppMethodBeat.o(65741); }
進入LuckyMoneyNotHookReceiveUI.this.aDF()方法后,發現有3個方法都在為同一個變量賦值:
if (C43670af.m67519rQ(this.zbZ)) { ayVar = new C43694az(this.yXY.msgType, this.yXY.channelId, this.yXY.yQE, this.yXY.egX, C43670af.efj(), C13715z.aUa(), stringExtra, "v1.0", this.yXY.yXR); } else if (this.gwE == 3) { ayVar = new C43660b(this.yXY.msgType, this.yXY.channelId, this.yXY.yQE, this.yXY.egX, C43670af.efj(), C13715z.aUa(), stringExtra, "v1.0", this.yXY.yXR, str2, getIntent().getStringExtra("key_live_id"), getIntent().getStringExtra("key_live_attach"), this.UXQ); } else { ayVar = new C43693ay(this.yXY.msgType, this.yXY.channelId, this.yXY.yQE, this.yXY.egX, C43670af.efj(), C13715z.aUa(), stringExtra, "v1.0", this.yXY.yXR, str2); }
分別進入這3個方法哈看,發現實現都大同小異,舉例如下:分別用各種字段填充hashmap結構體,其中包括很重要的timingIdentifier字段(也就是this.yYY.yXR參數),這里疑似要發請求包了;下面還有Log日志打印,內容是“NetSceneOpenLuckyMoney”,看着像是打開紅包的;不得不說:xxxx研發同學的心是真大,居然打印這么明顯的日志!
public C43660b(int i, int i2, String str, String str2, String str3, String str4, String str5, String str6, String str7, String str8, String str9, String str10, int i3) { AppMethodBeat.m16755i(258596); this.yQE = str; this.egX = str2; this.talker = str5; HashMap hashMap = new HashMap(); hashMap.put("msgType", String.valueOf(i)); hashMap.put("channelId", String.valueOf(i2)); hashMap.put("sendId", str); if (!Util.isNullOrNil(str2)) { hashMap.put("nativeUrl", URLEncoder.encode(str2)); } if (!Util.isNullOrNil(str3)) { hashMap.put("headImg", URLEncoder.encode(str3)); hashMap.put("nickName", URLEncoder.encode(Util.nullAsNil(str4))); } hashMap.put("ver", str6); hashMap.put("timingIdentifier", str7); hashMap.put("left_button_continue", str8); hashMap.put("liveid", str9); hashMap.put("liveattach", str10); if (i3 > 0) { hashMap.put("channellive_sender_flag", String.valueOf(i3)); } Log.m102527i("MicroMsg.NetSceneLiveOpenLuckyMoney", "NetSceneOpenLuckyMoney request"); setRequestData(hashMap); AppMethodBeat.m16756o(258596); }
回到onCreate方法,有個很大的分支如下: 最后面的if執行判斷條件:nativeurl(紅包內置的一個url)、紅包金額小於0、紅包時間超過1天了就執行另一個分支;正常情況下這個分支是可以忽略的,我們直接看另一個分支!
this.yUq = getIntent().getStringExtra("key_native_url"); this.zge = getIntent().getStringExtra("key_cropname"); this.gof = getIntent().getLongExtra("key_msgid", 0); this.qBv = getIntent().getIntExtra("key_material_flag", 0); this.zbZ = getIntent().getIntExtra("scene_id", 1002); this.mRa = getIntent().getStringExtra("key_username"); this.gwE = getIntent().getIntExtra("key_from", 0); this.UXO = getIntent().getStringExtra("key_live_id"); this.UXP = getIntent().getStringExtra("key_live_attach"); this.UYr = (ResultReceiver) getIntent().getParcelableExtra("key_open_result_receiver"); this.UXQ = getIntent().getIntExtra("key_live_anchor_type", 0); Log.m102527i("MicroMsg.LuckyMoneyNotHookReceiveUI", "nativeUrl= " + Util.nullAsNil(this.yUq)); initView(); Uri parse = Uri.parse(Util.nullAsNil(this.yUq)); try { this.yUc = parse.getQueryParameter("sendid"); } catch (Exception e) { } this.yUs = C59398t.fQE().aVA(this.yUq); if (this.yUs == null || this.yUs.field_receiveAmount <= 0 || Util.milliSecondsToNow(this.yUs.field_receiveTime) >= Util.MILLSECONDS_OF_DAY){
}
另一個分支的完整代碼:根據接受到的紅包消息對intent做各種字段的填充,然后調用startActivity生成開紅包的界面!
Log.m102528i("MicroMsg.LuckyMoneyNotHookReceiveUI", "use cache this item %s %s", Long.valueOf(this.yUs.field_receiveTime), Util.nullAsNil(this.yUq)); Intent intent = new Intent(); intent.setClass(getContext(), LuckyMoneyBeforeDetailUI.class); intent.putExtra("key_native_url", this.yUs.field_mNativeUrl); intent.putExtra("key_sendid", this.yUc); intent.putExtra("key_anim_slide", true); intent.putExtra("key_lucky_money_can_received", true); intent.putExtra("key_username", getIntent().getStringExtra("key_username")); intent.putExtra("scene_id", this.zbZ); intent.putExtra("key_msgid", this.gof); m68103aH(intent); C11767a bl = new C11767a().mo32323bl(intent); C11762a.m25042a(this, bl.axQ(), "com/tencent/mm/plugin/luckymoney/ui/LuckyMoneyNotHookReceiveUI", "onCreate", "(Landroid/os/Bundle;)V", "Undefined", "startActivity", "(Landroid/content/Intent;)V"); startActivity((Intent) bl.mo32324pG(0)); C11762a.m25041a(this, "com/tencent/mm/plugin/luckymoney/ui/LuckyMoneyNotHookReceiveUI", "onCreate", "(Landroid/os/Bundle;)V", "Undefined", "startActivity", "(Landroid/content/Intent;)V"); finish(); AppMethodBeat.m16756o(65743);
所以這里總結的思路:要想自動搶紅包,在hook了sqlite數據庫的insert方法后,如果是紅包消息,會先構造intent,然后startActivity,生成開紅包的界面!用戶一旦點擊“開”,立即調用onClick方法:這里又開始構造hashMap,收集各種紅包的請求參數,然后發給服務器請求開紅包;這里最難的就是構造紅包的請求包了:LuckyMoneyNotHookReceiveUI.this.aDF()中3個if分支構造請求包,最特殊的字段就是timingIdentifier了!這個字段的值是怎得來的了?繼續追蹤其調用關系:
上述幾個引用挨個查看后,發現只有第一個onGYNetEnd函數在讀取timingIdentifier的值,整個代碼如下:
public void onGYNetEnd(int p0,String p1,JSONObject p2){ JSONObject jObject; long vl = 0; AppMethodBeat.i(65310); Object[] objectArray = new Object[3]; objectArray[0] = Integer.valueOf(p0); objectArray[1] = p1; objectArray[2] = p2.toString(); Log.i("MicroMsg.NetSceneReceiveLuckyMoney", "errCode: %s, errMsg: %s ,json:%s", objectArray); this.yXN = p2.optString("sendNick"); this.yVe = p2.optString("sendHeadImg"); this.egZ = p2.optInt("hbStatus"); this.eha = p2.optInt("receiveStatus"); this.yVb = p2.optString("statusMess"); this.yPK = p2.optString("wishing"); this.yVm = p2.optInt("isSender"); this.yXO = p2.optLong("sceneAmount"); this.yXP = p2.optLong("sceneRecTimeStamp"); this.egY = p2.optInt("hbType"); this.yVt = p2.optString("watermark"); this.yRQ = p2.optString("externMess"); this.yVy = p2.optString("sendUserName"); if (Util.isNullOrNil(this.yXN)) { this.UXB = true; } if (!Util.isNullOrNil(this.yVy) && Util.isNullOrNil(this.yXN)) { this.yXN = g.af(b.class).getDisplayName(this.yVy); } bg bba = ac.ba(p2.optJSONObject("operationTail")); this.yVs = bba; this.yXr = p2.optInt("scenePicSwitch"); if ((jObject = p2.optJSONObject("agree_duty")) != null) { this.yWY = jObject.optString("agreed_flag", "-1"); this.yWZ = jObject.optString("title", ""); this.yXa = jObject.optString("service_protocol_wording", ""); this.yXb = jObject.optString("service_protocol_url", ""); this.yXc = jObject.optString("button_wording", ""); this.yXd = jObject.optLong("delay_expired_time", vl); } if (this.yXd-vl > 0) { g.aAi(); g.aAh().azQ().set(ar$a.NXr, Long.valueOf((System.currentTimeMillis()+(this.yXd*1000)))); } Log.i("MicroMsg.NetSceneReceiveLuckyMoney", "scenePicSwitch:".append(this.yXr).toString()); this.yXQ = p2.optInt("preStrainFlag", 1); Log.i("MicroMsg.NetSceneReceiveLuckyMoney", "preStrainFlag:".append(this.yXQ).toString()); this.yXD = p2.optInt("showYearExpression"); objectArray = new Object[1]; objectArray[0] = Integer.valueOf(this.yXD); Log.i("MicroMsg.NetSceneReceiveLuckyMoney", "showYearExpression:%s", objectArray); this.yUt = p2.optInt("showRecNormalExpression"); objectArray = new Object[1]; objectArray[0] = Integer.valueOf(this.yUt); Log.i("MicroMsg.NetSceneReceiveLuckyMoney", "showRecNormalExpression:%s", objectArray); g.aAi(); g.aAh().azQ().set(ar$a.NXg, Integer.valueOf(this.yXQ)); this.yXR = p2.optString("timingIdentifier"); this.ivQ = p2.optString("effectResource"); this.yXh = p2.optString("expression_md5"); this.yXi = p2.optInt("expression_type"); this.UXx = p2.optInt("not_show_avatar", 0); objectArray = new Object[2]; objectArray[0] = this.yXh; objectArray[1] = Integer.valueOf(this.yXi); Log.i("MicroMsg.NetSceneReceiveLuckyMoney", "expressionmd5:%s expressiontype:%s", objectArray); this.yXS = bi.bc(p2.optJSONObject("showSourceRec")); this.efk(); AppMethodBeat.o(65310); return; }
從代碼看,所有的關鍵字段都保存在JSONObject p2里面了!hook看看函數的調用棧,查查究竟是哪個函數給onGYNetEnd傳的JSONObject參數,探索timingIdentifier字段的起源!用objection hook后,結果如下:里面確實包含了timingIdentifier字段;經過多個紅包hook后的文本比對,發現第三個JSONObject參數只有sendId和timingIdentifier不同,其他都一樣!這么來看,后續我們做自動搶紅包脫機協議時需要考慮動態獲取這兩個字段了,其他字段都可以直接寫死!
com.tencent.mm on (HONOR: 6.0.1) [usb] # (agent) [927971] Called com.tencent.mm.plugin.luckymoney.model.bd.onGYNetEnd(int, java.lang.String, org.json.JSONObject) (agent) [927971] Backtrace: com.tencent.mm.plugin.luckymoney.model.bd.onGYNetEnd(Native Method) com.tencent.mm.plugin.luckymoney.model.ah.onGYNetEnd(SourceFile:170) com.tencent.mm.wallet_core.c.w.onGYNetEnd(SourceFile:37) com.tencent.mm.ak.w$2.run(SourceFile:124) android.os.Handler.handleCallback(Handler.java:743) android.os.Handler.dispatchMessage(Handler.java:95) com.tencent.mm.sdk.platformtools.MMHandler$2.dispatchMessage(SourceFile:350) android.os.Looper.loop(Looper.java:150) android.os.HandlerThread.run(HandlerThread.java:61) (agent) [927971] Arguments com.tencent.mm.plugin.luckymoney.model.bd.onGYNetEnd((none), ok, {"retcode":0,"retmsg":"ok","sendId":"1000039901202107106318211465184","wishing":"恭喜發 財,大吉大利","isSender":0,"receiveStatus":0,"hbStatus":2,"statusMess":"給你發了一個紅包","hbType":0,"watermark":"","scenePicSwitch":1,"preStrainFlag":1,"agree_duty":{"title":"","service_protocol_wording":"","service_protocol_url":"","button_wording":"","delay_expired_time":0,"agreed_flag":1},"sendUserName":"wahaha","timingIdentifier":"958F45D1560813371631E5DB53AE520B","showYearExpression":1,"expression_md5":"","showRecNormalExpression":1})
由於這里的timingIdentifier也是參數傳遞進來的,並不是自己生成的,所以繼續追溯上一層:com.tencent.mm.plugin.luckymoney.model.ah.onGYNetEnd,發現也是第5個參數傳進來的!
繼續追溯 com.tencent.mm.wallet_core.c.w.onGYNetEnd,發現關鍵參數也是傳進來的!
繼續往上追溯:發現包含timingIdentifier是構造對象傳進來的! w.f方法返回的是com.tencent.mm.network.s對象
發現這里有引用:
進入該方法,里面有個post方法,疑似發送請求:
post請求,hook看看都傳了啥參數:結果很失望,除了p4外其他參數都是null;也就是說new w$2的參數只有第一個this有值,其他的都是null! 從這里調用棧任然能看到com.tencent.mm.ak.w$2,說明這是最關鍵的類了;
com.tencent.mm on (HONOR: 6.0.1) [usb] # (agent) [459220] Called com.tencent.mm.ak.w.a(int, int, int, java.lang.String, com.tencent.mm.network.t, [B) (agent) [459220] Backtrace: com.tencent.mm.ak.w.a(Native Method) com.tencent.mm.network.n$a.onTransact(SourceFile:71) android.os.Binder.execTransact(Binder.java:453) (agent) [459220] Arguments com.tencent.mm.ak.w.a((none), (none), (none), (none), com.tencent.mm.ak.y@9d70429, (none)) (agent) [459220] Return Value: (none) (agent) [459220] Called com.tencent.mm.ak.w.a(com.tencent.mm.ak.w) (agent) [459220] Backtrace: com.tencent.mm.ak.w.a(Native Method) com.tencent.mm.ak.w$2.run(SourceFile:96) android.os.Handler.handleCallback(Handler.java:743) android.os.Handler.dispatchMessage(Handler.java:95) com.tencent.mm.sdk.platformtools.MMHandler$2.dispatchMessage(SourceFile:350) android.os.Looper.loop(Looper.java:150) android.os.HandlerThread.run(HandlerThread.java:61) (agent) [459220] Arguments com.tencent.mm.ak.w.a(com.tencent.mm.ak.w@48773ae) (agent) [459220] Return Value: (none)
回到com.tencent.mm.ak.w$2的run方法,這里面調用的onGYNetEnd的第5個參數是w.f(this.iMS)的返回值,也就是com.tencent.mm.network.s對象,如下:從成員函數看,getReqObj和getRespObj最可疑!這兩個函數會不會是發送驗證新信、得到服務器返回的timingIdentifier的函數了?
package com.tencent.mm.network.s; import java.lang.Object; import com.tencent.mm.protocal.l$d; import com.tencent.mm.protocal.l$e; import java.lang.String; public interface s extends Object // class@00119a { public boolean getIsLongPolling(); public boolean getIsUserCmd(); public int getLongPollingTimeout(); public int getNewExtFlags(); public int getOptions(); public l$d getReqObj(); public l$e getRespObj(); public int getTimeOut(); public byte[] getTransHeader(); public int getType(); public String getUri(); public boolean isSingleSession(); public boolean keepAlive(); public void setConnectionInfo(String p0); }
我們先看看getReqObj函數的返回值是l$d對象,進入看看: 果然有大量屬性的字段;從rsa、ecdh、sessionkey、verify、ClientVersion等關鍵字眼看,貌似已經深入到mmtls協議了;
public class l$d extends Object // class@002e16 { private boolean bShortSupport; private long bufferSize; private b$a cgiVerifyKeys; private long ecdhEngine; private int iClientVersion; private int iSceneStatus; private int iUin; private l$a mReqPackControl; private byte[] passKey; private int routeInfo; private ac rsaInfo; private String sDeviceID; private String sDeviceType; private byte[] sessionKey; private boolean useAxSession; private boolean useECDH; private static final String TAG; public void l$d(){ Object(); AppMethodBeat.i(133097); this.bShortSupport = true; this.useECDH = false; this.useAxSession = false; this.bufferSize = 0; this.iUin = 0; this.iClientVersion = 0; byte[] obyteArray = new byte[0]; this.sessionKey = obyteArray; this.sDeviceType = ""; this.sDeviceID = ""; this.iSceneStatus = 0; this.rsaInfo = new ac("", "", 0); this.routeInfo = 0; this.ecdhEngine = 0; AppMethodBeat.o(133097); }
其中有個成員變量:private l$a mReqPackControl; 目測包括了很多請求包的信息,進入l$a后發現是個接口類,找到接口的實現:從打印的日志看,已經是mmtls加解密相關了,這里暫時不深入!
final class q$a$1 extends Object implements l$a // class@004240 { final l$d KzR; final q$a KzS; void q$a$1(q$a p0,l$d p1){ this.KzS = p0; this.KzR = p1; Object(); } public final boolean a(PByteArray p0,int p1,byte[] p2,byte[] p3,int p4){ byte[][] ba; Object[] objectArray; Object[] objectArray1; byte[] bBuf; Object[] objectArray2; AppMethodBeat.i(152467); int vi = this.KzR; long vl = (long)this.KzR.getUin(); long vl1 = (CrashReportFactory.hasDebuger() && !vl)? d.KyN: vl; ac aRsaInfo = this.KzR.getRsaInfo(); if (p1 == 722) { Log.e("MicroMsg.MMEncryptCheckResUpdate", "MMEncryptCheckResUpdate reqToBuf rsaReqData"); ajl kzQ = this.KzR.KzQ; if ((ba = x.a(vl1, kzQ.LsW, kzQ.LsX)) == null) { AppMethodBeat.o(152467); }else if(MMProtocalJni.packHybrid(p0, p2, this.KzR.getDeviceID(), (int)vl1, vi.getFuncId(), aRsaInfo.ver, ba[0], ba[1], aRsaInfo.KAw.getBytes(), aRsaInfo.KAx.getBytes(), this.KzR.getPassKey(), p4, this.KzR.getRouteInfo())){ objectArray = new Object[2]; objectArray[0] = Integer.valueOf(p0.value.length); objectArray[1] = Integer.valueOf(p4); Log.d("MicroMsg.MMEncryptCheckResUpdate", "IRemoteReqDelegate reqToBuf packHybrid using protobuf ok, len:%d, flag:%d", objectArray); AppMethodBeat.o(152467); }else { label_0168 : AppMethodBeat.o(152467); }
回到LuckyMoneyNotHookReceiveUI.this.aDF()這個函數:里面有3個分支都用到了timingIdentifier(也就是下面紅框框的字段)。以上那個分支流程並未找到這個字段的生成代碼,這里只能換個思路繼續嘗試!
要想找到yXY.yXR的值,就要找到這個變量在哪賦值的!層層深入,發現yXR是com.tencent.mm.plugin.luckymoney.model.bd的成員變量,發現並沒有初始化這個成員變量的地方,而是直接調用到了 onGYNetEnd(int p0,String p1,JSONObject p2)函數來初始化this.yXY.yXR變量,這個函數上面已經分析過了!重新回到 com.tencent.mm.plugin.luckymoney.model.ah.onGYNetEnd,JSONObject最終是又sVar參數生成的,而這個參數是com.tencent.mm.network.s類,本身是個interface(前面也分析過了),並沒有成員變量,自然也找不到在哪初始化的!
不得不說jadx的人性化:檢測到了com.tencent.mm.network.s是接口,所有這里把對象強制轉換了一下,也就是說實際使用的對象是c1182d類(注意jadx這里改了一下類名),這個類的成員變量是iLL,繼續再往下找到iLR是com.tencent.mm.bw.a類的對象,這個對象又被強制轉成了com.tencent.mm.protocal.protobuf.cbz類,里面的MhT變量是SKBuiltinBuffer_t類,這里明顯是在發送前做最后的序列化工作;
從上面的代碼看,最關鍵的對象就是iLR了;這個對象的生成如下:
/* renamed from: com.tencent.mm.ak.d$b */ public static final class C1184b extends C8091l.C8095d implements C8091l.C8093b { public int cmdId; private int funcId; public C1436a iLR; private boolean needHeader; public C1184b(C1436a aVar, int i, int i2, boolean z, int i3) { AppMethodBeat.m2882i(132302); this.iLR = aVar; this.funcId = i; this.cmdId = i2; this.needHeader = z; setRouteInfo(i3); AppMethodBeat.m2883o(132302); }
/* renamed from: com.tencent.mm.ak.d$c */ public static final class C1185c extends C8091l.C8096e implements C8091l.C8094c { public int cmdId; public C1436a iLR = null; private boolean needHeader; public C1185c(C1436a aVar, int i, boolean z) { this.iLR = aVar; this.cmdId = i; this.needHeader = z; } @Override // com.tencent.mm.protocal.C8091l.C8094c public final int fromProtoBuf(byte[] bArr) { AppMethodBeat.m2882i(132304); this.iLR = this.iLR.parseFrom(bArr); if (!(this.iLR instanceof dyp)) { C8091l.m10518a(this, ((dpc) this.iLR).getBaseResponse()); int i = ((dpc) this.iLR).getBaseResponse().Ret; AppMethodBeat.m2883o(132304); return i; } int ret = ((dyp) this.iLR).getRet(); AppMethodBeat.m2883o(132304); return ret; }
這兩個構造函數分別被同com.tencent.mm.ak.d類的d函數調用:
public final class d extends o // class@000b26 { private int funcId; public d$b iLK; public d$c iLL; private boolean iLM; private boolean longPolling; private int longPollingTimeout; private int newExtFlag; public int option; private int timeout; byte[] transferHeader; private String uri; private void d(a p0,a p1,String p2,int p3,int p4,int p5,boolean p6,int p7,int p8,boolean p9,int p10,boolean p11){ super(); AppMethodBeat.i(197060); this.iLK = null; this.iLL = null; this.option = 0; boolean vb = (p6 && p0 instanceof dop)? true: false; this.iLK = new d$b(p0, p3, p4, vb, p8); this.iLL = new d$c(p1, p5, p6); this.uri = p2; this.funcId = p3; this.timeout = p7; this.longPolling = p9; this.longPollingTimeout = p10; this.newExtFlag = 0; this.transferHeader = null; this.iLM = p11; AppMethodBeat.o(197060); return; } void d(a p0,a p1,String p2,int p3,int p4,int p5,boolean p6,int p7,int p8,boolean p9,int p10,boolean p11,byte p12){ super(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11); }
這個構造函數調用的時候p1(是com.tencent.mm.bw.a類的對象)有沒有傳入timingIdentifier關鍵信息了? 我們后續hook看看!
說明:
1、xxxx android 客戶端版本:8.0.1
2、嘗試用method profiling跟蹤函數執行,試了好幾次都失敗:DDMS每次都卡死,直觀感覺是xxxx用了大量的AppMethodBeat插樁導致的;
3、https://blog.mythsman.com/post/60911a810527a03be7e558f6/ Frida爬蟲分析流程——以xxxx視頻號下載為例