0
点赞
收藏
分享

微信扫一扫

手把手讲解 Android Hook-实现无清单启动Activity

前言

学到老活到老,路漫漫其修远兮。与众君共勉 !


引子

鸣谢


正文大纲


正文

提示:本文所有源码索引图,都基于SDK 28 -android9.0系统.


1.整体思路

Activity启动流程的hook的Demo里,最后实现的效果是,每次跳转Activity,都能看到这个日志:

那么,我们既然侦测到了startActivity这个方法的调用,那么自然就可以拿到里面的实参,比如,Intent
Intent是跳转意图,从哪里来,跳到哪里去的信息,都包含在Intent里面.
而,manifest Activity的检测,也是要根据Intent里面的信息来的.

所以,要骗过系统,要假装我们跳的Activity是已经注册过的,那么只需要将Intent里面的信息换成 已经在manifest中注册的某个Activity就可以了(这里可能就有人像抬杠了,你怎么知道manifest里面一定有注册Activity....如果一个Activity都没有,你的app是怎么启动的呢,至少得有一个LauncherActivity吧 - -!).


2.源码索引

下图大致画出了:从 Activity.startActivity动作开始,到最终 跳转动作的最终执行者 全过程.

下面开始看源码,从Activity.startActivity开始:

这里开始分支:if(mParent==null),但是两个分支最终执行如下:

很显然,两者都是同样的调用过程:

Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity(....);
mMainThread.sendActivityResult(...);

先进入第一句Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity看看,既然是合法性校验,且看他是如何校验的。

再进入第二句mMainThread.sendActivityResult看真正的跳转动作是如何执行的:
ps:这里其实有个诀窍,既然我们的终极目标是要骗过系统的Activity Intent检测,那么,跟着Intent这个变量,就不会偏离方向.

既然intent被封装到了ClientTransaction,交给了mAppThread,那么继续:

final H mH = new H();
class H extends Handler {
        ...
        public static final int EXECUTE_TRANSACTION = 159;

        String codeToString(int code) {
            if (DEBUG_MESSAGES) {
                switch (code) {
                    case EXECUTE_TRANSACTION: return "EXECUTE_TRANSACTION";
                    case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY";
                }
            }
            return Integer.toString(code);
        }
        public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case EXECUTE_TRANSACTION:
                    final ClientTransaction transaction = (ClientTransaction) msg.obj;
                    mTransactionExecutor.execute(transaction);
                    if (isSystem()) {
                        // Client transactions inside system process are recycled on the client side
                        // instead of ClientLifecycleManager to avoid being cleared before this
                        // message is handled.
                        transaction.recycle();
                    }
                    // TODO(lifecycler): Recycle locally scheduled transactions.
                    break;
                    ...
            }
            Object obj = msg.obj;
            if (obj instanceof SomeArgs) {
                ((SomeArgs) obj).recycle();
            }
            if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
        }
    }

很明显,他就是一个Handler的普通子类,定义了主线程ActivityThread中可能发生的各种事件。
PS: 这里,我留下了case EXECUTE_TRANSACTION:分支,是因为,之前ClientTransactionHandler 抽象类里面,sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);,就是用的这个 EXECUTE_TRANSACTION常量。
终于找到了startActivity的最终执行代码!

final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);

Ok,就到这里了.(事实上,我本来还想往下追查,Intent被封装到ClientTransaction之后,又被得到了什么样的处理,最后发现居然查到了一个源码中都不存在的类,我表示看不懂了,就到这里吧,不影响我们hook)


3.hook核心代码

还记得我们的整体思路么?

说通俗一点就是,
第一,伪造一个Intent,骗过Activity Manifest检测。
第二,真正要跳转之前,把原始的Intent还原回去.

