8.插件化开发--Hook基础

1.基础:Activity的启动流程

  1. class loader
  2. AMS、PMS
  3. 反射及动态代理模式
  4. ActivityThread
  • 启动流程
    Activity1(A1)【IActicityManager】–IPC—>AMS——>PackageManagerServer(扫描清单文件)—–>找到Activity2()—>启动A2并停止A1

Hook后:

Activity1(A1)【IActicityManager】—–>Hook(使用代理Activity)–intent(proxy)—>AMS——>PackageManagerServer(扫描清单文件)—–>找到Activity2()-intent(real)–>启动A2并停止A1

2.具体项目示例

  • 基础Hook案例:
    启动未在清单文件中注册的Activity。
  • 实现思路:

根据ActivityThread源码可以看Activity的启动仅仅需要提供intent(主要提供目标Activity)就能启动一个Activity

//源码流程:ActivityThread.handleMessage() ==>handleLaunchActivity() ==>performLaunchActivity()==>mInstrumentation.newActivity()
//主要查看ActivityClientRecord数据包的流向
  public Activity newActivity(Class<?> clazz, Context context, 
            IBinder token, Application application, Intent intent, ActivityInfo info, 
            CharSequence title, Activity parent, String id,
            Object lastNonConfigurationInstance) throws InstantiationException, 
            IllegalAccessException {
        Activity activity = (Activity)clazz.newInstance();
        ActivityThread aThread = null;
        activity.attach(context, aThread, this, token, 0, application, intent,
                info, title, parent, id,
                (Activity.NonConfigurationInstances)lastNonConfigurationInstance,
                new Configuration(), null, null, null);
        return activity;
    }
  • 但是大家都知道,如果Activity没有在清单文件中注册就会混抛异常。那怎么办呢?(怎样能让系统不抛异常?)

    思路很简单 , 抛出异常原因是intent经过PMS后会进行扫描清单文件,如果没有就会抛异常。
    那么只要在PMS处理之前把intent的目的Activity地址改为已知的代理Activity,在PMS之后再恢复成原地址即可,

  • 那么如何去替换intent呢?在哪里换呢?
    替换intent可以使用反射机制去动态替换intent内容
    由上面的Activity启动流程图可以看出,最好的替换地点就在Activity intent发出和Acitvity的启动前进行替换和逆替换.其他地方都不是在UI线程上也不好替换。
    咱们从源码一个一个分析:
    发送Intent的具体流程:

//当然看源码从Acitvity.startActivity()中开始,看intent的流向
Acitvity.startActivity()==>startActivityForResult()
==>startActivityFromChild()
==>Instrumentation.execStartActivity()
==>ActivityManagerNative.getDefault().startActivity()

