# android_injector **Repository Path**: jimonik/android_injector ## Basic Information - **Project Name**: android_injector - **Description**: 对其他app进行注入和控制 分别有 1,jni内存修改的so库编译+注入 2,dex层注入 3,manifest清单修改 4,对原始代码smali反汇编 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2025-08-20 - **Last Updated**: 2026-01-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Android Memory Injector 一个用于 Android 应用的内存修改注入框架,可将内存操作能力(搜索、修改、冻结等)注入到任意目标 APK 中,无需 Root 权限即可在目标应用进程内进行内存操作。 --- ## 📋 目录 - [项目简介](#项目简介) - [项目架构](#项目架构) - [注入原理](#注入原理) - [使用方法](#使用方法) - [使用 Lua 脚本(推荐)](#7-使用-lua-脚本推荐) - [技术细节](#技术细节) - [注意事项](#注意事项) - [常见问题](#常见问题) --- ## 🎯 项目简介 ### 核心功能 本项目实现了一个完整的 Android 内存修改工具链,主要功能包括: 1. **APK 注入器**:将内存操作载荷(`.so` 和 `.dex` 文件)注入到目标 APK 2. **内存搜索**:在目标进程中搜索指定类型和值的内存地址 3. **内存修改**:修改指定地址的内存值 4. **内存冻结**:持续锁定某个地址的值(防止被游戏/应用改回) 5. **模块查询**:获取目标进程的模块基址、指针跳转等 6. **跨进程通信**:通过 AIDL 从控制端向注入的目标应用发送指令 7. **Lua 脚本引擎**:使用 Lua 脚本自动化内存操作,支持代码补全、语法校验、实时日志 ### 适用场景 - 游戏修改器开发 - 内存分析工具 - 逆向工程研究 - Android 安全研究 --- ## 🏗️ 项目架构 项目由 **3 个模块** 组成: ``` android_injector/ ├── api/ # AIDL 接口定义(跨进程通信协议) ├── injector/ # 注入载荷(生成 so + dex) └── app/ # 注入器 + 控制端 UI ``` ### 1. `api` 模块 - AIDL 接口定义 **作用**:定义跨进程通信(IPC)的接口协议 **核心文件**: - `ICommandService.aidl`:定义所有内存操作的 AIDL 接口 - `MemType.java`:定义内存类型常量(BYTE、WORD、DWORD、FLOAT 等) - `ApiConstant.java`:定义服务名称常量 **关键接口**: ```java interface ICommandService { // 内存搜索 long[] MemorySearch(String value, int type); // 内存修改 int MemoryOffsetWrite(String value, int type, int offset, boolean isRestore); // 内存冻结 int addFreezeItem(String value, long addr, int type); int startAllFreeze(); // 模块查询 long getModuleBaseAddr(String moduleName, int headType); // 指针跳转 long jump(long addr, int count); // ... 更多接口(共 40+ 个方法) } ``` --- ### 2. `injector` 模块 - 注入载荷 **作用**:生成将被注入到目标 APK 的核心文件(`.so` 库 + `.dex` 字节码) #### 2.1 Native 层(C++) **文件**:`src/main/cpp/memory.cpp` + `memory.h` **功能**:底层内存操作实现 - **内存搜索**:`MemorySearch()` - 读取 `/proc/self/maps` 获取内存布局 - 使用 `pread64` 或 `process_vm_readv`(fallback)扫描内存 - 支持 8 种数据类型:BYTE、WORD、DWORD、QWORD、FLOAT、DOUBLE - 限制:最多返回 20000 个结果,最多扫描 256MB - **内存修改**:`MemoryOffsetWrite()` - 使用 `pwrite64` 或 `process_vm_writev`(fallback)写入内存 - 使用 `mprotect` 修改页权限(应对只读保护) - **内存区域过滤**:支持 13 种内存区域 - `RANGE_ALL`:全部内存 - `RANGE_JAVA_HEAP`:Java 堆内存 - `RANGE_C_HEAP`:C++ 堆内存 - `RANGE_ANONYMOUS`:匿名内存 - `RANGE_STACK`:栈内存 - `RANGE_CODE_APP`:应用代码区 - ... 等 **权限处理**: - 优先使用 `/proc/self/mem`(需要 SELinux 允许) - SELinux 拒绝时自动 fallback 到 `process_vm_readv/writev`(自进程访问) #### 2.2 Java 层 **文件**: - `AlguiNativeMemTool.java`:JNI 接口封装 - `HostService.java`:主进程服务(IPC 主端点) - `CommandService.java`:AIDL 进程服务(IPC 子端点) **服务架构**(双进程 IPC): ``` 目标 APP 进程 ├── MainActivity(主进程) │ └── HostService(绑定到主进程,管理内存冻结线程) │ └── 调用 native so 库进行内存操作 │ └── CommandService(独立进程 android:process=":aidl") └── 接收控制端指令 → 转发给 HostService → 执行内存操作 ``` **为什么需要双进程?** 1. `CommandService` 在独立进程(`:aidl`),对外暴露 AIDL 接口(可被外部 APP 绑定) 2. `HostService` 在主进程,可以访问应用自身内存(`/proc/self/mem`) 3. 隔离控制逻辑和内存操作,提高稳定性 **核心功能**: - **结果管理**:维护搜索结果列表(最多 20000 条) - **内存冻结**:后台线程持续刷新指定地址的值 - **模块解析**:解析 `/proc/self/maps` 获取 so 库基址 - **指针计算**:支持多级指针跳转 --- ### 3. `app` 模块 - 注入器 + 控制端 **作用**:提供 UI 界面,完成 APK 注入和远程内存操作控制 #### 3.1 注入器功能 **核心文件**:`InjectActivity.java` **注入流程**: 1. **选择目标 APK** ``` 用户选择 APK → 解析包名 → 复制到缓存目录 ``` 2. **修改 AndroidManifest.xml** ```java // 添加权限(用于内存读写) // 修改 Application 属性 // 允许安装 // 添加 AIDL 服务(独立进程) // 添加主进程服务 ``` 3. **注入 Native 库** ``` 解压 injector.apk 的 lib/ ├── arm64-v8a/libAlgui.so ├── armeabi-v7a/libAlgui.so ├── x86/libAlgui.so └── x86_64/libAlgui.so → 复制到目标 APK 的 lib/ 目录 ``` 4. **注入 DEX 字节码** ``` 提取 injector.apk 的 classes.dex → 重命名为 injector.dex → 添加到目标 APK 作为 classesN.dex(N = 原 dex 数量 + 1) ``` 5. **重新打包 + 签名** ``` 使用 apksig 库重新签名 → 生成 output.apk(已注入) ``` **工具类**: - `Util.java`:APK 操作工具(解压、重打包、签名) - `ApkUtils.java`:APK 信息解析 - `aXMLEncoder/aXMLDecoder`:Android 二进制 XML 编解码 #### 3.2 控制端功能 **核心文件**:`ConnectActivity.java` **功能模块**: 1. **服务连接**(响应式状态管理) ```java // 使用 LiveData 监听连接状态 private final MutableLiveData serviceLiveData; // 自动刷新 UI(无延迟响应) serviceLiveData.observe(this, service -> { if (service != null) { // 显示已连接状态 // 显示 AIDL 进程 PID // 检测主进程连接状态 } else { // 显示未连接状态 } }); ``` 2. **内存搜索** ```java // 搜索指定值 service.MemorySearch("12345", MemType.TYPE_DWORD); // 返回所有匹配地址的数组 ``` 3. **内存修改** ```java // 修改所有搜索结果 service.MemoryOffsetWrite("99999", MemType.TYPE_DWORD, 0, false); ``` 4. **内存冻结** ```java // 添加冻结项(持续锁定某个地址的值) service.addFreezeItem("100", 0x12345678L, MemType.TYPE_DWORD); service.startAllFreeze(); // 启动冻结线程 ``` 5. **高级功能** - 指针跳转:`jump(addr, count)` / `jump32(addr)` / `jump64(addr)` - 模块基址:`getModuleBaseAddr("libunity.so", 0)` - 结果精炼:`ImproveValue()` / `ImproveOffset()` - 内存区域查询:`getMemoryAddrMapLine(addr)` 6. **Lua 脚本引擎**(`ScriptActivity` + `LuaEditorActivity`) **核心组件**: - `ScriptActivity`:脚本列表页面,管理 `ICommandService` 连接 - `LuaEditorActivity`:独立的脚本编辑器界面 - `ScriptManager`:脚本文件管理和执行 - `LuaBridge`:将 AIDL 接口桥接到 Lua 环境 - `SimpleLuaEditor`:基于 Sora Editor 的代码编辑器(支持行号、撤销/重做) - `SimpleLuaLanguage`:Lua 语言支持(代码补全) **功能特性**: ```java // 1. Lua 执行环境(使用 LuaJ) Globals globals = JsePlatform.standardGlobals(); LuaBridge bridge = new LuaBridge(service, activity, outputCallback); bridge.register(globals); // 注册自定义函数 // 2. 自动连接管理 connect("com.example.game", 5) // 自动复用已有连接 // 3. 语法校验 private String checkLuaSyntax(String code) { Globals.load(code); // 编译检查 } // 4. 文件防覆盖 if (scriptFile.exists() && !scriptFile.equals(currentScriptFile)) { // 询问是否覆盖 } ``` **UI 特性**: - 输出窗口可折叠(运行时自动显示) - 右下角悬浮按钮(快速切换输出窗口) - 文件名修改自动保存(防止冲突) - 关闭前自动语法校验 - 脚本列表自动刷新(按修改时间倒序) **UI 优化**: - 使用 `ViewBinding` 绑定布局 - 使用 `ScrollView` 支持滚动(所有功能可见) - 使用 `outputLog` TextView 显示操作结果 - 支持十六进制输入(`0x` 前缀自动识别) - 使用 `LiveData` 实现响应式状态管理 - 使用 `EdgeToEdge` 适配全面屏(状态栏、导航栏) --- ## 🔧 注入原理 ### 核心注入内容 | 注入项 | 位置 | 作用 | |--------|------|------| | **libAlgui.so** | `lib/{abi}/` | 底层内存读写操作(JNI 库) | | **injector.dex** | `classesN.dex` | Java 层服务实现(HostService、CommandService) | | **CommandService** | `AndroidManifest.xml` | 独立进程 AIDL 服务(接收外部指令) | | **HostService** | `AndroidManifest.xml` | 主进程服务(执行内存操作) | | **权限** | `AndroidManifest.xml` | 存储权限(可选,用于导出结果) | ### 工作流程 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ 控制端 APP (app) │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ ConnectActivity │ │ │ │ 1. 选择已注入的目标 APP 包名 │ │ │ │ 2. bindService(包名, CommandService) │ │ │ │ 3. 获取 ICommandService 代理 │ │ │ └────────────────────┬─────────────────────────────────────────┘ │ │ │ AIDL 跨进程调用 │ └───────────────────────┼──────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 目标 APP (:aidl 进程) │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ CommandService (独立进程) │ │ │ │ 1. 接收 AIDL 调用:MemorySearch(value, type) │ │ │ │ 2. 转发给 HostService (主进程) │ │ │ └────────────────────┬─────────────────────────────────────────┘ │ │ │ Binder IPC │ └───────────────────────┼──────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 目标 APP (主进程) │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ HostService │ │ │ │ 1. 调用 AlguiNativeMemTool.MemorySearch(value, type) │ │ │ │ ↓ │ │ │ │ 2. JNI 调用 libAlgui.so │ │ │ └────────────────────┬─────────────────────────────────────────┘ │ │ │ JNI │ │ ┌────────────────────▼─────────────────────────────────────────┐ │ │ │ libAlgui.so (Native C++) │ │ │ │ 1. 读取 /proc/self/maps(获取内存布局) │ │ │ │ 2. 使用 pread64 或 process_vm_readv 扫描内存 │ │ │ │ 3. 匹配指定值 + 类型 │ │ │ │ 4. 返回地址数组 │ │ │ └──────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘ ``` ### 关键技术点 #### 1. 无 Root 内存访问 - **自进程访问**:注入的 so 运行在目标进程中,可访问 `/proc/self/mem` - **SELinux 限制**:部分设备禁止 `/proc/self/mem` 访问 - **Fallback 方案**:使用 `process_vm_readv/writev` 系统调用(内核支持自进程访问) #### 2. 双进程 IPC 架构 ``` 为什么不直接把 CommandService 放在主进程? ❌ 单进程方案: 控制端 → CommandService (主进程) → 直接操作内存 问题:外部 APP 绑定时容易触发安全限制 ✅ 双进程方案: 控制端 → CommandService (:aidl 进程) → HostService (主进程) → 操作内存 优势:隔离控制层和执行层,提高稳定性 ``` #### 3. 内存搜索优化 - **分块读取**:每次读取 4096 字节(页大小) - **类型对齐**:根据数据类型(2/4/8 字节)调整步长 - **结果限制**:最多 20000 个结果(防止 OOM / Binder 事务限制) - **扫描限制**:最多扫描 256MB(防止耗时过长) #### 4. 内存冻结机制 ```java // HostService 中的冻结线程 private void startFreezeThread() { new Thread(() -> { while (isFreezing) { for (FreezeItem item : freezeList) { // 每 100ms 刷新一次 writeMemory(item.addr, item.value, item.type); } Thread.sleep(freezeDelayMs); } }).start(); } ``` --- ## 📖 使用方法 ### 1. 编译项目 ```bash # 克隆项目 git clone <项目地址> cd android_injector # 编译并生成注入载荷 bash injector.sh ``` **脚本执行流程**: 1. 编译 `injector` 模块 → 生成 `injector.apk` 2. 解压 `injector.apk`: - 提取 `classes.dex` → 重命名为 `injector.dex` - 提取 `lib/**/*.so` 3. 复制到 `app/src/main/assets/` 4. 编译 `app` 模块 → 生成控制端 APK **输出文件**: - `app/build/outputs/apk/debug/app.apk`:控制端 APP - `app/src/main/assets/injector.dex`:注入载荷(DEX) - `app/src/main/assets/lib/{abi}/*.so`:注入载荷(Native) ### 2. 安装控制端 ```bash adb install app/build/outputs/apk/debug/app.apk ``` ### 3. 注入目标 APK 1. 打开控制端 APP 2. 选择 "注入 APK" 3. 选择目标 APK 文件(从存储中) 4. 等待注入完成(显示进度日志) 5. 在 `/sdcard/Android/data/com.jimo.app/files/` 找到 `output.apk` ### 4. 安装已注入的 APK ```bash # 拉取到电脑 adb pull /sdcard/Android/data/com.jimo.app/files/output.apk # 安装 adb install output.apk ``` ### 5. 连接目标应用 1. 打开控制端 APP 2. 点击 "连接 AIDL" → 选择已注入的目标应用 3. 等待连接成功(显示两个进程的 PID) ### 6. 执行内存操作 #### 搜索内存 1. 选择内存类型(DWORD、FLOAT 等) 2. 选择内存区域(全部内存、Java 堆等) 3. 输入搜索值(如 `12345`) 4. 点击 "搜索" 5. 查看返回的地址数量 #### 修改内存 1. 输入新值(如 `99999`) 2. 点击 "修改" 3. 所有搜索到的地址会被修改 #### 精炼搜索 1. 修改游戏/应用中的值(如金币从 100 变为 50) 2. 在控制端输入新值 `50` 3. 点击 "精炼值" → 只保留当前值为 50 的地址 4. 重复 2-3 直到只剩几个地址 #### 冻结值 1. 输入地址(如 `0x12345678`) 2. 输入要冻结的值(如 `999`) 3. 选择数据类型 4. 点击 "添加冻结项" 5. 点击 "启动冻结" → 该地址的值会被持续锁定 #### 高级功能 - **模块基址**: ``` 输入模块名:libil2cpp.so 输入头类型:0 点击 "获取模块基址" → 返回 0x7000000000 ``` - **指针跳转**: ``` 输入地址:0x7000001234 输入跳转次数:3 点击 "跳转" → 返回最终地址 ``` ### 7. 使用 Lua 脚本(推荐) #### 7.1 脚本编辑器功能 **界面布局**: - **顶部**:脚本名称输入框 - **第二行**:撤销、重做、保存、运行、关闭按钮(可水平滚动) - **中间**:Lua 代码编辑器(支持行号、语法高亮、撤销/重做) - **底部**:输出日志窗口(可最小化,运行时自动显示) - **右下角**:悬浮按钮(显示/隐藏输出窗口) **特色功能**: - ✅ **自动语法校验**:关闭时检查语法错误,提示是否保存 - ✅ **自动保存**:修改文件名后自动保存 - ✅ **防止覆盖**:新建同名脚本时询问是否覆盖 - ✅ **代码补全**:支持 Lua 关键字、内置函数、自定义函数补全 - ✅ **实时日志**:脚本执行时输出日志到底部窗口 #### 7.2 创建脚本 1. 打开控制端 APP → 点击 "脚本" 2. 点击 "新建脚本" 3. 输入脚本名称(如 `test.lua`) 4. 编辑器自动打开,显示默认模板: ```lua -- 连接目标应用(自动复用已有连接) local conn = connect("com.example.game", 5) if not conn.success then print("✗ 连接失败: " .. (conn.error or "未知错误")) error("连接失败") end print("✓ AIDL PID: " .. conn.aidlPid .. ", 主进程 PID: " .. conn.mainPid) -- 设置内存区域为 Java 堆 setMemoryArea(AREA.JAVA_HEAP) -- 搜索 DWORD 值 4321 local results = search("4321", TYPE.DWORD) print("找到 " .. #results .. " 个结果") -- 修改所有搜索结果为 1234 if #results > 0 then write("1234", TYPE.DWORD, 0) print("✓ 修改完成!") else print("✗ 没有找到结果") end ``` #### 7.3 Lua API 说明 ##### 连接函数 ```lua -- 连接目标应用(自动复用已有连接) local conn = connect(packageName, timeout) -- 参数: -- packageName: 目标应用包名(字符串) -- timeout: 超时时间(秒,数字) -- 返回值:表(table) -- conn.success: 是否成功(布尔) -- conn.aidlPid: AIDL 进程 PID(数字) -- conn.mainPid: 主进程 PID(数字) -- conn.package: 包名(字符串) -- conn.error: 错误信息(字符串,失败时) -- 示例 local conn = connect("com.example.game", 5) if not conn.success then error("连接失败: " .. conn.error) end ``` ##### 内存操作函数 ```lua -- 设置内存区域 setMemoryArea(areaId) -- areaId: AREA.ALL, AREA.JAVA_HEAP, AREA.C_HEAP 等 -- 搜索内存 local results = search(value, type) -- 参数: -- value: 搜索值(字符串,如 "100") -- type: 数据类型(TYPE.DWORD, TYPE.FLOAT 等) -- 返回值:地址数组(表) -- 写入内存 local count = write(value, type, offset) -- 参数: -- value: 新值(字符串) -- type: 数据类型 -- offset: 偏移量(通常为 0) -- 返回值:成功写入的地址数量(数字) -- 冻结值 freeze(address, value, type) -- 参数: -- address: 内存地址(数字或字符串) -- value: 冻结的值(字符串) -- type: 数据类型 ``` ##### 数据类型常量 ```lua TYPE.BYTE -- 1 字节(int8) TYPE.WORD -- 2 字节(int16) TYPE.DWORD -- 4 字节(int32) TYPE.QWORD -- 8 字节(int64) TYPE.FLOAT -- 4 字节浮点数 TYPE.DOUBLE -- 8 字节浮点数 ``` ##### 内存区域常量 ```lua AREA.ALL -- 全部内存 AREA.JAVA_HEAP -- Java 堆(推荐用于 Java/Kotlin 变量) AREA.C_HEAP -- C++ 堆 AREA.C_ALLOC -- C 分配内存 AREA.ANONYMOUS -- 匿名内存 AREA.STACK -- 栈内存 AREA.CODE_APP -- 应用代码段 ``` ##### 辅助函数 ```lua -- 延迟执行 sleep(seconds) -- 参数:秒数(数字) -- 打印输出 print(message) -- 参数:消息(字符串或其他类型) -- 获取模块基址 local base = getModuleBase(moduleName) -- 参数:模块名(字符串,如 "libil2cpp.so") -- 返回值:基址(数字) ``` #### 7.4 脚本示例 **示例 1:自动刷金币** ```lua local conn = connect("com.example.game", 5) if not conn.success then error("连接失败") end setMemoryArea(AREA.JAVA_HEAP) -- 搜索当前金币值 print("请输入当前金币数量:") local current = "100" -- 假设当前是 100 local results = search(current, TYPE.DWORD) print("找到 " .. #results .. " 个地址") -- 修改为 999999 if #results > 0 then write("999999", TYPE.DWORD, 0) print("✓ 金币已修改为 999999") end ``` **示例 2:精炼搜索** ```lua local conn = connect("com.example.game", 5) if not conn.success then error("连接失败") end setMemoryArea(AREA.JAVA_HEAP) -- 第一次搜索 local results = search("100", TYPE.DWORD) print("第一次搜索: " .. #results .. " 个结果") -- 等待值变化,然后精炼 sleep(5) results = search("80", TYPE.DWORD) -- 假设变成了 80 print("精炼后: " .. #results .. " 个结果") -- 修改 if #results < 10 then write("999", TYPE.DWORD, 0) print("✓ 修改成功") end ``` **示例 3:冻结血量** ```lua local conn = connect("com.example.game", 5) if not conn.success then error("连接失败") end setMemoryArea(AREA.JAVA_HEAP) -- 搜索当前血量 local results = search("100", TYPE.FLOAT) print("找到 " .. #results .. " 个血量地址") -- 冻结第一个地址为 999.0 if #results > 0 then freeze(results[1], "999.0", TYPE.FLOAT) print("✓ 血量已冻结为 999") end ``` #### 7.5 脚本管理 **脚本列表**: - 显示所有已保存的脚本(按修改时间倒序) - 支持操作:加载编辑、运行、删除 - 自动刷新:从编辑器返回时自动刷新列表 **文件存储**: - 路径:`/data/data/com.jimo.app/files/scripts/` - 格式:`.lua` 文本文件 - 编码:UTF-8 **注意事项**: - 脚本运行需要先在"脚本"页面打开(建立服务连接) - `connect()` 函数会自动管理连接,多次调用不会重复连接 - 关闭编辑器时会自动语法校验,建议修复错误后再关闭 - 修改文件名时会自动保存,但如果文件名冲突则不会覆盖 --- ## 🔬 技术细节 ### 内存类型映射 | 类型 | C/C++ 类型 | Java/Kotlin 类型 | 字节数 | |------|------------|------------------|--------| | BYTE | `int8_t` | `byte` | 1 | | WORD | `int16_t` | `short` | 2 | | DWORD | `int32_t` | `int` | 4 | | QWORD | `int64_t` | `long` | 8 | | FLOAT | `float` | `float` | 4 | | DOUBLE | `double` | `double` | 8 | **Kotlin 变量对应关系**: ```kotlin class MainActivity : AppCompatActivity() { private var coins: Int = 100 // → DWORD private var score: Long = 999999L // → QWORD private var health: Float = 100.0f // → FLOAT private var position: Double = 1.5 // → DOUBLE } ``` ### 内存区域说明 | 区域 ID | 名称 | 说明 | 典型用途 | |---------|------|------|----------| | `RANGE_ALL` | 全部内存 | 扫描所有可读内存 | 首次搜索 | | `RANGE_JAVA_HEAP` | Java 堆 | Dalvik/ART 虚拟机堆 | Java/Kotlin 对象 | | `RANGE_C_HEAP` | C++ 堆 | `malloc/new` 分配的内存 | Native 对象 | | `RANGE_ANONYMOUS` | 匿名内存 | 未映射到文件的内存 | 动态分配 | | `RANGE_STACK` | 栈内存 | 线程栈 | 局部变量 | | `RANGE_CODE_APP` | 应用代码 | APK 中的 DEX/SO | 静态数据 | | `RANGE_ASHMEM` | 共享内存 | Android 共享内存 | Binder 传输 | ### DEX 注入细节 **为什么需要注入 DEX?** - so 库需要 Java 层服务来管理(HostService、CommandService) - AIDL 接口需要在 Java 层实现 - 冻结功能需要 Java 线程 **DEX 命名规则**: ``` 原 APK: classes.dex (主 DEX) classes2.dex (分包 DEX) 注入后: classes.dex (主 DEX,不变) classes2.dex (分包 DEX,不变) classes3.dex (注入的 injector.dex) ``` **类加载**: - Android 系统自动加载所有 `classes*.dex` - 无需手动 `DexClassLoader` ### Manifest 修改细节 **关键属性**: ```xml ``` **服务配置**: ```xml ``` ### Lua 脚本引擎技术细节 #### 架构设计 ``` ┌─────────────────────────────────────────────────────────────┐ │ LuaEditorActivity(独立编辑器界面) │ │ ├─ SimpleLuaEditor(代码编辑器,基于 Sora Editor) │ │ │ ├─ SimpleLuaLanguage(代码补全支持) │ │ │ ├─ 撤销/重做(基于 Sora Editor 内置功能) │ │ │ └─ 行号显示、语法高亮 │ │ ├─ 语法校验(基于 LuaJ Globals.load()) │ │ ├─ 文件防覆盖(同名检测 + 弹窗确认) │ │ └─ 输出窗口(可折叠,运行时自动显示) │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ ScriptManager(脚本管理器) │ │ ├─ readFile() / writeFile() │ │ └─ runScript() │ │ └─ 创建 Lua 环境 → 注册 LuaBridge → 执行脚本 │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ LuaBridge(Java ↔ Lua 桥接层) │ │ ├─ connect(package, timeout) → 返回连接信息表 │ │ ├─ setMemoryArea(areaId) │ │ ├─ search(value, type) → 返回地址数组 │ │ ├─ write(value, type, offset) → 返回成功数量 │ │ ├─ freeze(addr, value, type) │ │ ├─ getModuleBase(moduleName) → 返回基址 │ │ ├─ sleep(seconds) │ │ ├─ print(message) → 输出到回调 │ │ └─ TYPE / AREA 常量表 │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ ScriptActivity(服务连接管理) │ │ ├─ connectToServiceSync() → 同步等待连接(CountDownLatch) │ │ ├─ ScriptActivityHolder(弱引用持有,供编辑器访问) │ │ └─ ICommandService 实例(由 LuaBridge 调用) │ └─────────────────────────────────────────────────────────────┘ ``` #### 关键技术实现 **1. Lua 执行环境** ```java // 使用 LuaJ 标准库 Globals globals = JsePlatform.standardGlobals(); // 注册自定义函数 globals.set("connect", new TwoArgFunction() { @Override public LuaValue call(LuaValue pkg, LuaValue timeout) { // 实现连接逻辑,返回 LuaTable LuaTable result = new LuaTable(); result.set("success", LuaValue.TRUE); result.set("aidlPid", LuaValue.valueOf(pid)); return result; } }); // 执行脚本 LuaValue chunk = globals.load(code); chunk.call(); ``` **2. 连接复用机制** ```java // LuaBridge.connect() 自动管理连接 if (service != null) { // 已有连接,直接返回 return existingConnection; } else { // 首次连接,使用 CountDownLatch 等待 activity.connectToServiceSync(packageName, timeout); } ``` **3. 代码补全实现** ```java // SimpleLuaLanguage.requireAutoComplete() String prefix = CompletionHelper.computePrefix(content, position, this::isIdentifierPart); List items = new ArrayList<>(); // 匹配关键字 for (String keyword : KEYWORDS) { if (keyword.startsWith(prefix)) { items.add(new CompletionItem(keyword, keyword)); } } // 匹配自定义函数 for (String func : CUSTOM_FUNCTIONS) { if (func.startsWith(prefix)) { items.add(new CompletionItem(func, func + "(")); } } publisher.addItems(items); ``` **4. 语法校验机制** ```java // 关闭前校验语法 private String checkLuaSyntax(String code) { try { Globals globals = JsePlatform.standardGlobals(); globals.load(code); // 只加载不执行,检查语法 return null; // 无错误 } catch (LuaError e) { return e.getMessage(); // 返回错误信息 } } ``` **5. 文件防覆盖逻辑** ```java // 保存时检查文件是否存在 if (scriptFile.exists() && !scriptFile.equals(currentScriptFile)) { // 文件已存在且不是当前文件,询问是否覆盖 new AlertDialog.Builder(this) .setTitle("文件已存在") .setMessage("是否覆盖?") .setPositiveButton("覆盖", (dialog, which) -> performSave()) .setNegativeButton("取消", null) .show(); } ``` **6. 自动保存机制** ```java // 文件名修改时延迟保存(防止频繁 IO) binding.etScriptName.addTextChangedListener(new TextWatcher() { @Override public void afterTextChanged(Editable s) { binding.etScriptName.removeCallbacks(autoSaveRunnable); binding.etScriptName.postDelayed(autoSaveRunnable, 500); } }); ``` #### 依赖库说明 | 库 | 版本 | 用途 | |---|------|------| | `org.luaj:luaj-jse` | 3.0.1 | Lua 解释器(JVM 实现) | | `io.github.Rosemoe.sora-editor:editor` | 0.23.4 | 代码编辑器 UI | | `androidx.lifecycle:lifecycle-livedata` | - | 响应式状态管理 | **LuaJ 特性**: - 纯 Java 实现,无需 Native 库 - 支持 Lua 5.2 标准语法 - 可与 Java 对象互操作 - 轻量级(约 500KB) **Sora Editor 特性**: - 支持行号、语法高亮、代码折叠 - 内置撤销/重做栈 - 支持手势缩放、平滑滚动 - 可扩展语言支持(通过 `Language` 接口) ### APK 签名 使用 `apksig` 库(Android 官方签名工具): ```java ApkSigner.SignerConfig signerConfig = new ApkSigner.SignerConfig.Builder( "CERT", privateKey, List.of(certificate) ).build(); new ApkSigner.Builder(List.of(signerConfig)) .setInputApk(unsignedApk) .setOutputApk(signedApk) .setV1SigningEnabled(true) // JAR 签名(兼容旧设备) .setV2SigningEnabled(true) // APK 签名 v2 .build() .sign(); ``` --- ## ⚠️ 注意事项 ### 兼容性 - **最低 Android 版本**:Android 7.0 (API 24) - **推荐 Android 版本**:Android 10+ (API 29+) - **架构支持**:arm64-v8a、armeabi-v7a、x86、x86_64 ### 限制 1. **SELinux 限制** - 部分设备(如小米、华为)默认阻止 `/proc/self/mem` 访问 - 项目已实现 fallback 到 `process_vm_readv/writev` 2. **搜索结果限制** - 最多返回 20000 个地址(防止 UI 卡顿 / Binder 事务过大) - 最多扫描 256MB 内存(防止耗时过长) 3. **Binder 事务限制** - 单次 AIDL 调用最多传输 1MB 数据 - 搜索结果过多时只显示前 20 条预览 4. **目标应用限制** - 某些应用有完整性校验(检测 APK 签名 / DEX 哈希) - 某些游戏有反调试(检测 `debuggable` 属性) ### 安全建议 - **仅用于学习研究**:请勿用于破解商业应用/游戏 - **隐私保护**:注入的服务运行在目标应用进程,可访问应用内存 - **签名验证**:注入后 APK 签名会改变,部分应用可能拒绝运行 --- ## ❓ 常见问题 ### 1. 注入后安装失败? **错误**:`INSTALL_FAILED_TEST_ONLY` **解决**: ```bash adb install -t output.apk # 或在代码中设置 android:isTestOnly="false"(已实现) ``` ### 2. 连接 AIDL 失败? **可能原因**: - 目标应用未启动(先打开目标 APP) - 服务未正确注入(检查 Manifest 是否有 CommandService) - 包名不匹配(检查选择的包名是否正确) **调试**: ```bash # 查看目标应用是否有 :aidl 进程 adb shell ps | grep <包名> # 查看服务是否注册 adb shell dumpsys activity services | grep CommandService ``` ### 3. 内存搜索返回空数组? **可能原因**: - 搜索值不存在(确认游戏中的值) - 数据类型错误(Java `int` 应搜索 DWORD) - 内存区域错误(尝试搜索 "全部内存") - SELinux 限制(已自动 fallback,查看 logcat 确认) **调试**: ```bash # 查看 native 日志 adb logcat | grep "emmmmmmmmm" # 查看是否触发 fallback adb logcat | grep "fallback" ``` ### 4. 内存修改返回 -1? **可能原因**: - 地址无效(已被释放或权限不足) - 页保护(只读页,已尝试 `mprotect` 修改权限) - SELinux 限制(查看 logcat) **解决**: - 尝试 "安全写入模式"(`setIsSecureWrites(true)`,会先读取验证) ### 5. 冻结功能不生效? **可能原因**: - 未启动冻结(点击 "启动冻结") - 冻结延迟过长(默认 100ms,可调整) - 目标应用修改频率更高(尝试减小延迟到 10ms) **调试**: ```bash # 查看冻结线程是否运行 adb logcat | grep "FreezeThread" ``` ### 6. 某些游戏闪退? **可能原因**: - 完整性校验(检测 APK 签名变化) - 反调试(检测 `debuggable` 属性) - 反注入(检测注入的 DEX/SO) **尝试**: - 设置 `android:debuggable="false"`(已实现) - 移除不必要的权限 - 研究目标应用的保护机制 --- ## 📜 许可证 本项目仅供学习研究使用,请勿用于非法用途。 --- ## 🤝 贡献 欢迎提交 Issue 和 Pull Request! --- ## 📧 联系方式 如有问题或建议,欢迎联系。 --- **最后更新**:2026-01-15