16.Xposed库实践设计
目录介绍
- 01.整体概述介绍
- 1.1 项目背景
- 1.2 遇到问题
- 1.3 设计目标
- 1.4 产生收益
- 02.开发设计思路
- 2.1 整体设计思路
- 2.2 设计初始化思路
- 2.3 封装参数设计
- 2.4 解析路径设计
- 2.5 读取资源设计
- 2.6 缓存方案设计
- 2.7 图片解码和压缩设计
- 2.8 图片显示设计
- 2.9 其他一些设计
- 03.Glide原理思考
- 3.1 要思考一些问题
- 3.2 原理流程的概括
- 3.3 初始化流程
- 3.4 监听组件销毁
- 3.5 监听无用对象
- 3.6 监控内存泄漏
- 3.7 Dump内存快照
- 3.8 分析堆快照
- 3.9 输出分析报告
- 04.一些技术点思考
- 4.5 缓存方案思考
- 4.6 加载进度思考
- 05.方案基础设计
- 5.1 整体架构图
- 5.2 UML设计图
- 5.3 关键流程图
- 5.4 接口设计图
- 5.5 模块间依赖关系
- 06.其他设计说明
- 6.1 性能设计
- 6.2 稳定性设计
- 6.3 灰度设计
- 6.4 降级设计
- 6.5 异常设计
01.整体概述介绍
1.1 项目背景
1.2 基础概念
1.3 设计目标
1.4 产生收益
03.作弊器的监控
3.1 监控介绍
- 关于作弊器的监控,目前主要由安全SDK实现,分别监控手机是否安装了作弊器(例如Xposed)和我们APP运行时是否受作弊器的影响,将这些数据上报到后台。
- 不同的作弊器,监控的方法是不同的,随着作弊器技术的提升,相关的监控维度也在不断的扩大。
- 这里以Xposed作弊器的监控方法为例子。Xposed的监控分为Java层和Native层。
3.2 Xposed的Java层检测
- 判断是否安装Xposed Installer相关的软件包
- 最简单的检测,我们调用Android提供的PackageManager的API来遍历系统中App的安装情况来辨别是否有安装Xposed Installer相关的软件包。
- 通常情况下使用Xposed Installer框架都会屏蔽对其的检测,即Hook掉PackageManager的getInstalledApplications方法的返回值,以便过滤掉de.robv.android.xposed.installer来躲避这种检测。
PackageManager packageManager = context.getPackageManager(); List applicationInfoList = packageManager.getInstalledApplications(PackageManager.GET_META_DATA); for (ApplicationInfo applicationInfo: applicationInfoList) { if (applicationInfo.packageName.equals("de.robv.android.xposed.installer")) { // is Xposed TODO... } }
- 自造异常读取栈。
- Xposed Installer框架对每个由Zygote孵化的App进程都会介入,因此在程序方法异常栈中就会出现Xposed相关的“身影”,我们可以通过自造异常Catch来读取异常堆栈的形式,用以检查其中是否存在Xposed的调用方法。
/** * 通过主动抛出异常,检查堆栈信息来判断是否存在XP框架 * @return */ public static boolean isXposedExistByThrow() { try { throw new Exception("gg"); } catch (Throwable e) { return isXposedExists(e); } }
3.3 Xposed的Native层检测
- 为了有效提搞检测准确率,就须做到Java和Native层同时检测。
- 每个App在系统中都有对应的加载库列表,这些加载库列表在/proc/下对应的pid/maps文件中描述,在Native层读取/proc/self/maps文件不失为检测Xposed Installer的有效办法之一。
- 由于Xposed Installer通常只能Hook Java层,因此在Native层使用C来解析/proc/self/maps文件,搜检App自身加载的库中是否存在XposedBridge.jar、相关的Dex、Jar和So库等文件。
bool is_xposed() { bool rel = false; FILE *fp = NULL; char* filepath = "/proc/self/maps"; string xp_name = "XposedBridge.jar"; fp = fopen(filepath,"r")) while (!feof(fp)) { fgets(strLine,BUFFER_SIZE,fp); origin_str = strLine; str = trim(origin_str); if (contain(str,xp_name)) { rel = true; //检测到Xposed模块 break; } } }
04.作弊器的治理
4.1 治理业务背景
- 治理主要从作弊器对我们具体业务场景的影响做防御。
4.2 第一阶段
4.3 第二阶段
- 梳理项目中有哪些没有被混淆的类,探讨这些类是否可以被混淆。因为作弊器获取行程信息的入口就是从没有被混淆的Xxx类下手的。
- 与安全SDK的同事沟通,提升作弊器的检测的准确性,并与产品一起商讨如何处置目前的作弊用户。
05.Xposed必备知识
- Xposed技术的核心之一就是Hook Android系统启动流程和APP启动流程,为了方便各位能更好的理解后面要讲解的Xposed,接下来先讲解Android系统启动流程和APP启动流程。
5.1 Android系统启动流程
- 将Android系统的启动分成八层(或者说八个大步骤)
- 按下电源时引导芯片从代码从预定义的地方(固化在在Rom)开始执行,加载引导程序BootLoaer到RAM。
- BootLoader程序把系统OS拉起来并运行。
- Linux内核启动,这里面我们最关心的是init进程的启动,它是所有用户进程的鼻祖。
- 初始化init进程,这里面最重要的是启动Zygote进程,它是所有APP 进程的鼻祖(或者说是Java进程)。
- 初始化Zygote进程,创建运行APP所需要的服务,例如Java虚拟机、注册JNI方法以及启动SystemServer进程。
- 初始化SystemServer进程,这里最重要的就是启动Binder线程池以及一些核心服务,比如PMS、WMS、AMS等。
- AMS是管理Android 四大组件的核心服务,系统启动后会让AMS将系统桌面(也就是Launcher)加载出来。
- Launcher作为所有APP 的入口,点击Launcher上的图标后就会启动APP(如果APP进程不在,会先Fork Zygote进程来创建新进程)。
- Hook系统要点
- 上述流程中最关键的节点是启动Zygote,因为APP进程都是由Zygote进程孵化(fork)而来的,fork时不仅仅会获得Zygote进程中的Dalvik虚拟机实例拷贝,还会与Zygote一起共享Java运行时库,所以只要Hook了Zygote进程,就可以对手机里所有的APP实现全局注入攻击。
5.2 APP启动流程
- APP启动流程可以分三个阶段:
- Launcher请求AMS阶段。
- AMS到ApplicationThread的调用过程。
- ActivityThread启动Activity过程。
- Xposed Hook的是第三个阶段
- ActivityThread启动Activity的过程。因为在ActivityThread启动Activity的过程中,可以获取到当前Activity的所有信息以及Activity所在的进程信息。比如:
ClassLoader packageName processName applicationInfo
- 通过获取上述信息,就可以在目标进程中执行任意操作,比如显示一个弹窗或者修改、获取任意方法的返回值。上述信息被封装在AppBindData中。关于AppBindData的初始化,下面看下流程图:
- 图31.3
- 从上述的流程图中可以得出,获取目标APP核心信息最佳点就是ActivityThread里的bindApplication方法,这也是Xposed Hook的点。
5.3 Xposed框架概述
- Xposed原理简介
- Xposed是一个针对Android平台的动态劫持项目,通过替换/system/bin/app_process程序控制zygote进程,使得app_process在启动过程中会加载XposedBridge.jar这个jar包,从而完成对Zygote进程及其创建的Dalvik虚拟机的劫持,Xposed在开机的时候完成对所有的Hook Function的劫持,在原Function执行的前后加上自定义代码。框架核心思想在于将java层普通函数注册成本地JNI方法,以此来变相实现hook机制。
- Xposed工作原理图
- 31.4
5.4 如何Hook Android系统
- 主要分两步实现:
- 选择Hook点,Hook系统方法
- 替换系统文件
- Hook系统方法
- init进程→init.rc→app_process(app_main.cpp) →启动Zygote进程→ZygoteInit的main() →startSystemServer() →fork出system_server子进程。
app_main.cpp ...... if (zygote) { // 这里调用AndroidRuntime类的Start方法,里面会做如下几件重要的事情: // 1、初始化JNI接口,2、创建Java虚拟机,3、注册JNI方法,4、调用ZygoteInit类初始化SystemServer进程 runtime.start("com.android.internal.os.ZygoteInit", args, zygote); } else if (className) { runtime.start("com.android.internal.os.RuntimeInit", args, zygote); } else { fprintf(stderr, "Error: no class name or --zygote supplied.\n"); app_usage(); LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied."); }
- 概括起来编译生成自定义app_process→ 把原先调用ZygoteInit.main()处 改为调用XposedInit.main() → Hook资源和一些准备工作 → 调用系统原本启动Zygote的方法。
- 替换系统文件
- 因为替换流程非常复杂,这里不做详细分析。给大家一个结论,替换流程如下:XposedInstaller下载补丁包 → 获取root权限 → 解压复制update-binary文件到特定目录 → 文件执行时会调用flash-script.sh脚本,将app_process、Xposedbridge.jar、so库等写到系统私有目录。