Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in / Register
Toggle navigation
S
SandHook
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
SandHook
Commits
3489c801
Commit
3489c801
authored
Apr 03, 2019
by
swift_gan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
update doc
parent
78dc131d
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
359 additions
and
111 deletions
+359
-111
doc.md
doc/doc.md
+359
-111
sandhook_arch.png
doc/res/sandhook_arch.png
+0
-0
No files found.
doc/doc.md
View file @
3489c801
...
@@ -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
#### StopTheWor
l
d
那么我们需要暂停所有线程,并且等待 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 入口,不是则跳到备份的指令继续执行原方法
---
----
#### 跳转图
#### 跳转图


---
---
-
#### 入口相同的情况
#### 入口相同的情况
...
@@ -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) {
}
}
}
}
```
```
---
---
-
#### 汇编代码
#### 汇编代码


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
;
}
}
```
----
## 架构图

---
## 进程注入
到上面为止,Hook 的大部分细节已经介绍完了,但是本进程的 Hook 不是我们想要的。我们想要将 Hook 作用于其他进程则必须将 Hook 逻辑注入到目标进程。
doc/res/sandhook_arch.png
0 → 100644
View file @
3489c801
43.9 KB
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment