Android插件化、热修复原理简介

1、如何规避Android P对私有API的访问限制

● Android:Pie — 9.0 — 28
● 一般都是通过反射访问私有 API

1.1、私有API

Android源码查看网址
Android插件化、热修复原理简介_第1张图片

(1)hide public

Android插件化、热修复原理简介_第2张图片
hide public方法无法直接使用,可以自行编译系统源码,并导入项目工程,从而访问到。
比如 convertFromTranslucent() 是 Acticity 中的方法,我们可以直接把 Activity 的源码放到工程里,就可以访问到这个方法。
(android.jar编译的Activity中没有convertFromTranslucent()方法,我们可以自己搞一个privaded.jar把系统源码搞过来,就可以骗过编译器,运行时加载的那个类里面肯定是有这个方法的)

(2)private

Android插件化、热修复原理简介_第3张图片

private 彻底访问不到,只能通过反射。

Method initMethod = AssetManager.class.getDeclaredMethod("init");
initMethod.setAccessible(true);

反射:1)不仅可以绕过访问权限控制 2)还可以修改final变量

1.2、Android P 的API名单

Android插件化、热修复原理简介_第4张图片

● 白名单中的API:谁都能用
● 浅灰名单中的API:反射可用
● 深灰名单中的API:SDK < 28,允许使用;SDK >= 28,不允许使用,同黑名单
● 黑名单中的API:反射不可用。如 getDeclaredField()、getDeclaredMethod() 获取不到任何东西

1.3、Android P对反射做了什么

Android插件化、热修复原理简介_第5张图片


Android插件化、热修复原理简介_第6张图片

  • 如果 action == kDeny,就会返回 true,就会 block

Android插件化、热修复原理简介_第7张图片

  • 有三个 return,只要让这三个 return 的返回值不是 kDeny 就不会 block

(1)第一个 hook 点

Android插件化、热修复原理简介_第8张图片


Android插件化、热修复原理简介_第9张图片

● GetActionFromAccessFlags() 中,如果是白名单 kWhitelist,就返回 kAllow
● 否则,获取 policy,如果 policy 是 kNoChecks,就返回 kAllow

在这里插入图片描述

● GetHiddenApiEnforcementPolicy() 返回的是一个 Runtime 里的成员 hidden_api_policy_
● 只要能拿到 Runtime 就可以把它篡改掉

修改 Runtime 的 hidden_api_policy

Android插件化、热修复原理简介_第10张图片


在这里插入图片描述

● javaVM 我们可以拿到,在写 JNI 的时候就会经常遇到 javaVM
● 要把 javaVM 强转成 JavaVMExt,然后通过指针获取到 runtime
● 但是 JavaVMExt 是系统代码里的,我们拿不到

Android插件化、热修复原理简介_第11张图片

● JavaVMExt 继承自 JavaVM,JavaVMExt 中只有两个成员变量,一个是父类继承过来的 JNIInvokeInterface*,一个是自己的 Runtime,剩下的都是函数
● 我们可以利用C++的特性,自己定义一个结构体 struct JavaVMExt,就两个成员,一个叫 functions,一个叫 runtime,管你是什么类型,直接定义成任意类型 void*,后面我想要什么类型时就强转成什么类型,这样就可以拿到 Runtime,这就是 C++
● 需要注意,JavaVMExt 中没有虚方法表,否则自定义的 struct JavaVMExt 结构体中还需要加一个虚方法表的指针

Android插件化、热修复原理简介_第12张图片

● 我们的 runtime 是 void*,想要 Runtime 类型是拿不到的,因为 Runtime 是定义在系统源码里的
● 我们自己定义一个 PartialRuntime
● C++ 结构体中的变量都是4个字节4个字节的一个个排下来的,runtime 的指针指向这个结构体的第一个成员,那我们就一个个往下数,直到数到 target_sdk_version,target_sdk_version 我们是知道的,就是我们定义在 build.gradle(:app) 中的 api 版本,比如28。只要数到一个值是28,那么就是 target_sdk_version,就把这个地址作为 PartialRuntime 的起点,然后依次往后数,数到 EnforcementPolicy 就可以了

Android插件化、热修复原理简介_第13张图片

  • EnforcementPolicy,ExperimentalFlags 等可以直接自己定义

Android插件化、热修复原理简介_第14张图片

  • 找的代码也很简单,就是一个 for 循环,每次 +4,因为是4个字节
hook流程总结

Android插件化、热修复原理简介_第15张图片

● 首先拿到 javaVM 的指针:一种方式是从 JNI_ON_LOAD 拿,这个代码一定是个 so 库;另一种方式是直接通过 JNI_ENV去get。此处采用的是方式二
● 然后将拿到的 javaVM 强转成自己定义的 JavaVMExt
● 于是就可以拿到我们定义的 runtime,但它是一个 void *
● 然后我们通过 findOffset() 4个字节4个字节的数到 targetSdkVersion 的地址(指针)
● 于是我们就可以把 targetSdkVersion 指针强转成我们定义的 PartialRuntime*
● 于是就可以获取到 hidden_api_policy_,并对其进行修改

