Added new documentations files

This commit is contained in:
2022-09-17 14:46:07 +08:00
parent dbd5a74a5c
commit 237d5cbed0
99 changed files with 20686 additions and 0 deletions

View File

@@ -0,0 +1,677 @@
# 用法示例
> 这里介绍了 `YukiHookAPI` 的基本工作方式以及列举了简单的 Hook 例子和常用功能。
## 结构图解
> 下方的结构描述了 `YukiHookAPI` 的基本工作方式和原理。
```:no-line-numbers
Host Environment
└── YukiMemberHookCreator
└── Class
└── MemberHookCreator
└── Member
├── Before
└── After
MemberHookCreator
└── Member
├── Before
└── After
...
YukiResourcesHookCreator
└── Resources
└── ResourcesHookCreator
└── Drawable
└── Replace
ResourcesHookCreator
└── Layout
└── Inject
...
```
> 上方的结构换做代码将可写为如下形式。
```kotlin
TargetClass.hook {
injectMember {
method {
// Your code here.
}
beforeHook {
// Your code here.
}
afterHook {
// Your code here.
}
}
}
resources().hook {
injectResource {
conditions {
// Your code here.
}
replaceTo(...)
}
}
```
## Demo
> 你可以在下方找到 API 提供的 Demo 来学习 `YukiHookAPI` 的使用方法。
- 宿主 APP Demo [点击这里查看](https://github.com/fankes/YukiHookAPI/tree/master/demo-app)
- 模块 APP Demo [点击这里查看](https://github.com/fankes/YukiHookAPI/tree/master/demo-module)
同时安装宿主和模块 Demo通过激活模块来测试宿主中被 Hook 的功能。
## 一个简单的 Hook 例子
> 这里给出了 Hook APP、Hook 系统框架与 Hook Resources 等例子,可供参考。
### Hook APP
假设,我们要 Hook `com.android.browser` 中的 `onCreate` 方法并弹出一个对话框。
在 `encase` 方法体中添加代码。
> 示例如下
```kotlin
loadApp(name = "com.android.browser") {
ActivityClass.hook {
injectMember {
method {
name = "onCreate"
param(BundleClass)
returnType = UnitType
}
afterHook {
AlertDialog.Builder(instance())
.setTitle("Hooked")
.setMessage("I am hook!")
.setPositiveButton("OK", null)
.show()
}
}
}
}
```
至此,`onCreate` 方法将被成功 Hook 并在 `com.android.browser` 中的每个 `Activity` 启动时弹出此对话框。
那么,我想继续 Hook `onStart` 方法要怎么做呢?
在刚刚的代码中,继续插入一个 `injectMember` 方法体即可。
> 示例如下
```kotlin
loadApp(name = "com.android.browser") {
ActivityClass.hook {
injectMember {
method {
name = "onCreate"
param(BundleClass)
returnType = UnitType
}
afterHook {
AlertDialog.Builder(instance())
.setTitle("Hooked")
.setMessage("I am hook!")
.setPositiveButton("OK", null)
.show()
}
}
injectMember {
method {
name = "onStart"
emptyParam()
returnType = UnitType
}
afterHook {
// Your code here.
}
}
}
}
```
对于当前项目下没有的 `Class`,你可以使用 `stub` 方式或 `findClass` 方法来得到需要 Hook 的类。
比如,我要得到 `com.example.demo.TestClass`。
> 示例如下
```kotlin
findClass(name = "com.example.demo.TestClass").hook {
injectMember {
// Your code here.
}
}
```
若 `com.example.demo` 是你要 Hook 的 APP那么写法可以更简单。
> 示例如下
```kotlin
findClass(name = "$packageName.TestClass").hook {
injectMember {
// Your code here.
}
}
```
到这里有些同学可能就开始说了,在某些场景下 `findClass` 显得有些繁琐。
因为可能有些同学有如下需求。
> 示例如下
```kotlin
const val TestClass = "com.example.demo.TestClass"
TestClass.hook {
injectMember {
// Your code here.
}
}
```
没关系,你还可以使用字符串类名直接创建一个 Hook。
> 示例如下
```kotlin
"$packageName.TestClass".hook {
injectMember {
// Your code here.
}
}
```
::: tip
更多功能请参考 [MemberHookCreator](../api/public/com/highcapable/yukihookapi/hook/core/YukiMemberHookCreator#memberhookcreator-class)。
:::
### Hook Zygote
在 APP 启动时,新的进程被 fork 后的第一个事件 `initZygote`。
假设我们要全局 Hook 一个 APP `Activity` 的 `onCreate` 事件
在 `encase` 方法体中添加代码。
> 示例如下
```kotlin
loadZygote {
ActivityClass.hook {
injectMember {
method {
name = "onCreate"
param(BundleClass)
returnType = UnitType
}
afterHook {
// Your code here.
}
}
}
}
```
::: warning
在 **loadZygote** 中进行的功能十分有限,几乎很少的情况下需要用到 **loadZygote** 方法。
:::
### Hook 系统框架
在 `YukiHookAPI` 中Hook 系统框架的实现非常简单。
假设,你要得到 `ApplicationInfo` 与 `PackageInfo` 并对它们进行一些操作。
在 `encase` 方法体中添加代码。
> 示例如下
```kotlin
loadSystem {
ApplicationInfoClass.hook {
// Your code here.
}
PackageInfoClass.hook {
// Your code here.
}
}
```
::: danger
**loadZygote** 与 **loadSystem** 有直接性区别,**loadZygote** 会在 **initZygote** 中装载,系统框架被视为 **loadApp(name = "android")** 而存在,若要 Hook 系统框架,可直接使用 **loadSystem**。
:::
### Hook Resources
假设,我们要 Hook `com.android.browser` 中 `string` 类型的 `app_name` 内容替换为 `123`。
在 `encase` 方法体中添加代码。
> 示例如下
```kotlin
loadApp(name = "com.android.browser") {
resources().hook {
injectResource {
conditions {
name = "app_name"
string()
}
replaceTo("123")
}
}
}
```
若当前 APP 使用 `app_name` 设置了标题栏文本,则它就会变成我们的 `123`。
你还可以使用当前 Xposed 模块的 Resources 替换 Hook APP 的 Resources。
假设,我们要继续 Hook `com.android.browser` 中 `mipmap` 类型的 `ic_launcher`。
> 示例如下
```kotlin
loadApp(name = "com.android.browser") {
resources().hook {
injectResource {
conditions {
name = "ic_launcher"
mipmap()
}
replaceToModuleResource(R.mipmap.ic_launcher)
}
}
}
```
至此目标 APP 的图标将会被替换为我们设置的图标。
若你想替换系统框架的资源,同样也可以这样实现,只需要把 `loadApp` 换成 `loadZygote` 即可。
> 示例如下
```kotlin
loadZygote {
resources().hook {
// Your code here.
}
}
```
::: tip
更多功能请参考 [ResourcesHookCreator](../api/public/com/highcapable/yukihookapi/hook/core/YukiResourcesHookCreator#resourceshookcreator-class)。
:::
### 解除 Hook
原生的 Xposed 为我们提供了一个 `XC_MethodHook.Unhook` 功能,可以从 Hook 队列中将当前 Hook 移除,`YukiHookAPI` 同样可以实现此功能。
第一种方法,保存当前注入对象的 `Result` 实例,在适当的时候和地方调用 `remove` 即可解除该注入对象。
> 示例如下
```kotlin
// 设置一个变量保存当前实例
val hookResult = injectMember {
method {
name = "test"
returnType = UnitType
}
afterHook {
// ...
}
}
// 在适当的时候调用如下方法即可
hookResult.remove()
```
第二种方法,在 Hook 回调方法中调用 `removeSelf` 移除自身。
> 示例如下
```kotlin
injectMember {
method {
name = "test"
returnType = UnitType
}
afterHook {
// 直接调用如下方法即可
removeSelf()
}
}
```
::: tip
更多功能请参考 [MemberHookCreator](../api/public/com/highcapable/yukihookapi/hook/core/YukiMemberHookCreator#memberhookcreator-class)。
:::
## 异常处理
> `YukiHookAPI` 重新设计了对异常的监听,任何异常都不会在 Hook 过程中抛出,避免打断下一个 Hook 流程导致 Hook 进程“死掉”。
### 监听异常
你可以处理 Hook 方法过程发生的异常。
> 示例如下
```kotlin
injectMember {
// Your code here.
}.result {
// 处理 Hook 开始时的异常
onHookingFailure {}
// 处理 Hook 过程中的异常
onConductFailure { param, throwable -> }
// 处理全部异常
onAllFailure {}
// ...
}
```
在 Resources Hook 时此方法同样适用。
> 示例如下
```kotlin
injectResource {
// Your code here.
}.result {
// 处理 Hook 时的任意异常
onHookingFailure {}
// ...
}
```
你还可以处理 Hook 的 `Class` 不存在时发生的异常。
> 示例如下
```kotlin
TargetClass.hook {
injectMember {
// Your code here.
}
}.onHookClassNotFoundFailure {
// Your code here.
}
```
你还可以处理查找方法时的异常。
> 示例如下
```kotlin
method {
// Your code here.
}.onNoSuchMethod {
// Your code here.
}
```
::: tip
更多功能请参考 [MemberHookCreator.Result](../api/public/com/highcapable/yukihookapi/hook/core/YukiMemberHookCreator#result-class)、[ResourcesHookCreator.Result](../api/public/com/highcapable/yukihookapi/hook/core/YukiResourcesHookCreator#result-class)。
:::
这里介绍了可能发生的常见异常,若要了解更多请参考 [API 异常处理](../config/api-exception)。
### 抛出异常
在某些情况下,你可以**手动抛出异常**来达到提醒某些功能存在问题的目的。
上面已经介绍过,在 `hook` 方法体内抛出的异常会被 `YukiHookAPI` 接管,避免打断下一个 Hook 流程导致 Hook 进程“死掉”。
以下是 `YukiHookAPI` 接管时这些异常的运作方式。
> 示例如下
```kotlin
// <情景1>
injectMember {
method {
throw RuntimeException("Exception Test")
}
afterHook {
// ...
}
}.result {
// 能够捕获到 RuntimeException
onHookingFailure {}
}
// <情景2>
injectMember {
method {
// ...
}
afterHook {
throw RuntimeException("Exception Test")
}
}.result {
// 能够捕获到 RuntimeException
onConductFailure { param, throwable -> }
}
```
以上情景只会在 (Xposed) 宿主环境被处理,不会对宿主自身造成任何影响。
若我们想将这些异常直接抛给宿主,原生的 Xposed 为我们提供了 `param.throwable` 方法,`YukiHookAPI` 同样可以实现此功能。
若想在 Hook 回调方法体中将一个异常直接抛给宿主,可以有如下实现方法。
> 示例如下
```kotlin
injectMember {
method {
// ...
}
afterHook {
RuntimeException("Exception Test").throwToApp()
}
}
```
你也可以直接在 Hook 回调方法体中抛出异常,然后标识将异常抛给宿主。
> 示例如下
```kotlin
injectMember {
method {
// ...
}
afterHook {
throw RuntimeException("Exception Test")
}.onFailureThrowToApp()
}
```
以上两种方法均可在宿主接收到异常从而使宿主进程崩溃。
::: warning
为了保证 Hook 调用域与宿主内调用域相互隔离,异常只有在 **beforeHook** 与 **afterHook** 回调方法体中才能抛给宿主。
:::
::: tip
更多功能请参考 [Throwable.throwToApp](../api/public/com/highcapable/yukihookapi/hook/param/HookParam#throwable-throwtoapp-i-ext-method)、[YukiMemberHookCreator.MemberMookCreator.HookCallback](../api/public/com/highcapable/yukihookapi/hook/core/YukiMemberHookCreator#hookcallback-class)。
:::
## 状态监听
在使用 `XposedHelpers` 的同学往往会在 Hook 后打印 `Unhook` 的方法确定是否 Hook 成功。
在 `YukiHookAPI` 中,你可以用以下方法方便地重新实现这个功能。
首先我们可以监听 Hook 已经准备开始。
> 示例如下
```kotlin
YourClass.hook {
// Your code here.
}.onPrepareHook {
loggerD(msg = "$instanceClass hook start")
}
```
::: danger
**instanceClass** 建议只在 **onPrepareHook** 中使用,否则被 Hook 的 **Class** 不存在会抛出无法拦截的异常导致 Hook 进程“死掉”。
:::
然后,我们还可以对 Hook 的方法结果进行监听是否成功。
> 示例如下
```kotlin
injectMember {
// Your code here.
}.onHooked { member ->
loggerD(msg = "$member has hooked")
}
```
## 扩展用法
> 你可以在 Hook 过程中使用下面的方法方便地实现各种判断和功能。
### 多个宿主
如果你的模块需要同时处理多个 APP 的 Hook 事件,你可以使用 `loadApp` 方法体来区分你要 Hook 的 APP。
> 示例如下
```kotlin
loadApp(name = "com.android.browser") {
// Your code here.
}
loadApp(name = "com.android.phone") {
// Your code here.
}
```
::: tip
更多功能请参考 [PackageParam.loadApp](../api/public/com/highcapable/yukihookapi/hook/param/PackageParam#loadapp-method)。
:::
### 多个进程
如果你 Hook 的宿主 APP 有多个进程,你可以使用 `withProcess` 方法体来对它们分别进行 Hook。
> 示例如下
```kotlin
withProcess(mainProcessName) {
// Your code here.
}
withProcess(name = "$packageName:tool") {
// Your code here.
}
```
::: tip
更多功能请参考 [PackageParam.withProcess](../api/public/com/highcapable/yukihookapi/hook/param/PackageParam#withprocess-method)。
:::
## 写法优化
为了使代码更加简洁,你可以删去 `YukiHookAPI` 的名称,将你的 `onHook` 入口写作 `lambda` 形式。
> 示例如下
```kotlin
override fun onHook() = encase {
// Your code here.
}
```
## Xposed 模块判断自身激活状态
通常情况下,我们会选择写一个方法,使其返回 `false`,然后 Hook 掉这个方法使其返回 `true` 来证明 Hook 已经生效。
在 `YukiHookAPI` 中你完全不需要再这么做了,`YukiHookAPI` 已经帮你封装好了这个操作,你可以直接进行使用。
现在,你可以直接使用 `YukiHookAPI.Status.isXposedModuleActive` 在模块中判断自身是否被激活。
> 示例如下
```kotlin
if(YukiHookAPI.Status.isXposedModuleActive) {
// Your code here.
}
```
由于一些特殊原因,在太极、无极中的模块无法使用标准方法检测激活状态。
此时你可以使用 `YukiHookAPI.Status.isTaiChiModuleActive` 判断自身是否被激活。
> 示例如下
```kotlin
if(YukiHookAPI.Status.isTaiChiModuleActive) {
// Your code here.
}
```
若你想使用两者得兼的判断方案,`YukiHookAPI` 同样为你封装了便捷的方式。
此时你可以使用 `YukiHookAPI.Status.isModuleActive` 判断自身是否在 Xposed 或太极、无极中被激活。
> 示例如下
```kotlin
if(YukiHookAPI.Status.isModuleActive) {
// Your code here.
}
```
::: tip
更多功能请参考 [YukiHookAPI.Status](../api/public/com/highcapable/yukihookapi/YukiHookAPI#status-object)。
:::
::: warning
若模块激活判断中包含太极、无极中的激活状态,就必须将模块的 **Application** 继承于 **ModuleApplication** 或直接使用 **ModuleApplication**
**1.0.91** 版本后的 API 修改了激活逻辑判断方式,现在你可以在模块与 Hook APP (宿主) 中同时使用此 API
需要确保 **YukiHookAPI.Configs.isEnableHookModuleStatus** 是启用状态;
除了提供标准 API 的 Hook 框架之外,其它情况下模块可能都将无法判断自己是否被激活。
:::

View File

@@ -0,0 +1,186 @@
# 介绍
> 这是一个 Hook API 框架,本身不提供任何 Hook 功能,需要 Xposed 基础 API 的支持。
## 背景
这是一个使用 `Kotlin` 重新构建的高效 Xposed Hook API。
名称取自 [《ももくり》女主 栗原 雪(Yuki)](https://www.bilibili.com/bangumi/play/ss5016)。
前身为 [开发学习项目](https://github.com/fankes/TMore) 中使用的 Innocent Xposed API现在重新命名并开源。
## 用途
`YukiHookAPI` 完全采用 `Kotlin` `lambda` 语法构建。
抛弃原始不太友好的 `XposedHelpers`,你可以使用它来轻松创建 Xposed 模块以及轻松实现自定义 Hook API。
## 语言要求
请使用 `Kotlin`,框架部分代码构成同样兼容 `Java` 但基础 Hook 场景的实现**可能完全无法使用**。
文档全部的 Demo 示例代码都将使用 `Kotlin` 进行描述,如果你完全不会使用 `Kotlin` 那你将有可能无法使用 `YukiHookAPI`
部分 Java Demo 代码可在 [这里](https://github.com/fankes/YukiHookAPI/tree/master/demo-module/src/main/java/com/highcapable/yukihookapi/demo_module/hook/java) 找到,但不推荐使用。
## 灵感来源
以前,我们在构建 Xposed 模块的时候,首先需要在 `assets` 下创建 `xposed_init` 文件。
然后,将自己的入口类名手动填入文件中,使用 `XposedHelpers` 去实现我们的 Hook 逻辑。
`Kotlin` 作为 Android 主要开发语言以来,这套 API 用起来确实已经不是很优雅了。
有没有什么 **好用、轻量、优雅** 的解决办法呢?
本着这样的想法,`YukiHookAPI` 诞生了。
现在,我们只需要编写少量的代码,一切时间开销和花费交给自动化处理。
借助 `Kotlin` 优雅的 `lambda` 写法以及 `YukiHookAPI`,可以让你的 Hook 逻辑更加美观清晰。
> 示例如下
:::: code-group
::: code-group-item Yuki Hook API
```kotlin
@InjectYukiHookWithXposed
class HookEntry : IYukiHookXposedInit {
override fun onHook() = encase {
loadZygote {
ActivityClass.hook {
injectMember {
method {
name = "onCreate"
param(BundleClass)
}
beforeHook {
// Your code here.
}
afterHook {
// Your code here.
}
}
}
resources().hook {
injectResource {
conditions {
name = "sym_def_app_icon"
mipmap()
}
replaceToModuleResource(R.mipmap.ic_launcher)
}
}
}
loadApp(name = "com.android.browser") {
ActivityClass.hook {
injectMember {
method {
name = "onCreate"
param(BundleClass)
}
beforeHook {
// Your code here.
}
afterHook {
// Your code here.
}
}
}
resources().hook {
injectResource {
conditions {
name = "ic_launcher"
mipmap()
}
replaceToModuleResource(R.mipmap.ic_launcher)
}
}
}
}
}
```
:::
::: code-group-item Xposed API
```kotlin
class HookEntry : IXposedHookZygoteInit, IXposedHookLoadPackage, IXposedHookInitPackageResources {
private lateinit var moduleResources: XModuleResources
override fun initZygote(sparam: IXposedHookZygoteInit.StartupParam) {
moduleResources = XModuleResources.createInstance(sparam.modulePath, null)
XResources.setSystemWideReplacement(
"android", "mipmap", "sym_def_app_icon",
moduleResources.fwd(R.mipmap.ic_launcher)
)
XposedHelpers.findAndHookMethod(
Activity::class.java.name,
null, "onCreate",
Bundle::class.java,
object : XC_MethodHook() {
override fun beforeHookedMethod(param: MethodHookParam?) {
// Your code here.
}
override fun afterHookedMethod(param: MethodHookParam?) {
// Your code here.
}
})
}
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
if (lpparam.packageName == "com.android.browser")
XposedHelpers.findAndHookMethod(
Activity::class.java.name,
lpparam.classLoader, "onCreate",
Bundle::class.java,
object : XC_MethodHook() {
override fun beforeHookedMethod(param: MethodHookParam?) {
// Your code here.
}
override fun afterHookedMethod(param: MethodHookParam?) {
// Your code here.
}
})
}
override fun handleInitPackageResources(resparam: XC_InitPackageResources.InitPackageResourcesParam) {
if (resparam.packageName == "com.android.browser")
resparam.res.setReplacement(
"com.android.browser", "mipmap", "ic_launcher",
moduleResources.fwd(R.mipmap.ic_launcher)
)
}
}
```
:::
::::
是的,你没有看错,仅仅就需要这些代码,就能完全取代 Xposed API 实现同样的功能。
现在,借助高效强大的 `YukiHookAPI`,你就可以实现一个非常简单的 Xposed 模块。
## 支持的 Hook 框架
以下是 `YukiHookAPI` 支持的 `Hook Framework` 以及 Xposed 框架。
| Hook Framework | ST | Description |
| --------------------------------------------------------- | --- | ----------------------------------------------------------------------------------------- |
| [LSPosed](https://github.com/LSPosed/LSPosed) | ✅ | 多场景下稳定使用 |
| [LSPatch](https://github.com/LSPosed/LSPatch) | ⭕ | 将在此项目完善后逐渐加入 API 支持 |
| [EdXposed](https://github.com/ElderDrivers/EdXposed) | ❎ | 已停止维护,不再推荐使用 |
| [Pine](https://github.com/canyie/pine) | ⭕ | 可以使用 |
| [SandHook](https://github.com/asLody/SandHook) | ⭕ | 可以使用 |
| [Whale](https://github.com/asLody/whale) | ⭕ | 需要 [xposed-hook-based-on-whale](https://github.com/WindySha/xposed-hook-based-on-whale) |
| [YAHFA](https://github.com/PAGalaxyLab/YAHFA) | ❗ | 需要自行实现 Xposed API |
| [FastHook](https://github.com/turing-technician/FastHook) | ❗ | 需要自行实现 Xposed API |
| [Epic](https://github.com/tiann/epic) | ❗ | 需要自行对接 [Dexposed](https://github.com/alibaba/dexposed) |
| [TaiChi](https://github.com/taichi-framework/TaiChi) | ⭕ | 可以作为模块使用 |
| [Xposed](https://github.com/rovo89/Xposed) | ❎ | 未测试,不再推荐使用 |

View File

@@ -0,0 +1,85 @@
# 基础知识
> 这里收集了 Xposed 相关的介绍以及开启前需要掌握的知识要点,已经了解的同学可以略过。
基础知识内容<u>**并不一定完全准确**</u>,请根据自己的见解酌情阅读,若发现内容**有错误欢迎指正并帮助我们完善和改进**。
## 相关介绍
> 这里介绍了 Xposed 以及 Hook 的工作原理。
### Xposed 是什么
> Xposed 框架(Xposed Framework)是一套开源的、在 Android 高权限模式下运行的框架服务,可以在不修改 APK 文件的情况下影响程序运行(修改系统)的框架服务,基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。
上述内容复制自百度百科。
### Xposed 能做什么
> 下方的结构描述了 Xposed 的基本工作方式和原理。
```:no-line-numbers
Xposed Framework
└── App's Environment
└── Hooker (Hooked)
...
App's Environment
└── Hooker (Hooked)
...
...
```
我们可以在宿主 (APP) 运行时通过注入宿主 (APP) 来达到控制其行为的最终目的。
Xposed 的这种运行方式被称为**寄生**Xposed 模块跟随宿主的生命周期,在宿主的生命周期内完成自己的生命历程。
我们可以通过反射的方式调用宿主的方法、变量、构造方法,以及使用 `XposedBridge` 所提供的 Hook 操作动态地在宿主 (APP) 要执行的方法前后插入自己的代码,或完全替换目标,甚至是拦截。
### 发展过程
如今的 Xposed 管理器已完全被其衍生作品替代,而 **SuperSU** 的时代也已经落幕了,现在,借助 **Magisk** 使后面的一切又成为了可能。
> 其发展史大致可分为 **Xposed(Dalvik)** → **Xposed(ART)** → **Xposed(Magisk)** → **EdXposed(Riru)**/**LSPosed(Riru/Zygisk)**
### 衍生产品
> 下方的结构描述了类似 Xposed 的 Hook Framework 的工作方式和原理。
```:no-line-numbers
App's Environment
└── Hook Framework
└── Hooker (Hooked)
...
```
通过 Xposed 的运行原理,从而衍生了很多同类型框架,随着当今时代的移动设备获取 Root 权限甚至刷机越来越困难且不是刚需的时候,一些免 Root 框架也随之产生,例如**太极**。
这些在 ART 层面上的 Hook 框架同样也可不借助 Xposed API 完成其和 Xposed 原理一样的 Hook 流程,免 Root 的运行原理为修改 APK 并将 Hook 进程注入宿主,通过外部模块对其进行控制。
另外一种产品就是利用 Android 运行环境现有的功能虚拟出一个完全与当前设备系统一样的环境,并在其中运行 APP这个就是虚拟 APP 技术 **VirtualApp**,后来衍生为 **VirtualXposed**。
上述提到的免 Root 框架分别为**太极/无极**、**VirtualXposed/SandVXposed**。
### YukiHookAPI 做了什么
自从 Xposed 出现到现在为止,除了开发者人人皆知的 `XposedHelpers`,依然没有一套针对 `Kotlin` 打造的语法糖以及用法封装十分完善的 API。
本 API 框架的诞生就是希望在 Xposed 的如今时代,能让更多有动手能力的 Xposed 模块开发者少走弯路,更容易、更简单地完成整个开发流程。
未来,`YukiHookAPI` 将在使用 Xposed API 的目标基础上适配更多第三方 Hook 框架,使得整个生态得到完善,并帮助更多开发者让 Xposed 模块开发变得更加简单和易懂。
## 让我们开始吧
在开始之前,你需要拥有以下基础才能更好地使用 `YukiHookAPI`。
- 掌握并了解 Android 开发及简单的系统运行原理
- 掌握并了解 Android APK 内部结构以及简单的反编译知识要领,可参考 [Jadx](https://github.com/skylot/jadx) 与 [ApkTool](https://github.com/iBotPeaches/Apktool)
- 掌握并熟练使用 Java 反射,了解简单的 Smali 语法,了解 DEX 文件结构,会使用逆向分析定位方法位置
- 掌握基础的原生 [Xposed API](https://api.xposed.info) 用法,了解 Xposed 的运行原理,可参考本文以及 [这里](https://blog.ketal.icu/2022/01/13/Xposed%E6%A8%A1%E5%9D%97%E5%BC%80%E5%8F%91%E5%85%A5%E9%97%A8%E4%BF%9D%E5%A7%86%E7%BA%A7%E6%95%99%E7%A8%8B) **(友情链接)**
- 掌握 Kotlin 语言,学会灵活运用 [Kotlin lambda](https://blog.ketal.icu/2022/01/01/kotlin-lambda%E5%85%A5%E9%97%A8) **(友情链接)**
- 掌握并了解 Kotlin 与 Java 混编、互相调用以及 Kotlin 生成的 Java 字节码

View File

@@ -0,0 +1,259 @@
# 从 Xposed API 迁移
> 若你熟悉 Xposed API你可以参考下方的相同点将自己的 API 快速迁移到 `YukiHookAPI`。
## 迁移 Hook 入口点
> 从 `XC_LoadPackage.LoadPackageParam` 迁移至 `PackageParam`。
`YukiHookAPI``PackageParam` 实现了 `lambda` 方法体 `this` 用法,在 `encase` 方法体内即可全局得到 `PackageParam` 对象。
> API 功能差异对比如下
:::: code-group
::: code-group-item Yuki Hook API
```kotlin
override fun onHook() = encase {
// 得到当前 Hook 的包名
packageName
// 得到当前 Hook 的 ApplicationInfo
appInfo
// 得到系统上下文对象
systemContext
// 得到宿主 Application 生命周期
appContext
// Hook 指定的 APP
loadApp(name = "com.demo.test") {
// Class Hook
findClass("com.demo.test.TestClass").hook {
injectMember {
method {
name = "test"
param(BooleanType)
}
afterHook {
// ...
}
}
}
// Resources Hook (固定用法)
resources().hook {
injectResource {
conditions {
name = "ic_launcher"
mipmap()
}
replaceToModuleResource(R.mipmap.ic_launcher)
}
}
}
}
```
:::
::: code-group-item Xposed API
```kotlin
private lateinit var moduleResources: XModuleResources
override fun initZygote(sparam: IXposedHookZygoteInit.StartupParam) {
moduleResources = XModuleResources.createInstance(sparam.modulePath, null)
}
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
// 得到当前 Hook 的包名
lpparam.packageName
// 得到当前 Hook 的 ApplicationInfo
lpparam.applicationInfo
// 得到系统上下文对象
// 在原生 Xposed API 中没有现成的调用方法,你需要自行反射 ActivityThread 来实现
// 得到宿主 Application 生命周期
AndroidAppHelper.currentApplication()
// Class Hook
if(lpparam.packageName == "com.demo.test")
XposedHelpers.findAndHookMethod(
"com.demo.test.TestClass", lpparam.classLoader,
"test", Boolean::class.java,
object : XC_MethodHook() {
override fun afterHookedMethod(param: MethodHookParam) {
// ...
}
}
)
}
override fun handleInitPackageResources(resparam: XC_InitPackageResources.InitPackageResourcesParam) {
// 得到当前 Hook 的包名
resparam.packageName
// Resources Hook
resparam.res.setReplacement(
"com.demo.test", "mipmap","ic_launcher",
moduleResources.fwd(R.mipmap.ic_launcher)
)
}
```
:::
::::
## 迁移 Hook 方法体
> 从 `XC_MethodHook.MethodHookParam` 迁移至 `HookParam`。
### Before/After Hook
`YukiHookAPI` 同样对 `HookParam` 实现了 `lambda` 方法体 `this` 用法,在 `beforeHook``afterHook` 等方法体内即可全局得到 `HookParam` 对象。
> API 功能差异对比如下
:::: code-group
::: code-group-item Yuki Hook API
```kotlin
afterHook {
// 得到当前 Hook 的实例
instance
// 得到当前 Hook 的 Class 实例
instanceClass
// 得到并 cast 当前 Hook 的实例为指定类型 T
instance<T>()
// 得到方法参数数组
args
// 得到方法参数的第一位 T
args().first().cast<T>()
// 得到方法参数的最后一位 T
args().last().cast<T>()
// 得到方法参数的任意下标 T这里用 2 举例
args(index = 2).cast<T>()
// 设置方法参数的任意下标,这里用 2 举例
args(index = 2).set(...)
// 得到返回值
result
// 得到返回值并 cast 为 T
result<T>()
// 修改返回值内容
result = ...
// 删除返回值内容
resultNull()
// 向 Hook APP 抛出异常
Throwable("Fatal").throwToApp()
// 执行未经 Hook 的原始方法并使用原始方法参数调用,泛型可略
callOriginal<Any?>()
// 执行未经 Hook 的原始方法并自定义方法参数调用,泛型可略
invokeOriginal<Any?>(...)
}
```
:::
::: code-group-item Xposed API
```kotlin
override fun afterHookedMethod(param: MethodHookParam) {
// 得到当前 Hook 的实例
param.thisObject
// 得到当前 Hook 的 Class 实例
param.thisObject.javaClass
// 得到并 cast 当前 Hook 的实例为指定类型 T
param.thisObject as T
// 得到方法参数数组
param.args
// 得到方法参数的第一位 T
param.args[0] as T
// 得到方法参数的最后一位 T
param.args[param.args.lastIndex] as T
// 得到方法参数的任意下标 T这里用 2 举例
param.args[2] as T
// 设置方法参数的任意下标,这里用 2 举例
param.args[2] = ...
// 得到返回值
param.result
// 得到返回值并 cast 为 T
param.result as T
// 修改返回值内容
param.result = ...
// 删除返回值内容
param.result = null
// 向 Hook APP 抛出异常
param.throwable = Throwable("Fatal")
// 执行未经 Hook 的原始方法
XposedBridge.invokeOriginalMethod(param.method, param.thisObject, ...)
}
```
:::
::::
### Replace Hook
`replaceHook` 方法比较特殊,`YukiHookAPI` 为它做出了多种形式以供选择。
> API 功能差异对比如下
:::: code-group
::: code-group-item Yuki Hook API
```kotlin
/// 无返回值的方法 void
replaceUnit {
// 直接在这里实现被替换的逻辑
}
/// 有返回值的方法
replaceAny {
// 在这里实现被替换的逻辑
// ...
// 需要返回方法对应的返回值,无需写 return只需将参数放到最后一位
// 假设这个方法的返回值是 Int我们只需要保证最后一位是我们需要的返回值即可
0
}
/// 有些方法我们只需替换其返回值,则有如下实现
/// 需要注意的是:直接替换返回值的方法传入的参数是固定不变的,若想实现动态替换返回值请使用上面的 replaceAny 方法体
// 替换为你需要的返回值
replaceTo(...)
// 替换为 Boolean 类型的返回值
replaceToTrue()
// 拦截返回值
intercept()
```
:::
::: code-group-item Xposed API
```kotlin
/// 无返回值的方法 void
override fun replaceHookedMethod(param: MethodHookParam): Any? {
// 直接在这里实现被替换的逻辑
return null
}
/// 有返回值的方法
override fun replaceHookedMethod(param: MethodHookParam): Int {
// 在这里实现被替换的逻辑
// ...
// 假设这个方法的返回值是 Int
return 0
}
/// 有些方法我们只需替换其返回值,则有如下实现
// 替换为你需要的返回值
override fun replaceHookedMethod(param: MethodHookParam) = ...
// 替换为 Boolean 类型的返回值
override fun replaceHookedMethod(param: MethodHookParam) = true
// 拦截返回值
override fun replaceHookedMethod(param: MethodHookParam) = null
```
:::
::::
## 迁移其它功能
`YukiHookAPI` 对 Xposed API 进行了完全重写,你可以参考 [API 文档](../api/home) 以及 [特色功能](../api/special-features/reflection) 来决定一些功能性的迁移和使用。

View File

@@ -0,0 +1,208 @@
# 快速开始
> 集成 `YukiHookAPI` 到你的项目中。
## 环境要求
- Windows 7 及以上/macOS 10.14 及以上/Linux 发行版(Arch/Debian)
- Android Studio 2021.1 及以上
- IntelliJ IDEA 2021.1 及以上
- Kotlin 1.7.0 及以上
- Android Gradle Plugin 7.0 及以上
- Gradle 7.0 及以上
- Jvm 11 及以上 (Since API `1.0.80`)
## 自动构建项目
`YukiHookAPI` 提供了一个自动化构建工具,它可以帮助你快速构建一个拥有 Xposed 模块依赖的 Android 标准项目模板,使用构建好的模板即可直接开始下一步工作。
你可以 [点击这里](../tools/yukihookapi-projectbuilder) 进行查看。
## 手动配置项目
若你不想使用自动化构建工具,你依然可以按照以下方式手动配置项目依赖。
### 创建项目
使用 `Android Studio``IntelliJ IDEA` 创建新的 Android 项目,并在 `Language` 一栏选择 `Kotlin` 以自动添加基础依赖。
### 集成依赖
在你的项目 `build.gradle` 中添加依赖。
> 示例如下
```groovy
repositories {
google()
mavenCentral()
// ❗若你的 Plugin 版本过低,作为 Xposed 模块使用务必添加,其它情况可选
maven { url "https://dl.bintray.com/kotlin/kotlin-eap" }
// ❗作为 Xposed 模块使用务必添加,其它情况可选
maven { url "https://api.xposed.info/" }
// MavenCentral 有 2 小时缓存,若无法集成最新版本请添加此地址
maven { url "https://s01.oss.sonatype.org/content/repositories/releases" }
}
```
在你的 app `build.gradle` 中添加 `plugin`
> 示例如下
```groovy
plugins {
// ❗作为 Xposed 模块使用务必添加,其它情况可选
id 'com.google.devtools.ksp' version '<ksp-version>'
}
```
在你的 app `build.gradle` 中添加依赖。
> 示例如下
```groovy
dependencies {
// 基础依赖
implementation 'com.highcapable.yukihookapi:api:<yuki-version>'
// ❗作为 Xposed 模块使用务必添加,其它情况可选
compileOnly 'de.robv.android.xposed:api:82'
// ❗作为 Xposed 模块使用务必添加,其它情况可选
ksp 'com.highcapable.yukihookapi:ksp-xposed:<yuki-version>'
}
```
请将 **&lt;ksp-version&gt;** 修改为 [这里](https://github.com/google/ksp/releases) 的最新版本 **(请注意选择你当前对应的 Kotlin 版本)**。
请将 **&lt;yuki-version&gt;** 修改为 [这里](../about/changelog) 的最新版本。
::: danger
**YukiHookAPI****api****ksp-xposed** 依赖的版本必须一一对应,否则将会造成版本不匹配错误。
:::
在你的 app `build.gradle` 中修改 `Kotlin` 的 Jvm 版本为 11 及以上。
> 示例如下
```groovy
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '11'
}
}
```
::: warning
自 API **1.0.80** 版本后 Jvm 版本默认为 11不再支持 1.8 及以下版本。
:::
### 作为 Xposed 模块使用
在你的 `AndroidManifest.xml` 中添加基础代码。
> 示例如下
```xml
<!-- 设置为 Xposed 模块 -->
<meta-data
android:name="xposedmodule"
android:value="true" />
<!-- 设置你的模块描述 -->
<meta-data
android:name="xposeddescription"
android:value="填写你的 Xposed 模块描述" />
<!-- 最低 Xposed 版本号,若你正在使用 EdXposed/LSPosed建议最低为 93 -->
<meta-data
android:name="xposedminversion"
android:value="93" />
<!-- 可选:配置支持 New XSharePrefs 可无需调整 xposedminversion 为 93 -->
<meta-data
android:name="xposedsharedprefs"
android:value="true"/>
```
在你的项目中创建一个 Hook 入口类,继承于 `IYukiHookXposedInit` 并加入注解 `@InjectYukiHookWithXposed`
> 示例如下
```kotlin
@InjectYukiHookWithXposed
class HookEntry : IYukiHookXposedInit {
override fun onHook() = YukiHookAPI.encase {
// Your code here.
}
}
```
::: tip 建议
你可以将你的模块 APP 的 **Application** 继承于 **ModuleApplication** 以实现完整使用体验。
更多功能请参考 [ModuleApplication](../api/public/com/highcapable/yukihookapi/hook/xposed/application/ModuleApplication)。
:::
然后,你就可以开始编写 Hook 代码了。
有关作为 Xposed 模块使用的相关配置详细内容,你可以 [点击这里](../config/xposed-using) 继续阅读。
若你目前正在使用 Xposed API你可以参考 [从 Xposed API 迁移](../guide/move-to-new-api)。
### 作为 Hook API 使用
#### 集成方式
创建你的自定义 `Application`
::: danger
无论使用任何 **Hook Framework**,你都需要加入其对接的 Xposed 依赖支持。
若目标 **Hook Framework** 没有集成 Xposed API 你需要自行实现并对接 **XposedBridge**
:::
`attachBaseContext` 中添加 `YukiHookAPI.encase` 方法。
> 示例如下
```kotlin
override fun attachBaseContext(base: Context?) {
// 装载 Hook Framework
//
// Your code here.
//
// 装载 YukiHookAPI
YukiHookAPI.encase(base) {
// Your code here.
}
super.attachBaseContext(base)
}
```
然后,你就可以开始编写 Hook 代码了,方式与作为 Xposed 模块使用基本一致。
有关作为 Hook API 使用的相关配置详细内容,你可以 [点击这里](../config/api-using) 继续阅读。
::: warning
使用自定义的 Hook 框架而并非完整的 Xposed 模块时,**YukiHookModuleStatus**、**YukiHookModulePrefs**、**YukiHookDataChannel** 以及 Resources Hook 功能将失效。
:::