开始撸代码,大量反射代码即将到来,注释应该很详尽了,特别注意看反射代码要对照源代码来看,不然很容易走神

    /**
     * 这里对AMS进行hook
     *
     * @param context
     */
    private static void hookAMS(Context context) {
        try {
            Class<?> ActivityManagerClz;
            final Object IActivityManagerObj;//这个就是AMS实例
            Method getServiceMethod;
            Field IActivityManagerSingletonField;
            if (ifSdkOverIncluding26()) {//26,27,28的ams获取方式是通过ActivityManager.getService()
                ActivityManagerClz = Class.forName("android.app.ActivityManager");
                getServiceMethod = ActivityManagerClz.getDeclaredMethod("getService");
                IActivityManagerSingletonField = ActivityManagerClz.getDeclaredField("IActivityManagerSingleton");//单例类成员的名字也不一样
            } else {//25往下,是ActivityManagerNative.getDefault()
                ActivityManagerClz = Class.forName("android.app.ActivityManagerNative");
                getServiceMethod = ActivityManagerClz.getDeclaredMethod("getDefault");
                IActivityManagerSingletonField = ActivityManagerClz.getDeclaredField("gDefault");//单例类成员的名字也不一样
            }
            IActivityManagerObj = getServiceMethod.invoke(null);//OK,已经取得这个系统自己的AMS实例

            // 2.现在创建我们的AMS实例
            // 由于IActivityManager是一个接口,那么其实我们可以使用Proxy类来进行代理对象的创建
            // 结果被摆了一道,IActivityManager这玩意居然还是个AIDL,动态生成的类,编译器还不认识这个类,怎么办?反射咯
            Class<?> IActivityManagerClz = Class.forName("android.app.IActivityManager");

            // 构建代理类需要两个东西用于创建伪装的Intent
            String packageName = Util.getPMName(context);
            String clz = Util.getHostClzName(context, packageName);
            Object proxyIActivityManager =
                    Proxy.newProxyInstance(
                            Thread.currentThread().getContextClassLoader(),
                            new Class[]{IActivityManagerClz},
                            new ProxyInvocation(IActivityManagerObj, packageName, clz));

            //3.拿到AMS实例,然后用代理的AMS换掉真正的AMS,代理的AMS则是用 假的Intent骗过了 activity manifest检测.
            //偷梁换柱
            IActivityManagerSingletonField.setAccessible(true);
            Object IActivityManagerSingletonObj = IActivityManagerSingletonField.get(null);
            Class<?> SingletonClz = Class.forName("android.util.Singleton");//反射创建一个Singleton的class
            Field mInstanceField = SingletonClz.getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);
            mInstanceField.set(IActivityManagerSingletonObj, proxyIActivityManager);

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

    private static final String ORI_INTENT_TAG = "origin_intent";
    /**
     * 把InvocationHandler的实现类提取出来,因为这里包含了核心技术逻辑,最好独立,方便维护
     */
    private static class ProxyInvocation implements InvocationHandler {

        Object amsObj;
        String packageName;//这两个String是用来构建Intent的ComponentName的
        String clz;

        public ProxyInvocation(Object amsInstance, String packageName, String clz) {
            this.amsObj = amsInstance;
            this.packageName = packageName;
            this.clz = clz;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //proxy是创建出来的代理类,method是接口中的方法,args是接口执行时的实参
            if (method.getName().equals("startActivity")) {
                Log.d("GlobalActivityHook", "全局hook 到了 startActivity");

                Intent currentRealIntent = null;//侦测到startActivity动作之后,把intent存到这里
                int intentIndex = -1;
                //遍历参数,找到Intent
                for (int i = 0; i < args.length; i++) {
                    Object temp = args[i];
                    if (temp instanceof Intent) {
                        currentRealIntent = (Intent) temp;//这是原始的Intent,存起来,后面用得着
                        intentIndex = i;
                        break;
                    }
                }

                //构造自己的Intent,这是为了绕过manifest检测(这个Intent是伪造的!只是为了让通过manifest检测)
                Intent proxyIntent = new Intent();
                ComponentName componentName = new ComponentName(packageName, clz);//用ComponentName重新创建一个intent
                proxyIntent.setComponent(componentName);
                proxyIntent.putExtra(ORI_INTENT_TAG, currentRealIntent);//将真正的proxy作为参数,存放到extras中,后面会拿出来还原

                args[intentIndex] = proxyIntent;//替换掉intent
                //哟,已经成功绕过了manifest清单检测. 那么,我不能老让它跳到 伪装的Activity啊,我要给他还原回去,那么,去哪里还原呢?
                //继续看源码。

            }
            return method.invoke(amsObj, args);
        }
    }
public class Handler {
...
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
}

下面是Hook Mh的完整代码:

    //下面进行ActivityThread的mH的hook,这是针对SDK28做的hook
    private static void hookActivityThread_mH_After28() {

        try {
            //确定hook点,ActivityThread类的mh
            // 先拿到ActivityThread
            Class<?> ActivityThreadClz = Class.forName("android.app.ActivityThread");
            Field field = ActivityThreadClz.getDeclaredField("sCurrentActivityThread");
            field.setAccessible(true);
            Object ActivityThreadObj = field.get(null);//OK,拿到主线程实例

            //现在拿mH
            Field mHField = ActivityThreadClz.getDeclaredField("mH");
            mHField.setAccessible(true);
            Handler mHObj = (Handler) mHField.get(ActivityThreadObj);//ok,当前的mH拿到了
            //再拿它的mCallback成员
            Field mCallbackField = Handler.class.getDeclaredField("mCallback");
            mCallbackField.setAccessible(true);

            //2.现在,造一个代理mH,
            // 他就是一个简单的Handler子类
            ProxyHandlerCallback proxyMHCallback = new ProxyHandlerCallback();//错,不需要重写全部mH,只需要对mH的callback进行重新定义

            //3.替换
            //将Handler的mCallback成员,替换成创建出来的代理HandlerCallback
            mCallbackField.set(mHObj, proxyMHCallback);

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

    private static class ProxyHandlerCallback implements Handler.Callback {

        private int EXECUTE_TRANSACTION = 159;//这个值,是android.app.ActivityThread的内部类H 中定义的常量EXECUTE_TRANSACTION

        @Override
        public boolean handleMessage(Message msg) {
            boolean result = false;//返回值,请看Handler的源码,dispatchMessage就会懂了
            //Handler的dispatchMessage有3个callback优先级,首先是msg自带的callback,其次是Handler的成员mCallback,最后才是Handler类自身的handlerMessage方法,
            //它成员mCallback.handleMessage的返回值为true,则不会继续往下执行 Handler.handlerMessage
            //我们这里只是要hook,插入逻辑,所以必须返回false,让Handler原本的handlerMessage能够执行.
            if (msg.what == EXECUTE_TRANSACTION) {//这是跳转的时候,要对intent进行还原
                try {
                    //先把相关@hide的类都建好
                    Class<?> ClientTransactionClz = Class.forName("android.app.servertransaction.ClientTransaction");
                    Class<?> LaunchActivityItemClz = Class.forName("android.app.servertransaction.LaunchActivityItem");

                    Field mActivityCallbacksField = ClientTransactionClz.getDeclaredField("mActivityCallbacks");//ClientTransaction的成员
                    mActivityCallbacksField.setAccessible(true);
                    //类型判定,好习惯
                    if (!ClientTransactionClz.isInstance(msg.obj)) return true;
                    Object mActivityCallbacksObj = mActivityCallbacksField.get(msg.obj);//根据源码,在这个分支里面,msg.obj就是 ClientTransaction类型,所以,直接用
                    //拿到了ClientTransaction的List<ClientTransactionItem> mActivityCallbacks;
                    List list = (List) mActivityCallbacksObj;

                    if (list.size() == 0) return true;
                    Object LaunchActivityItemObj = list.get(0);//所以这里直接就拿到第一个就好了

                    if (!LaunchActivityItemClz.isInstance(LaunchActivityItemObj)) return true;
                    //这里必须判定 LaunchActivityItemClz,
                    // 因为 最初的ActivityResultItem传进去之后都被转化成了这LaunchActivityItemClz的实例

                    Field mIntentField = LaunchActivityItemClz.getDeclaredField("mIntent");
                    mIntentField.setAccessible(true);
                    Intent mIntent = (Intent) mIntentField.get(LaunchActivityItemObj);
                    Intent oriIntent = (Intent) mIntent.getExtras().get(ORI_INTENT_TAG);
                    //那么现在有了最原始的intent,应该怎么处理呢?
                    Log.d("1", "2");
                    mIntentField.set(LaunchActivityItemObj, oriIntent);
                    return result;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return result;
        }
    }

OK,大功告成,安装好 android 9.0 SDK 28的模拟器,启动起来,运行程序,看看能不能无清单跳转:

结果,脸一黑:报错!***

一份大礼:
2019-02-27 18:20:12.287 28253-28253/study.hank.com.activityhookdemo E/AndroidRuntime: FATAL EXCEPTION: main
    Process: study.hank.com.activityhookdemo, PID: 28253
    java.lang.RuntimeException: Unable to start activity ComponentInfo{study.hank.com.activityhookdemo/study.hank.com.activityhookdemo.methodA.Main2Activity}: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{study.hank.com.activityhookdemo/study.hank.com.activityhookdemo.methodA.Main2Activity}
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
     Caused by: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{study.hank.com.activityhookdemo/study.hank.com.activityhookdemo.methodA.Main2Activity}
        at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:222)
        at android.support.v7.app.AppCompatDelegateImplV9.onCreate(AppCompatDelegateImplV9.java:155)
        at android.support.v7.app.AppCompatDelegateImplV14.onCreate(AppCompatDelegateImplV14.java:61)
        at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:72)
        at study.hank.com.activityhookdemo.methodA.Main2Activity.onCreate(Main2Activity.java:14)
        at android.app.Activity.performCreate(Activity.java:7136)
        at android.app.Activity.performCreate(Activity.java:7127)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:193) 
        at android.app.ActivityThread.main(ActivityThread.java:6669) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858) 
     Caused by: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{study.hank.com.activityhookdemo/study.hank.com.activityhookdemo.methodA.Main2Activity}
        at android.app.ApplicationPackageManager.getActivityInfo(ApplicationPackageManager.java:435)
        at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:240)
        at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:219)
        at android.support.v7.app.AppCompatDelegateImplV9.onCreate(AppCompatDelegateImplV9.java:155) 
        at android.support.v7.app.AppCompatDelegateImplV14.onCreate(AppCompatDelegateImplV14.java:61) 
        at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:72) 
        at study.hank.com.activityhookdemo.methodA.Main2Activity.onCreate(Main2Activity.java:14) 
        at android.app.Activity.performCreate(Activity.java:7136) 
        at android.app.Activity.performCreate(Activity.java:7127) 
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271) 
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893) 
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:193) 
        at android.app.ActivityThread.main(ActivityThread.java:6669) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858) 

