Java反射與hook混用反射某支付的方法

YunSoul發表於2018-08-27

反射某支付軟體apk的方法 思路:

1、可以先取得某支付軟體的classLoader,可以通過hook某支付軟體的必須方法(如:LauncherActivity的attachBaseContext方法)來取得某支付軟體classLoader;

2、取得某支付軟體的classLoader,則可以查詢某支付軟體dex中的所有方法和變數,就反射某支付軟體的方法和變數了;

注意:在activity中,反射生命週期中賦值的成員變數時,需要取得當前開啟的activity的物件,再在此基礎上反射物件中的成員變數。(activity類和普通類的反射區別)

比如反射PayeeQRSetMoneyActivity中的方法和變數,程式碼如下:

private void hookLaunch(final ClassLoader classLoader)
    {
        XposedHelpers.findAndHookMethod("com.***.***.quinox.LauncherActivity", classLoader,
                "attachBaseContext", Context.class, new XC_MethodHook()
                {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable
                    {
                        LogUtil.i("????", "start hook hookLaunch beforeHookedMethod...");
                        super.beforeHookedMethod(param);
                        doReflect(classLoader);
                    }

                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable
                    {
                        super.afterHookedMethod(param);
                    }
                });
    }

    private void doReflect(ClassLoader classLoader)
    {
        try
        {
            //反射成員變數g
            Class<?> aClass = classLoader.loadClass("com.***.***.payee.ui.PayeeQRSetMoneyActivity");
            Object instance = aClass.newInstance();
            Field field = aClass.getField("g");
            field.set(instance,"1000");
            LogUtil.i("????","filed value = "+field.get(instance)); //輸出為“filed value = 1000”

            //反射方法a
            Method method = aClass.getDeclaredMethod("a");
            method.invoke(instance);
            LogUtil.i("????","reflect method end...");
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }
複製程式碼

上述程式碼能反射成員變數g,並改變a的值;

但是反射方法a時,丟擲空指標異常,指向method.invoke(instance); 觀察a方法發原始碼:

final void a() {
        ConsultSetAmountReq v0 = new ConsultSetAmountReq();
        v0.amount = this.g;
        v0.desc = this.c.getUbbStr();
        v0.sessionId = this.h;
        new RpcRunner(new dk(this), new dj(this)).start(new Object[]{v0});
    }
複製程式碼

由於this.g能正常賦值,並且hook getUbbStr()方法的流程並未進入,因為可能是this.c空指標;最後通過反射成員變數c,確實取得null。

觀察原始碼發現this.c在onCreate方法中初始化,所以就想通過反射onCreate使this.c的初始化工作執行完成,但是忘記了activity的生命週期,得到的結果是成員變數c仍然為空。

private static void doReflectOnCreate(ClassLoader classLoader)
    {
        try
        {
            Class<?> aClass = classLoader.loadClass("com.***.***.payee.ui.PayeeQRSetMoneyActivity");
            Object instance = aClass.newInstance();

            Method onCreate = aClass.getDeclaredMethod("onCreate", Bundle.class);
            LogUtil.i("????","onCreate = "+onCreate);
            onCreate.invoke(instance, new Bundle());

            Field field = aClass.getField("c");
            LogUtil.i("????","filed value = "+field.get(instance));

            //反射方法a
            Method method = aClass.getDeclaredMethod("a");
            method.invoke(instance);
            LogUtil.i("????","reflect method end...");
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }
複製程式碼

onCreate.invoke(instance, new Bundle());這句話報空指標,最後聽說直接反射onCreate方法是不行的,因為除了onCreate方法,還有很多系統幫忙呼叫的方法,一一反射呼叫也是很大的工程,因為就放棄了。

所以當需要反射activity的生命週期方法時,最後是開啟activity讓系統幫忙呼叫。但是不能像上述那樣,使用classLoader直接loadClass,因為loadClass是重新載入一次PayeeQRSetMoneyActivity類,與當前開啟的頁面不處於同一個物件。因此得到的結果是:直接開啟activity可以取得已賦值的成員變數viewById的值,但是loadClass反射的成員變數viewById取得的結果為空。

因此在activity中,反射生命週期中賦值的成員變數時,需要取得當前開啟的activity的物件,再在此基礎上反射物件中的成員變數。

private static void getCInstance(final ClassLoader classLoader)//, final Object[] objects)
    {
        new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                try
                {
                    Thread.sleep(200);//睡眠是為了等待頁面開啟

                    //獲取activity物件,執行生碼
                    Activity activity = Util.getActivity();
                    if (activity != null)
                    {
                        Class<? extends Activity> aClass = activity.getClass();
                        //獲取C
                        Field field = aClass.getDeclaredField("c");
                        field.setAccessible(true);
                        Object cObj = field.get(activity);
                        LogUtil.i("????", "end getCInstance field c = " + cObj);
                        if (cObj != null)
                        {
                            Field gField = aClass.getDeclaredField("g");
                            gField.set(activity,"100");
                            LogUtil.i("????", "end setMoney = " + gField.get(activity));

                            Method method = aClass.getDeclaredMethod("a");
                            LogUtil.i("????", "invokeCreateQrCode method = " + method);
                            method.setAccessible(true);
                            method.invoke(activity);
                            LogUtil.i("????", "end invokeCreateQrCode");
                        }
                    }
                } catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
        }).start();
    }
    
    //獲取棧中,paused為FALSE的activity的物件,即沒有被onPause的頁面物件,即當前可視的
    public static Activity getActivity() {
        Class activityThreadClass = null;
        try {
            activityThreadClass = Class.forName("android.app.ActivityThread");
            Object activityThread = activityThreadClass.getMethod("currentActivityThread").invoke(null);
            Field activitiesField = activityThreadClass.getDeclaredField("mActivities");
            activitiesField.setAccessible(true);
            Map activities = (Map) activitiesField.get(activityThread);
            Collection values = activities.values();
            for (Object activityRecord : values) {
                Class activityRecordClass = activityRecord.getClass();
                Field pausedField = activityRecordClass.getDeclaredField("paused");
                pausedField.setAccessible(true);
                boolean aBoolean = pausedField.getBoolean(activityRecord);
                if (!aBoolean) {
                    Field activityField = activityRecordClass.getDeclaredField("activity");
                    activityField.setAccessible(true);
                    Activity activity = (Activity) activityField.get(activityRecord);
                    return activity;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
複製程式碼

此時獲取到的變數c是已經賦值後的,因此再反射呼叫a方法的時候,不會再報空指標。

最後發現並不需要hook某支付軟體取得某支付軟體的classload,只要當前可視activity的物件即可反射某支付軟體當前開啟頁面中的方法。系統提供了當前開啟的activity的物件,再獲得該物件的Class和classloader即可反射該activity的方法和變數。

某支付軟體PayeeQRSetMoneyActivity的原始碼:
package com.***.***.payee.ui;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import com.alipay.android.hackbyte.ClassVerifier;
import com.***.***.beehive.rpc.RpcRunner;
import com.***.***.beehive.util.KeyBoardUtil;
import com.***.***.common.logging.api.LoggerFactory;
import com.***.***.commonui.inputfomatter.APMoneyFormatter;
import com.***.***.commonui.widget.APButton;
import com.***.***.commonui.widget.APInputBox;
import com.***.***.commonui.widget.APTextView;
import com.***.***.framework.app.ui.BaseActivity;
import com.***.***.framework.service.common.RpcService;
import com.***.***.payee.R$id;
import com.***.***.payee.R$layout;
import com.***.***.payee.R$string;
import com.***.***.payee.util.Logger;
import com.***.***.payee.util.SpmHelper;
import com.alipay.transferprod.rpc.CollectMoneyRpc;
import com.alipay.transferprod.rpc.req.ConsultSetAmountReq;
import com.alipay.transferprod.rpc.result.ConsultSetAmountRes;

public class PayeeQRSetMoneyActivity extends BaseActivity {
    public static final Logger a;
    protected APInputBox b;
    protected APInputBox c;
    protected APTextView d;
    protected APButton e;
    CollectMoneyRpc f;
    String g;
    private String h;

    static {
        PayeeQRSetMoneyActivity.a = Logger.a(PayeeQRSetMoneyActivity.class);
    }

    public PayeeQRSetMoneyActivity() {
        super();
        this.g = "";
        if(Boolean.FALSE.booleanValue()) {
            ClassVerifier.class.toString();
        }
    }

    final void a() {
        ConsultSetAmountReq v0 = new ConsultSetAmountReq();
        v0.amount = this.g;
        v0.desc = this.c.getUbbStr();
        v0.sessionId = this.h;
        new RpcRunner(new dk(this), new dj(this)).start(new Object[]{v0});
    }

    protected final void a(ConsultSetAmountRes arg2) {
        this.runOnUiThread(new di(this, arg2));
    }

    protected void onCreate(Bundle arg5) {
        int v3 = 2;
        super.onCreate(arg5);
        this.f = this.mApp.getServiceByInterface(RpcService.class.getName()).getRpcProxy(CollectMoneyRpc.class);
        Intent v0 = this.getIntent();
        if(v0 != null) {
            try {
                this.h = v0.getStringExtra("sessionId");
            }
            catch(Exception v0_1) {
                LoggerFactory.getTraceLogger().warn(PayeeQRPayerPayResultActivity.class.getSimpleName(), ((Throwable)v0_1));
            }
        }

        this.setContentView(R$layout.payee_qr_set_money);
        this.b = this.findViewById(R$id.payee_QRmoneySetInput);
        this.c = this.findViewById(R$id.payee_QRmoneySetBeiZhuInput);
        this.d = this.findViewById(R$id.payee_QRAddBeiZhuLink);
        this.e = this.findViewById(R$id.payee_NextBtn);
        this.getWindow().setSoftInputMode(16);
        this.d.setOnClickListener(new df(this));
        this.e.setOnClickListener(new dg(this));
        this.c.setInputName(this.getString(R$string.payee_reason), v3);
        this.b.setInputName(this.getString(R$string.payee_money), v3);
        this.b.addTextChangedListener(new dh(this));
        this.b.setTextFormatter(new APMoneyFormatter());
        this.b.getEtContent().requestFocus();
        KeyBoardUtil.showSoftInput(((Context)this), this.b.getEtContent(), 0, 1);
    }

    protected void onPause() {
        super.onPause();
        SpmHelper.b("a87.b1481", this);
    }

    protected void onResume() {
        super.onResume();
        SpmHelper.a("a87.b1481", this);
    }
}
複製程式碼

#### YunSoul技術分享,掃碼關注微信公眾號##
    ——只要你學會了之前所不會的東西,只要今天的你強過了昨天的你,那你就一直是在進階的路上了。 Java反射與hook混用反射某支付的方法

相關文章