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视频号下载为例

posted @ 2021-07-17 22:41  第七子007  阅读(2063)  评论(0编辑  收藏  举报