Android黑科技:如何啟動未註冊的Activity

xiangzhihong 發表於 2022-01-29
Android

一、背景

如果有人問你,未在配置檔案中註冊的Activity可以啟動嗎。可能你一開始會回答不行,但是細細思考,你會發現,使用Android Hook等技術啟動未註冊的Activity也是可以的,這也是Android Hook 外掛化技術原理的基礎。

使用Android Hook 技術啟動未註冊的Activity,需要了解Java的反射機制Android App啟動流程非常熟悉。

下面,我們從兩點來講解Android Hook 技術啟動未註冊的Activity:

  • 通過對Instrumentation進行Hook
  • 通過對AMN進行Hook

二、 對startActivity方法進行Hook

通過查閱startActivity的原始碼,我們可以看到startActivity最終都會走到startActivityFoResult()方法中,原始碼如下:

public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
    if(this.mParent == null) {
        ActivityResult ar = this.mInstrumentation.execStartActivity(this, this.mMainThread.getApplicationThread(), this.mToken, this, intent, requestCode, options);
        if(ar != null) {
            this.mMainThread.sendActivityResult(this.mToken, this.mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData());
        }

        if(requestCode >= 0) {
            this.mStartedActivity = true;
        }
    } else if(options != null) {
        this.mParent.startActivityFromChild(this, intent, requestCode, options);
    } else {
        this.mParent.startActivityFromChild(this, intent, requestCode);
    }

}

接下來,我們再看一下mInstrumentation.execStartActivity()方法。

public Instrumentation.ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {
    IApplicationThread whoThread = (IApplicationThread)contextThread;
    if(this.mActivityMonitors != null) {
        Object e = this.mSync;
        synchronized(this.mSync) {
            int N = this.mActivityMonitors.size();

            for(int i = 0; i < N; ++i) {
                Instrumentation.ActivityMonitor am = (Instrumentation.ActivityMonitor)this.mActivityMonitors.get(i);
                if(am.match(who, (Activity)null, intent)) {
                    ++am.mHits;
                    if(am.isBlocking()) {
                        return requestCode >= 0?am.getResult():null;
                    }
                    break;
                }
            }
        }
    }

    try {
        intent.setAllowFds(false);
        intent.migrateExtraStreamToClipData();
        int var16 = ActivityManagerNative.getDefault().startActivity(whoThread, intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null?target.mEmbeddedID:null, requestCode, 0, (String)null, (ParcelFileDescriptor)null, options);
        checkStartActivityResult(var16, intent);
    } catch (RemoteException var14) {
        ;
    }

    return null;
}

而execStartActivity()方法最終又會走到checkStartActivityResult()方法。所以,如果我們想要對startActivity方法進行Hook麼,那麼就需要在checkStartActivityResult()方法之前進行Hook。

對mInstrumentation進行Hook

接下來,我們使用一個簡單的例子:列印日誌來說明如果使用mInstrumentation進行Hook

首先,開啟Activity.class類,在裡面我們可以Activity.class類中定義了私有變數Instrumentation。

private Instrumentation mInstrumentation;

我們要做的就是修改這個私有變數的值,在執行execStartActivity()方法前列印一行日誌。首先,我們通過反射來獲取這一私有變數。

Instrumentation instrumentation = (Instrumentation) Reflex.getFieldObject(Activity.class,MainActivity.this,"mInstrumentation");

然後,將這個Instrumentation替換成我們自己的Instrumentation,所以下面我們新建MyInstrumentation繼承自Instrumentation,並且MyInstrumentation的execStartActivity方法不變。

public class MyInstrumentation extends Instrumentation {
    private Instrumentation instrumentation;

    public MyInstrumentation(Instrumentation instrumentation) {
        this.instrumentation = instrumentation;
    }

    public  ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {


        Log.d("MyInstrumentation","Instrumentation Hook11111");
        Class[] classes = {Context.class,IBinder.class,IBinder.class,Activity.class,Intent.class,int.class, Bundle.class};
        Object[] objects = {who,contextThread,token,target,intent,requestCode,options};
        Log.d("MyInstrumentation","Instrumentation Hook22222");
        return (ActivityResult) ReflexUtil.invokeInstanceMethod(instrumentation,"execStartActivity",classes,objects);

    }

}

熟悉Java反射的同學都知道,我們可以使用Class.forName(name)來獲取類名,也可以使用getDeclaredMethod來獲取類的引數。為了方便使用,我們對這些常用的反射進行了封裝。

public class ReflexUtil {
    /**
     * 獲取無參建構函式
     * @param className
     * @return
     */
    public static Object createObject(String className) {
        Class[] pareTyples = new Class[]{};
        Object[] pareVaules = new Object[]{};

        try {
            Class r = Class.forName(className);
            return createObject(r, pareTyples, pareVaules);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 獲取無參構造方法
     * @param clazz
     * @return
     */
    public static Object createObject(Class clazz) {
        Class[] pareTyple = new Class[]{};
        Object[] pareVaules = new Object[]{};

        return createObject(clazz, pareTyple, pareVaules);
    }

    /**
     * 獲取一個引數的建構函式  已知className
     *
     * @param className
     * @param pareTyple
     * @param pareVaule
     * @return
     */
    public static Object createObject(String className, Class pareTyple, Object pareVaule) {

        Class[] pareTyples = new Class[]{pareTyple};
        Object[] pareVaules = new Object[]{pareVaule};

        try {
            Class r = Class.forName(className);
            return createObject(r, pareTyples, pareVaules);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        return null;
    }


    /**
     * 獲取單個引數的構造方法 已知類
     *
     * @param clazz
     * @param pareTyple
     * @param pareVaule
     * @return
     */
    public static Object createObject(Class clazz, Class pareTyple, Object pareVaule) {
        Class[] pareTyples = new Class[]{pareTyple};
        Object[] pareVaules = new Object[]{pareVaule};

        return createObject(clazz, pareTyples, pareVaules);
    }

    /**
     * 獲取多個引數的構造方法 已知className
     * @param className
     * @param pareTyples
     * @param pareVaules
     * @return
     */
    public static Object createObject(String className, Class[] pareTyples, Object[] pareVaules) {
        try {
            Class r = Class.forName(className);
            return createObject(r, pareTyples, pareVaules);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        return null;
    }


    /**
     * 獲取構造方法
     *
     * @param clazz
     * @param pareTyples
     * @param pareVaules
     * @return
     */
    public static Object createObject(Class clazz, Class[] pareTyples, Object[] pareVaules) {
        try {
            Constructor ctor = clazz.getDeclaredConstructor(pareTyples);
            ctor.setAccessible(true);
            return ctor.newInstance(pareVaules);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }


    /**
     * 獲取多個引數的方法
     * @param obj
     * @param methodName
     * @param pareTyples
     * @param pareVaules
     * @return
     */
    public static Object invokeInstanceMethod(Object obj, String methodName, Class[] pareTyples, Object[] pareVaules) {
        if (obj == null) {
            return null;
        }

        try {
            //呼叫一個private方法 //在指定類中獲取指定的方法
            Method method = obj.getClass().getDeclaredMethod(methodName, pareTyples);
            method.setAccessible(true);
            return method.invoke(obj, pareVaules);

        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 獲取一個引數的方法
     * @param obj
     * @param methodName
     * @param pareTyple
     * @param pareVaule
     * @return
     */
    public static Object invokeInstanceMethod(Object obj, String methodName, Class pareTyple, Object pareVaule) {
        Class[] pareTyples = {pareTyple};
        Object[] pareVaules = {pareVaule};

        return invokeInstanceMethod(obj, methodName, pareTyples, pareVaules);
    }

    /**
     * 獲取無參方法
     * @param obj
     * @param methodName
     * @return
     */
    public static Object invokeInstanceMethod(Object obj, String methodName) {
        Class[] pareTyples = new Class[]{};
        Object[] pareVaules = new Object[]{};

        return invokeInstanceMethod(obj, methodName, pareTyples, pareVaules);
    }


    /**
     * 無參靜態方法
     * @param className
     * @param method_name
     * @return
     */
    public static Object invokeStaticMethod(String className, String method_name) {
        Class[] pareTyples = new Class[]{};
        Object[] pareVaules = new Object[]{};

        return invokeStaticMethod(className, method_name, pareTyples, pareVaules);
    }

    /**
     * 獲取一個引數的靜態方法
     * @param className
     * @param method_name
     * @param pareTyple
     * @param pareVaule
     * @return
     */
    public static Object invokeStaticMethod(String className, String method_name, Class pareTyple, Object pareVaule) {
        Class[] pareTyples = new Class[]{pareTyple};
        Object[] pareVaules = new Object[]{pareVaule};

        return invokeStaticMethod(className, method_name, pareTyples, pareVaules);
    }

    /**
     * 獲取多個引數的靜態方法
     * @param className
     * @param method_name
     * @param pareTyples
     * @param pareVaules
     * @return
     */
    public static Object invokeStaticMethod(String className, String method_name, Class[] pareTyples, Object[] pareVaules) {
        try {
            Class obj_class = Class.forName(className);
            return invokeStaticMethod(obj_class, method_name, pareTyples, pareVaules);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 無參靜態方法
     * @param method_name
     * @return
     */
    public static Object invokeStaticMethod(Class clazz, String method_name) {
        Class[] pareTyples = new Class[]{};
        Object[] pareVaules = new Object[]{};

        return invokeStaticMethod(clazz, method_name, pareTyples, pareVaules);
    }

    /**
     * 一個引數靜態方法
     * @param clazz
     * @param method_name
     * @param classType
     * @param pareVaule
     * @return
     */
    public static Object invokeStaticMethod(Class clazz, String method_name, Class classType, Object pareVaule) {
        Class[] classTypes = new Class[]{classType};
        Object[] pareVaules = new Object[]{pareVaule};

        return invokeStaticMethod(clazz, method_name, classTypes, pareVaules);
    }

    /**
     * 多個引數的靜態方法
     * @param clazz
     * @param method_name
     * @param pareTyples
     * @param pareVaules
     * @return
     */
    public static Object invokeStaticMethod(Class clazz, String method_name, Class[] pareTyples, Object[] pareVaules) {
        try {
            Method method = clazz.getDeclaredMethod(method_name, pareTyples);
            method.setAccessible(true);
            return method.invoke(null, pareVaules);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }


    public static Object getFieldObject(String className, Object obj, String filedName) {
        try {
            Class obj_class = Class.forName(className);
            return getFieldObject(obj_class, obj, filedName);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static Object getFieldObject(Class clazz, Object obj, String filedName) {
        try {
            Field field = clazz.getDeclaredField(filedName);
            field.setAccessible(true);
            return field.get(obj);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }


    public static void setFieldObject(Class clazz, Object obj, String filedName, Object filedVaule) {
        try {
            Field field = clazz.getDeclaredField(filedName);
            field.setAccessible(true);
            field.set(obj, filedVaule);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void setFieldObject(String className, Object obj, String filedName, Object filedVaule) {
        try {
            Class obj_class = Class.forName(className);
            setFieldObject(obj_class, obj, filedName, filedVaule);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }


    public static Object getStaticFieldObject(String className, String filedName) {
        return getFieldObject(className, null, filedName);
    }

    public static Object getStaticFieldObject(Class clazz, String filedName) {
        return getFieldObject(clazz, null, filedName);
    }

    public static void setStaticFieldObject(String classname, String filedName, Object filedVaule) {
        setFieldObject(classname, null, filedName, filedVaule);
    }

    public static void setStaticFieldObject(Class clazz, String filedName, Object filedVaule) {
        setFieldObject(clazz, null, filedName, filedVaule);
    }

可以看到,在MyInstrumentation類中,我們直接反射execStartActivity方法來和預設的方法保持一致。

(ActivityResult) Reflex.invokeInstanceMethod(instrumentation,"execStartActivity",classes,objects)

然後,再使用我們自定義的MyInstrumentation替換原來的Instrumentation即可。完整程式碼如下:

Instrumentation instrumentation = (Instrumentation) ReflexUtil.getFieldObject(Activity.class,this,"mInstrumentation");
MyInstrumentation instrumentation1 = new MyInstrumentation(instrumentation);
ReflexUtil.setFieldObject(Activity.class,this,"mInstrumentation",instrumentation1);

2.2 對AMN進行Hook

如果大家去看execStartActivity()方法的原始碼,就可以看得到,execStartActivity()方法最終會走到ActivityManagerNative.getDefault().startActivity()方法。

try {
    intent.setAllowFds(false);
    intent.migrateExtraStreamToClipData();
    int var16 = ActivityManagerNative.getDefault().startActivity(whoThread, intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null?target.mEmbeddedID:null, requestCode, 0, (String)null, (ParcelFileDescriptor)null, options);
    checkStartActivityResult(var16, intent);
} catch (RemoteException var14) {
    ;
}

繼續看ActivityManagerNative的getDefault()方法。

public static IActivityManager getDefault() {
    return (IActivityManager)gDefault.get();
}

public final T get() {
    synchronized(this) {
        if(this.mInstance == null) {
            this.mInstance = this.create();
        }

        return this.mInstance;
    }
}

可以看出IActivityManager是一個介面,gDefault.get()返回的是一個泛型,如果直接使用反射是無法入手的,所以我們這裡要用動態代理方案。

首先,我們定義一個AmsHookHelperUtils類,在AmsHookHelperUtils類中處理反射程式碼。

public class AMNInvocationHandler implements InvocationHandler {
    private String actionName = "startActivity";

    private Object target;

    public AMNInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals(actionName)) {
            Log.d("AMNInvocationHandler", "I Am AMN Hook");
            return method.invoke(target, args);
        }
        return method.invoke(target, args);
    }

}

所有的代理類都要實現InvocationHandler介面,在invoke方法中method.invoke(target,args);表示的就是執行被代理物件所對應的方法。

然後,我們將IActivityManager介面中gDefault欄位替換為我們的代理類,如下。

ReflexUtil.setFieldObject("android.util.Singleton",gDefault,"mInstance",proxy);

我們定義一個AmsHookHelperUtil類,然後新增一個hook方法,裡面使用代理的方式進行Hook。

public class AmsHookHelperUtil {
    public static void hookAmn() throws ClassNotFoundException {
        Object gDefault = ReflexUtil.getStaticFieldObject("android.app.ActivityManagerNative","gDefault");
        Object mInstance = ReflexUtil.getFieldObject("android.util.Singleton",gDefault,"mInstance");

        Class<?> classInterface = Class.forName("android.app.IActivityManager");
        Object proxy = Proxy.newProxyInstance(classInterface.getClassLoader(),
                new Class<?>[]{classInterface},new AMNInvocationHandler(mInstance));
        ReflexUtil.setFieldObject("android.util.Singleton",gDefault,"mInstance",proxy);
    }
}

三、如何啟動一個未註冊的Activity

如何啟動一個未註冊的Activity,首先我們瞭解Activity的啟動流程,如果還不瞭解Activity啟動流程的,可以參考:Android Activity啟動流程分析

假設,現在MainActivity,Main2Activity,Main3Activity,其中Main3Activity未註冊,我們在MainActivity中啟動Main3Activity,當啟動Main3Activity的時候,AMS會在配置檔案中檢查,是否有Main3Activity的配置資訊如果不存在則報錯,存在則啟動Main3Activity,這是我們已經知道的常規流程。

所以,如果要啟動未註冊的Activity,那麼我們可以將要啟動的Activity在傳送給AMS之前,替換未已經註冊Activity Main2Activity,這樣AMS就可以檢驗通過,當AMS要啟動目標Activity的時候再將Main2Activity替換為真正要啟動的Activity即可,也是很多熱修復空間的的Hook的原理。

我們按照上面邏輯先對startActivity方法進行Hook,這裡採用對AMN Hook的方式。和上述程式碼一樣,不一樣的地方在於mInstance的代理類不同。首先, 新建一個AMNInvocationHanlder物件同樣繼承自InvocationHandler,只攔截startActivity方法。

if (method.getName().equals(actionName)){}

在這裡我們要做的就是將要啟動的Main3Activity替換為Main2Activity,這樣能繞過AMS的檢驗,首先我們從目標方法中取出目標Activity。

Intent intent;
int index = 0;
for (int i = 0;i<args.length;i++){
    if (args[i] instanceof Intent){
        index = i;
        break;
    }
}

你可能會問,怎麼知道args中一定有intent類的引數。因為Java反射的invoke方法中最終會執行下面的程式碼:

return method.invoke(target,args);

而Android的startActivity()方法的原始碼如下。

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

所以說,args中肯定有個intent型別的引數,獲取真實目標Activity之後,我們就可以獲取目標的包名。

intent = (Intent) args[index];
String packageName = intent.getComponent().getPackageName();

接下來,我們新建一個Intent ,然後將intent設定為Main2Activity的替換者。

Intent newIntent = new Intent();
ComponentName componentName = new ComponentName(packageName,Main2Activity.class.getName());
newIntent.setComponent(componentName);

args[index] = newIntent;

這樣目標Activity就成功替換了Main2Activity,不過這個替換者還要將原本的目標攜帶過去,等待真正開啟的時候再替換回來,否則就真的啟動這個替換者了。

newIntent.putExtra(AmsHookHelperUtils.TUREINTENT,intent);

startActivity(new Intent(this,Main3Activity.class));

接下來,我們要做的就是,如何將冒充者再重新替換為目標者。我們可以使用ActivityThread通過mH發訊息給AMS實現替換。

synchronized(this) {
    Message msg = Message.obtain();
    msg.what = what;
    msg.obj = obj;
    msg.arg1 = arg1;
    msg.arg2 = arg2;
    this.mH.sendMessage(msg);
}

然後,AMS收到訊息後進行處理。

public void handleMessage(Message msg) {
    ActivityThread.ActivityClientRecord data;
    switch(msg.what) {
    case 100:
        Trace.traceBegin(64L, "activityStart");
        data = (ActivityThread.ActivityClientRecord)msg.obj;
        data.packageInfo = ActivityThread.this.getPackageInfoNoCheck(data.activityInfo.applicationInfo, data.compatInfo);
        ActivityThread.this.handleLaunchActivity(data, (Intent)null);
        Trace.traceEnd(64L);

mH是Handler型別的訊息處理類,所以sendMessage方法會呼叫callback。新建hookActivityThread方法,首先我們獲取當前的ActivityThread物件,然後獲取物件的mH物件,將mH替換為我們的自己自定義的MyCallback。

Object currentActivityThread = Reflex.getStaticFieldObject("android.app.ActivityThread", "sCurrentActivityThread");

Handler mH = (Handler) Reflex.getFieldObject(currentActivityThread, "mH");
ReflexUtil.setFieldObject(Handler.class, mH, "mCallback", new MyCallback(mH));

自定義MyCallback需要處理Handler.Callback介面,然後處理handleMessage方法。

@Override
public boolean handleMessage(Message msg) {

    switch (msg.what) {
      
        case 100:
            handleLaunchActivity(msg);
            break;
        default:
            break;

    }

    mBase.handleMessage(msg);
    return true;
}

然後,獲取傳遞過來的目標物件,從目標物件中取出攜帶過來的真實物件,並將intent修改為真實目標物件的資訊,這樣就可以啟動真實的目標Activity。

Object obj = msg.obj;
Intent intent = (Intent) ReflexUtil.getFieldObject(obj, "intent");

Intent targetIntent = intent.getParcelableExtra(AmsHookHelperUtils.TUREINTENT);
intent.setComponent(targetIntent.getComponent());

以下是MyCallbackt的完整程式碼:

public class MyCallback implements Handler.Callback {

    Handler mBase;

    public MyCallback(Handler base) {
        mBase = base;
    }

    @Override
    public boolean handleMessage(Message msg) {

        switch (msg.what) {
            case 100:
                handleLaunchActivity(msg);
                break;
            default:
                break;
        }

        mBase.handleMessage(msg);
        return true;
    }

    private void handleLaunchActivity(Message msg) {
        Object obj = msg.obj;
        Intent intent = (Intent) ReflexUtil.getFieldObject(obj, "intent");
        Intent targetIntent = intent.getParcelableExtra(AmsHookHelperUtils.TUREINTENT);
        intent.setComponent(targetIntent.getComponent());
    }

}

然後,再啟動未註冊的Main3Activity就可以成功啟動了,是不是很簡單。