Commit 3489c801 authored by swift_gan's avatar swift_gan

update doc

parent 78dc131d
...@@ -9,7 +9,7 @@ Android Art Hook ...@@ -9,7 +9,7 @@ Android Art Hook
Swift Gan Swift Gan
---- ---
## Agenda ## Agenda
...@@ -20,17 +20,18 @@ Swift Gan ...@@ -20,17 +20,18 @@ Swift Gan
- Xposed 支持 - Xposed 支持
- inline 处理 - inline 处理
- Android Q - Android Q
- Demo - 架构图
- 进程注入
---- ---
## 简介 ## 简介
SandHook 是作用在 Android ART 虚拟机上的 Java 层 Hook 框架 SandHook 是作用在 Android ART 虚拟机上的 Java 层 Hook 框架,作用于进程内是不需要 Root 的
https://github.com/ganyao114/SandHook https://github.com/ganyao114/SandHook
--- ----
### OS ### OS
- 4.4(JNI 不支持 call 原方法) - 4.4(JNI 不支持 call 原方法)
...@@ -41,7 +42,7 @@ https://github.com/ganyao114/SandHook ...@@ -41,7 +42,7 @@ https://github.com/ganyao114/SandHook
- 9.0 - 9.0
- 10.0 - 10.0
--- ----
### ARCH ### ARCH
...@@ -49,7 +50,7 @@ https://github.com/ganyao114/SandHook ...@@ -49,7 +50,7 @@ https://github.com/ganyao114/SandHook
- THUMb32 - THUMb32
- AARCH64 - AARCH64
--- ----
### 方法范围 ### 方法范围
...@@ -60,23 +61,23 @@ https://github.com/ganyao114/SandHook ...@@ -60,23 +61,23 @@ https://github.com/ganyao114/SandHook
- JNI Methods - JNI Methods
- 不支持 abstract 方法 - 不支持 abstract 方法
--- ----
### 如何使用 ### 如何使用
```gradle ```gradle
implementation 'com.swift.sandhook:hooklib:3.3.8' implementation 'com.swift.sandhook:hooklib:3.4.0'
implementation 'com.swift.sandhook:xposedcompat:3.3.8' // 不使用 Xposed API 则不需要引入
implementation 'com.swift.sandhook:xposedcompat:3.4.0'
``` ```
- Annotation API - Annotation API
- Xposed API - Xposed API
--- ----
#### Annotation API #### Annotation API
```java ```java
@HookClass(Activity.class) @HookClass(Activity.class)
//@HookReflectClass("android.app.Activity") //@HookReflectClass("android.app.Activity")
...@@ -84,6 +85,7 @@ public class ActivityHooker { ...@@ -84,6 +85,7 @@ public class ActivityHooker {
@HookMethodBackup("onCreate") @HookMethodBackup("onCreate")
@MethodParams(Bundle.class) @MethodParams(Bundle.class)
//@SkipParamCheck //忽略参数匹配,如果 Hooker 里面没有同名 Hook 函数
static Method onCreateBackup; static Method onCreateBackup;
@HookMethodBackup("onPause") @HookMethodBackup("onPause")
...@@ -105,7 +107,7 @@ public class ActivityHooker { ...@@ -105,7 +107,7 @@ public class ActivityHooker {
} }
``` ```
--- ----
#### Xposed API #### Xposed API
...@@ -130,14 +132,14 @@ XposedHelpers.findAndHookMethod(Activity.class, "onResume", new XC_MethodHook() ...@@ -130,14 +132,14 @@ XposedHelpers.findAndHookMethod(Activity.class, "onResume", new XC_MethodHook()
} }
}); });
``` ```
--- ----
### Edxposed ### Edxposed
非官方 Xposed 框架,支持 8.0 - 9.0 非官方 Xposed 框架,支持 8.0 - 9.0
https://github.com/ElderDrivers/EdXposed/tree/sandhook https://github.com/ElderDrivers/EdXposed/tree/sandhook
--- ----
### with VA ### with VA
...@@ -145,7 +147,7 @@ https://github.com/ElderDrivers/EdXposed/tree/sandhook ...@@ -145,7 +147,7 @@ https://github.com/ElderDrivers/EdXposed/tree/sandhook
https://github.com/ganyao114/SandVXposed https://github.com/ganyao114/SandVXposed
---- ---
## ART Invoke 代码生成 ## ART Invoke 代码生成
...@@ -158,14 +160,14 @@ https://github.com/ganyao114/SandVXposed ...@@ -158,14 +160,14 @@ https://github.com/ganyao114/SandVXposed
- 8.0 之后 - 8.0 之后
- 结论 - 结论
--- ----
### 前言 ### 前言
在正式聊 Hook 方案之前,我们需要先了解一下 ART 对 invoke 字节码的实现,因为这会决定 Hook 的部分实现。 在正式聊 Hook 方案之前,我们需要先了解一下 ART 对 invoke 字节码的实现,因为这会决定 Hook 的部分实现。
这里的实现理论上分为:解释器实现和编译实现(JIT/AOT) 这里的实现理论上分为:解释器实现和编译实现(JIT/AOT)
实际上解释器的实现比较稳定和单一,我们仅仅需要关注编译器实现即可。 实际上解释器的实现比较稳定和单一,我们仅仅需要关注编译器实现即可。
--- ----
### ArtMethod ### ArtMethod
...@@ -176,21 +178,21 @@ https://github.com/ganyao114/SandVXposed ...@@ -176,21 +178,21 @@ https://github.com/ganyao114/SandVXposed
- 有一些虚拟机比较在意的类型,例如 Class,Method,Thread 这些 Art 内部所需要的类型,他们在 mirror 中是有对应的类型的 - 有一些虚拟机比较在意的类型,例如 Class,Method,Thread 这些 Art 内部所需要的类型,他们在 mirror 中是有对应的类型的
- 成员变量的内存布局也是对应映射的 - 成员变量的内存布局也是对应映射的
--- ----
#### Mirror #### Mirror
- 在大约 6.0 及之前,java 层中有隐藏类 ArtMethod,Method 与之一对一,而 mirror::ArtMethod 就是 java 层 ArtMethod 的映射。 - 在大约 6.0 及之前,java 层中有隐藏类 ArtMethod,Method 与之一对一,而 mirror::ArtMethod 就是 java 层 ArtMethod 的映射。
- 6.0 之后,java ArtMethod 不复存在,Method 与 mirror::ArtMethod 一对一映射,只不过大部分 Field 被 "隐藏" 了。 - 6.0 之后,java ArtMethod 不复存在,Method 与 mirror::ArtMethod 一对一映射,只不过大部分 Field 被 "隐藏" 了。
--- ----
#### GC #### GC
- ArtMethod 以及类似的 ArtField 在 Linear 堆区,是不会 Moving GC 的。 - ArtMethod 以及类似的 ArtField 在 Linear 堆区,是不会 Moving GC 的。
- 原因很简单,ArtMethod/ArtField 是有可能 JIT/AOT 在 native code 中的,如果随时变化则不好同步。 - 原因很简单,ArtMethod/ArtField 是有可能 JIT/AOT 在 native code 中的,如果随时变化则不好同步。
--- ----
#### jmethodId -> ArtMethod #### jmethodId -> ArtMethod
...@@ -218,7 +220,7 @@ static inline ArtMethod* DecodeArtMethod(jmethodID method_id) { ...@@ -218,7 +220,7 @@ static inline ArtMethod* DecodeArtMethod(jmethodID method_id) {
可以看到只是简单的 cast,jmethodId 是 ArtMethod 的透明引用。 可以看到只是简单的 cast,jmethodId 是 ArtMethod 的透明引用。
--- ----
#### ArtMethod 结构 #### ArtMethod 结构
...@@ -253,7 +255,7 @@ static inline ArtMethod* DecodeArtMethod(jmethodID method_id) { ...@@ -253,7 +255,7 @@ static inline ArtMethod* DecodeArtMethod(jmethodID method_id) {
``` ```
--- ----
#### 方法入口 #### 方法入口
...@@ -292,7 +294,7 @@ static inline ArtMethod* DecodeArtMethod(jmethodID method_id) { ...@@ -292,7 +294,7 @@ static inline ArtMethod* DecodeArtMethod(jmethodID method_id) {
} }
``` ```
--- ----
### Quick & Optimizing ### Quick & Optimizing
ART 中的 Compiler 有两种 Backend: ART 中的 Compiler 有两种 Backend:
...@@ -307,8 +309,7 @@ ART 中的 Compiler 有两种 Backend: ...@@ -307,8 +309,7 @@ ART 中的 Compiler 有两种 Backend:
下面以 Optimizing Compiler 为例分析 ART 方法调用的生成。 下面以 Optimizing Compiler 为例分析 ART 方法调用的生成。
----
---
### Optimizing ### Optimizing
Optimizing比Quick生成速度慢,但是会附带各种优化: Optimizing比Quick生成速度慢,但是会附带各种优化:
...@@ -322,20 +323,21 @@ Optimizing比Quick生成速度慢,但是会附带各种优化: ...@@ -322,20 +323,21 @@ Optimizing比Quick生成速度慢,但是会附带各种优化:
- Intrinsic 函数替换 。。。 - Intrinsic 函数替换 。。。
其中包括 Invoke 代码生成: 其中包括 Invoke 代码生成:
---
----
### Sharpening ### Sharpening
**invoke-static/invoke-direct 代码生成默认使用 Sharpening 优化** **invoke-static/invoke-direct 代码生成默认使用 Sharpening 优化**
--- ----
#### Sharpening 做了两件事情 #### Sharpening 做了两件事情
- 确定加载 ArtMethod 的方式和位置 - 确定加载 ArtMethod 的方式和位置
- 确定直接 blr 入口调用方法还是查询 ArtMethod -> CodeEntry 调用方法 - 确定直接 blr 入口调用方法还是查询 ArtMethod -> CodeEntry 调用方法
--- ----
#### 结果保存在两个 enum 中 #### 结果保存在两个 enum 中
...@@ -344,7 +346,7 @@ Optimizing比Quick生成速度慢,但是会附带各种优化: ...@@ -344,7 +346,7 @@ Optimizing比Quick生成速度慢,但是会附带各种优化:
我们重点关注 CodePtrLocation,但是 CodePtrLocation 在 8.0 有重大变化。 我们重点关注 CodePtrLocation,但是 CodePtrLocation 在 8.0 有重大变化。
--- ----
### 8.0 之前 ### 8.0 之前
...@@ -370,7 +372,7 @@ Optimizing比Quick生成速度慢,但是会附带各种优化: ...@@ -370,7 +372,7 @@ Optimizing比Quick生成速度慢,但是会附带各种优化:
}; };
``` ```
--- ----
### 代码生成 ### 代码生成
...@@ -412,7 +414,7 @@ switch (invoke->GetCodePtrLocation()) { ...@@ -412,7 +414,7 @@ switch (invoke->GetCodePtrLocation()) {
} }
``` ```
--- ----
#### 汇编 #### 汇编
...@@ -438,7 +440,7 @@ ldr lr [RegMethod(X0), #CodeEntryOffset] ...@@ -438,7 +440,7 @@ ldr lr [RegMethod(X0), #CodeEntryOffset]
blr lr blr lr
``` ```
--- ----
### 8.0 之后 ### 8.0 之后
...@@ -450,7 +452,7 @@ ldr lr [RegMethod(X0), #CodeEntryOffset] ...@@ -450,7 +452,7 @@ ldr lr [RegMethod(X0), #CodeEntryOffset]
这一步。 这一步。
--- ----
#### CodePtrLocation #### CodePtrLocation
...@@ -462,7 +464,7 @@ ldr lr [RegMethod(X0), #CodeEntryOffset] ...@@ -462,7 +464,7 @@ ldr lr [RegMethod(X0), #CodeEntryOffset]
}; };
``` ```
--- ----
#### 代码生成 #### 代码生成
...@@ -496,7 +498,7 @@ switch (invoke->GetCodePtrLocation()) { ...@@ -496,7 +498,7 @@ switch (invoke->GetCodePtrLocation()) {
} }
``` ```
--- ----
### invoke-virtual/interface ### invoke-virtual/interface
...@@ -531,7 +533,7 @@ switch (invoke->GetCodePtrLocation()) { ...@@ -531,7 +533,7 @@ switch (invoke->GetCodePtrLocation()) {
} }
``` ```
--- ----
#### 伪代码 #### 伪代码
...@@ -540,13 +542,13 @@ switch (invoke->GetCodePtrLocation()) { ...@@ -540,13 +542,13 @@ switch (invoke->GetCodePtrLocation()) {
- ldr lr method->CodeEntry - ldr lr method->CodeEntry
- blr lr - blr lr
--- ----
#### 为何不 Hook Abstract #### 为何不 Hook Abstract
修改 VTable 是否可行? 修改 VTable 是否可行?
--- ----
#### SingleImplementation #### SingleImplementation
...@@ -581,7 +583,7 @@ ArtMethod { ...@@ -581,7 +583,7 @@ ArtMethod {
``` ```
--- ----
##### 优化步骤 ##### 优化步骤
...@@ -597,7 +599,7 @@ CHA 优化属于内联优化 ...@@ -597,7 +599,7 @@ CHA 优化属于内联优化
如果 ART 发现是单实现,则将指令修改为 direct calls 如果 ART 发现是单实现,则将指令修改为 direct calls
--- ----
```java ```java
private void test() { private void test() {
...@@ -606,14 +608,14 @@ CHA 优化属于内联优化 ...@@ -606,14 +608,14 @@ CHA 优化属于内联优化
} }
``` ```
--- ----
### InvokeRuntime ### InvokeRuntime
一些特殊方法,主要服务于需要在 Runtime 时期才能确定的 Invoke,例如类初始化 <cinit> 函数。(kQuickInitializeType) 一些特殊方法,主要服务于需要在 Runtime 时期才能确定的 Invoke,例如类初始化 <cinit> 函数。(kQuickInitializeType)
InvokeRuntime 会从当前 Thread 中查找 CodeEntry: InvokeRuntime 会从当前 Thread 中查找 CodeEntry:
--- ----
#### 代码生成 #### 代码生成
...@@ -630,7 +632,7 @@ void CodeGeneratorARM64::InvokeRuntime(int32_t entry_point_offset, ...@@ -630,7 +632,7 @@ void CodeGeneratorARM64::InvokeRuntime(int32_t entry_point_offset,
} }
``` ```
--- ----
#### 汇编代码 #### 汇编代码
...@@ -645,7 +647,7 @@ ldr x20, [x0, #0x310] ...@@ -645,7 +647,7 @@ ldr x20, [x0, #0x310]
blr x20 blr x20
``` ```
--- ----
### Intrinsics 优化 ### Intrinsics 优化
...@@ -665,7 +667,7 @@ ART 额外维护了一批系统函数的高效实现,这些高效实现利用 ...@@ -665,7 +667,7 @@ ART 额外维护了一批系统函数的高效实现,这些高效实现利用
``` ```
--- ----
#### Thread.currentThread() #### Thread.currentThread()
...@@ -682,13 +684,13 @@ void IntrinsicCodeGeneratorARM64::VisitThreadCurrentThread(HInvoke* invoke) { ...@@ -682,13 +684,13 @@ void IntrinsicCodeGeneratorARM64::VisitThreadCurrentThread(HInvoke* invoke) {
ldr x5, [x19, #PeerOffset] ldr x5, [x19, #PeerOffset]
``` ```
--- ----
### 结论 ### 结论
当 8.0 以上时,我们使用 ArtMethod 入口替换即可基本满足 Hook 需求。但如果 8.0 以下,如果不开启 debug 或者 deoptimize 的话,则必须使用 inline hook,否则会漏掉很多调用。 当 8.0 以上时,我们使用 ArtMethod 入口替换即可基本满足 Hook 需求。但如果 8.0 以下,如果不开启 debug 或者 deoptimize 的话,则必须使用 inline hook,否则会漏掉很多调用。
---- ---
## 基本实现 ## 基本实现
...@@ -702,7 +704,7 @@ ldr x5, [x19, #PeerOffset] ...@@ -702,7 +704,7 @@ ldr x5, [x19, #PeerOffset]
- 选择寄存器 - 选择寄存器
- 开始 hook - 开始 hook
--- ----
### ArtMethod 内存布局 ### ArtMethod 内存布局
...@@ -721,14 +723,14 @@ ldr x5, [x19, #PeerOffset] ...@@ -721,14 +723,14 @@ ldr x5, [x19, #PeerOffset]
- 我们可以在 Java 层反射得到一些值,或者说我们可以根据指定方法的属性确定预测值(accessFlag),然后我们根据预测值在 ArtMethod 中搜索偏移 - 我们可以在 Java 层反射得到一些值,或者说我们可以根据指定方法的属性确定预测值(accessFlag),然后我们根据预测值在 ArtMethod 中搜索偏移
- 根据元素在 ArtMethod 中的相对位置确定(code_entry 在最后) - 根据元素在 ArtMethod 中的相对位置确定(code_entry 在最后)
--- ----
### Hooker 项解析 ### Hooker 项解析
- 首先 Hook 项承载了目标方法的信息,我们根据这些信息找到目标方法。 - 首先 Hook 项承载了目标方法的信息,我们根据这些信息找到目标方法。
- 因为被 Hook 的方法会直接调到我们的 Hook 入口,Hook 入口本身也是一个 java 方法,所以参数需要和原方法匹配。 - 因为被 Hook 的方法会直接调到我们的 Hook 入口,Hook 入口本身也是一个 java 方法,所以参数需要和原方法匹配。
--- ----
### resolve 静态方法 ### resolve 静态方法
...@@ -736,7 +738,7 @@ ldr x5, [x19, #PeerOffset] ...@@ -736,7 +738,7 @@ ldr x5, [x19, #PeerOffset]
- 如果一个类没有被加载,那么其中的静态方法的入口统一为 art_quick_proxy_invoke_handler - 如果一个类没有被加载,那么其中的静态方法的入口统一为 art_quick_proxy_invoke_handler
- 第一次调用时,art_quick_proxy_invoke_handler 会走到类初始化流程 - 第一次调用时,art_quick_proxy_invoke_handler 会走到类初始化流程
--- ----
#### resolve 静态方法 #### resolve 静态方法
...@@ -766,7 +768,7 @@ ldr x5, [x19, #PeerOffset] ...@@ -766,7 +768,7 @@ ldr x5, [x19, #PeerOffset]
} }
``` ```
--- ----
### resolve dex cache ### resolve dex cache
...@@ -776,7 +778,7 @@ ldr x5, [x19, #PeerOffset] ...@@ -776,7 +778,7 @@ ldr x5, [x19, #PeerOffset]
当然后面我们我们也可以使用反射调用原方法来解决这一问题。 当然后面我们我们也可以使用反射调用原方法来解决这一问题。
--- ----
#### resolve 实现 #### resolve 实现
...@@ -785,7 +787,7 @@ ldr x5, [x19, #PeerOffset] ...@@ -785,7 +787,7 @@ ldr x5, [x19, #PeerOffset]
- 8.1 以上 DexCache 最大为 1024,index 实际为真实 index % 1024 再去取,则有可能在运行期间被覆盖 - 8.1 以上 DexCache 最大为 1024,index 实际为真实 index % 1024 再去取,则有可能在运行期间被覆盖
- 所以 8.1 以后建议通过反射 invoke 调用原方法 - 所以 8.1 以后建议通过反射 invoke 调用原方法
--- ----
### 手动 JIT ### 手动 JIT
#### 编译策略 #### 编译策略
...@@ -795,7 +797,7 @@ ldr x5, [x19, #PeerOffset] ...@@ -795,7 +797,7 @@ ldr x5, [x19, #PeerOffset]
- 则我们如果想使用 inline hook 的话,则必须手动将目标方法编译 - 则我们如果想使用 inline hook 的话,则必须手动将目标方法编译
- 除此之外,将 hook 入口方法编译可以避免一些意想不到的问题 - 除此之外,将 hook 入口方法编译可以避免一些意想不到的问题
--- ----
#### 如何编译 #### 如何编译
...@@ -803,7 +805,7 @@ ldr x5, [x19, #PeerOffset] ...@@ -803,7 +805,7 @@ ldr x5, [x19, #PeerOffset]
- 我们只需要 dlsym libart-compiler.so 的导出方法 jit_compile_method 调用即可 - 我们只需要 dlsym libart-compiler.so 的导出方法 jit_compile_method 调用即可
- 需要注意 Android N 以上对 dlsym 的限制 - 需要注意 Android N 以上对 dlsym 的限制
--- ----
```cpp ```cpp
extern "C" void* jit_load(bool* generate_debug_info) { extern "C" void* jit_load(bool* generate_debug_info) {
...@@ -824,14 +826,14 @@ extern "C" bool jit_compile_method( ...@@ -824,14 +826,14 @@ extern "C" bool jit_compile_method(
} }
``` ```
--- ----
#### 其他需要注意的 #### 其他需要注意的
- 手动编译 JNI 方法将发生未知错误 - 手动编译 JNI 方法将发生未知错误
- 在某些系统进程(zygote, system_server)里面,Compiler 是不需要初始化的,所以手动编译将会报错,很好理解,默认这些进程早就被 OAT 了 - 在某些系统进程(zygote, system_server)里面,Compiler 是不需要初始化的,所以手动编译将会报错,很好理解,默认这些进程早就被 OAT 了
--- ----
#### 禁用某个方法的 JIT #### 禁用某个方法的 JIT
...@@ -842,7 +844,7 @@ extern "C" bool jit_compile_method( ...@@ -842,7 +844,7 @@ extern "C" bool jit_compile_method(
ResolveCompilingMethodsClass -> ClassLinker::ResolveMethod -> CheckIncompatibleClassChange -> ThrowIncompatibleClassChangeError ResolveCompilingMethodsClass -> ClassLinker::ResolveMethod -> CheckIncompatibleClassChange -> ThrowIncompatibleClassChangeError
``` ```
--- ----
#### 如何禁用? #### 如何禁用?
...@@ -870,9 +872,9 @@ ART 的判断逻辑 ...@@ -870,9 +872,9 @@ ART 的判断逻辑
- 正好其他线程调到了正在被修改的方法 - 正好其他线程调到了正在被修改的方法
- 其他线程发生栈回朔(异常),回朔到了正在修改的 ArtMethod - 其他线程发生栈回朔(异常),回朔到了正在修改的 ArtMethod
--- ----
#### StopTheWord #### StopTheWorld
那么我们需要暂停所有线程,并且等待 GC 完成 那么我们需要暂停所有线程,并且等待 GC 完成
幸运的是,ART 等待调试器也需要这一操作,不仅仅是暂停所有线程,还需要等待 GC。 幸运的是,ART 等待调试器也需要这一操作,不仅仅是暂停所有线程,还需要等待 GC。
...@@ -891,7 +893,7 @@ void Dbg::ResumeVM() { ...@@ -891,7 +893,7 @@ void Dbg::ResumeVM() {
} }
``` ```
--- ----
### 备份原方法 ### 备份原方法
...@@ -902,7 +904,7 @@ void Dbg::ResumeVM() { ...@@ -902,7 +904,7 @@ void Dbg::ResumeVM() {
这里我选择写 Stub,因为 New 有致命缺陷 这里我选择写 Stub,因为 New 有致命缺陷
--- ----
#### New 的缺点 #### New 的缺点
...@@ -911,7 +913,7 @@ void Dbg::ResumeVM() { ...@@ -911,7 +913,7 @@ void Dbg::ResumeVM() {
- 但是 ArtMethod 中的 declaring_class 是 GCRoot,是回 Moving GC 的,ART 并不知道他的存在,显然 GC 不会帮你更新假 "ArtMethod" 中的 declaring_class。 - 但是 ArtMethod 中的 declaring_class 是 GCRoot,是回 Moving GC 的,ART 并不知道他的存在,显然 GC 不会帮你更新假 "ArtMethod" 中的 declaring_class。
- 那么还是一样,只要不使用 stub 都需要频繁手动更新地址 - 那么还是一样,只要不使用 stub 都需要频繁手动更新地址
--- ----
### 选择寄存器 ### 选择寄存器
...@@ -919,7 +921,7 @@ void Dbg::ResumeVM() { ...@@ -919,7 +921,7 @@ void Dbg::ResumeVM() {
- 如果通过保存恢复现场来保护寄存器和栈,在 ART 中也是不可行的(或者说仅仅在解释器模式下有希望) - 如果通过保存恢复现场来保护寄存器和栈,在 ART 中也是不可行的(或者说仅仅在解释器模式下有希望)
- 因为无论是 GC 还是栈回朔,以及其他的一些 ART 的动态行为,都依赖于栈和一些约定寄存器 - 因为无论是 GC 还是栈回朔,以及其他的一些 ART 的动态行为,都依赖于栈和一些约定寄存器
--- ----
#### ART 寄存器 的使用 #### ART 寄存器 的使用
...@@ -933,7 +935,7 @@ void Dbg::ResumeVM() { ...@@ -933,7 +935,7 @@ void Dbg::ResumeVM() {
最终选择 X17,X16 在跳板中有用到。 最终选择 X17,X16 在跳板中有用到。
---- ---
### 开始 Hook ### 开始 Hook
#### Inline 与否 #### Inline 与否
...@@ -945,7 +947,7 @@ SandHook 支持两种 Hook "截获" 方案,inline 以及入口替换 ...@@ -945,7 +947,7 @@ SandHook 支持两种 Hook "截获" 方案,inline 以及入口替换
- 当条件不符合时,例如代码太短,放不下跳板等(后面指令检查细说),只能使用入口替换 - 当条件不符合时,例如代码太短,放不下跳板等(后面指令检查细说),只能使用入口替换
- 当然也可以自己选择 - 当然也可以自己选择
--- ----
#### hook 流程 #### hook 流程
...@@ -956,7 +958,7 @@ SandHook 支持两种 Hook "截获" 方案,inline 以及入口替换 ...@@ -956,7 +958,7 @@ SandHook 支持两种 Hook "截获" 方案,inline 以及入口替换
- 禁止 origin JIT - 禁止 origin JIT
- 当 OS >= O 并且 debug 模式下,将 origin 设为 native - 当 OS >= O 并且 debug 模式下,将 origin 设为 native
--- ----
#### 备份原方法 #### 备份原方法
...@@ -966,7 +968,7 @@ SandHook 支持两种 Hook "截获" 方案,inline 以及入口替换 ...@@ -966,7 +968,7 @@ SandHook 支持两种 Hook "截获" 方案,inline 以及入口替换
- 如果原方法非静态方法,要保证其是 private - 如果原方法非静态方法,要保证其是 private
- 调用原方法,只需要反射调用 backup 方法即可 - 调用原方法,只需要反射调用 backup 方法即可
--- ----
#### 为何? #### 为何?
...@@ -1002,7 +1004,7 @@ inline ArtMethod* Class::FindVirtualMethodForVirtualOrInterface(ArtMethod* metho ...@@ -1002,7 +1004,7 @@ inline ArtMethod* Class::FindVirtualMethodForVirtualOrInterface(ArtMethod* metho
所以就不会直接调用原方法的 CodeEntry 所以就不会直接调用原方法的 CodeEntry
--- ----
#### 跳板安装 #### 跳板安装
##### 入口替换 ##### 入口替换
...@@ -1011,7 +1013,7 @@ inline ArtMethod* Class::FindVirtualMethodForVirtualOrInterface(ArtMethod* metho ...@@ -1011,7 +1013,7 @@ inline ArtMethod* Class::FindVirtualMethodForVirtualOrInterface(ArtMethod* metho
- 将 X0 替换成 hook 的 ArtMethod,因为原来是 origin 的 ArtMethod - 将 X0 替换成 hook 的 ArtMethod,因为原来是 origin 的 ArtMethod
- 跳转到 hook 的 CodeEntry - 跳转到 hook 的 CodeEntry
--- ----
##### inline ##### inline
inline 稍显复杂 inline 稍显复杂
...@@ -1021,12 +1023,13 @@ inline 稍显复杂 ...@@ -1021,12 +1023,13 @@ inline 稍显复杂
- 二段跳板首先判断 Callee 是否是需要 Hook 的方法 - 二段跳板首先判断 Callee 是否是需要 Hook 的方法
- 是则设置 X0 跳转到 Hook 入口,不是则跳到备份的指令继续执行原方法 - 是则设置 X0 跳转到 Hook 入口,不是则跳到备份的指令继续执行原方法
--- ----
#### 跳转图 #### 跳转图
![inline_flow.png](res/inline_flow.png) ![inline_flow.png](res/inline_flow.png)
--- ----
#### 入口相同的情况 #### 入口相同的情况
...@@ -1034,13 +1037,13 @@ inline 稍显复杂 ...@@ -1034,13 +1037,13 @@ inline 稍显复杂
- 逻辑相同的代码 - 逻辑相同的代码
- 入口相同的方法依然可以重复 inline,因为其组成了责任链模式 - 入口相同的方法依然可以重复 inline,因为其组成了责任链模式
--- ----
### 跳板 ### 跳板
跳板是一个个模版代码 跳板是一个个模版代码
--- ----
#### 入口替换跳板 #### 入口替换跳板
...@@ -1059,7 +1062,7 @@ addr_code_entry: ...@@ -1059,7 +1062,7 @@ addr_code_entry:
FUNCTION_END(REPLACEMENT_HOOK_TRAMPOLINE) FUNCTION_END(REPLACEMENT_HOOK_TRAMPOLINE)
``` ```
--- ----
#### inline 跳板 #### inline 跳板
##### 一段 ##### 一段
...@@ -1079,7 +1082,7 @@ addr_code_entry: ...@@ -1079,7 +1082,7 @@ addr_code_entry:
FUNCTION_END(REPLACEMENT_HOOK_TRAMPOLINE) FUNCTION_END(REPLACEMENT_HOOK_TRAMPOLINE)
``` ```
--- ----
##### 二段 ##### 二段
...@@ -1116,7 +1119,7 @@ addr_hook_code_entry: ...@@ -1116,7 +1119,7 @@ addr_hook_code_entry:
FUNCTION_END(INLINE_HOOK_TRAMPOLINE) FUNCTION_END(INLINE_HOOK_TRAMPOLINE)
``` ```
--- ----
##### call origin ##### call origin
...@@ -1134,7 +1137,7 @@ addr_call_origin_code: ...@@ -1134,7 +1137,7 @@ addr_call_origin_code:
FUNCTION_END(CALL_ORIGIN_TRAMPOLINE) FUNCTION_END(CALL_ORIGIN_TRAMPOLINE)
``` ```
--- ----
### O & debug ### O & debug
...@@ -1144,6 +1147,87 @@ FUNCTION_END(CALL_ORIGIN_TRAMPOLINE) ...@@ -1144,6 +1147,87 @@ FUNCTION_END(CALL_ORIGIN_TRAMPOLINE)
---- ----
<font size=5>
解释器 Switch -> DoInvoke -> DoCall -> DoCallCommon -> PerformCall
</font>
```cpp
bool ClassLinker::ShouldUseInterpreterEntrypoint(ArtMethod* method, const void* quick_code) {
if (UNLIKELY(method->IsNative() || method->IsProxyMethod())) {
return false;
}
if (quick_code == nullptr) {
return true;
}
Runtime* runtime = Runtime::Current();
instrumentation::Instrumentation* instr = runtime->GetInstrumentation();
if (instr->InterpretOnly()) {
return true;
}
if (runtime->GetClassLinker()->IsQuickToInterpreterBridge(quick_code)) {
// Doing this check avoids doing compiled/interpreter transitions.
return true;
}
if (Dbg::IsForcedInterpreterNeededForCalling(Thread::Current(), method)) {
// Force the use of interpreter when it is required by the debugger.
return true;
}
if (Thread::Current()->IsAsyncExceptionPending()) {
// Force use of interpreter to handle async-exceptions
return true;
}
if (runtime->IsJavaDebuggable()) {
// For simplicity, we ignore precompiled code and go to the interpreter
// assuming we don't already have jitted code.
// We could look at the oat file where `quick_code` is being defined,
// and check whether it's been compiled debuggable, but we decided to
// only rely on the JIT for debuggable apps.
jit::Jit* jit = Runtime::Current()->GetJit();
return (jit == nullptr) || !jit->GetCodeCache()->ContainsPc(quick_code);
}
if (runtime->IsNativeDebuggable()) {
DCHECK(runtime->UseJitCompilation() && runtime->GetJit()->JitAtFirstUse());
// If we are doing native debugging, ignore application's AOT code,
// since we want to JIT it (at first use) with extra stackmaps for native
// debugging. We keep however all AOT code from the boot image,
// since the JIT-at-first-use is blocking and would result in non-negligible
// startup performance impact.
return !runtime->GetHeap()->IsInBootImageOatFile(quick_code);
}
return false;
}
inline void PerformCall(Thread* self,
const CodeItemDataAccessor& accessor,
ArtMethod* caller_method,
const size_t first_dest_reg,
ShadowFrame* callee_frame,
JValue* result,
bool use_interpreter_entrypoint)
REQUIRES_SHARED(Locks::mutator_lock_) {
if (LIKELY(Runtime::Current()->IsStarted())) {
if (use_interpreter_entrypoint) {
interpreter::ArtInterpreterToInterpreterBridge(self, accessor, callee_frame, result);
} else {
interpreter::ArtInterpreterToCompiledCodeBridge(
self, caller_method, callee_frame, first_dest_reg, result);
}
} else {
interpreter::UnstartedRuntime::Invoke(self, accessor, callee_frame, result, first_dest_reg);
}
}
```
---
## 指令检查 ## 指令检查
- Code 长度检查 - Code 长度检查
...@@ -1151,13 +1235,13 @@ FUNCTION_END(CALL_ORIGIN_TRAMPOLINE) ...@@ -1151,13 +1235,13 @@ FUNCTION_END(CALL_ORIGIN_TRAMPOLINE)
- 指令对齐 - 指令对齐
- PC 寄存器相关指令 - PC 寄存器相关指令
--- ----
### 指令长度 ### 指令长度
如果我们需要 inline 一个已经编译的方法,我们就必须知道该方法 Code 的长度能否放下我们的跳转指令,否则就会破坏其他 Code。 如果我们需要 inline 一个已经编译的方法,我们就必须知道该方法 Code 的长度能否放下我们的跳转指令,否则就会破坏其他 Code。
--- ----
#### 获取指令长度 #### 获取指令长度
...@@ -1174,7 +1258,7 @@ FUNCTION_END(CALL_ORIGIN_TRAMPOLINE) ...@@ -1174,7 +1258,7 @@ FUNCTION_END(CALL_ORIGIN_TRAMPOLINE)
uint8_t code_[0]; uint8_t code_[0];
``` ```
--- ----
#### 获取指令长度 #### 获取指令长度
...@@ -1209,13 +1293,13 @@ JitCompile->CommitCode->CommitCodeInternal ...@@ -1209,13 +1293,13 @@ JitCompile->CommitCode->CommitCodeInternal
reinterpret_cast<char*>(code_ptr + code_size)); reinterpret_cast<char*>(code_ptr + code_size));
``` ```
--- ----
#### 获取指令长度 #### 获取指令长度
那么可得出 CodeEntry - 4 就是存放 Code Size 的地址 那么可得出 CodeEntry - 4 就是存放 Code Size 的地址
--- ----
### Thumb 指令 ### Thumb 指令
...@@ -1228,7 +1312,7 @@ bool isThumbCode(Size codeAddr) { ...@@ -1228,7 +1312,7 @@ bool isThumbCode(Size codeAddr) {
return (codeAddr & 0x1) == 0x1; return (codeAddr & 0x1) == 0x1;
} }
``` ```
--- ----
### 指令对齐 ### 指令对齐
...@@ -1237,7 +1321,7 @@ bool isThumbCode(Size codeAddr) { ...@@ -1237,7 +1321,7 @@ bool isThumbCode(Size codeAddr) {
- 那么问题来了,我们一级跳板是 4 字节对齐的,原始指令是 2 字节对齐 - 那么问题来了,我们一级跳板是 4 字节对齐的,原始指令是 2 字节对齐
- 所以在备份原指令的时候一定要注意指令的完整性 - 所以在备份原指令的时候一定要注意指令的完整性
--- ----
### PC 寄存器相关指令 ### PC 寄存器相关指令
...@@ -1251,15 +1335,16 @@ public void setCloseOnTouchOutsideIfNotSet(boolean close) { ...@@ -1251,15 +1335,16 @@ public void setCloseOnTouchOutsideIfNotSet(boolean close) {
} }
} }
``` ```
--- ----
#### 汇编代码 #### 汇编代码
![pc_relate.png](res/pc_relate.png) ![pc_relate.png](res/pc_relate.png)
CBNZ 依赖于当前 PC 值。
虽然这种情况不多,但是检查是必要的,如果我们发现这种指令,直接转用入口替换即可。 虽然这种情况不多,但是检查是必要的,如果我们发现这种指令,直接转用入口替换即可。
---- ---
## Xposed 兼容 ## Xposed 兼容
...@@ -1268,7 +1353,7 @@ public void setCloseOnTouchOutsideIfNotSet(boolean close) { ...@@ -1268,7 +1353,7 @@ public void setCloseOnTouchOutsideIfNotSet(boolean close) {
- 实现 - 实现
- 性能优化 - 性能优化
--- ----
### 为何要兼容 Xposed? ### 为何要兼容 Xposed?
...@@ -1276,17 +1361,17 @@ public void setCloseOnTouchOutsideIfNotSet(boolean close) { ...@@ -1276,17 +1361,17 @@ public void setCloseOnTouchOutsideIfNotSet(boolean close) {
- Xposed 特殊的(Callback)分发模式可支持多个模块同时 Hook 同一个函数 - Xposed 特殊的(Callback)分发模式可支持多个模块同时 Hook 同一个函数
- 原版 Xposed 需要 Root 并且替换定制 ART,并且 8.0 已经停止更新 - 原版 Xposed 需要 Root 并且替换定制 ART,并且 8.0 已经停止更新
--- ----
### 可选方案 ### 可选方案
我们目前的方案需要手写一个签名与原方法类似的 Hook 方法,而 Xposed API 则使用 Callback,所以我们需要运行期间动态生成方法。 我们目前的方案需要手写一个签名与原方法类似的 Hook 方法,而 Xposed API 则使用 Callback,所以我们需要运行期间动态生成方法。
- libffi 动态生成 Native 方法,将 origin 方法注册为 JNI 方法(Frida) - libffi 动态生成 Native 方法,将 origin 方法注册为 JNI 方法(Frida/Whale),相当于入口替换
- DexMaker 生成 Java 方法,性能略差 - DexMaker 生成 Java 方法,第一次生成较慢,但不存在兼容性
- 或者自己根据调用约定从栈捞参数(epic),兼容性存疑 - 或者自己根据调用约定从栈捞参数(epic),Hook 时较快,但运行时较慢(一次 Hook 调入需要调用很多方法解析参数),兼容性存疑
--- ----
### DexMaker ### DexMaker
...@@ -1296,8 +1381,9 @@ public void setCloseOnTouchOutsideIfNotSet(boolean close) { ...@@ -1296,8 +1381,9 @@ public void setCloseOnTouchOutsideIfNotSet(boolean close) {
- 性能可接受,使用缓存只需要生成一次 - 性能可接受,使用缓存只需要生成一次
- 只需要完成参数打包的工作,生成的代码及其有限 - 只需要完成参数打包的工作,生成的代码及其有限
- 稳定不会有兼容问题 - 稳定不会有兼容问题
- 运行时是最快的
--- ----
### 性能优化 ### 性能优化
...@@ -1308,7 +1394,7 @@ public void setCloseOnTouchOutsideIfNotSet(boolean close) { ...@@ -1308,7 +1394,7 @@ public void setCloseOnTouchOutsideIfNotSet(boolean close) {
- 这样如果是基本类型参数,则可以简单转换得到值,Object 参数收到的则是内存地址 - 这样如果是基本类型参数,则可以简单转换得到值,Object 参数收到的则是内存地址
- 参数多的 stub 可以兼容较少参数的情况 - 参数多的 stub 可以兼容较少参数的情况
--- ----
写了一个 python 脚本以自动生成 写了一个 python 脚本以自动生成
```java ```java
...@@ -1318,7 +1404,7 @@ public void setCloseOnTouchOutsideIfNotSet(boolean close) { ...@@ -1318,7 +1404,7 @@ public void setCloseOnTouchOutsideIfNotSet(boolean close) {
} }
``` ```
--- ----
#### 参数转换 #### 参数转换
...@@ -1348,7 +1434,7 @@ public static Object addressToObject64(Class objectType, long address) { ...@@ -1348,7 +1434,7 @@ public static Object addressToObject64(Class objectType, long address) {
} }
``` ```
--- ----
#### 对象与地址互转 #### 对象与地址互转
...@@ -1356,15 +1442,14 @@ public static Object addressToObject64(Class objectType, long address) { ...@@ -1356,15 +1442,14 @@ public static Object addressToObject64(Class objectType, long address) {
- 地址 -> 对象, 使用 ART 内部的 AddWeakGlobalRef - 地址 -> 对象, 使用 ART 内部的 AddWeakGlobalRef
- 对象 -> 地址, 直接使用 Java 层的 Unsafe 类 - 对象 -> 地址, 直接使用 Java 层的 Unsafe 类
----
---
#### 结果 #### 结果
- 如此,几乎 9 成以上的函数 Hook 都走内部 Stub 的方式,Hook 耗时在 1ms 以内 - 如此,几乎 9 成以上的函数 Hook 都走内部 Stub 的方式,Hook 耗时在 1ms 以内
- DexMaker 方式第一次大约需要 80ms 左右,第二次直接加载约为 3 - 5ms,其实也能接受 - DexMaker 方式第一次大约需要 80ms 左右,后面直接加载约为 3 - 5ms,其实也能接受
---- ---
## Inline 处理 ## Inline 处理
...@@ -1373,14 +1458,14 @@ public static Object addressToObject64(Class objectType, long address) { ...@@ -1373,14 +1458,14 @@ public static Object addressToObject64(Class objectType, long address) {
- 阻止 dex2oat Inline(Profile) - 阻止 dex2oat Inline(Profile)
- 系统类中 Inline 的如何处理 - 系统类中 Inline 的如何处理
--- ----
### ART Inline 优化 ### ART Inline 优化
ART 的 inline 类似其他语言的编译器优化,在 Runtime(JIT) 或者 dex2oat 期间, ART 将 “invoke 字节码指令” 替换成 callee 的方法体。 ART 的 inline 类似其他语言的编译器优化,在 Runtime(JIT) 或者 dex2oat 期间, ART 将 “invoke 字节码指令” 替换成 callee 的方法体。
往往被 inline 的都是较为简单的方法。 往往被 inline 的都是较为简单的方法。
--- ----
### 阻止 JIT 期间的 Inline ### 阻止 JIT 期间的 Inline
...@@ -1410,12 +1495,12 @@ const CompilerOptions& compiler_options = compiler_driver_->GetCompilerOptions() ...@@ -1410,12 +1495,12 @@ const CompilerOptions& compiler_options = compiler_driver_->GetCompilerOptions()
return false; return false;
} }
``` ```
--- ----
当被 inline 方法的 code units 大于设置的阈值的时候,方法 Inline 失败。 当被 inline 方法的 code units 大于设置的阈值的时候,方法 Inline 失败。
这个阈值是 CompilerOptions -> inline_max_code_units_ 这个阈值是 CompilerOptions -> inline_max_code_units_
--- ----
经过搜索,CompilerOptions 一般与 JitCompiler 绑定: 经过搜索,CompilerOptions 一般与 JitCompiler 绑定:
...@@ -1436,7 +1521,7 @@ class JitCompiler { ...@@ -1436,7 +1521,7 @@ class JitCompiler {
} // namespace art } // namespace art
``` ```
--- ----
ART 的 JitCompiler 为全局单例: ART 的 JitCompiler 为全局单例:
...@@ -1461,23 +1546,186 @@ extern "C" void* jit_load(bool* generate_debug_info) { ...@@ -1461,23 +1546,186 @@ extern "C" void* jit_load(bool* generate_debug_info) {
} }
``` ```
--- ----
#### 结论 #### 结论
ok,那么我们就得到了 “static void* jit_compiler_handle_” 的 C++ 符号 “_ZN3art3jit3Jit20jit_compiler_handle_E“ ok,那么我们就得到了 “static void* jit_compiler_handle_” 的 C++ 符号 “_ZN3art3jit3Jit20jit_compiler_handle_E“
最后修改里面的值就可以了。 最后修改里面的值就可以了。
--- SandHook.disableVMInline()
----
### 阻止 dex2oat Inline ### 阻止 dex2oat Inline
- Android N 以上默认的 ART 编译策略为 speed-profile - Android N 以上默认的 ART 编译策略为 speed-profile
- 除了 JIT 期间的内联,系统在空闲实践会根据这个所谓的 profile 进行 speed 模式的 dex2oat - 除了 JIT 期间的内联,系统在空闲实践会根据这个所谓的 profile 进行 speed 模式的 dex2oat
- speed 模式包含 Inline 优化 - speed 模式包含 Inline 优化
- 现象是 App 多打开几次发现 Hook 不到了
--- ----
#### 如何阻止 #### 如何阻止
- 提前主动调用 dex2oat,附带 --inline-max-code-units=0 参数,要求 Dex 是动态加载的 (ArtDexOptimizer)
- 如果上述条件无法满足,则需要干掉 profile 文件以阻止空闲时 dex2oat,当然会对性能造成一定影响 (SandHook.tryDisableProfile)
----
#### profile
- 首先明确一点是 profile 是安装时创建的,并非由 ART 创建,ART 只负责读取以及写入
- profile 存储了方法的热度,以指导 dex2oat 进程编译高热度方法
- 如果你从 Google Play 下载某个热门 App,是有可能带有一份已经有内容的 profile 的
- 综上,我们只需要删除这个文件,如果不保险,则可以修改权限使之不可写
----
#### profile path
<font size=5>
/data/misc/profiles/cur/" + userId + "/" + selfPackageName + "/primary.prof"
</font>
```cpp
bool ProfileCompilationInfo::Load(const std::string& filename, bool clear_if_invalid) {
ScopedTrace trace(__PRETTY_FUNCTION__);
std::string error;
if (!IsEmpty()) {
return kProfileLoadWouldOverwiteData;
}
int flags = O_RDWR | O_NOFOLLOW | O_CLOEXEC;
ScopedFlock profile_file = LockedFile::Open(filename.c_str(), flags,
/*block*/false, &error);
if (profile_file.get() == nullptr) {
LOG(WARNING) << "Couldn't lock the profile file " << filename << ": " << error;
return false;
}
```
----
### 系统类中被 Inline 的
- 理论上非 root 的环境是没什么好的办法的
- 只能特例一个个解决
- dexOptimize Caller
----
#### dexOptimize
- 我们 dexOptimize Caller 使 Caller 重返解释执行,那么就可以顺利的调入我们的 Hook 方法
- dexOptimize 其实就是将 ArtMethod 的 CodeEntry 重新设置成解释 bridge
- 依然是上面提到的 art_quick_to_interpreter_bridge/art_quick_generic_jni_trampoline
----
#### 跳板获取
- 这两个跳板不在动态链接表中,(fake)dlsym 无法搜索到
- 但是这个符号是被保留的,在 SHT_SYMTAB 中,这个表存储了所有的符号,所以我重新实现了符号搜索
- 除此之外也可以从从未调用的方法中获取,但是可能遇到被强制 dex2oat 的情况
https://github.com/ganyao114/AndroidELF
---
## Android Q
- AccessFlag
- Hidden API
----
### AccessFlag
这个比较好解决,Android Q 上也是因为 Hidden Api 机制为每个方法增加了一个 Flag,导致我使用预测 Flag 值在 ArtMethod 中搜索 Offset 未能搜到。
kAccPublicApi = 0x10000000 代表此方法/Field 为公开 API
----
```cpp
uint32_t accessFlag = getIntFromJava(jniEnv, "com/swift/sandhook/SandHook",
"testAccessFlag");
if (accessFlag == 0) {
accessFlag = 524313;
//kAccPublicApi
if (SDK_INT >= ANDROID_Q) {
accessFlag |= 0x10000000;
}
}
int offset = findOffset(p, getParentSize(), 2, accessFlag);
```
----
### hidden api
这个是从 Android P 就开始引入的反射限制机制。
目前来说有几种方案:
- Hook 法,Hook 某些判断函数,修改 ART 对限制 API 的判断流程
- Hidden API 在内部抽象为 Policy,修改全局的 Policy 策略,一般是 Runtime 中的某个 Flag
- 系统函数调 Hidden API 是 OK 的,想办法让 ART 误以为是系统函数调用 API,一般是修改 ClassLoader
----
### P 与 Q 的区别
P 上,判断函数较为集中,Policy 的 Flag 也较为好搜索,然而到了 Q 上就多了,至于在 Runtime 中搜索 Flag,由于 Runtime 是个巨大的结构体,这并不是一个健壮的方法。。。
----
### 另外一种解决办法
- 最后是想办法让 ART 误以为是系统函数调用 API,还有一种办法是双重反射,即用反射调用 Class.getDeclareMethod 之类的 API 去使用反射,也能达到目的。(这个方法还是在贴吧偶然看到的,OpenJDK 也有这个问题)
- 后面就简单了,依据此法找到 Hidden API 的开关方法,调用即可。
----
#### 实现
```java
static {
try {
getMethodMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class);
forNameMethod = Class.class.getDeclaredMethod("forName", String.class);
vmRuntimeClass = (Class) forNameMethod.invoke(null, "dalvik.system.VMRuntime");
addWhiteListMethod = (Method) getMethodMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", new Class[]{String[].class});
Method getVMRuntimeMethod = (Method) getMethodMethod.invoke(vmRuntimeClass, "getRuntime", null);
vmRuntime = getVMRuntimeMethod.invoke(null);
} catch (Exception e) {
Log.e("ReflectionUtils", "error get methods", e);
}
}
public static boolean passApiCheck() {
try {
addReflectionWhiteList("Landroid/", "Lcom/android/");
return true;
} catch (Throwable throwable) {
throwable.printStackTrace();
return false;
}
}
```
----
## 架构图
![sandhook_arch.png](res/sandhook_arch.png)
---
## 进程注入
到上面为止,Hook 的大部分细节已经介绍完了,但是本进程的 Hook 不是我们想要的。我们想要将 Hook 作用于其他进程则必须将 Hook 逻辑注入到目标进程。
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment