什么是Hook?
其实Hook并不是Android的专属。其实在Android出现之前,HOOK(钩子,挂钩)是一种实现Windows平台下类似于中断的机制。HOOK机制允许应用程序拦截并处理Windows消息或指定事件,当指定的消息发出后,HOOK程序就可以在消息到达目标窗口之前将其捕获,从而得到对消息的控制权,进而可以对该消息进行处理或修改,加入我们所需的功能。钩子按使用范围分,可分为线程钩子和系统钩子,其中,系统钩子具有相当大的功能,几乎可以实现对所有Windows消息的拦截、处理和监控。这项技术涉及到两个重要的API,一个是SetWindowsHookEx,安装钩子;另一个是UnHookWindowsHookEx,卸载钩子。
Android Hook技术
而在 Android 操作系统中系统维护了自己的一套事件分发机制。应用程序,包括应用触发事件和后台逻辑处理,也是根据事件流程一步步地向下执行。而Android Hook正如Windows平台,在事件传送到终点前截获并监控事件的传输,像个钩子钩上事件一样,并且能够在钩上事件时,处理一些自己特定的事件。
Hook 的这个本领,使它能够将自身的代码「融入」被勾住(Hook)的程序的进程中,成为目标进程的一个部分。API Hook 技术是一种用于改变 API 执行结果的技术,能够将系统的 API 函数执行重定向。在 Android 系统中使用了沙箱机制,普通用户程序的进程空间都是独立的,程序的运行互不干扰。这就使我们希望通过一个程序改变其他程序的某些行为的想法不能直接实现,但是 Hook 的出现给我们开拓了解决此类问题的道路。当然,根据 Hook 对象与 Hook 后处理的事件方式不同,Hook 还分为不同的种类,比如消息 Hook、API Hook 等。
我们可以看到,对象A调用对象B,待对象B处理完后,将结果回调给对象A。而钩子,正是在这个类对象之间的调用与回调过程中,通过反射或代理的方式,串改系统进程的代码执行顺序,如下图:
可以看出Hook将自己注入到他要劫持的系统对象B所在的进程中,成为系统进程的一部分。而对象B就是就是我们所说的Hook点。
Hook分类
Hook Java 和 Hook Native:
● Hook Java 主要通过反射和代理实现,应用与SDK开发环境中修改Java代码;
● Hook Native 应用与NDK开发环境和系统开发修改Native代码。
应用程序进程Hook和全局Hook:
● 应用程序进程Hook只Hook当前所在的应用程序进程;
● 如果对Zygote进程进行Hook,那就可以实现将所以应用程序进程进行串改,这就是全局Hook。
Hook Demo
通过Java放射实现Android Hook 的小Demo(Android P之前)
● 目标:我们通过hook,在不通过MAndroidManifest.xml注册的情况下,手动将MainActivity替换成我们的TargetActivity。
● 基本思路:
我们知道要启动一个Activity,一般都会调用Intent.startActivity,那么hook点就可以从这里面入手。
//frameworks/base/core/java/android/app/Instrumentation.java
public Activity newActivity(Class<?> clazz, Context context,
IBinder token, Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
Object lastNonConfigurationInstance) throws InstantiationException,
IllegalAccessException {
Activity activity = (Activity)clazz.newInstance();
ActivityThread aThread = null;
activity.attach(context, aThread, this, token, 0, application, intent,
info, title, parent, id,
(Activity.NonConfigurationInstances)lastNonConfigurationInstance,
new Configuration(), null, null, null);
return activity;
}
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}
1.创建InstrumentationHook继承系统的Instrumentation,并重写父类的newActivity方法
public class InstrumentationHook extends Instrumentation {
@Override
public Activity newActivity(Class<?> clazz, Context context, IBinder token,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
Object lastNonConfigurationInstance) throws InstantiationException,
IllegalAccessException {
Log.d(this, " InstrumentationHook#newActivity call 1");
return super.newActivity(clazz, context, token, application, intent, info,
title, parent, id, lastNonConfigurationInstance);
}
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
Log.d(this, " InstrumentationHook#newActivity call 3 parmas className:" + className + " intent:" + intent.toString());
Activity activity = createActivity(intent);
if (activity != null) {
return activity;
}
return super.newActivity(cl, className, intent);
}
/*可以在createActivity拦截替换某个activity,下面自是一个简单例子*/
protected Activity createActivity(Intent intent) {
String className = intent.getComponent().getClassName();
Log.d(this, "createActivity className=" + className);
if ("hook.zecong.com.androidhook.MainActivity".equals(className)) {
try {
Class<? extends Activity> PluginActivity = (Class<? extends Activity>) Class
.forName("hook.zecong.com.androidhook.TargetActivity");
return PluginActivity.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}
2.获取当前应用的ActivityThread,并替换系统默认定义的mInstrumentation实例
public class HookManager {
static Object activityThreadInstance;
public static void init() throws ClassNotFoundException,
NoSuchMethodException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
Class<?> activityThread = Class.forName("android.app.ActivityThread");
Method currentActivityThread = activityThread
.getDeclaredMethod("currentActivityThread");
activityThreadInstance = currentActivityThread.invoke(null);
}
public static void injectInstrumentation() throws NoSuchFieldException,
IllegalAccessException, IllegalArgumentException {
Log.i(HookManager.class, " start injectInstrumentation");
Field field_instrumentation = activityThreadInstance.getClass()
.getDeclaredField("mInstrumentation");
field_instrumentation.setAccessible(true);
InstrumentationHook instrumentationHook = new InstrumentationHook();
field_instrumentation.set(activityThreadInstance, instrumentationHook);
}
}
3.在MyApplication的onCreate里替换ActivityThread里的mInstrumentation
public class MyApplication extends Application {
@Override
public void onCreate() {
try {
Log.d(this, " onCreate starting init");
HookManager.init();
HookManager.injectInstrumentation();
} catch (Exception e) {
Log.d(this, " onCreate e:" + e.toString());
}
super.onCreate();
}
}
通过动态代理实现Android Hook的小Demo
hook ClipboardManager.mService
思路:
● 得到 ClipboardManager 的 mService
● 初始化动态代理对象
● 偷梁换柱,使用 proxyNotiMng 替换系统的 mService
核心代码
public static void hookClipboardService(final Context context) throws Exception {
ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
Field mServiceFiled = ClipboardManager.class.getDeclaredField("mService");
mServiceFiled.setAccessible(true);
// 第一步:得到系统的 mService
final Object mService = mServiceFiled.get(clipboardManager);
// 第二步:初始化动态代理对象
Class aClass = Class.forName("android.content.IClipboard");
Object proxyInstance = Proxy.newProxyInstance(context.getClass().getClassLoader(), new
Class[]{aClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.d(TAG, "invoke(). method:" + method);
String name = method.getName();
if (args != null && args.length > 0) {
for (Object arg : args) {
Log.d(TAG, "invoke: arg=" + arg);
}
}
if ("setPrimaryClip".equals(name)) {
Object arg = args[0];
if (arg instanceof ClipData) {
ClipData clipData = (ClipData) arg;
int itemCount = clipData.getItemCount();
for (int i = 0; i < itemCount; i++) {
ClipData.Item item = clipData.getItemAt(i);
Log.i(TAG, "invoke: item=" + item);
}
}
Toast.makeText(context, "检测到有人设置粘贴板内容", Toast.LENGTH_SHORT).show();
} else if ("getPrimaryClip".equals(name)) {
Toast.makeText(context, "检测到有人要获取粘贴板的内容", Toast.LENGTH_SHORT).show();
}
// 操作交由 sOriginService 处理,不拦截通知
return method.invoke(mService, args);
}
});
// 第三步:偷梁换柱,使用 proxyNotiMng 替换系统的 mService
Field sServiceField = ClipboardManager.class.getDeclaredField("mService");
sServiceField.setAccessible(true);
sServiceField.set(clipboardManager, proxyInstance);
}
常用的 Hook 框架
- Xposed
在Android系统中,应用程序进程都是由Zygote进程孵化出来的,而Zygote进程是由Init进程启动的。Zygote进程在启动时会创建一个Dalvik虚拟机实例,每当它孵化一个新的应用程序进程时,都会将这个Dalvik虚拟机实例复制到新的应用程序进程里面去,从而使得每一个应用程序进程都有一个独立的Dalvik虚拟机实例。这也是Xposed选择替换app_process的原因。
Zygote进程在启动的过程中,除了会创建一个Dalvik虚拟机实例之外,还会将Java运行时库加载到进程中来,以及注册一些Android核心类的JNI方法来前面创建的Dalvik虚拟机实例中去。注意,一个应用程序进程被Zygote进程孵化出来的时候,不仅会获得Zygote进程中的Dalvik虚拟机实例拷贝,还会与Zygote一起共享Java运行时库。这也就是可以将XposedBridge这个jar包加载到每一个Android应用程序中的原因。XposedBridge有一个私有的Native(JNI)方法hookMethodNative,这个方法也在app_process中使用。这个函数提供一个方法对象利用Java的Reflection机制来对内置方法覆写。有能力的可以针对xposed的源码进行分析,不得不说,作者对于android的机制和java的了解已经相当深入了。
- frida
Frida是一款基于python + javascript 的hook框架,支持android\ios\linux\win\osx等各平台,由于是基于脚本的交互,因此相比xposed和substrace cydia更加便捷。
- Cydia Substrate
Cydia Substrate 框架为苹果用户提供了越狱相关的服务框架,当然也推出了 Android 版 。Cydia Substrate 是一个代码修改平台,它可以修改任何进程的代码。不管是用 Java 还是 C/C++(native代码)编写的,而 Xposed 只支持 Hook app_process 中的 Java 函数。
- Legend
Legend 是 Android 免 Root 环境下的一个 Apk Hook 框架,该框架代码设计简洁,通用性高,适合逆向工程时一些 Hook 场景。大部分的功能都放到了 Java 层,这样的兼容性就非常好。 原理是这样的,直接构造出新旧方法对应的虚拟机数据结构,然后替换信息写到内存中即可。