使用unidbg执行安卓so层中的方法
字数 841 2025-08-22 12:23:30
Unidbg安卓SO层方法执行教学文档
一、Unidbg简介
Unidbg是一个基于Java的开源项目,主要用于模拟安卓或iOS设备环境,能够直接执行SO文件中的算法而无需逆向分析内部实现。主要功能包括:
- 模拟执行ARM/MIPS/x86架构代码
- 内存访问与操作
- 指令级跟踪
- 函数调用跟踪
- JNI环境模拟
支持多种后端:
- Unicorn(功能全面但速度较慢)
- Dynarmic(速度快但不支持所有特性)
- QEMU(稳定性高)
二、环境配置
1. 开发环境要求
- JDK 8+
- IntelliJ IDEA
- Maven构建工具
2. 项目导入步骤
- 克隆或下载unidbg项目
- 使用IDEA打开项目
- 等待Maven依赖自动下载完成
- 运行
com包下的测试用例验证环境
三、核心API详解
1. 模拟器创建与初始化
// 创建32位模拟器实例
emulator = AndroidEmulatorBuilder
.for32Bit() // 指定32位CPU
.addBackendFactory(new DynarmicFactory(true)) // 使用Dynarmic后端
.setProcessName("com.example.target") // 设置进程名
.build();
// 内存操作接口
Memory memory = emulator.getMemory();
// 设置Android SDK版本(23对应Android 6.0)
LibraryResolver resolver = new AndroidResolver(23);
memory.setLibraryResolver(resolver);
// 创建Dalvik虚拟机
vm = emulator.createDalvikVM(new File("target.apk"));
// 加载SO文件
DalvikModule dm = vm.loadLibrary(new File("libtarget.so"), false);
// 获取模块句柄
module = dm.getModule();
// 调用JNI_OnLoad
dm.callJNI_OnLoad(emulator);
2. 常用模拟器操作
// 获取内存接口
Memory memory = emulator.getMemory();
// 获取进程ID
int pid = emulator.getPid();
// 寄存器操作
emulator.showRegs(); // 显示寄存器状态
RegisterContext context = emulator.getContext();
// 跟踪调试
emulator.traceRead(1, 0); // 跟踪内存读取
emulator.traceWrite(1, 0); // 跟踪内存写入
emulator.traceCode(1, 0); // 跟踪代码执行
// 获取后端CPU
Backend backend = emulator.getBackend();
四、SO函数调用方法
1. 符号调用(推荐简单场景)
public void callBySymbol() {
// 创建代理对象(包名必须与目标一致)
DvmObject<?> obj = ProxyDvmObject.createObject(vm, this);
// 调用JNI方法
// 格式: methodName(参数类型签名)返回值类型签名
DvmObject<?> result = obj.callJniMethodObject(
emulator,
"md5(Ljava/lang/String;)Ljava/lang/String;",
"inputData");
// 获取结果
String value = (String) result.getValue();
System.out.println("Result: " + value);
}
2. 地址调用(复杂但灵活)
private void callByAddress() {
// 获取JNI环境指针
Pointer jniEnv = vm.getJNIEnv();
// 创建参数列表
List<Object> args = new ArrayList<>();
args.add(jniEnv); // 第一个参数总是JNIEnv*
args.add(vm.addLocalObject(ProxyDvmObject.createObject(vm, this))); // this
// 添加字符串参数
StringObject data = new StringObject(vm, "inputData");
args.add(vm.addLocalObject(data));
// 调用函数(地址偏移需要+1)
Number result = module.callFunction(emulator, 0x849 + 1, args.toArray());
// 处理返回值
DvmObject<?> object = vm.getObject(result.intValue());
String value = (String) object.getValue();
System.out.println("Result: " + value);
}
五、调试技巧
1. 控制台调试
// 附加调试器
Debugger debugger = emulator.attach(DebuggerType.CONSOLE);
// 设置断点
debugger.addBreakPoint(module.base + 0xC65);
// 常用调试命令:
// c - 继续执行
// s - 单步步入
// n - 单步步过
// reg - 查看寄存器
// mem - 查看内存
// bt - 查看调用栈
2. 日志输出配置
// 启用详细JNI调用日志
vm.setVerbose(true);
// 重定向输出到文件
PrintStream out = new PrintStream(new FileOutputStream("unidbg.log"));
emulator.getBackend().setStdout(out);
六、实战案例:PlzDebugMe分析
1. Java层分析
目标APK关键逻辑:
public void onClick(View view) {
String input = text.getText().toString();
String key = g4tk4y(); // 来自SO层
String encrypted = new FishEnc(key).doEnc(input); // Blowfish加密
boolean result = check(encrypted); // SO层验证
// 显示结果...
}
2. SO层函数调用
g4tk4y函数实现
JNIEXPORT jstring JNICALL Java_work_pangbai_debugme_MainActivity_g4tk4y
(JNIEnv *env, jobject obj) {
// 初始化检查
if (check()) exit(0);
// 魔改Base64编码
char table[] = "4KBbSzwWClkZ2gsr1qA+Qu0FtxOm6/iVcJHPY9GNp7EaRoDf8UvIjnL5MydTX3eh";
char input[] = "7h4K4y";
char output[8];
// 特殊编码过程
output[0] = table[input[0] & 0x3F];
output[1] = table[((input[0] >> 6) & 0x3) | (4 * (input[1] & 0xF))];
// ...其他位处理
return (*env)->NewStringUTF(env, output);
}
check函数实现
JNIEXPORT jboolean JNICALL Java_work_pangbai_debugme_MainActivity_check
(JNIEnv *env, jobject obj, jstring input) {
// 获取输入字符串
const char *str = (*env)->GetStringUTFChars(env, input, 0);
// 加密算法核心
for (int i = 0; i < len; i++) {
int tmp = input[i+1] ^ input[i];
if (v6) {
tmp = (tmp << (31-i)) | (tmp >> (i+1));
} else {
tmp = (tmp >> (31-i)) | (tmp << (i+1));
}
input[i+1] = tmp ^ input[i];
}
// 与硬编码值比较
return memcmp(result, expected, len) == 0;
}
3. Unidbg解决方案
获取g4tk4y返回值
public void getKey() {
DvmObject<?> obj = ProxyDvmObject.createObject(vm, this);
DvmObject<?> result = obj.callJniMethodObject(
emulator,
"g4tk4y()Ljava/lang/String;");
String key = (String) result.getValue();
System.out.println("Key: " + key);
}
逆向加密算法
def decrypt(encrypted):
# 逆向check函数中的加密算法
for i in range(len(enc)-1, -1, -1):
tmp = enc[(i+1)%12] ^ enc[i]
if v6: # 根据实际情况确定
tmp = ((tmp << (i+1)) | (tmp >> (32-(i+1)))) & 0xFFFFFFFF
else:
tmp = ((tmp >> (i+1)) | (tmp << (32-(i+1)))) & 0xFFFFFFFF
enc[(i+1)%12] = tmp ^ enc[i]
# 转换为字符串
return ''.join(pack('<I', x).decode() for x in enc)
七、最佳实践建议
-
环境配置
- 优先使用Dynarmic后端提升速度
- 设置正确的SDK版本匹配目标环境
- 保持包名与目标一致以避免JNI问题
-
函数调用
- 简单调用使用符号调用方式
- 复杂场景使用地址调用
- 注意ARM模式下地址需要+1
-
调试技巧
- 关键函数设置断点
- 结合IDA等工具交叉分析
- 记录完整执行日志
-
性能优化
- 避免不必要的内存操作
- 复用模拟器实例
- 适当减少日志输出
-
常见问题解决
- JNIEnv错误检查JNI调用约定
- 内存访问错误验证指针有效性
- 无输出检查SO加载是否正确
通过本教学文档,您应该能够掌握使用Unidbg执行安卓SO层方法的核心技术,包括环境搭建、API使用、函数调用和逆向分析等关键技能。