阅读 1610

Activity不用注册?那就来Hook吧

1.引言

以前一直很好奇,启动一个新的Activity,为什么非要在清单文件里注册,到底是哪里地方进行了校验,整个启动的流程是什么样子的。如果想实现插件化机制,启动一个插件中新的Activity的话有什么其它方法去做到。这篇文章本来是想写在Activity的启动流程分析之后的,但是里面确实涉及的类,逻辑很多,写起来可能会有些漏缺,而且比较无聊,所以先写一下android的hook技术,先大概讲一下Activity的启动流程,里面会涉及到一些进程交互,如果对android中的Binder机制不熟悉的朋友可以看我上篇文章3分钟带你看懂android的Binder机制

2.Activity大致启动流程

启动一个Activity大致会经历一下几个方法:

  • Activity.startActivity()
  • Activity.startActivityForResult()
  • Instrumentation.execStartActivity()
  • ActivityManagerService.startActivity()
  • ApplicationThread.scheduleLaunchActivity()
  • ActivityThread.Handler.handleMessage()

具体方法本文就不详细说了,免得篇幅太长,引用一张图来表述整个的交互过程:

从上图我们可以看出整个通信过程是涉及到2次Binder通信过程的,APP进程和system_server进程分别作为了一次client和server端。APP进程也就是我们自己的应用进程,system_server进程是系统进程,javaframework框架的核心载体,里面运行了大量的系统服务,比如这里提供ApplicationThreadProxy),ActivityManagerService,结合图,启动流程大致如下:

  • APP进程入口是ActivityThread,初始化时便会创建一个Binder线程(具体是指ApplicationThread,Binder的服务端,用于接收系统服务AMS发送来的事件),ApplicationThread主要有两个作用:1.作为Binder服务端接受system_server中的client端ApplicationThreadProxy发送过来的消息,对应上面的方法是ActivityManagerService------>ApplicationThread,2.作为消息的中转站,将msg通过handler传递到ActivityThread中,也就是传递到上述的最后一个方法中。
  • system_server进程中的ActivityManagerService作为Binder的服务端,接受ActivityManagerProxy作为client端发送过来的消息,也就是Instrumentation.execStartActivity()---->ActivityManagerService.startActivity()。

这里主要看下Instrumentation.execStartActivity这个方法,比较关键,跳过去可能有的朋友比较模糊,主要代码如下:

 public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        IApplicationThread whoThread = (IApplicationThread) contextThread;
        ....
        try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess(who);
            int result = ActivityManager.getService()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }
复制代码

这里的contextThread也就是上面讲的ApplicationThread对象,主要看下面ActivityManager.getService(),返回的是一个IActivityManager接口类型对象,继续看:

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

 private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            IActivityManager am = asInterface(b);//注意这一行
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };

static public IActivityManager asInterface(IBinder obj) {
        if (obj == null) {
            return null;
        }
        IActivityManager in =
            (IActivityManager)obj.queryLocalInterface(descriptor);
        if (in != null) {
            return in;
        }
        return new ActivityManagerProxy(obj);
    }

复制代码

这里我用的API25,API26及以上,实现的代码不太一样,废弃了ActivityManagerProxy,改用了AIDL来实现通信,为了让大家伙更理解Binder,这里就用之前的API了,逻辑应该很清晰通过ServiceManager拿到IBinder对象,再在本地进行查找,如果不在同一个进程,就返回ActivityManagerProxy代理对象,所以很清晰,Instrumentation.execStartActivity()实际上最后就调用到了ActivityManagerProxy中。

3.Hook实现

3.1 hook实现思路

咳咳!!我们回到正题,上面只是铺垫,我们的主题是hook,怎么启动一个没注册的Activity呢,先将下思路,既然最终检查是在AMS中,那我们可以在之前做一些骚操作,来个狸猫换太子,具体思路如下:

  • 直接startActivity()开启一个未注册的TargetActivity

  • 既然在AMS之前,消息是从ActivityManagerProxy中发出去的,我们可以动态代理生成一个类(不熟悉动态代理的朋友只能自行google了~),代理ActivityManagerProxy对象,拦截其中的startActivity()方法,拿到其中的intent参数对象。我们把真正的intent替换成我们一个已经注册过的ProxyActivity,先把AMS的check这一关给过了,再把真正的intent当做对象存在ProxyActivity的intent中。

  • 既然intent替换了,AMS是过了,那肯定得给它搞回来,要不然不就直接打开了ProxyActivity?我们知道最好消息时回到了ActivityThread.Handler.handleMessage()中的,嘿嘿,熟悉Handler机制的朋友应该知道,在这之前是会先走dispatchMessage方法的,不熟悉的可以看下我之前的文章Android源码学习之handler,

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
复制代码

是分别会先后执行handleCallback(msg)--->mCallback.handleMessage(msg)--->handleMessage(msg),而Activity中正好是最好一个,那我们可以hook一下这个mCallback,让我们在最后消息执行时,把我们的intent给替换回去

3.2 上代码!!

