初探Hook技术

QuibblerQuibbler 2020-08-20 约 23 分钟 197 次阅读

初探 Hook 技术

Hook(钩子)是 Android 进阶绕不开的技术。插件化框架能启动未在清单注册的 Activity、热修复能动态替换线上 Bug、双开应用能在同一台手机跑两份微信、无埋点 SDK 能自动采集所有点击事件——这些"魔法"背后都是 Hook。它听起来神秘,本质却只是"劫持方法调用、插入自定义逻辑"。本文初探 Hook,讲清它是什么、Java 层两大核心手段(反射 + 动态代理)、经典应用场景,并做 Hook 点击事件、Hook AMS 两个实战。参考 Hook 技术看这篇就够了 与 Android Hook 机制之简单实战。

1、Hook 是什么

Hook 原意"钩子",在 Windows 时代就有消息 Hook(SetWindowsHookEx),用来拦截键盘鼠标消息。到了 Android/Java 世界,Hook 指劫持程序原有的方法调用,在调用前后插入自己的逻辑,甚至替换整个方法的实现。

举个直观例子:一个 Button 的 onClick 你写死了跳转 A 页面,但现在你想"在不改源码的前提下,每次点击都先打一条日志再跳转"——用 Hook 拦截 OnClickListener,就能做到无侵入地加上埋点。这就是 Hook 的价值:不改源码,改变行为。

核心实现(本质认知):

1. Hook = 钩子,本质是拦截方法调用 + 注入自定义逻辑

2. 它不修改源码,运行期"偷换"目标对象/方法,对调用方透明

3. 拦截点(Hook 点)通常是系统的单例对象或保存在成员变量里的接口实例,因为它们能被反射拿到并替换

4. 一句话:找到 Hook 点 → 用代理/反射替换它 → 在替换对象里加私货

2、Hook 的分类与常用框架

按 Hook 所处的层级和手段,可分为三大类,对应的框架和场景也各不相同:

层级          手段                    典型场景                常用框架
------------------------------------------------------------------------
Java 层       反射 + 动态代理          插件化/热修复/埋点       VirtualApp/DroidPlugin
Native 层     PLT/Inline Hook          so 注入/逆向分析         Xposed/Frida/Substrate
字节码层      编译期/加载期插桩        AOP 监控/热修复          ASM/AspectJ/epic

常见框架速览:

       - Xposed:需 root,Hook zygote,能改任意 App 的任意方法,刷机党做模块的最爱

       - VirtualXposed:无需 root,在虚拟空间里跑 Xposed 模块,门槛低

       - Frida:跨平台动态 Hook,Python 控制端 + JS 注入,逆向调试神器

       - epic / SandHook:Art 方法 Hook,常用于热修复(在 Art 层替换方法)

本文聚焦 Java 层 Hook——它不需要 root、不需要 NDK,纯 Java 就能玩,是 Android 应用层开发最实用的 Hook 形式。

3、Java 层两大核心手段

Java 层 Hook 几乎都建立在这两件武器上:反射拿到私有对象,动态代理包装替换它。两者经常配合使用。

3.1、反射:突破访问限制

系统的关键对象往往是 private 的成员变量(如 View 的 mListenerInfo、ActivityManagerNative 的 gDefault)。反射能拿到并修改它们:

// 反射:读取/修改对象的私有字段
Class<?> clazz = obj.getClass();
Field field = clazz.getDeclaredField("mOnClickListener");
field.setAccessible(true);          // 突破 private 限制
Object value = field.get(obj);      // 读取原值
field.set(obj, newValue);           // 替换为新值

// 反射修改系统静态单例(如 ActivityManagerNative.gDefault)
Field gDefault = Class.forName("android.app.ActivityManagerNative")
        .getDeclaredField("gDefault");
gDefault.setAccessible(true);
Object rawIActivityManager = gDefault.get(null);   // 静态字段传 null

核心实现:

1. getDeclaredField 能拿到任意声明字段(含 private),getField 只能拿 public

2. setAccessible(true) 是关键,否则 private 字段拒绝访问

3. 修改静态字段时 field.get(null) 传 null;实例字段传具体对象

4. 反射是 Hook 的"开锁 工具",负责拿到系统内部那个要替换的对象

3.2、动态代理:注入自定义逻辑

拿到原始对象后,要在它的方法调用前后加逻辑,最快的方式是动态代理——为接口生成一个代理对象,任何方法调用都会先进入 InvocationHandler.invoke:

// 动态代理:为接口生成代理对象,在 invoke 里统一拦截
Object proxy = Proxy.newProxyInstance(
        classLoader,                                  // 类加载器
        new Class[]{View.OnClickListener.class},      // 代理的接口(必须接口)
        new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 任何方法调用都会先到这里
                if ("onClick".equals(method.getName())) {
                    Log.d("Hook", "方法被拦截,可插入埋点");
                }
                return method.invoke(original, args);  // 调用原对象的真实逻辑
            }
        });