提取关键信息:

Caused by: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{study.hank.com.activityhookdemo/study.hank.com.activityhookdemo.methodA.Main2Activity}
        at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:222)

居然找不到包?
问题出在:
NavUtils.getParentActivityName

Ok,看到IBinder,就知道应该无法继续往下追查了,已经跨进程了.
前面提到了,从主线程拿到的pm,被封装成了ApplicationPackageManager,那么,进入它里面去找:getActivityInfo方法:

接下来用同样的方式对PMS的检测进行hook,让它不再报异常.
此次hook的参照的源码是:

hook核心代码如下(对sPackageManager进行代理替换,让代理类检查的永远是合法的Activity):

private static void hookPMAfter28(Context context) throws ClassNotFoundException,
            NoSuchFieldException, IllegalAccessException, NoSuchMethodException,
            InvocationTargetException {
        String pmName = Util.getPMName(context);
        String hostClzName = Util.getHostClzName(context, pmName);

        Class<?> forName = Class.forName("android.app.ActivityThread");//PM居然是来自ActivityThread
        Field field = forName.getDeclaredField("sCurrentActivityThread");
        field.setAccessible(true);
        Object activityThread = field.get(null);
        Method getPackageManager = activityThread.getClass().getDeclaredMethod("getPackageManager");
        Object iPackageManager = getPackageManager.invoke(activityThread);

        String packageName = Util.getPMName(context);
        PMSInvocationHandler handler = new PMSInvocationHandler(iPackageManager, packageName, hostClzName);
        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);
    }

    static class PMSInvocationHandler implements InvocationHandler {

        private Object base;
        private String packageName;
        private String hostClzName;

        public PMSInvocationHandler(Object base, String packageName, String hostClzName) {
            this.packageName = packageName;
            this.base = base;
            this.hostClzName = hostClzName;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            if (method.getName().equals("getActivityInfo")) {
                ComponentName componentName = new ComponentName(packageName, hostClzName);
                return method.invoke(base, componentName, PackageManager.GET_META_DATA, 0);//破费,一定是这样
            }

            return method.invoke(base, args);
        }
    }

4.最终效果

大功告成~喜欢的大佬,现在就可以下载demo了.

结语

举报

相关推荐

0 条评论