(2)第二个 hook 点

Android插件化、热修复原理简介_第16张图片

  • fn_caller_is_trusted():如果是一个信任的调用者就没问题,通常是系统类

Android插件化、热修复原理简介_第17张图片

class loader是空,就返回true

【方式 1】Java 反射

Android插件化、热修复原理简介_第18张图片

  • classLoader在浅灰名单中,可以通过反射修改

【方式 2】C++

Android插件化、热修复原理简介_第19张图片

  • Java的Class对应着Native层的一个struct Class,将他的classLoader置为空即可

注意:
ClassLoader 置空只限于你使用的这一个类,如果想让所有类都可以访问私有API,那么就要将所有类的ClassLoader 置空,工作量比较大

(3)第三个 hook 点

Android插件化、热修复原理简介_第20张图片


Android插件化、热修复原理简介_第21张图片

● GetHiddenApiExemptions()就是豁免了,就是前两个条件没有满足,但到第三个条件时,被豁免了,直接返回kAllow。一般是给合作的厂商豁免
● 想要在此处Hook只需要修改runtime中的hidden_api_exemptions,跟第一个Hook点的思想是一模一样的

2、VirtualApk实现插件化

  • 如何实现类加载
  • 如何实现资源加载
  • 如何实现对四大组件的支持

2.1、VirtualApk

Android插件化、热修复原理简介_第22张图片

● VirtualApk本身就是一个app,只不过它具有加载其他app的能力的代码,如加载类加载资源等
● 每个插件都是独立的Apk,他们之间通过隔离的方式互相不可见。但VirtualApk并不是完全不可见,完全不可见的是DroidPlugin

Android插件化、热修复原理简介_第23张图片

2.2、运行插件代码

Android插件化、热修复原理简介_第24张图片

Android插件化、热修复原理简介_第25张图片

● 宿主VirtualApk通过LoadedPlugin加载插件Apk,类似Android系统通过loadedApk加载VirtualApk。
● DexClassLoader加载插件的dex
● PathClassLoader加载宿主的dex
● BootClassLoader加载系统的资源
● 将插件的dex插入宿主的dex中
● baseDexElements是宿主的
● newDexElement是插件的
● 将插件的dex插入到宿主的dex中,与热修复有点像,但是热修复是将自己的dex插入宿主的前面,而VirtualApk是将dex插入到宿主dex的后面,这样宿主就拥有自己的和所有插件的dex

2.3、处理插件资源

Android插件化、热修复原理简介_第26张图片


Android插件化、热修复原理简介_第27张图片


Android插件化、热修复原理简介_第28张图片

Android插件化、热修复原理简介_第29张图片


● 插件和宿主的资源没有重复,编译过滤
● 编译期Gradle会将插件资源的id修改,定制资源表。如上图原本资源的id都是0x7f,修改后变成了0x6f
● 资源文件修改之后,R文件的值也会被相应修改,确保代码引用到正确的资源

2.4、运行插件Activity

Android插件化、热修复原理简介_第30张图片

● StubActivity是一个占坑Activity


Android插件化、热修复原理简介_第31张图片

● Instrumentation.execStartActivity 启动插件Activity时,会判断是否是插件Activity,如果是,则会put一些字段,如插件Activity的包名/类名


Android插件化、热修复原理简介_第32张图片

● Instrumentation.newActivity返回来的时候,也会判断是否是插件Activity,如果是插件Activity,则会获取携带的插件Activity包名/类名,并进行替换

3、Tinker实现热修复

  • 如何对代码更新
  • 如何对资源更新

3.1、Tinker工作流程

Android插件化、热修复原理简介_第33张图片

● Base.apk是用户手机上安装的apk,New.apk是新包,对比得到差异包patch.zip,然后将差异包发送给客户端,客户端将Base.apk与patch.zip合并得到New.apk并运行


Android插件化、热修复原理简介_第34张图片

● New.apk的dex会插在Base.apk的dex的前面,这样代码运行时就会先运行新代码
● 对于资源Assets,只需把AssetsManager替换掉就可以
● android有一个classLoader叫做pathClassLoader,它里面有一堆的dexElements,只要把新的dex插入到这些dex的前面,就可以优先加载我们的dex实现热修复

4、插件化的两个流派

4.1、静态代理流派(零Hook):如Shadow

Android插件化、热修复原理简介_第35张图片

● ProxyActivity是一个代理,他是真正存在的,Activity的启动、生命周期的调用,都是通过它来控制
● PluginActivity则是被弱化了,看似是一个Activity,但实际编译时会被篡改成其他的

4.2、动态Hook流派:如VirtualApk

Android插件化、热修复原理简介_第36张图片

● StubActivity就是一个占坑Activity,就是相当于一个符号
● 我们真正要启动的是PluginActivty

注:Google对于动态Hook限制越来越高

你可能感兴趣的:(Android,Learning,android,java,插件化,热修复)