14.原始碼閱讀(啟動一個沒有在AndroidManifest中註冊的Activity)

黑夜路口發表於2018-04-18

在上一篇部落格中已經分析了一部分如何繞過AndroidManifest檢查啟動一個未註冊的Activity,這次就來實現這個功能

分析一下總的實現流程:

啟動中有三個hook點,第一個就是Instrumentation中

int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);

ActivityManagerNative.getDefault()得到的是IActivtyMangager這個介面的實現類,在這裡使用動態代理,攔截到startActivity這個方法,當呼叫這個方法的時候,我們修改這個方法中的Intent引數,因為這個Intent中是包含我們要啟動的那個沒有註冊的Activity資訊的,直接使用肯定無法啟動,因為沒有註冊,所以我們換一個提前註冊好的傀儡Activity在這裡,只為繞過檢查

@Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            LogUtils.d(TAG, "method --> " + method.getName());
            if ("startActivity".equals(method.getName())) {
                //獲取原來的intent(IActivityManager中startActivity方法第三個引數就是intent)
                Intent originIntent = (Intent)args[2];
                //建立一個安全的intent(這個intent中包含一個在本地進行了註冊的Activity作為一個臨時替換).clazz是傀儡Activity的class檔案
                Intent safeIntent = new Intent(context,clazz);
                //將這個intent替換掉原來的intent
                args[2] = safeIntent;
                //將原來的intent繫結到這個intent中待launch activity的時候使用
                safeIntent.putExtra(EXTRA_ORIGIN_INTENT,originIntent);
            }
            //反射注入
            return method.invoke(object,args);
        }

第二個hook點
ActivityThread中,當要launch這個Activity的時候,通過Handler傳送訊息接收訊息在handleMessage回撥中啟動,這時候訊息傳遞過來的obj就是final ActivityClientRecord r = (ActivityClientRecord) msg.obj;這個物件儲存了一系列的要啟動activity的相關資訊,其中有個成員變數Intent intent;這個intent就是我們上邊替換後的intent,當然它裡邊也持有著最初的那個Intent,我們要做的就是將這裡的intent重新替換為最初的那個Intent,handleMessage時Handler中的mCallback成員回撥過來的,同樣在這裡使用動態代理攔截到handleMessage方法

@Override
        public boolean handleMessage(Message msg) {
            //沒發一個訊息都會走一次這個方法
            //ActivityThread中的內部類Handler中的LaunchActivity常量 = 100
            //public static final int LAUNCH_ACTIVITY = 100;
            if (msg.what == 100){
                handleLaunchActivity(msg);
            }
            return false;
        }

private void handleLaunchActivity(Message msg) {
        //看原始碼可以知道這個Object就是類ActivityClientRecord
        //final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
        Object record = msg.obj;
        //從中獲取intent
        try {
            Field intentField = record.getClass().getDeclaredField("intent");
            intentField.setAccessible(true);
            Intent safeIntent = (Intent) intentField.get(record);
            //從這個intent中獲取我們繫結的原本的intent
            Intent originIntent = safeIntent.getParcelableExtra(EXTRA_ORIGIN_INTENT);
            //將傀儡intent重新替換為原來的intent,開啟我們需要的未註冊的activity
            if (originIntent != null){
                intentField.set(record,originIntent);
            }
}

進行到這一步,如果我們要啟動的那個沒有註冊的activity是繼承自Activity的話,啟動已經沒有問題了,但是如果它是繼承自AppCompatActivity 的話,仍會有異常,具體原因已經在註釋中詳細說明了,這裡只列出第三個hook點,在NavUtils類的getParentActivityName方法中,可以先找到AppCompatDelegateImplV7中的onCreate方法,攔截IPackageManager的getActivityInfo方法,將引數componentName替換為傀儡的componentName

@Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // Log.e("TAG", "methodName = " + method.getName());
            if (method.getName().startsWith("getActivityInfo")) {
                ComponentName componentName = new ComponentName(context, clazz);
                //將getActivityInfo方法中的第一個引數componentName替換為一個可以監測到的也就是我們上邊用過的那個
                args[0] = componentName;
            }
            return method.invoke(mActivityManagerObject, args);
        }

使用方法

/**
 * Created by rzm on 2017/10/21.
 */

public class TestHookActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_hook_test);
        //TestHookRegisteredActivity是在AndroidManifest中註冊的一個傀儡Activity,是個空的,沒有實際功能
        //只是為了躲過檢查
        HookActivityUtil hookActivityUtil = new HookActivityUtil(this,TestHookRegisteredActivity.class);
        try {
            hookActivityUtil.hookStartActivity();
            hookActivityUtil.hookLaunchActivity();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void open(View view) {
        //TestHookUnRegisteredActivity是沒有註冊的Activity,這個是我們真正要啟動的Activity
        startActivity(new Intent(getApplicationContext(),TestHookUnRegisteredActivity.class));
    }
}

工具類:

package com.rzm.commonlibrary.general.hook;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Message;
import com.rzm.commonlibrary.utils.LogUtils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * Created by rzm on 2017/10/21.
 */

public class HookActivityUtil {

    public static final String EXTRA_ORIGIN_INTENT = "EXTRA_ORIGIN_INTENT";
    private static final String TAG = "HookActivityUtil";
    private final Context context;
    private final Class clazz;

    public HookActivityUtil(Context context,Class clazz) {
        this.context = context.getApplicationContext();
        this.clazz = clazz;
    }

    /**
     * 這個方法可以跳過activity啟動對AndroidManifest檔案的檢查,成功將檢查的過程替換成了一個已註冊的傀儡Activity,startActivity過程成功
     * 但是這一步進行完只能保證程式不會奔潰,但是啟動的activity確實那個傀儡,而不是我們的目標activity
     * 那麼下一步就是在launchActivity的時候重新將目標Activity也就是沒有註冊的那個Activity啟動起來,這個過程在hookLaunchActivity方法中
     * @throws Exception
     * ActivityManagerNative
     */
    public void hookStartActivity() throws Exception {
        Class<?> aClass = Class.forName("android.app.IActivityManager");

        //動態代理傳入需要被代理的介面,動態代理以介面為切入點,回撥中傳入的是這個介面實現類的一個例項,這兩個引數是進行動態代理的前提
        //從原始碼中可以找到這個實現類的例項是Singleton中的mInstance物件,所以接下來我們需要通過一系列手段獲取到這個例項

        //獲取ActivityManagerNative中的gDefault
        Class<?> amClass = Class.forName("android.app.ActivityManagerNative");
        Field gDefault = amClass.getDeclaredField("gDefault");
        gDefault.setAccessible(true);
        Object gDefaultObj = gDefault.get(null);//靜態傳null

        //獲取gDefault中的mInstance屬性
        Class<?> singleTonClass = Class.forName("android.util.Singleton");
        Field mInstance = singleTonClass.getDeclaredField("mInstance");
        mInstance.setAccessible(true);
        Object iamInstance = mInstance.get(gDefaultObj);

        Object o = Proxy.newProxyInstance(HookActivityUtil.class.getClassLoader(), new Class[]{aClass}, new StartActivityInvocationHandler(iamInstance));

        //反射將mInstance例項替換為我們得到的代理物件
        mInstance.set(gDefaultObj, o);
    }

    /**
     * startActivity後有一個檢查註冊檔案的過程,檢查通過之後就會啟動這個activity,啟動的過程是在ActivityThread中執行的,通過handler傳送訊息進行
     * 啟動等等的一系列處理,收到訊息後會在handler的callback中執行啟動的動作,我們可以在這裡進行hook,將之前的intent重新設定過來,以達到啟動一個
     * 未進行註冊的activity的目的
     * @throws Exception
     */
    public void hookLaunchActivity() throws Exception {
        //獲取ActivityThread例項
        Class<?> aClass = Class.forName("android.app.ActivityThread");
        //ActivityThread類中有一個sCurrentActivityThread變數就是他的例項
        Field aClassDeclaredField = aClass.getDeclaredField("sCurrentActivityThread");
        aClassDeclaredField.setAccessible(true);
        Object sCurrentActivityThread = aClassDeclaredField.get(null);

        //獲取ActivityThread中的Handler mH
        Field mhField = aClass.getDeclaredField("mH");
        mhField.setAccessible(true);
        Object mHandler = mhField.get(sCurrentActivityThread);

        //通過反射給handler設定callback
        Class<?> handlerClass = Class.forName("android.os.Handler");

        //Handler的handleMessage方法是通過Handler內部的mCallback呼叫的,mCallback也就是通常傳入的那個回撥
        //我們攔截到handleMessage這個方法,在這裡將假的intent重新替換回來
        Field mCallback = handlerClass.getDeclaredField("mCallback");
        mCallback.setAccessible(true);
        mCallback.set(mHandler,new HandlerCallBack());
    }

