背景与目标
在使用 Unidbg 对 Android .so 进行离线执行时,常会遇到 native 函数内部调用 Java 方法(JNIEnv 回调) 的情况。若未在 Unidbg 中提供对应的 Java 实现或模拟,通常会报出 "not implemented"、"FindClass 失败"、"GetMethodID 返回 null"、"类型不匹配"等错误。
UnsupportedOperationException
FindClass 失败
GetMethodID 为 null
类型不匹配
native 崩溃
出现错误
错误类型
在 AbstractJni 中实现相应方法
确认类名准确;考虑加载 APK/Dex 或在 findClass 中处理
校验 JNI 签名与方法名
返回正确 Dvm 类型(StringObject、DvmInteger 等)
开启 verbose;用 Inspector 查看寄存器/内存;必要时 patch 或绕过
原理
native 如何调用 Java?
在真实的 Android 环境中,native 获取到 JNIEnv* 后,会使用如下调用:
FindClass("com/example/Device")GetStaticMethodID(...) / GetMethodID(...)CallStaticObjectMethod(...) / CallObjectMethod(...)- 读取字段:
GetStaticFieldID(...) / GetObjectField(...)
这些都属于"native 回调 Java"的过程。只要 native 里有这些调用,Unidbg 默认是"不知道如何返回"的,除非你提供"Java 世界"的实现或 stub。
Unidbg 如何承接 JNI 回调?
- Unidbg 在创建 VM(Dalvik/ART)后,会通过
vm.setJni(...)绑定一个AbstractJni的子类。 - 当 native 通过 JNIEnv 调用 Java 方法时,Unidbg 会把这次调用"回调"到你的
AbstractJni子类中对应的函数,例如:callObjectMethod(...)callStaticObjectMethod(...)getStaticIntField(...)/getStaticObjectField(...)等
- 你需要在这些方法里,根据"类名 + 方法名 + 签名"判断当前调用是谁,并返回合适的
DvmObject<?>(如StringObject、DvmInteger)。这样 native 就能得到期望值,继续执行。
是
否
启动:配置 Emulator + VM
加载 libdemo.so
执行 JNI_OnLoad
调用 JNI 导出函数
native 是否通过 JNIEnv 调用 Java?
路由到 AbstractJni 对应方法
返回模拟的 DvmObject 或基础类型
native 继续执行业务逻辑
将执行结果返回给 Unidbg
常见报错与原因
java.lang.UnsupportedOperationException: callObjectMethod ... not implemented
你的 AbstractJni 子类没有处理该 Java 方法的调用(类/方法/签名没匹配)。
FindClass("xxx") 返回空或抛异常
你的 Jni 没有正确处理 FindClass,或 vm.resolveClass("xxx") 对应的类没有可用(未加载 APK/Dex 或未 stub)。
GetMethodID/GetStaticMethodID 返回空
与上类似;签名不匹配尤为常见(注意 JNI 签名格式)。
返回类型不对导致崩溃
例如 native 期望 String,你却返回了一个非 StringObject;或者签名是 ()I(返回 int),你却返回了 DvmObject。
场景:
- native 函数
doWork(Context ctx)需要调用 Java 层获取设备信息(如 packageName、deviceId、Build.MODEL 等),再拼接返回一个签名字符串。 - 在 Unidbg 中,我们用自定义 Jni 拦截这些调用并返回"伪造数据",让函数跑通。
native 中的伪代码示例:
c
jstring Java_com_example_NativeBridge_doWork(JNIEnv* env, jclass clazz, jobject ctx) {
jclass devCls = env->FindClass("com/example/Device");
jmethodID getId = env->GetStaticMethodID(devCls, "getDeviceId", "(Landroid/content/Context;)Ljava/lang/String;");
jstring deviceId = (jstring) env->CallStaticObjectMethod(devCls, getId, ctx);
jclass ctxCls = env->FindClass("android/content/Context");
jmethodID pkg = env->GetMethodID(ctxCls, "getPackageName", "()Ljava/lang/String;");
jstring pkgName = (jstring) env->CallObjectMethod(ctx, pkg);
jclass buildCls = env->FindClass("android/os/Build");
jfieldID modelField = env->GetStaticFieldID(buildCls, "MODEL", "Ljava/lang/String;");
jstring model = (jstring) env->GetStaticObjectField(buildCls, modelField);
// 拼接签名(伪)
// return "sign:" + deviceId + "|" + pkgName + "|" + model;
}
解决方案
方案一、用 AbstractJni 自定义方法返回值(最常用)
- 优点:简单直接,适合没有完整 APK 的场景
- 做法:匹配类名/方法名/签名,返回合理的 DvmObject 或基础类型,让 native 满足条件分支即可
unidbg 调用流程
java
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.utils.Inspector;
public class UnidbgJniDemo {
private AndroidEmulator emulator;
private VM vm;
private Module module;
public UnidbgJniDemo() {
// 1) 构建 32 位 Emulator(如需 64 位:for64Bit)
emulator = AndroidEmulatorBuilder.for32Bit().build();
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23)); // 指定 API Level
// 2) 创建 VM(不加载 APK,亦可传 APK 文件以更真实)
vm = emulator.createDalvikVM(null);
vm.setVerbose(true); // 打开详细日志
// 3) 设置自定义 Jni
vm.setJni(new MyJni(vm));
// 4) 加载目标 so 并执行 JNI_OnLoad
// 确保 libdemo.so 可被找到(工作目录或搜索路径)
DalvikModule dm = vm.loadLibrary("demo", true);
module = dm.getModule();
dm.callJNI_OnLoad(emulator);
}
// 调用导出 JNI 函数:Java_com_example_NativeBridge_doWork
public void callDoWork() {
DvmClass bridge = vm.resolveClass("com/example/NativeBridge");
// 构造一个假的 Context 对象(native 内部可能调用其方法)
DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
DvmObject<?> result = bridge.callStaticJniMethodObject(
emulator,
"doWork(Landroid/content/Context;)Ljava/lang/String;",
context
);
System.out.println("doWork result = " + result.getValue());
}
public static void main(String[] args) {
UnidbgJniDemo demo = new UnidbgJniDemo();
demo.callDoWork();
}
// 自定义 Jni:模拟 Java 层方法
static class MyJni extends AbstractJni {
private final VM vm;
public MyJni(VM vm) { this.vm = vm; }
@Override
public DvmClass findClass(VM vm, String className) {
// 允许常用类被 resolve;若返回 null 则 FindClass 会失败
return vm.resolveClass(className);
}
@Override
public DvmObject<?> callStaticObjectMethod(
VM vm, DvmClass dvmClass, String methodName, String signature, VarArgs varArgs) {
String className = dvmClass.getName();
// com/example/Device.getDeviceId(Landroid/content/Context;)Ljava/lang/String;
if ("com/example/Device".equals(className)
&& "getDeviceId".equals(methodName)
&& "(Landroid/content/Context;)Ljava/lang/String;".equals(signature)) {
DvmObject<?> context = varArgs.getObjectArg(0);
return new StringObject(vm, "FAKE-DEVICE-ID-123456");
}
throw new UnsupportedOperationException("callStaticObjectMethod not implemented: "
+ className + "." + methodName + signature);
}
@Override
public DvmObject<?> callObjectMethod(
VM vm, DvmObject<?> dvmObject, String methodName, String signature, VarArgs varArgs) {
String className = dvmObject.getObjectType().getName();
// android/content/Context.getPackageName()Ljava/lang/String;
if ("android/content/Context".equals(className)
&& "getPackageName".equals(methodName)
&& "()Ljava/lang/String;".equals(signature)) {
return new StringObject(vm, "com.example.app");
}
throw new UnsupportedOperationException("callObjectMethod not implemented: "
+ className + "." + methodName + signature);
}
@Override
public DvmObject<?> getStaticObjectField(VM vm, DvmClass dvmClass, String fieldName, String signature) {
String className = dvmClass.getName();
// android/os/Build.MODEL:Ljava/lang/String;
if ("android/os/Build".equals(className)
&& "MODEL".equals(fieldName)
&& "Ljava/lang/String;".equals(signature)) {
return new StringObject(vm, "Pixel-FAKE-Model");
}
// android/os/Build.MANUFACTURER:Ljava/lang/String;
if ("android/os/Build".equals(className)
&& "MANUFACTURER".equals(fieldName)
&& "Ljava/lang/String;".equals(signature)) {
return new StringObject(vm, "Google");
}
return super.getStaticObjectField(vm, dvmClass, fieldName, signature);
}
@Override
public int getStaticIntField(VM vm, DvmClass dvmClass, String fieldName) {
String className = dvmClass.getName();
// android/os/Build$VERSION.SDK_INT
if ("android/os/Build$VERSION".equals(className) && "SDK_INT".equals(fieldName)) {
return 23;
}
return super.getStaticIntField(vm, dvmClass, fieldName);
}
@Override
public void registerNativeMethods(VM vm, DvmClass dvmClass, String className, DvmNativeMethod[] methods) {
System.out.println("RegisterNatives for " + className + ", methods count=" + methods.length);
super.registerNativeMethods(vm, dvmClass, className, methods);
}
}
}
方案二、加载 APK
配置说明:
将真实APK文件放在指定路径(修改new File("path/to/your/app.apk"))
确保APK中包含com.example.NativeBridge类
将目标SO库(如libnative-lib.so)放在工作目录或指定路径
只需在JNI中补充APK中缺少的Android系统方法
java
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.Module;
import java.io.File;
public class ApkLoadedDemo {
private AndroidEmulator emulator;
private VM vm;
private Module module;
public ApkLoadedDemo() {
// 1. 创建模拟器
emulator = AndroidEmulatorBuilder.for32Bit().build();
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23)); // API Level 23
// 2. 加载真实APK文件
File apkFile = new File("path/to/your/app.apk");
vm = emulator.createDalvikVM(apkFile);
vm.setVerbose(true); // 启用详细日志
// 3. 设置自定义Jni实现
vm.setJni(new ApkJni(vm));
// 4. 加载目标SO库
DalvikModule dm = vm.loadLibrary("native-lib", true);
module = dm.getModule();
dm.callJNI_OnLoad(emulator);
}
public void executeNativeMethod() {
// 获取NativeBridge类
DvmClass bridge = vm.resolveClass("com/example/NativeBridge");
// 创建Context对象(实际应用中可能从APK中获取真实Context)
DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
// 调用doWork方法
DvmObject<?> result = bridge.callStaticJniMethodObject(
emulator,
"doWork(Landroid/content/Context;)Ljava/lang/String;",
context
);
System.out.println("Native method result: " + result.getValue());
}
public static void main(String[] args) {
ApkLoadedDemo demo = new ApkLoadedDemo();
demo.executeNativeMethod();
}
// 自定义JNI实现 - 仅需补充APK中缺少的方法
static class ApkJni extends AbstractJni {
private final VM vm;
public ApkJni(VM vm) { this.vm = vm; }
@Override
public DvmObject<?> getStaticObjectField(VM vm, DvmClass dvmClass, String fieldName, String signature) {
// 补充Build.MODEL字段
if ("android/os/Build".equals(dvmClass.getName()) &&
"MODEL".equals(fieldName) &&
"Ljava/lang/String;".equals(signature)) {
return new StringObject(vm, "Pixel-5");
}
return super.getStaticObjectField(vm, dvmClass, fieldName, signature);
}
@Override
public int getStaticIntField(VM vm, DvmClass dvmClass, String fieldName) {
// 补充SDK版本
if ("android/os/Build$VERSION".equals(dvmClass.getName()) &&
"SDK_INT".equals(fieldName)) {
return 30;
}
return super.getStaticIntField(vm, dvmClass, fieldName);
}
}
}
方案三、轻量Dex实现
java
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.Module;
import com.github.unidbg.utils.Inspector;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
public class LightDexImplementation {
private AndroidEmulator emulator;
private VM vm;
private Module module;
public LightDexImplementation() {
// 创建模拟器
emulator = AndroidEmulatorBuilder.for32Bit().build();
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23)); // API Level 23
// 创建虚拟机
vm = emulator.createDalvikVM(null);
vm.setVerbose(true); // 启用详细日志
// 加载轻量Dex
byte[] dexBytes = generateLightDex();
DalvikModule dm = vm.load(dexBytes);
dm.callJNI_OnLoad(emulator);
// 设置Jni实现
vm.setJni(new LightJni(vm));
// 加载目标SO库
dm = vm.loadLibrary("demo", true);
module = dm.getModule();
dm.callJNI_OnLoad(emulator);
}
// 生成轻量Dex文件
private byte[] generateLightDex() {
DexBuilder dexBuilder = new DexBuilder();
// 添加NativeBridge类
dexBuilder.addClass("com/example/NativeBridge",
"public class NativeBridge {\n" +
" public static native String doWork(android.content.Context ctx);\n" +
"}");
// 添加Device类
dexBuilder.addClass("com/example/Device",
"public class Device {\n" +
" public static String getDeviceId(android.content.Context ctx) {\n" +
" return \"LIGHT-DEX-DEVICE-ID\";\n" +
" }\n" +
"}");
return dexBuilder.build();
}
// 可选:生成轻量APK
private void generateLightApk() throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
JarOutputStream jar = new JarOutputStream(baos);
// 添加Dex文件
jar.putNextEntry(new JarEntry("classes.dex"));
jar.write(generateLightDex());
jar.closeEntry();
// 添加AndroidManifest.xml
jar.putNextEntry(new JarEntry("AndroidManifest.xml"));
jar.write((
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
"<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
" package=\"com.example.lightapp\">\n" +
" <application android:label=\"LightApp\">\n" +
" </application>\n" +
"</manifest>"
).getBytes());
jar.closeEntry();
jar.close();
// 保存APK文件
try (FileOutputStream fos = new FileOutputStream("light-app.apk")) {
fos.write(baos.toByteArray());
}
}
public void executeDoWork() {
// 从Dex中获取NativeBridge类
DvmClass bridge = vm.resolveClass("com/example/NativeBridge");
// 创建Context对象
DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
// 调用doWork方法
DvmObject<?> result = bridge.callStaticJniMethodObject(
emulator,
"doWork(Landroid/content/Context;)Ljava/lang/String;",
context
);
System.out.println("doWork result: " + result.getValue());
}
public static void main(String[] args) {
LightDexImplementation demo = new LightDexImplementation();
demo.executeDoWork();
}
// 轻量Jni实现
static class LightJni extends AbstractJni {
private final VM vm;
public LightJni(VM vm) {
this.vm = vm;
}
@Override
public DvmObject<?> callObjectMethod(
VM vm, DvmObject<?> dvmObject, String methodName, String signature, VarArgs varArgs) {
// Context.getPackageName()
if ("android/content/Context".equals(dvmObject.getObjectType().getName()) &&
"getPackageName".equals(methodName) &&
"()Ljava/lang/String;".equals(signature)) {
return new StringObject(vm, "com.example.lightapp");
}
return super.callObjectMethod(vm, dvmObject, methodName, signature, varArgs);
}
@Override
public DvmObject<?> getStaticObjectField(VM vm, DvmClass dvmClass, String fieldName, String signature) {
// Build.MODEL
if ("android/os/Build".equals(dvmClass.getName()) &&
"MODEL".equals(fieldName) &&
"Ljava/lang/String;".equals(signature)) {
return new StringObject(vm, "LightDex-Device");
}
return super.getStaticObjectField(vm, dvmClass, fieldName, signature);
}
}
}
/*Dex 生成
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import com.android.dx.dex.DexOptions;
import com.android.dx.dex.file.DexFile;
import com.android.dx.dex.cf.CfTranslator;
import com.android.dx.cf.direct.DirectClassFile;
import com.android.dx.cf.direct.StdAttributeFactory;
public class DexGenerator {
public static byte[] generateClass(String className, String code) throws IOException {
// 使用ASM生成类字节码
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
// 类头
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC,
className.replace('.', '/'),
null, "java/lang/Object", null);
// 添加方法
if (code.contains("getDeviceId")) {
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
"getDeviceId", "()Ljava/lang/String;", null, null);
mv.visitCode();
mv.visitLdcInsn("ASM-GENERATED-ID");
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(1, 0);
mv.visitEnd();
}
// 其他方法生成...
cw.visitEnd();
return cw.toByteArray();
}
public static byte[] createDexFile(byte[][] classBytes) throws IOException {
DexOptions options = new DexOptions();
DexFile dexFile = new DexFile(options);
for (byte[] classByte : classBytes) {
DirectClassFile cf = new DirectClassFile(classByte, null, null, false);
cf.setAttributeFactory(StdAttributeFactory.THE_ONE);
dexFile.add(CfTranslator.translate(
cf,
classByte,
new CfOptions(),
options
));
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
dexFile.writeTo(out, null, false);
return out.toByteArray();
}
}
*/
方案四、RegisterNatives 监控与分析
一些 .so 通过 RegisterNatives 动态注册 JNI 方法,Unidbg 会回调到 registerNativeMethods,便于记录与分析。
- 添加 RegisterNatives 钩子监控
- 捕获并解析所有动态注册的 JNI 方法
- 输出方法名、签名和函数指针
- 适用于逆向分析和安全审计
- 不影响正常功能执行
java
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Backend;
public class RegisterNativesMonitor {
private AndroidEmulator emulator;
private VM vm;
private Module module;
public RegisterNativesMonitor() {
// 创建模拟器
emulator = AndroidEmulatorBuilder.for32Bit().build();
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23)); // API Level 23
// 创建虚拟机
vm = emulator.createDalvikVM(null);
vm.setVerbose(true); // 启用详细日志
// 设置Jni实现
vm.setJni(new MonitorJni(vm));
// 加载目标SO库
DalvikModule dm = vm.loadLibrary("demo", true);
module = dm.getModule();
dm.callJNI_OnLoad(emulator);
// 设置RegisterNatives钩子
setupRegisterNativesHook();
}
private void setupRegisterNativesHook() {
// 查找RegisterNatives函数地址
long registerNativesAddr = module.findSymbol("RegisterNatives");
if (registerNativesAddr == 0) {
System.out.println("RegisterNatives symbol not found");
return;
}
// 添加钩子
emulator.getBackend().addHook(new RegisterNativesHook(), registerNativesAddr);
}
public void executeDoWork() {
// 解析NativeBridge类
DvmClass bridge = vm.resolveClass("com/example/NativeBridge");
// 创建Context对象
DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
// 调用doWork方法
DvmObject<?> result = bridge.callStaticJniMethodObject(
emulator,
"doWork(Landroid/content/Context;)Ljava/lang/String;",
context
);
System.out.println("doWork result: " + result.getValue());
}
public static void main(String[] args) {
RegisterNativesMonitor demo = new RegisterNativesMonitor();
demo.executeDoWork();
}
// RegisterNatives钩子实现
class RegisterNativesHook extends BlockHook {
@Override
public void hook(Backend backend, long address, int size, Object user) {
// 获取参数
long envPtr = backend.getRegisters()[0]; // JNIEnv*
long clazz = backend.getRegisters()[1]; // jclass
long methodsPtr = backend.getRegisters()[2]; // JNINativeMethod*
int methodCount = backend.getRegisters()[3]; // jint
// 解析类名
String className = vm.findClassByAddress(clazz).getName();
System.out.println("RegisterNatives called for class: " + className);
System.out.println("Method count: " + methodCount);
// 解析JNINativeMethod数组
long currentPtr = methodsPtr;
for (int i = 0; i < methodCount; i++) {
// 读取方法名指针
long namePtr = backend.readPointer(currentPtr);
String name = backend.readCString(namePtr);
// 读取方法签名指针
long sigPtr = backend.readPointer(currentPtr + emulator.getPointerSize());
String signature = backend.readCString(sigPtr);
// 读取函数指针
long fnPtr = backend.readPointer(currentPtr + emulator.getPointerSize() * 2);
System.out.printf("|-- Method %d: %s%s @ 0x%x%n", i + 1, name, signature, fnPtr);
// 移动到下一个方法结构
currentPtr += emulator.getPointerSize() * 3;
}
}
}
// 监控专用Jni实现
static class MonitorJni extends AbstractJni {
private final VM vm;
public MonitorJni(VM vm) {
this.vm = vm;
}
@Override
public DvmObject<?> callStaticObjectMethod(
VM vm, DvmClass dvmClass, String methodName, String signature, VarArgs varArgs) {
// Device.getDeviceId()
if ("com/example/Device".equals(dvmClass.getName()) &&
"getDeviceId".equals(methodName) &&
"(Landroid/content/Context;)Ljava/lang/String;".equals(signature)) {
return new StringObject(vm, "MONITOR-DEVICE-ID");
}
return super.callStaticObjectMethod(vm, dvmClass, methodName, signature, varArgs);
}
@Override
public DvmObject<?> callObjectMethod(
VM vm, DvmObject<?> dvmObject, String methodName, String signature, VarArgs varArgs) {
// Context.getPackageName()
if ("android/content/Context".equals(dvmObject.getObjectType().getName()) &&
"getPackageName".equals(methodName) &&
"()Ljava/lang/String;".equals(signature)) {
return new StringObject(vm, "com.example.monitor");
}
return super.callObjectMethod(vm, dvmObject, methodName, signature, varArgs);
}
@Override
public DvmObject<?> getStaticObjectField(VM vm, DvmClass dvmClass, String fieldName, String signature) {
// Build.MODEL
if ("android/os/Build".equals(dvmClass.getName()) &&
"MODEL".equals(fieldName) &&
"Ljava/lang/String;".equals(signature)) {
return new StringObject(vm, "Monitor-Device");
}
return super.getStaticObjectField(vm, dvmClass, fieldName, signature);
}
}
}