插件化学习之Hook 是怎么回事

最近在学插件化的东西,插件化提到了一个Hook 技术,发现Hook 这个词什么意思都不知道,经过研究学习后有了一定的理解,这里做一下对Hook 理解的笔记。

下面通过一个例子来理解Hook ,启动一个没有在AndroidManifest.xml 注册的Activity;

我们知道Android 的Activity 是需要在AndroidManifest.xml 注册的,如果没注册是不能创建成功的,会抛出xxx ,不过用Hook 技术就可以实现,听起来还是很有意思的,那我们一起来看一下吧。

首先要清楚Activity 启动流程,虽然我们调用startActivity 可以在直接完成跳转,但是细分的话,这还是一个漫长的过程,一系列的方法调用不可谓不多,不清楚的可以先看一下Android 8.0 Activity 启动流程 ,清楚这个流程才能很好的理解例子的Hook 点;

对于反射与动态代理也是会用到的,所以不清楚的可以看一下插件化基础知识(反射,动态代理,类加载器)

思路

就是我们先在AndroidManifest.xml 里注册一个中转的Activity ,这个Activity 也可以成为坑位,先让系统以为启动的是AndroidManifest 声明的那个坑位Activity ,暂时骗过系统,然后到合适的时候又替换回我们需要启动的真正的Activity;

这个校检过程发生在AMS所在的进程system_server,没有标记的话,会标记上 ActivityManager.START_CLASS_NOT_FOUND;

    if (err == ActivityManager.START_SUCCESS && aInfo == null) {
            // We couldn't find the specific class specified in the Intent.
            // Also the end of the line.
            err = ActivityManager.START_CLASS_NOT_FOUND;
        }

系统服务执行完返回后,会在用户进程检查标记是 ActivityManager.START_CLASS_NOT_FOUND ,会抛出异常,如下:

public static void checkStartActivityResult(int res, Object intent) {
        // ...
        case ActivityManager.START_CLASS_NOT_FOUND:
            if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
                throw new ActivityNotFoundException(
                        "Unable to find explicit activity class "
                                + ((Intent)intent).getComponent().toShortString()
                                + "; have you declared this activity in your AndroidManifest.xml?");
            throw new ActivityNotFoundException(
                    "No Activity found to handle " + intent);
        // ...
    }

所以如果我们在到达用户进程之前进行Hook ,让Intent 里的Activity 信息是我们在AndroidManifest 里注册的Activity ,这样就可以通过AMS 的检测(当然检测完要换回来)。

我们先看这一步,

1.首先在AndroidManifest.xml 里声明一个坑位,这里是StubActivity

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

    <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, 用来欺骗AMS  -->
        <activity android:name=".StubActivity"/>
        
    </application>

</manifest>

调用startActivity 方法启动目标,就是启动TargetActivity ,这个TargetActivity 是没有注册的;

startActivity(new Intent(MainActivity.this, TargetActivity.class));

直接运行肯定会抛出ActivityNotFoundException ,所接下来就要Hook 了;

使用坑位Activity 绕过AMS

由于AMS 进程会对Activity做显式声明验证,因此在调用到AMS 之前,需要把TargetActivity 替换成替身StubActivity ;

在这之间一段调用链,都Hook 掉,但是由于调用关系复杂,Hook 的越靠前,就越会影响到之后的操作,所以选择在进入AMS 进程的入口进行Hook ;

那么这个入口是哪呢?通过看源码(这里以Android 8.0 为例)知道最终通过ActivityManager.getService() .startActivity() 远程调用了AMS 的startActivity 方法;

public static IActivityManager getService() {
        return IActivityManagerSingleton.get();
    }

    private static final Singleton<IActivityManager> IActivityManagerSingleton =
            new Singleton<IActivityManager>() {
                @Override
                protected IActivityManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                    final IActivityManager am = IActivityManager.Stub.asInterface(b);
                    return am;
                }
            };

IActivityManager 实际上就是ActivityManagerService 这个远程对象的Binder 代理对象;每次需要与AMS打交道的时候,需要借助这个代理对象通过驱动进而完成IPC调用。

由于FrameWork 如此频繁的与AMS 打交道,Framework使用了一个单例,这样只要需要与AMS进行IPC调用,获取这个单例即可,那么这里就是通往AMS 的入口啦;

下面使用反射与动态代理Hook 这个单例,如下:

    /**
     * Hook AMS
     * 主要完成的操作是,代理Hook AMS 在应用进程的本地代理IActivityManager ,
     * 把真正要启动的Activity临时替换为在AndroidManifest.xml 里坑位Activity
     * 进而骗过AMS
     */
    public static void hookIActivityManager() throws Exception {
        
        /**
         * 单例的结构
            public abstract class Singleton<T> {
                private T mInstance;

                protected abstract T create();

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

        Field amSingletonField =null;
        if (Build.VERSION.SDK_INT >= 26) {
            Class<?> activityManagerClass = Class.forName("android.app.ActivityManager");
            amSingletonField = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
        }else{
            Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
            amSingletonField = activityManagerNativeClass.getDeclaredField("gDefault");
        }
        amSingletonField.setAccessible(true);

        Object iamSingletonObj = amSingletonField.get(null);

        // 得到iamSingletonObj ,得到iamSingletonObj 对象里的mInstance 字段值,这个值就是那个需要的单例,
        // 就是AMS 在应用进程的本地代理对象
        Class<?> singleton = Class.forName("android.util.Singleton");
        Field mInstanceField = singleton.getDeclaredField("mInstance");
        mInstanceField.setAccessible(true);

        // 原始的 IActivityManager对象
        Object rawIActivityManager = mInstanceField.get(iamSingletonObj);

        // 用动态代理,这里在执行相应方法执行我们的一些逻辑(这里指的是修改Intent 使用坑位Activity ,从而可以越过AMS)
        // 创建一个这个对象的代理对象, 然后替换这个字段, 让我们的代理对象帮忙干活
        Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
        Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class<?>[] { iActivityManagerInterface }, new IActivityManagerHandler(rawIActivityManager));
        mInstanceField.set(iamSingletonObj, proxy);
    }

IActivityManagerHandler 为实现的代理类调用处理器

class IActivityManagerHandler implements InvocationHandler {

    private static final String TAG = "IActivityManagerHandler";
    Object mBase;

    public IActivityManagerHandler(Object base) {
        mBase = base;
    }

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

        if ("startActivity".equals(method.getName())) {
            // 找到参数里面的第一个Intent 对象
            Intent raw;
            int index = 0;
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                    index = i;
                    break;
                }
            }
            raw = (Intent) args[index];
            Intent newIntent = new Intent();
            // 替身Activity的包名, 也就是我们自己的包名
            String stubPackage = "com.example.hookdemo";
            // 这里我们把启动的Activity临时替换为坑位 StubActivity
            ComponentName componentName = new ComponentName(stubPackage, StubActivity.class.getName());
            newIntent.setComponent(componentName);
            // 把我们原始要启动的TargetActivity先存起来
            newIntent.putExtra(HookHelper.EXTRA_TARGET_INTENT, raw);
            // 替换掉Intent, 达到欺骗AMS的目的
            args[index] = newIntent;
            return method.invoke(mBase, args);
        }
        return method.invoke(mBase, args);
    }
}

Hook 之后,再调用startActivity(new Intent(MainActivity.this, TargetActivity.class)); 也不会抛出ActivityNotFoundException ,只不过打开的不是TargetActivity ,而是坑位Activity 。

拦截Callback 恢复目标Activity

经过上面的Hook 之后,调用到 ActivityManager.getService() .startActivity() 会调用到AMS 的startActivity 方法,在系统服务进程进行Activity 的一些验证,不过坑位Activity 在AndroidManifest 注册过了,因此可以成功绕过检查;

接下来就是恢复目标Activity 啦,在哪里恢复呢,AMS 在系统服务进程肯定不行,不过通过Activity 的启动流程我们知道,当系统服务进程操作完了会回到我们的应用进程,然后通过Handler 机制发送相应的Message ,执行相应的逻辑,创建Activity 的实例,回调声明周期方法,所以我们要在Activity 创建之前恢复目标Activity ;

我们知道发送的Message 可在dispatchMessage 方法进行处理,那么让我们看看Handler 的源码,

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

可以知道,

1.如果传递的Message本身就有callback,那么直接使用Message对象的callback方法;

2.如果Handler类的成员变量mCallback存在,那么首先执行这个mCallback回调,

如果mCallback.handleMessage(msg) 返回true ,那么直接结束,反之还会调用Handler 的handleMessage 方法;

所以,我们可以把这个H类的mCallback替换为我们的自定义实现的,这样dispathMessage就会首先使用这个自定义的mCallback,然后看情况调用重载的handleMessage 方法;

这个Handler.Callback是一个接口,可以使用动态代理或者普通代理完成Hook,这里使用普通的静态代理方式;

class ActivityThreadHandlerCallback implements Handler.Callback {

    Handler mBase;

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

    @Override
    public boolean handleMessage(Message msg) {
        switch (msg.what) {
            // ActivityThread里面 "LAUNCH_ACTIVITY" 这个字段的值是100
            // 本来使用反射的方式获取最好, 这里为了简便而直接使用硬编码
            case 100:
                handleLaunchIntent(msg);
                break;
        }
        mBase.handleMessage(msg);
        return true;
    }

    private void handleLaunchIntent(Message msg) {
        // 这里简单起见,直接取出TargetActivity;
        Object obj = msg.obj;
        // 根据源码,这个msg.obj 对象是 ActivityClientRecord 类型,修改它的intent字段,恢复目标Activity
        try {
            // 把替身恢复成真身
            Field intent = obj.getClass().getDeclaredField("intent");
            intent.setAccessible(true);
            Intent raw = (Intent) intent.get(obj);

            Intent target = raw.getParcelableExtra(HookHelper.EXTRA_TARGET_INTENT);
            raw.setComponent(target.getComponent());
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

这个Callback类的使命很简单:把坑位StubActivity恢复成目标TargetActivity,接着需要把ActivityThread里面处理消息的Handler类H 的mCallback设置为自定义Callback类的对象;

    /**
     * 由于之前我们用替身欺骗了AMS; 现在我们要换回我们真正需要启动的Activity ,不然就真的启动替身了
     * 到最终要启动Activity的时候,会交给ActivityThread 的一个内部类叫做 H 来完成
     * H 会完成这个消息转发,这里对H 的mCallback 进行处理
     */
    public static void hookActivityThreadHandler() throws Exception {

        // 先获取到当前的ActivityThread 对象
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        Field currentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
        currentActivityThreadField.setAccessible(true);
        Object currentActivityThread = currentActivityThreadField.get(null);

        // 获取ActivityThread 的字段mH 对象
        Field mHField = activityThreadClass.getDeclaredField("mH");
        mHField.setAccessible(true);
        Handler mH = (Handler) mHField.get(currentActivityThread);

        Field mCallBackField = Handler.class.getDeclaredField("mCallback");
        mCallBackField.setAccessible(true);

        // 设置我们的代理CallBack
        mCallBackField.set(mH, new ActivityThreadHandlerCallback(mH));
    }

到这里通过Hook,已成功地绕过AMS ;

下面在在点击执行之前完成Hook 的操作,

    try {
            HookHelper.hookIActivityManager();
            HookHelper.hookActivityThreadHandler();
        } catch (Exception e) {
            e.printStackTrace();
        }

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this, TargetActivity.class));
            }
        });

这样的点击按钮,可以成功的启动TargetActivity ,然而TargetActivity 并没有在AndroidManifest.xml 注册。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值