● Android:Pie — 9.0 — 28
● 一般都是通过反射访问私有 API
hide public方法无法直接使用,可以自行编译系统源码,并导入项目工程,从而访问到。
比如 convertFromTranslucent() 是 Acticity 中的方法,我们可以直接把 Activity 的源码放到工程里,就可以访问到这个方法。
(android.jar编译的Activity中没有convertFromTranslucent()方法,我们可以自己搞一个privaded.jar把系统源码搞过来,就可以骗过编译器,运行时加载的那个类里面肯定是有这个方法的)
private 彻底访问不到,只能通过反射。
Method initMethod = AssetManager.class.getDeclaredMethod("init");
initMethod.setAccessible(true);
反射:1)不仅可以绕过访问权限控制 2)还可以修改final变量
● 白名单中的API:谁都能用
● 浅灰名单中的API:反射可用
● 深灰名单中的API:SDK < 28,允许使用;SDK >= 28,不允许使用,同黑名单
● 黑名单中的API:反射不可用。如 getDeclaredField()、getDeclaredMethod() 获取不到任何东西
● GetActionFromAccessFlags() 中,如果是白名单 kWhitelist,就返回 kAllow
● 否则,获取 policy,如果 policy 是 kNoChecks,就返回 kAllow
● GetHiddenApiEnforcementPolicy() 返回的是一个 Runtime 里的成员 hidden_api_policy_
● 只要能拿到 Runtime 就可以把它篡改掉
● javaVM 我们可以拿到,在写 JNI 的时候就会经常遇到 javaVM
● 要把 javaVM 强转成 JavaVMExt,然后通过指针获取到 runtime
● 但是 JavaVMExt 是系统代码里的,我们拿不到
● JavaVMExt 继承自 JavaVM,JavaVMExt 中只有两个成员变量,一个是父类继承过来的 JNIInvokeInterface*,一个是自己的 Runtime,剩下的都是函数
● 我们可以利用C++的特性,自己定义一个结构体 struct JavaVMExt,就两个成员,一个叫 functions,一个叫 runtime,管你是什么类型,直接定义成任意类型 void*,后面我想要什么类型时就强转成什么类型,这样就可以拿到 Runtime,这就是 C++
● 需要注意,JavaVMExt 中没有虚方法表,否则自定义的 struct JavaVMExt 结构体中还需要加一个虚方法表的指针
● 我们的 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 就可以了
● 首先拿到 javaVM 的指针:一种方式是从 JNI_ON_LOAD 拿,这个代码一定是个 so 库;另一种方式是直接通过 JNI_ENV去get。此处采用的是方式二
● 然后将拿到的 javaVM 强转成自己定义的 JavaVMExt
● 于是就可以拿到我们定义的 runtime,但它是一个 void *
● 然后我们通过 findOffset() 4个字节4个字节的数到 targetSdkVersion 的地址(指针)
● 于是我们就可以把 targetSdkVersion 指针强转成我们定义的 PartialRuntime*
● 于是就可以获取到 hidden_api_policy_,并对其进行修改
class loader是空,就返回true
【方式 1】Java 反射
【方式 2】C++
注意:
ClassLoader 置空只限于你使用的这一个类,如果想让所有类都可以访问私有API,那么就要将所有类的ClassLoader 置空,工作量比较大
● GetHiddenApiExemptions()就是豁免了,就是前两个条件没有满足,但到第三个条件时,被豁免了,直接返回kAllow。一般是给合作的厂商豁免
● 想要在此处Hook只需要修改runtime中的hidden_api_exemptions,跟第一个Hook点的思想是一模一样的
● VirtualApk本身就是一个app,只不过它具有加载其他app的能力的代码,如加载类加载资源等
● 每个插件都是独立的Apk,他们之间通过隔离的方式互相不可见。但VirtualApk并不是完全不可见,完全不可见的是DroidPlugin
● 宿主VirtualApk通过LoadedPlugin加载插件Apk,类似Android系统通过loadedApk加载VirtualApk。
● DexClassLoader加载插件的dex
● PathClassLoader加载宿主的dex
● BootClassLoader加载系统的资源
● 将插件的dex插入宿主的dex中
● baseDexElements是宿主的
● newDexElement是插件的
● 将插件的dex插入到宿主的dex中,与热修复有点像,但是热修复是将自己的dex插入宿主的前面,而VirtualApk是将dex插入到宿主dex的后面,这样宿主就拥有自己的和所有插件的dex
● 插件和宿主的资源没有重复,编译过滤
● 编译期Gradle会将插件资源的id修改,定制资源表。如上图原本资源的id都是0x7f,修改后变成了0x6f
● 资源文件修改之后,R文件的值也会被相应修改,确保代码引用到正确的资源
● StubActivity是一个占坑Activity
● Instrumentation.execStartActivity 启动插件Activity时,会判断是否是插件Activity,如果是,则会put一些字段,如插件Activity的包名/类名
● Instrumentation.newActivity返回来的时候,也会判断是否是插件Activity,如果是插件Activity,则会获取携带的插件Activity包名/类名,并进行替换
● Base.apk是用户手机上安装的apk,New.apk是新包,对比得到差异包patch.zip,然后将差异包发送给客户端,客户端将Base.apk与patch.zip合并得到New.apk并运行
● New.apk的dex会插在Base.apk的dex的前面,这样代码运行时就会先运行新代码
● 对于资源Assets,只需把AssetsManager替换掉就可以
● android有一个classLoader叫做pathClassLoader,它里面有一堆的dexElements,只要把新的dex插入到这些dex的前面,就可以优先加载我们的dex实现热修复
● ProxyActivity是一个代理,他是真正存在的,Activity的启动、生命周期的调用,都是通过它来控制
● PluginActivity则是被弱化了,看似是一个Activity,但实际编译时会被篡改成其他的
● StubActivity就是一个占坑Activity,就是相当于一个符号
● 我们真正要启动的是PluginActivty
注:Google对于动态Hook限制越来越高