目录:
- Hook 介绍
- Hook 原理
- Hook demo
- Hook 框架
1. Hook 介绍
Hook 翻译过来就是"钩子"的意思,那在什么时候使用 Hook 呢?在 Android 操作系统中系统维护着一套自己的事件分发机制。应用程序,包括应用触发事件和后台逻辑处理,也是根据事件流程一步步地向下执行。而 Hook 的意思,就是在事件传送到终点前截获并监控事件的传输,像个钩子钩上事件一样,并且能够在钩上事件时,处理一些自己特定的事件。
Hook 的这个本领,使它能够将自身的代码融入被勾住(Hook)的程序的进程中,成为目标进程的一个部分。API Hook 技术是一种用于改变 API 执行结果的技术,能够将系统的 API 函数执行重定向。在 Android 系统中使用了沙箱机制,普通用户程序的进程空间都是独立的,程序的运行互不干扰。这就使我们希望通过一个程序改变其他程序的某些行为的想法不能直接实现,但是 Hook 的出现给我们开拓了解决此类问题的道路。当然,根据 Hook 对象与 Hook 后处理的事件方式不同,Hook 还分为不同的种类,比如消息 Hook、API Hook 等。
关于 Android 中的 Hook 机制,大致有两个方式:
- 要 root 权限,直接 Hook 系统,可以干掉所有的 App。
- 免 root 权限,但是只能 Hook 自身,对系统其它 App 无能为力。
2. Hook 原理
Hook 的核心思想,是通过反射,将系统对象替换成我们创建的代理类对象。
反射的知识:Java篇 - 反射机制分析(附面试中的坑)
代理的知识:Java篇 - 代理模式和动态代理实现原理
3. Hook demo
我们要实现的就是 hook startActivity() 这个方法,调用 startActivity() 会进入 Framework,然后通过 IActivityManager 对象的startActivity() 方法启动 Activity,分成下面几步:
- 通过反射,拿到 IActivityManager 对象;
- 自定义一个代理类的调度类,它要实现 InvocationHandler 的 invoke() 方法;
- 以 IActivityManager 为参数,创建调度类实例;
- 以 IActivityManager 和调度类实例为参数,生成一个代理类的实例;
- 用代理类的实例,替换第一步拿到的 IActivityManager 对象。
- 3.1 拿到 IActivityManager 对象
startActivity() 会调用到 Instrumentation 类中的 execStartActivity 方法,并执行下面的代码:
// 执行启动Activity函数
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
// ...
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
// 使用ActivityManagerNative的startActivity启动
/**
* 关于 ActivityManagerNative.getDefault()返回的是ActivityManagerProxy对象.
* 此处startActivity()的共有10个参数, 下面说说每个参数传递AMP.startActivity()每一项的对应值:
*
* caller: 当前应用的ApplicationThread对象mAppThread;
* callingPackage: 调用当前ContextImpl.getBasePackageName(),获取当前Activity所在包名;
* intent: 这便是启动Activity时,传递过来的参数;
* resolvedType: 调用intent.resolveTypeIfNeeded而获取;
* resultTo: 来自于当前Activity.mToken
* resultWho: 来自于当前Activity.mEmbeddedID
* requestCode = -1;
* startFlags = 0;
* profilerInfo = null;
* options = null;
*/
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
// 检查activity是否启动成功
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
ActivtyManagerNatvie.getDefault() 拿到的是 ActivityManagerProxy 类型,实现了 IActivityManager 接口:
public abstract class ActivityManagerNative extends Binder implements IActivityManager
{
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
// 先从ServiceManager中获取ActivityManagerService
IBinder b = ServiceManager.getService("activity");
if (false) {
Log.v("ActivityManager", "default service binder = " + b);
}
// proxy
IActivityManager am = asInterface(b);
if (false) {
Log.v("ActivityManager", "default service = " + am);
}
return am;
}
};
static public IActivityManager getDefault() {
return gDefault.get();
}
class ActivityManagerProxy implements IActivityManager {
}
}
我们通过下面的代码获得 IActivityManager 实例:
public static void hook() {
try {
// 通过完整的类名拿到class
Class<?> ActivityManagerNativeClss = Class.forName("android.app.ActivityManagerNative");
// 拿到这个类的某个field
Field gDefaultFiled = ActivityManagerNativeClss.getDeclaredField("gDefault");
// field为private,设置为可访问的
gDefaultFiled.setAccessible(true);
// 拿到ActivityManagerNative的gDefault的Field的实例
// gDefault为static类型,不需要传入具体的对象
Object gDefaultFiledValue = gDefaultFiled.get(null);
// 拿到Singleton类
Class<?> SingletonClass = Class.forName("android.util.Singleton");
// 拿到类对应的field
Field mInstanceField = SingletonClass.getDeclaredField("mInstance");
// field是private
mInstanceField.setAccessible(true);
// gDefaultFiledValue是Singleton的实例对象
// 拿到IActivityManager对象
Object iActivityManagerObject = mInstanceField.get(gDefaultFiledValue);
HookInvocationHandler handler = new HookInvocationHandler(iActivityManagerObject);
// 拿到IActivityManager类
Class<?> IActivityManagerIntercept = Class.forName("android.app.IActivityManager");
// 传入当前线程的ClassLoader,IActivityManager.class,InvocationHandler
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[]{IActivityManagerIntercept}, handler);
// 替换要hook的系统对象
mInstanceField.set(gDefaultFiledValue, proxy);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
现在拿到了系统 startActivity() 用到的 IActivityManager,下面我们要创建一个代理类替换它。
- 3.2 自定义一个代理类的调度类
private static class HookInvocationHandler implements InvocationHandler {
private Object iActivityManagerObject;
private HookInvocationHandler(Object iActivityManagerObject) {
this.iActivityManagerObject = iActivityManagerObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.i(TAG, method.getName());
if ("startActivity".contains(method.getName())) {
Log.e(TAG, "Activity start, hook");
}
return method.invoke(iActivityManagerObject, args);
}
}
- 3.3 完整代码
package io.kzw.hookdemo;
import android.util.Log;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class HookStartActivity {
private static final String TAG = "HookStartActivity";
public static void hook() {
try {
// 通过完整的类名拿到class
Class<?> ActivityManagerNativeClss = Class.forName("android.app.ActivityManagerNative");
// 拿到这个类的某个field
Field gDefaultFiled = ActivityManagerNativeClss.getDeclaredField("gDefault");
// field为private,设置为可访问的
gDefaultFiled.setAccessible(true);
// 拿到ActivityManagerNative的gDefault的Field的实例
// gDefault为static类型,不需要传入具体的对象
Object gDefaultFiledValue = gDefaultFiled.get(null);
// 拿到Singleton类
Class<?> SingletonClass = Class.forName("android.util.Singleton");
// 拿到类对应的field
Field mInstanceField = SingletonClass.getDeclaredField("mInstance");
// field是private
mInstanceField.setAccessible(true);
// gDefaultFiledValue是Singleton的实例对象
// 拿到IActivityManager对象
Object iActivityManagerObject = mInstanceField.get(gDefaultFiledValue);
HookInvocationHandler handler = new HookInvocationHandler(iActivityManagerObject);
// 拿到IActivityManager类
Class<?> IActivityManagerIntercept = Class.forName("android.app.IActivityManager");
// 传入当前线程的ClassLoader,IActivityManager.class,InvocationHandler
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[]{IActivityManagerIntercept}, handler);
// 替换要hook的系统对象
mInstanceField.set(gDefaultFiledValue, proxy);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
private static class HookInvocationHandler implements InvocationHandler {
private Object iActivityManagerObject;
private HookInvocationHandler(Object iActivityManagerObject) {
this.iActivityManagerObject = iActivityManagerObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.i(TAG, method.getName());
if ("startActivity".contains(method.getName())) {
Log.e(TAG, "Activity start, hook");
}
return method.invoke(iActivityManagerObject, args);
}
}
}
在 Application 中初始化一下:
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
HookStartActivity.hook();
}
}
测试一下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.start_second_activity_btn).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
startActivity(new Intent(MainActivity.this, SecondActivity.class));
}
});
}
}
执行输出:
Activity start, hook
4. Hook 框架
- 4.1 Xposed
通过替换 /system/bin/app_process 程序控制 Zygote 进程,使得 app_process 在启动过程中会加载 XposedBridge.jar 这个 Jar 包,从而完成对 Zygote 进程及其创建的 Dalvik 虚拟机的劫持。Xposed 在开机的时候完成对所有的 Hook Function 的劫持,在原 Function 执行的前后加上自定义代码。
- 4.2 VirtualXposed
https://github.com/android-hacker/VirtualXposed
VirtualXposed 是基于 VirtualApp 和 epic 在非 ROOT 环境下运行 Xposed 模块的实现(支持5.0~9.0)。
与 Xposed 相比,目前 VirtualXposed 有两个限制:
- 不支持修改系统 (可以修改普通 APP 中对系统 API 的调用),因此重力工具箱,应用控制器等无法使用。
- 暂不支持资源 HOOK,因此资源钩子不会起任何作用;使用资源 HOOK 的模块,相应的功能不会生效。
打开 VirtualXposed,在里面安装要使用的APP,以及相应的Xposed模块即可。
注意:所有的工作 (安装Xposed模块,安装APP) 必须在 VirtualXposed 中进行,否则 Xposed 模块不会有任何作用!比如,将微信直接安装在系统上 (而非VirtualXposed中),防撤回安装在 VirtualXposed 中;或者把微信安装在 VirtualXposed 上,防撤回插件直接安装在系统上;或者两者都直接安装在系统上,均不会起任何作用。
在 VirtualXposed 中安装 App 有两种方式:
- 直接复制已经在系统中安装好的 APP,比如如果你系统中装了微信,那么可以直接复制一份。
- 通过外置存储直接安装 APK 文件;点主界面的底部按钮-添加应用,然后选择后面两个 TAB 即可。
在 VirtualXposed 中安装 Xposed 模块,可以跟安装正常的 APK 一样,以上两种安装 App 的方式也适用于安装 Xposed 模块。不过,你也可以通过 VirtualXposed 中内置的 XposedInstaller 来安装和管理模块,跟通常的 XposedInstaller 使用方式一样;去下载页面,下载安装即可。
- 4.3 Cydia Substrate
Cydia Substrate 框架为苹果用户提供了越狱相关的服务框架,当然也推出了 Android 版 。Cydia Substrate 是一个代码修改平台,它可以修改任何进程的代码。不管是用 Java 还是 C/C++(native代码)编写的,而 Xposed 只支持 Hook app_process 中的 Java 函数。
- 4.4 Legend
Legend 是 Android 免 Root 环境下的一个 Apk Hook 框架,该框架代码设计简洁,通用性高,适合逆向工程时一些 Hook 场景。大部分的功能都放到了 Java 层,这样的兼容性就非常好。原理是这样的,直接构造出新旧方法对应的虚拟机数据结构,然后替换信息写到内存中即可。