android逆向奇技淫巧十七:android客户端自动x红包(一):代码原理分析
前面介绍了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视频号下载为例