// 把 proxy 替换回原位置,Hook 即生效

核心实现:

1. Proxy.newProxyInstance 为接口生成代理类,它只能代理接口不能代理类

2. 所有方法调用都汇聚到 invoke,按 method.getName() 区分要拦截哪个

3. method.invoke(original, args) 转发给原对象,保证原逻辑不丢

4. 反射 + 动态代理的套路:反射拿原对象 → 代理包装 → 反射塞回去

4、经典应用场景

Hook 之所以重要,是因为一大半 Android 黑科技都建立在它之上:

       - 插件化:Hook AMS / Instrumentation,让未在清单注册的插件 Activity 能被启动(VirtualApp、DroidPlugin)

       - 热修复:Hook ClassLoader 或 Art 方法,运行时把有 Bug 的类替换成修复后的类(AndFix、Sophix)

       - 应用双开/沙箱:Hook 一系列系统服务,在宿主进程里虚拟出一套运行环境(VirtualApp)

       - 无侵入埋点:Hook View.OnClickListener、EditText 文本变化等,自动采集用户行为

       - 推送保活:Hook AMS 拦截 stopService / removeTask,让后台 Service 难被杀

       - Xposed 模块:Hook 任意第三方 App,去广告、改 UI、加功能(需 root)

5、实战一:Hook 点击事件做埋点

这是最经典的入门 Hook:给一个 View 的点击事件套一层代理,实现"不改业务代码、自动上报埋点"。思路是反射拿到 View 内部的 mOnClickListener,用动态代理包装后塞回去。

// Hook View 的 OnClickListener,插入无埋点逻辑
public static void hookView(View view) {
    try {
        // 1. 反射拿 View 的 mListenerInfo 字段
        Class<?> viewClass = View.class;
        Field liField = viewClass.getDeclaredField("mListenerInfo");
        liField.setAccessible(true);
        Object listenerInfo = liField.get(view);
        if (listenerInfo == null) return;

        // 2. 从 ListenerInfo 拿原始 mOnClickListener
        Class<?> liClass = listenerInfo.getClass();
        Field onClickField = liClass.getDeclaredField("mOnClickListener");
        onClickField.setAccessible(true);
        View.OnClickListener original =
                (View.OnClickListener) onClickField.get(listenerInfo);
        if (original == null) return;

        // 3. 动态代理包装原 Listener
        Object proxy = Proxy.newProxyInstance(
                view.getContext().getClassLoader(),
                new Class[]{View.OnClickListener.class},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object p, Method method, Object[] args) throws Throwable {
                        Log.d("Hook", "点击被拦截,上报埋点");
                        return method.invoke(original, args);   // 调用原逻辑
                    }
                });

        // 4. 替换回 ListenerInfo,Hook 生效
        onClickField.set(listenerInfo, proxy);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

核心实现:

1. 反射 View.mListenerInfo → 再反射 ListenerInfo.mOnClickListener,两层拿到原 Listener

2. 用 Proxy.newProxyInstance 为 OnClickListener 接口生成代理

3. invoke 里先打日志/上报,再 method.invoke(original) 转发,保证原点击逻辑不丢

4. 把代理塞回 mOnClickListener 字段,之后点击就会先走代理——这就是"无埋点 SDK"的雏形

6、实战二:Hook AMS 启动插件 Activity

插件化的核心难题:插件 Activity 没在宿主清单注册,startActivity 时 AMS 校验过不了。解法是 Hook AMS 的 IActivityManager,在它执行 startActivity 前"偷换" intent——用宿主里一个已注册的"占坑 Activity"骗过校验,真正启动时再换回插件 Activity。

// Hook AMS:用动态代理拦截 startActivity(思路骨架,省略 intent 偷换细节)
public static void hookAMS() throws Exception {
    // 1. 反射拿 ActivityManagerNative 的 gDefault 单例
    Class<?> amnClass = Class.forName("android.app.ActivityManagerNative");
    Field gDefaultField = amnClass.getDeclaredField("gDefault");
    gDefaultField.setAccessible(true);
    Object gDefault = gDefaultField.get(null);

    // 2. gDefault 是 Singleton,取它内部的 mInstance(真正的 IActivityManager)
    Class<?> singletonClass = Class.forName("android.util.Singleton");
    Field mInstanceField = singletonClass.getDeclaredField("mInstance");
    mInstanceField.setAccessible(true);
    final Object rawIAM = mInstanceField.get(gDefault);

    // 3. 为 IActivityManager 生成动态代理
    Class<?> iamClass = Class.forName("android.app.IActivityManager");
    Object proxy = Proxy.newProxyInstance(
            Thread.currentThread().getContextClassLoader(),
            new Class[]{iamClass},
            new InvocationHandler() {
                @Override
                public Object invoke(Object p, Method method, Object[] args) throws Throwable {
                    if ("startActivity".equals(method.getName())) {
                        // 这里把目标 intent 换成占坑 Activity,骗过 AMS 清单校验
                        // ...替换 args 中的 intent...
                    }
                    return method.invoke(rawIAM, args);   // 转发给系统真实 AMS
                }
            });

    // 4. 把代理塞回 Singleton.mInstance,全局生效
    mInstanceField.set(gDefault, proxy);
}

