Hook概念
Hook翻译过来是钩子的意思,我们都知道无论是手机还是电脑运行的时候都依赖系统各种各样的API,当某些API不能满足我们的要求时,我们就得去修改某些api,使之能满足我们的要求。这样api hook就自然而然的出现了。我们可以通过api hook,改变一个系统api的原有功能。基本的方法就是通过hook“接触”到需要修改的api函数入口点,改变它的地址指向新的自定义的函数。当然这种技术同样适用于Android系统,在Android开发中,我们同样能利用Hook的原理让系统某些方法运行时调用的是我们定义的方法,从而满足我们的要求
Hook原则
Android中主要是依靠分析系统源码类来做到的,首先我们得找到被Hook的对象,我称之为Hook点;什么样的对象比较好Hook呢?自然是容易找到的对象。什么样的对象容易找到?静态变量和单例;在一个进程之内,静态变量和单例变量是相对不容易发生变化的,因此非常容易定位,而普通的对象则要么无法标志,要么容易改变。我们根据这个原则找到所谓的Hook点
在安卓中实现hook主要通过两种方式
1.反射技术和代理实现,当然代理不管是动态还是静态的都是可以实现的,但是只能hook自己应用内存中的对象;
2.在root的情况下,Xposed通过替换/system/bin/app_process程序控制zygote进程,使得app_process在启动过程中会加载XposedBridge.jar这个jar包,从而完成对Zygote进程及其创建的Dalvik 虚拟机的劫持,可以达到hook整个系统中所有进程内存里面的对象的目的;
HookAms实践
插件技术中很重要的一项就是宿主启动插件APK中的Activity,因为插件都是后面业务迭代加进来的,所以Activity不可能提前注册在宿主Activity的清单文件中的,所以正常的情况下是不可能启动插件里的Activity的,因为启动Activity的过程是需要在清单文件中寻找是否注册,若没有,则直接crash。
所以想实现跳过系统检查,做法就是先在宿主里注册一个ProxyActivity,在启动插件的Activity的时候,把我们这个真实意图Intent替换为可以通过检查的启动ProxyActivity的代理意图Intent,然后让真实意图作为Extra添加进代理意图里,在通过检查后再取出来替换回来,而这样的功能,是通过hook实现的。
我们知道Activity的启动过程是通过AIDL Binder的方式跟AMS进行一系列的交互,最终通过反射newInstance创建出来的,由于AMS处于系统进程中,所以我们是没法从它里面寻找hook点的。所以这里所说的hookAms,其实是hook位于我们自己的应用这边的与AMS交互的AIDL的接口IActivityManeger,那怎么才能找到这个对象并且进行替换呢,需要看FrameWork源码
Instrumentation execStartActivity
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
........
try {
// 通过ActivityManagerNative.getDefault()的startActivity来启动activity
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
// 根据返回的result结果,给用户对应的提示
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
ActivityManagerNative
public abstract class ActivityManagerNative extends Binder implements IActivityManager
{
static public IActivityManager getDefault() {
return gDefault.get();
}
private static final Singleton gDefault = new Singleton() {
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;
}
};
}
这里可以看到ActivityManagerNative.getDefault()内部直接返回gDefault.get(),其中gDefault是Singleton泛型类
public abstract class Singleton {
private T mInstance;
protected abstract T create();
// get方法返回对应的泛型类,这里是IActivityManager
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
接下来要揭开gDefault.get()的面纱,我们知道其中在Singleton中维护了一个mInstance变量,并且gDefault.get()方法最终返回的就是mInstance对应的泛型类实例,也就是说通过ContextImpl#startActivity方法启动activity的时候,最终是通过mInstance也就是IActivityManager来启动的,其实就是ActivityManagerService,所以我们要做的就是通过反射对mInstance重新赋值,将我们自己的代理类赋值给mInstance,然后在代理类中根据系统获取的IActivityManager在执行操作,所以这里我们就可以在对应的操作前后,进行一些记录或者关键log的打印
创建一个代理类,需要实现InvocationHandler接口,用来代理IActivityManager的操作
public class AMSProxy implements InvocationHandler {
private static final String TAG = "HookAMS";
private Object iActivityManager;
public AMSProxy(Object iActivityManager) {
this.iActivityManager = iActivityManager;
}
@Override
public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
Log.d(TAG, "method name is :" + method.getName() + " args length is :" + args.length + " args is :" + args);
if ("startActivity".equals(method.getName())) {
// 第三个参数是intent
Intent intent = (Intent)args[2];
Log.d(TAG, "method name is :"+ method.getName()+" intent is :"+intent+" extradata is :"+intent.getStringExtra("DATA"));
}
return method.invoke(iActivityManager, args);
}
}
接下来开始Hook AMS
Class activityManagerNativeClazz = Class.forName("android.app.ActivityManagerNative");
Field gDefaultField = activityManagerNativeClazz.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
Object gDefault = gDefaultField.get(null); // 获取gDefault实例,由于是静态类型的属性,所以这里直接传递null参数
// 下面通过反射执行gDefault.get();操作,最终返回IActivityManager,也就是ActivityManagerService的实例
Class singleTonClazz = Class.forName("android.util.Singleton");
Field mInstanceField = singleTonClazz.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
Object iActivityManager = mInstanceField.get(gDefault);
Class iActivityManagerClazz = Class.forName("android.app.IActivityManager");
// 指定被代理对象的类加载器
// 指定被代理对象所实现的接口,这里就是代理IActivityManager
// 表示这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
Object myProxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class>
[]{iActivityManagerClazz}, new AMSProxy(iActivityManager));
mInstanceField.set(gDefault,myProxy);
此时运行我们的程序,启动activity,就可以看到,每次在启动activity之前都会打印相关的参数,其实不只是启动activity,启动service也可以,只要最终是通过ActivityManagerNative来操作的都可以拦截。
总结
Hook是个蛮有乐趣的调用方式。
Hook 的选择点:静态变量和单例,因为一旦创建对象,它们不容易变化,非常容易定位。
Hook 过程:
寻找 Hook 点,原则是静态变量或者单例对象,尽量 Hook public 的对象和方法。
选择合适的代理方式,如果是接口可以用动态代理。
偷梁换柱——用代理对象替换原始对象。
Hook 的这个本领,使它能够将自身的代码「融入」被勾住(Hook)的程序的进程中,成为目标进程的一个部分。API Hook 技术是一种用于改变 API 执行结果的技术,能够将系统的 API 函数执行重定向。在 Android 系统中使用了沙箱机制,普通用户程序的进程空间都是独立的,程序的运行互不干扰。这就使我们希望通过一个程序改变其他程序的某些行为的想法不能直接实现,但是 Hook 的出现给我们开拓了解决此类问题的道路。当然,根据 Hook 对象与 Hook 后处理的事件方式不同,Hook 还分为不同的种类,比如消息 Hook、API Hook 等。