### 模块插件使用场景

主要用于在云机中为指定应用注入自己的代码，对java层或native进行修改。

### 支持的功能

自动注入dex
自动注入so
内置java层Hook框架LspLant
内置native层Hook框架Dobby

### 模块示例下载
[dplus_demo源码](https://resource-wangsu.helplook.net/docker_production/wkpufm/article/wXQ8ezGk/attachments/dplus_demo.zip)
[dplus_demo模块](https://resource-wangsu.helplook.net/docker_production/wkpufm/article/8FACyMwh/attachments/app-debug.apk)

### 编译环境

该项目当前使用：

- Android Gradle Plugin：8.6.0
- Gradle Wrapper：8.7
- compileSdk / targetSdk：34
- minSdk：29
- CMake：3.22.1
- JDK：**21**

> 注意：请使用 **JDK 21** 编译。若使用更高版本的 JDK（例如 JDK 23），执行 `./gradlew assembleDebug` 时可能出现如下错误：
>
> `Unsupported class file major version 67`

### 环境准备

建议使用 Android Studio 打开并编译项目，同时提前安装好以下组件：

- Android SDK Platform 34
- Android SDK Build-Tools
- CMake 3.22.1
- Android NDK
- JDK 21

### 下载和配置 JDK 21

#### 方式一：通过 Android Studio 下载

1. 打开 Android Studio。
2. 进入 `Settings` / `Preferences` -> `Build, Execution, Deployment` -> `Build Tools` -> `Gradle`。
3. 在 `Gradle JDK` 中选择 `Download JDK...`。
4. 版本选择 `21`，下载完成后应用配置即可。

如果你只在 Android Studio 中编译，这是最省事的方式。

#### 方式二：手动安装 JDK 21

你也可以自行安装 OpenJDK 21 / Temurin 21，然后配置 `JAVA_HOME`。

Linux / macOS 示例：

```bash
export JAVA_HOME=/path/to/jdk-21
export PATH=$JAVA_HOME/bin:$PATH

java -version
```

确认输出中包含 `21` 后，再执行编译命令。

如果你使用 `zsh`，可以将上面的两行环境变量追加到 `~/.zshrc` 中长期生效。

如果你不想修改系统默认 JDK，也可以单独为 Gradle 指定 JDK 21。在 `~/.gradle/gradle.properties` 或项目根目录的 `gradle.properties` 中加入：

```properties
org.gradle.java.home=/path/to/jdk-21
```

这样即使终端默认的 Java 版本不是 21，Gradle 也会优先使用这里指定的 JDK。

### 编译方法

#### 使用 Android Studio 编译

1. 使用 Android Studio 打开项目。
2. 确认 `Gradle JDK` 为 **JDK 21**。
3. 等待 Gradle Sync 完成。
4. 执行 `Build` -> `Make Project`，或者直接编译 `assembleDebug`。

#### 使用命令行编译

先确认当前终端的 Java 版本为 21：

```bash
java -version
./gradlew -version
```

然后执行：

```bash
./gradlew assembleDebug
```

编译成功后，生成的 APK 默认位于：

```bash
app/build/outputs/apk/debug/app-debug.apk
```

### 常见问题

#### 1. 出现 `Unsupported class file major version 67`

原因：当前 Gradle 进程使用了错误的 JDK 版本，通常是 JDK 23，而不是 JDK 21。

处理方式：

```bash
export JAVA_HOME=/path/to/jdk-21
export PATH=$JAVA_HOME/bin:$PATH
java -version
./gradlew assembleDebug
```

如果是在 Android Studio 中编译，请把 `Gradle JDK` 切换为 JDK 21。

如果你有多个 JDK 并且不想频繁切换，也可以使用上面的 `org.gradle.java.home` 方式固定 Gradle 使用 JDK 21。


### 模块安装

使用Android Studio打开该项目。编译后得到模块app-debug.apk

将该文件上传自云机中，例如/sdcard/Download/app-debug.apk路径下

通过下面的命令可进行安装和查看
```
//首先连接到云机
adb connect xx.xx.xx.xx

//进入云机终端
adb shell

//然后安装模块
dplus install patch:/sdcard/Download/app-debug.apk

//成功安装后可以查看模块
dplus dump

//可以通过模块name卸载删除模块
dplus uninstall dplus_demo
```

### 模块开发介绍

模块源码结构如下图

![](https://resource-wangsu.helplook.net/docker_production/wkpufm/article/Jnu4zw3n/68ec705330f5a.png)

config.json文件用于配置模块

native-lib.cpp中实现dobby的hook例子

Entry.java是java层hook的入口

### config配置说明

该文件主要用于描述模块，并且引导注入的，样例数据如下
```
{
    "name": "dplus_demo",   //模块名称，卸载时使用
    "package":"com.example.dplus_demo",  //模块的包名
    "desc": "module",       //模块的描述
    "type": "user",         //模块的类型，主要为system和user。
    "libs": "libdplus_demo.so",  //要注入的so，可以不配置或者通过';'配置多条
    "pattern": [
        "com.android.settings"   //需要生效的包名
    ]
}
```

### dobby例子

下面演示了如何简单的对openat函数进行hook输出
```

int (*source_openat)(int fd, const char *path, int oflag, int mode) = nullptr;

int MyOpenAt(int fd, const char *pathname, int flags, int mode) {
    LOGI("MyOpenAt  pathname %s",pathname);
    return source_openat(fd, pathname, flags, mode);
}

void HookOpenAt() {
    //查找函数地址
    void *__openat =
            DobbySymbolResolver("libc.so", "__openat");

    if (__openat == nullptr) {
        LOGI("__openat null ");
        return;
    }

    LOGI("拿到 __openat 地址 ");
    
    //通过dobby替换原函数
    if (DobbyHook((void *) __openat,
                  (void *) MyOpenAt,
                  (void **) &source_openat) == 0) {
        LOGI("DobbyHook __openat sucess");
    }
}

jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {

    LOGI("Test JNI_OnLoad 开始加载");
    HookOpenAt();
    return JNI_VERSION_1_6;
}

```

### java层例子

init函数是作为应用启动完后的一个入口点，在这里执行相关逻辑即可。LspLant相关的代码进行了简单的修改，所以部分类和函数不是原名称。

```

public class Entry {

    public String TAG="demo_Entry";

    public void init(Application app){
        DPLog.i(TAG,"enter init");
        LSPHelpers.findAndHookMethod("java.util.HashMap", app.getClassLoader(), "put",Object.class,Object.class, new LSP_MethodH() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                super.beforeHookedMethod(param);
                Log.i(TAG,"enter HashMap.put key:"+param.args[0]+",value:"+param.args[1]);
            }

            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                super.afterHookedMethod(param);
                Log.i(TAG,"leave HashMap.put");
            }
        });
    }

}
```

### 模块注入后的效果

该模块对应用Setting进行测试，关掉Setting应用重新打开时相关日志如下图

![](https://resource-wangsu.helplook.net/docker_production/wkpufm/article/Jnu4zw3n/68ec7375b7f5f.png)