    class HandlerCallBack implements Handler.Callback {

        @Override
        public boolean handleMessage(Message msg) {
            //沒發一個訊息都會走一次這個方法
            //ActivityThread中的內部類Handler中的LaunchActivity常量 = 100
            //public static final int LAUNCH_ACTIVITY = 100;
            if (msg.what == 100){
                handleLaunchActivity(msg);
            }
            return false;
        }
    }

    private void handleLaunchActivity(Message msg) {
        //看原始碼可以知道這個Object就是類ActivityClientRecord
        //final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
        Object record = msg.obj;
        //從中獲取intent
        try {
            Field intentField = record.getClass().getDeclaredField("intent");
            intentField.setAccessible(true);
            Intent safeIntent = (Intent) intentField.get(record);
            //從這個intent中獲取我們繫結的原本的intent
            Intent originIntent = safeIntent.getParcelableExtra(EXTRA_ORIGIN_INTENT);
            //將傀儡intent重新替換為原來的intent,開啟我們需要的未註冊的activity
            if (originIntent != null){
                intentField.set(record,originIntent);
            }

            /**
             * 程式寫到這裡還是不完善的,目前只在被啟動的activity繼承自Activity的時候有效,如果繼承自
             * AppCompatActivity就會報錯,
             *
             * Caused by: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{com.app.rzm/com.app.rzm.test.TestHookUnRegisteredActivity}
             * at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:285)
             * at android.support.v7.app.AppCompatDelegateImplV9.onCreate(AppCompatDelegateImplV9.java:158)
             * at android.support.v7.app.AppCompatDelegateImplV14.onCreate(AppCompatDelegateImplV14.java:58)
             * at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:72)
             * at com.app.rzm.test.TestHookUnRegisteredActivity.onCreate(TestHookUnRegisteredActivity.java:12)
             * at android.app.Activity.performCreate(Activity.java:6366)
             * at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1126)
             * at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2661)
             * at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2779) 
             * at android.app.ActivityThread.-wrap11(ActivityThread.java) 
             * at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593) 
             * at android.os.Handler.dispatchMessage(Handler.java:111) 
             * at android.os.Looper.loop(Looper.java:207) 
             * at android.app.ActivityThread.main(ActivityThread.java:5979) 
             * at java.lang.reflect.Method.invoke(Native Method) 
             * at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:939) 
             * at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:800) 
             *
             * 找到AppCompatDelegateImplV9 onCreate方法這裡有一個NavUtils.getParentActivityName((Activity) mOriginalWindowCallback)
             *
             * 找到原始碼中的位置在NavUtils方法中
             *
             * Return the fully qualified class name of sourceActivity`s parent activity as specified by
             * a {@link #PARENT_ACTIVITY} &lt;meta-data&gt; element within the activity element in
             * the application`s manifest.
             *
             * @param sourceActivity Activity to fetch a parent class name for
             * @return The fully qualified class name of sourceActivity`s parent activity or null if
             *         it was not specified
             *
             * @Nullable
             *   public static String getParentActivityName(Activity sourceActivity) {
             *       try {
             *           return getParentActivityName(sourceActivity, sourceActivity.getComponentName());
             *       } catch (PackageManager.NameNotFoundException e) {
             *           // Component name of supplied activity does not exist...?
             *           throw new IllegalArgumentException(e);
             *       }
             *   }
             * Return the fully qualified class name of a source activity`s parent activity as specified by
             * a {@link #PARENT_ACTIVITY} &lt;meta-data&gt; element within the activity element in
             * the application`s manifest. The source activity is provided by componentName.
             *
             * @param context Context for looking up the activity component for the source activity
             * @param componentName ComponentName for the source Activity
             * @return The fully qualified class name of sourceActivity`s parent activity or null if
             *         it was not specified
             *
             *   @Nullable
             *  public static String getParentActivityName(Context context, ComponentName componentName)
             *   throws PackageManager.NameNotFoundException {
             *       PackageManager pm = context.getPackageManager();
             *       ActivityInfo info = pm.getActivityInfo(componentName, PackageManager.GET_META_DATA);
             *       String parentActivity = IMPL.getParentActivityName(context, info);
             *       return parentActivity;
             *   }
             *
             *   可以看到繼承自AppCompatActivity的話pm.getActivityInfo再次執行了,這個方法最初在startActivity的時候
             *   已經呼叫了一次,從儲存Activity的集合中根據全類名查詢這個Activity,如果找不到丟擲Activity沒有在AndroidManifest
             *   中註冊的異常資訊,這個操作其實和前邊是一樣的,會再次呼叫PackageManagerService的getActivityInfo方法,因為
             *   沒有註冊導致集合中沒有這個activity再次出錯
             *
             *   PackageManager pm = context.getPackageManager();
             *   錯誤就發生在這裡
             *   ActivityInfo info = pm.getActivityInfo(componentName, PackageManager.GET_META_DATA);
             */
            //錯誤發生在getActivityInfo方法中,所以我們可以再次進行動態代理在這個方法執行的時候繼續把傀儡扔過去讓他
            //取獲取activity info, 所以就要對IPackageManger進行代理,並且獲取到它的實現類的例項,這裡通過ActivityThread
            //中的getPackageManager方法去獲取

            // 相容AppCompatActivity報錯問題
            Class<?> forName = Class.forName("android.app.ActivityThread");
            Field field = forName.getDeclaredField("sCurrentActivityThread");
            field.setAccessible(true);
            Object activityThread = field.get(null);
            // 我自己執行一次那麼就會建立PackageManager,系統再獲取的時候就是下面的iPackageManager
            Method getPackageManager = activityThread.getClass().getDeclaredMethod("getPackageManager");
            Object iPackageManager = getPackageManager.invoke(activityThread);

            PackageManagerHandler handler = new PackageManagerHandler(iPackageManager);
            Class<?> iPackageManagerIntercept = Class.forName("android.content.pm.IPackageManager");
            Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                    new Class<?>[]{iPackageManagerIntercept}, handler);

            // 獲取 sPackageManager 屬性
            Field iPackageManagerField = activityThread.getClass().getDeclaredField("sPackageManager");
            iPackageManagerField.setAccessible(true);
            iPackageManagerField.set(activityThread, proxy);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    class PackageManagerHandler implements InvocationHandler {
        private Object mActivityManagerObject;

        public PackageManagerHandler(Object iActivityManagerObject) {
            this.mActivityManagerObject = iActivityManagerObject;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // Log.e("TAG", "methodName = " + method.getName());
            if (method.getName().startsWith("getActivityInfo")) {
                ComponentName componentName = new ComponentName(context, clazz);
                //將getActivityInfo方法中的第一個引數componentName替換為一個可以監測到的也就是我們上邊用過的那個
                args[0] = componentName;
            }
            return method.invoke(mActivityManagerObject, args);
        }
    }

    class StartActivityInvocationHandler implements InvocationHandler {

        private final Object object;

        public StartActivityInvocationHandler(Object instance) {
            this.object = instance;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            LogUtils.d(TAG, "method --> " + method.getName());
            if ("startActivity".equals(method.getName())) {
                //獲取原來的intent(IActivityManager中startActivity方法第三個引數就是intent)
                Intent originIntent = (Intent)args[2];
                //建立一個安全的intent(這個intent中包含一個在本地進行了註冊的Activity作為一個臨時替換)
                Intent safeIntent = new Intent(context,clazz);
                //將這個intent替換掉原來的intent
                args[2] = safeIntent;
                //將原來的intent繫結到這個intent中待launch activity的時候使用
                safeIntent.putExtra(EXTRA_ORIGIN_INTENT,originIntent);
            }
            return method.invoke(object,args);
        }
    }
}


相關文章