编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题

目录介绍

  • 02.Hook基本流程
  • 03.运行期Hook技术
  • 04.编译器Hook技术
  • 07.反编译apk看代码

02.Hook基本流程

基本流程 1、根据需求确定 要 hook 的对象
2、寻找要hook的对象的持有者,拿到要 hook 的对象
3、定义“要 hook 的对象”的代理类,并且创建该类的对象
4、使用上一步创建出来的对象,替换掉要 hook 的对象

使用示例

/**
* hook的核心代码
* 这个方法的唯一目的:用自己的点击事件,替换掉 View 原来的点击事件
*
* @param view hook的范围仅限于这个view
*/
@SuppressLint({"DiscouragedPrivateApi", "PrivateApi"})
public static void hook(Context context, final View view) {
    try {
        // 反射执行View类的getListenerInfo()方法,拿到v的mListenerInfo对象,这个对象就是点击事件的持有者
        Method method = View.class.getDeclaredMethod("getListenerInfo");
        method.setAccessible(true);//由于getListenerInfo()方法并不是public的,所以要加这个代码来保证访问权限
        Object mListenerInfo = method.invoke(view);//这里拿到的就是mListenerInfo对象,也就是点击事件的持有者

        // 要从这里面拿到当前的点击事件对象
        Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");// 这是内部类的表示方法
        Field field = listenerInfoClz.getDeclaredField("mOnClickListener");
        final View.OnClickListener onClickListenerInstance = (View.OnClickListener) field.get(mListenerInfo);//取得真实的mOnClickListener对象

        // 2. 创建我们自己的点击事件代理类
        //   方式1:自己创建代理类
        //   ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance);
        //   方式2:由于View.OnClickListener是一个接口,所以可以直接用动态代理模式
        // Proxy.newProxyInstance的3个参数依次分别是:
        // 本地的类加载器;
        // 代理类的对象所继承的接口(用Class数组表示,支持多个接口)
        // 代理类的实际逻辑,封装在new出来的InvocationHandler内
        Object proxyOnClickListener = Proxy.newProxyInstance(context.getClass().getClassLoader(), new Class[]{View.OnClickListener.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Log.d("HookSetOnClickListener", "点击事件被hook到了");//加入自己的逻辑
                return method.invoke(onClickListenerInstance, args);//执行被代理的对象的逻辑
            }
        });
        // 3. 用我们自己的点击事件代理类,设置到"持有者"中
        field.set(mListenerInfo, proxyOnClickListener);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

// 自定义代理类
static class ProxyOnClickListener implements View.OnClickListener {
    View.OnClickListener oriLis;

    public ProxyOnClickListener(View.OnClickListener oriLis) {
        this.oriLis = oriLis;
    }

    @Override
    public void onClick(View v) {
        Log.d("HookSetOnClickListener", "点击事件被hook到了");
        if (oriLis != null) {
            oriLis.onClick(v);
        }
    }
}

03.运行期Hook技术

3.1 Xposed

  • 如果你对Xposed比较熟悉,并且手头有个root的设备安装了Xposed框架,那么直接开发一个Xposed模块来hook指定方法就可以了。
  • 由于我的测试设备是有root权限的,Xposed方案对我来说难度不大,不过对于普通用户,有没有免root的方式呢?

3.2 VirtualXposed

  • VirtualXposed 是基于VirtualApp 和 epic 在非ROOT环境下运行Xposed模块的实现(支持5.0~10.0)。
    • https://github.com/android-hacker/VirtualXposed
  • VirtualXposed其实就是一个支持Xposed的虚拟机,我们把开发好的Xposed模块和对应需要hook的App安装上去就能实现hook功能。

3.3 epic

  • 在ART上重新实现了Dexposed,有着与Dexposed完全相同的能力和API,项目地址是epic。https://github.com/tiann/epic
  • 所以如果不想折腾 Xposed 或者 VirtualXposed,只要在应用内接入epic,就可以实现应用内Xposed hook功能,满足运行hook需求。
  • epic 存在兼容性问题,例如Android 11 只支持64位App,所以建议只在debug环境使用。

04.编译器Hook技术

  • 手把手讲解 Android Hook入门Demo
    • https://www.jianshu.com/p/74c12164ffca?tdsourcetag=s_pcqq_aiomsg
  • ASM hook隐私方法调用,防止App被下架
    • https://juejin.cn/post/7043399520486424612
  • hook库
    • https://github.com/eleme/lancet

07.反编译apk看代码

  • 反编译apk文件查看源代码工具
    • apktool
    • dex2jar
    • jd-gui
  • 三个工具介绍
    • apktool
      • 作用:资源文件获取,可以提取出图片文件和布局文件进行使用查看
      • 下载地址:https://ibotpeaches.github.io/Apktool/
    • dex2jar
      • 作用:将apk反编译成java源码(classes.dex转化成jar文件)
      • 下载地址:https://github.com/pxb1988/dex2jar
    • jd-gui
      • 作用:查看APK中classes.dex转化成出的jar文件,即源码文件。
      • 下载地址:https://mac.softpedia.com/get/Development/Java/JD-GUI.shtml
      • 将生成的classes-dex2jar.jar直接拖动进入界面中,就可以看到反编译之后的源码结构了。
      • 注:jd-gui 可视化反编译查看 .jar 文件。这里存在一个坑,jd-gui只能适配java8,否则无法正常打开运行
  • 反编译流程
    • 第一步:apk反编译得到程序的源代码。直接执行:apktool d xxx.apk
      • 执行命令apktool d apk文件会生成一个文件夹,这个文件夹下可以看到apk中的资源文件,如图片,xml文件等等
      • 运行命令:apktool b 反编译生成的文件夹,会生成build文件夹。其中classes.dex文件就是我们所需要的了,不同的apk会生成不同数量的classes.dex文件
    • 第二步:直接运行./d2j-dex2jar.sh apk路径
      • 可以在dex2jar文件夹中看到classes-dex2jar.jar文件,这个就是最终的java文件
    • 第三步:直接打开gui,把编译的jar直接拖到该工具中查看,即可完成操作。
贡献者: yangchong211