# 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