Complete Example
Complete Example
Minimal SO call (no JNI)
1public class MinimalTest {
2 public static void main(String[] args) throws Exception {
3 AndroidEmulator emulator = AndroidEmulatorBuilder.for64Bit()
4 .setProcessName("com.example.test")
5 .build();
6
7 Memory memory = emulator.getMemory();
8 memory.setLibraryResolver(new AndroidResolver(23));
9
10 Module module = emulator.loadLibrary(new File("libnative.so"));
11 Number result = module.callFunction(emulator, "native_func");
12 System.out.println("Result: " + result.intValue());
13
14 emulator.close();
15 }
16}
Android JNI full workflow
1public class MyApp extends AbstractJni {
2
3 private final AndroidEmulator emulator;
4 private final VM vm;
5 private final DvmClass myClass;
6
7 public MyApp() throws Exception {
8 emulator = AndroidEmulatorBuilder.for32Bit()
9 .setProcessName("com.example.app")
10 .setRootDir(new File("target/rootfs/default"))
11 .addBackendFactory(new DynarmicFactory(true))
12 .build();
13
14 Memory memory = emulator.getMemory();
15 memory.setLibraryResolver(new AndroidResolver(23));
16
17 vm = emulator.createDalvikVM(new File("app.apk"));
18 vm.setVerbose(true); // log unhandled JNI calls
19 vm.setJni(this);
20
21 DalvikModule dm = vm.loadLibrary(new File("libnative.so"), true);
22 dm.callJNI_OnLoad(emulator);
23
24 myClass = vm.resolveClass("com/example/app/NativeHelper");
25 }
26
27 // JNI callback: native code calls Java
28 @Override
29 public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
30 if ("com/example/app/Utils->getDeviceId()Ljava/lang/String;".equals(signature)) {
31 return new StringObject(vm, "fake-device-id-12345");
32 }
33 return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
34 }
35
36 @Override
37 public int callStaticIntMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
38 if ("android/os/Build$VERSION->SDK_INT:I".equals(signature)) {
39 return 23;
40 }
41 return super.callStaticIntMethod(vm, dvmClass, signature, varArg);
42 }
43
44 // Invoke the target function
45 public byte[] sign(String input) throws Exception {
46 DvmObject<?> result = myClass.callStaticJniMethodObject(emulator,
47 "sign(Ljava/lang/String;)[B",
48 new StringObject(vm, input));
49
50 ByteArray byteArray = (ByteArray) result;
51 return byteArray.getValue();
52 }
53
54 public static void main(String[] args) throws Exception {
55 MyApp app = new MyApp();
56 byte[] signed = app.sign("data-to-sign");
57 System.out.println(Arrays.toString(signed));
58 app.emulator.close();
59 }
60}
Hook + call pattern
1// Hook before calling to intercept internals
2IxHook xHook = XHookImpl.getInstance(emulator);
3xHook.register("libnative.so", "RAND_bytes", new ReplaceCallback() {
4 @Override
5 public HookStatus onCall(Emulator<?> emulator, long originFunction) {
6 // Fix randomness for reproducible output
7 RegisterContext ctx = emulator.getContext();
8 UnidbgPointer buf = ctx.getPointerArg(0);
9 int len = ctx.getIntArg(1);
10 buf.write(0, new byte[len], 0, len); // zero fill
11 return HookStatus.LR(emulator, 1); // return success
12 }
13});
14xHook.refresh();
15
16Number result = module.callFunction(emulator, "encrypt", dataPtr, dataLen);
Trace-and-call pattern
1// Enable tracing for a specific function range
2long funcAddr = module.base + 0x2000;
3long funcEnd = module.base + 0x2200;
4emulator.traceCode(funcAddr, funcEnd);
5
6// Call will print disassembly as it executes
7module.callFunction(emulator, 0x2000);
Test class location
Reference implementations in the project:
| File | Location |
|---|---|
AndroidTest.java | unidbg-android/src/test/java/com/github/unidbg/android/ |
Android64Test.java | unidbg-android/src/test/java/com/github/unidbg/android/ |
QDReaderJni.java | unidbg-android/src/test/java/com/github/unidbg/android/ |
AndroidNativeEmuTest.java | unidbg-android/src/test/java/org/aeonlucid/ |