最近在学插件化的东西,插件化提到了一个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 注册。