public class HookActivityUtils {
    private static final String TAG = "HookActivityUtils";
    private volatile static HookActivityUtils sHookActivityUtils;
    public static HookActivityUtils getInstance(){
        if (sHookActivityUtils==null){
            synchronized (HookActivityUtils.class){
                if (sHookActivityUtils==null){
                    sHookActivityUtils = new HookActivityUtils();
                }
            }
        }
        return sHookActivityUtils;
    }
    private HookActivityUtils(){
    }
    public void hooks(Context mContext){
        Object object;
        try {
            //寻找hook点,最好是静态或者单例,不容易发生改变,因为是静态,所以传入null即可
            //因为版本差异,所以要分开处理
            if (Build.VERSION.SDK_INT>=26){
                Field iActivityManagerSingleton = ActivityManager.class.getDeclaredField("IActivityManagerSingleton");
                iActivityManagerSingleton.setAccessible(true);
                object = iActivityManagerSingleton.get(null);
            }else{
                Field gDefault = Class.forName("android.app.ActivityManagerNative").getDeclaredField("gDefault");
                gDefault.setAccessible(true);
                object = gDefault.get(null);
            }

            //获取单例对象,实现IActivityManager接口的实现类
            Field mFieldInstance = Class.forName("android.util.Singleton").getDeclaredField("mInstance");
            mFieldInstance.setAccessible(true);
            Object mInstance = mFieldInstance.get(object);
            //寻找到hook点后,新建一个代理对象
            ActivityManagerDelegate managerDelegate = new ActivityManagerDelegate(mInstance,mContext);
            Class<?> aClass = Class.forName("android.app.IActivityManager");
            Object proxy = Proxy.newProxyInstance(aClass.getClassLoader(), new Class<?>[]{aClass}, managerDelegate);
            //替换动态代理对象
            mFieldInstance.set(object,proxy);
        } catch (Exception mE) {
            mE.printStackTrace();
        }
    }
    public void hookHanlder(){
        try {
            Class<?> aClass = Class.forName("android.app.ActivityThread");
            Method currentActivityThread = aClass.getDeclaredMethod("currentActivityThread");
            currentActivityThread.setAccessible(true);
            //ActivityThread 本身对象
            Object invoke = currentActivityThread.invoke(null);
            Field mH = aClass.getDeclaredField("mH");
            mH.setAccessible(true);
            //获取handler对象
            Object handler = mH.get(invoke);
            //获取handler中的mCallback
            Field mCallback = Handler.class.getDeclaredField("mCallback");
            mCallback.setAccessible(true);
            mCallback.set(handler,new HookCallBack((Handler) handler));
        } catch (Exception mE) {
            mE.printStackTrace();
        }
    }
}
复制代码

主要也就是对应的两个方法,一个通过反射拿到实现IActivityManager接口的对象,并生成一个代理此对象的代理对象,另外一个是反射拿到ActivityThread中的mH Handler对象,然后传入一个实现Handler.callback接口的对象,这样Handler中的mcallback就不为空了,也就达到了我们的目的

然后是我们的代理对象:

public class ActivityManagerDelegate implements InvocationHandler {
    private static final String TAG = "ActivityManagerDelegate";
    private Object mObject;
    private Context mContext;
    public ActivityManagerDelegate(Object mObject,Context mContext) {
        this.mObject = mObject;
        this.mContext = mContext;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("startActivity")){
            //拦截方法
            Log.e(TAG,"i got you");
            Intent intent =null;
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent){
                    intent = (Intent) args[i];
                    //找到了intent参数
                    Intent mIntent = new Intent();
                    ComponentName componentName = new ComponentName(mContext,ProxyActivity.class);
                    //将真正的intent带上,后续替换
                    mIntent.setComponent(componentName);
                    mIntent.putExtra("realObj",intent);
                    //修改为已注册Activity的intent,先让AMS检查通过
                    args[i] = mIntent;
                }
            }

        }
        return method.invoke(mObject,args);
    }
}
复制代码

我们拦截startActivity,然后将ProxyActivity的ComponentName传递进去,狸猫换太子,同时将真正的intent带过去,接下来就是处理消息了:

public class HookCallBack implements Handler.Callback {
    private static final String TAG = "HookCallBack";
    private Handler mHandler;

    public HookCallBack(Handler mHandler) {
        this.mHandler = mHandler;
    }

    @Override
    public boolean handleMessage(Message msg) {
        if (msg.what==100){
            handleHookMsg(msg);
        }
        mHandler.handleMessage(msg);
        return false;
    }

    private void handleHookMsg(Message mMsg) {
        Object obj = mMsg.obj;
        try {
            Field intent = obj.getClass().getDeclaredField("intent");
            //这时候拿出之前存进来真正的intent
            intent.setAccessible(true);
            Intent proxyIntent = (Intent) intent.get(obj);
            Intent realIntent = proxyIntent.getParcelableExtra("realObj");
            proxyIntent.setComponent(realIntent.getComponent());
        } catch (Exception mE) {
            mE.printStackTrace();
        }
    }
}
复制代码

什么?为什么要拦截msg.what等于100的消息?

private class H extends Handler {
        ....
        public static final int LAUNCH_ACTIVITY         = 100;
        public static final int PAUSE_ACTIVITY          = 101;
        ....
}
复制代码

这下明白了吧,拦截到这个消息后,把事先存进去的intent的Component再set回去就完美了~~

主页面MainActivity:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        HookActivityUtils.getInstance().hooks(this);
        HookActivityUtils.getInstance().hookHanlder();
        findViewById(R.id.textView).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,TargetActivity.class);
                startActivity(intent);
            }
        });
    }
}
复制代码

这里我们打开的是TargetActivity,但是清单文件中并没有声明:

<application
        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=".ProxyActivity"></activity>
    </application>
复制代码

这样的话,就实现打开一个未注册的Activity了,是不是也是挺easy的,在实现插件化机制的时候,要打开插件中的activity的话,因为没有在原宿主中的清单文件注册,是无法直接调转的,这时候我们这个代理activity就可以起很大的作用了。

有兴趣的朋友可以跟着一起实现一下,感谢观看,溜了溜了~~

项目地址:github.com/weirdLin/ho…

关注下面的标签,发现更多相似文章
评论