哈哈找到IActivityManager(ActivityManagerNative.getDefault()就是它)了
Activity就是根据IActivityManager与AMS进行跨进程通信的(IPC)
那么只要代理替换掉IActivityManager中的intent就可以了。

  • 下面是具体实现方法(其实为什么比怎么实现的更重要!)
     public void hookAms() throws Exception {
       Logger.d(TAG, "start hook ");
       Class<?> forName = Class.forName("android.app.ActivityManagerNative");
       Field defaultField = forName.getDeclaredField("gDefault");
       defaultField.setAccessible(true);
       Object defaultValue = defaultField.get(null);

       Class<?> singleton = Class.forName("android.util.Singleton");
       Field instanceField = singleton.getDeclaredField("mInstance");
       instanceField.setAccessible(true);
       //获取到IActivityManager
       Object iActivityManagerObj = instanceField.get(defaultValue);
       //代理对象-hook
       Class<?> iActivityManagerIntercept = Class.forName("android.app.IActivityManager");
       AmsInvocationHanadel hanadel = new AmsInvocationHanadel(iActivityManagerObj);

       Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{iActivityManagerIntercept}, hanadel);

       instanceField.set(defaultValue, proxy);
   }

   class AmsInvocationHanadel implements InvocationHandler {
       private Object iActivityManagerObj;

       public AmsInvocationHanadel(Object iActivityManagerObj) {
           this.iActivityManagerObj = iActivityManagerObj;
       }

       @Override
       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
           Logger.d(TAG, "----------------AmsInvocationHanadel--------------------");
           if ("startActivity".equals(method.getName())) {
               Logger.d(TAG, "methodName:" + method.getName());
               Intent intent = null;
               int index = 0;
               for (int i = 0; i < args.length; i++) {
                   if (args[i] instanceof Intent) {
                       //找到intent;
                       index = i;
                       intent = (Intent) args[i];
                       break;
                   }
               }
               Intent proxyIntent = new Intent();

               ComponentName compnnentName = new ComponentName(context, proxyActivity);
               proxyIntent.setComponent(compnnentName);
               proxyIntent.putExtra("oldIntent", intent);
               Logger.d(TAG, "index :" + index);
               args[index] = proxyIntent;
               Logger.d(TAG, "替换成proxyIntent :" + proxyIntent.getComponent().getPackageName() + "; " + proxyIntent.getComponent().getShortClassName());
               return method.invoke(iActivityManagerObj, args);
           }
           return method.invoke(iActivityManagerObj, args);
       }
   }
  • 逆替换
           //----------逆替换------------
        public void hookSystemHandler() {
        try {
            Class<?> forName = Class.forName("android.app.ActivityThread");
            Field sCurrentActivityThread = forName.getDeclaredField("sCurrentActivityThread");
            sCurrentActivityThread.setAccessible(true);
            Object activityThreadValue = sCurrentActivityThread.get(null); //系统程序入口
            Field handlerField = forName.getDeclaredField("mH");
            handlerField.setAccessible(true);
            Handler handlerObj = (Handler) handlerField.get(activityThreadValue);
            Field mCallbackField = Handler.class.getDeclaredField("mCallback");
            mCallbackField.setAccessible(true);
            mCallbackField.set(handlerObj, new ActivityThreadHandlerCallback(handlerObj));


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

    class ActivityThreadHandlerCallback implements Handler.Callback {

        Handler handler;

        public ActivityThreadHandlerCallback(Handler handler) {
            super();
            this.handler = handler;
        }

        @Override
        public boolean handleMessage(Message msg) {
            Logger.d(TAG, "message callback");
            //替换回去
            if (msg.what == 100) {
                Logger.d(TAG, "what:" + msg.what);
                Logger.d(TAG, "Activity lunchActivity");
                handleLunchActivity(msg);
            }
            handler.handleMessage(msg);
            return true;
        }

        private void handleLunchActivity(Message msg) {
            Object obj = msg.obj;//ActivityClientRecord

            try {
                Field intentField = obj.getClass().getDeclaredField("intent");
                intentField.setAccessible(true);
                Intent proxyIntent = (Intent) intentField.get(obj); //代理Intent
                Logger.d(TAG, "获取到proxyIntent");
                Intent realIntent = proxyIntent.getParcelableExtra("oldIntent");
                if (realIntent != null) {
                    Logger.d(TAG, " have  realIntent ");
                    proxyIntent.setComponent(realIntent.getComponent());//替换属性
                } else {
                    Logger.d(TAG, "realIntent is null");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

  • 重写Application方法
 /**
 * Created by zlw on 2017/4/5.
 */
public class MyApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        HookAmsUtils amsUtils = new HookAmsUtils(this, ProxyActivity.class);
        try {
            amsUtils.hookAms();
            amsUtils.hookSystemHandler();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

最后配置下清单文件就可以了

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.zlw.main.myhookdemo">

    <application
        android:name=".base.MyApp"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".base.ProxyActivity" />
    </application>

</manifest>
    原文作者:android_赵乐玮
    原文地址: https://www.jianshu.com/p/e3f790843b9b
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