核心实现:

1. 反射 ActivityManagerNative.gDefault 拿到 Singleton 单例,再取其 mInstance 即 IActivityManager

2. 为 IActivityManager 接口生成动态代理,拦截 startActivity

3. 在拦截里偷换 intent:用占坑 Activity 通过 AMS 校验,后续再换回插件 Activity

4. 把代理塞回 mInstance,全局生效——之后所有 startActivity 都先走代理

- 这是《Android 进阶解密》Hook 章节的核心套路,Hook Instrumentation 也是同理

7、注意事项与限制

Hook 是把利器也是把双刃剑,实战中有不少坑和限制:

① 隐藏 API 限制。Android 9 (P) 起,系统对反射访问 @hide 隐藏 API 做了限制,分白名单/灰名单/黑名单——黑名单字段反射直接抛异常。Hook AMS 这类深度依赖隐藏 API 的方案,在新版本需借助 freeReflection 等手段绕过。

② 版本兼容。系统类名和字段随版本变化:如 ActivityManagerNative.gDefault 在高版本已迁移到 ActivityManager.getIActivityManager。Hook 代码必须按版本分支处理,否则直接崩。

③ Native Hook 需 root。Xposed、Frida 等 Native Hook 要么 root 后 Hook zygote,要么注入目标进程,普通应用做不到。

④ 性能与稳定性。反射有开销,动态代理在热点路径上可能拖慢性能;Hook 系统服务一旦失败可能进程崩溃,务必 try-catch 兜底。

⑤ 合规风险。Hook 第三方 App、篡改其他应用行为可能违反平台政策或法律,线上产品慎用。

8、总结

Hook 的本质是"拦截方法调用、注入自定义逻辑",在 Android 上它撑起了插件化、热修复、双开、无埋点等一整套黑科技。Java 层 Hook 的核心,就是"反射拿对象 + 动态代理包一层 + 塞回去"三步走。

关键要点:

       - Hook = 钩子,不改源码、改变行为,Hook 点常是系统单例或成员变量里的接口

       - Java 层两大手段:反射(突破访问限制拿对象)+ 动态代理(注入逻辑)

       - 经典场景:插件化(Hook AMS)、热修复(Hook ClassLoader)、双开、无埋点、保活

       - 实战套路:Hook OnClickListener 做埋点;Hook IActivityManager 启动插件 Activity

       - 注意:Android P 隐藏 API 限制、版本兼容、Native Hook 需 root、性能与合规风险

对于想从应用开发进阶到框架、插件化、热修复领域的开发者而言,掌握"反射 + 动态代理"这套 Java 层 Hook 基本功是必要的——它是理解 VirtualApp、Tinker、Sophix 等开源框架的钥匙。先把 Hook 点击事件这种小目标跑通,再去啃 Hook AMS / Instrumentation 启动插件 Activity 的完整流程,就能逐步摸透 Android 运行时的"地下世界",为后续做插件化框架、热修复方案打下底层基础。

相关推荐

精选
ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager
Android

ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager

ViewPager1、ViewPagerandroidx.viewpager.widget.ViewPager,Android中使用非常广泛的控件,可以说是APP必备:首次打开引导页、页面Banner广告等。常用方法:setAdapter() 设置适配器setOffscreenPageLimit() 设置缓存的页面个数,默认是 1setCurrentItem() 跳转到特定的页面setOnPage

1.1k
获取Android内置WebView内核版本
Android

获取Android内置WebView内核版本

获取Android内置WebView内核版本竟然能遇到这样奇葩的事情,网页用的技术过于新颖,以至于只支持高版本Chromium内核的,低版本安卓系统中内置的内核版本较低,无法加载前端页面。1、设置查看在系统设置里 > 应用 > 应用管理 > 显示系统应用,查看WebView组件:2、页面查看通过WebView发起的网络请求,都会带上浏览器的UA,通常页面都可以通过UA判断浏览器的内核版本。这里有两

1.1w
Android 14适配总结
Android

Android 14适配总结

Android 14适配总结毕业工作至今已经适配了三个Android大版本,从Android 11到Android 12、再到Android 13。2023年,Google即将推出的Android 14,上半年已经开始第一批适配。现在,第四个Android版本已经适配完,总结记录一下。1、Android 14计划Google一般会在2月份对外发布预告,同时放出开发者预览版。“拉通”各大平台、厂商以

1.1w