## RegisterNatives() - Used to match Java method to the native function - [Static linking](https://www.ragingrock.com/AndroidAppRE/reversing_native_libs.html) use [RegisterNatives](https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html#wp5833) API ```java jint RegisterNatives(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods); typedef struct { char *name; char *signature; void *fnPtr; } JNINativeMethod; // method signature format (arg-types) ret-type // Z: boolean // B: byte // C: char // S: short // I: int // J: long // F: float // D: double // L: fully-qualifed-class; // V: void // array type[] ``` - example JNINativeMethod struct array in IDA: ![image](https://hackmd.io/_uploads/B1pp_rnMlx.png) ## List all native function in .so 1. frida hook - [hook_RegisterNatives.js](https://github.com/lasting-yang/frida_hook_libart/blob/master/hook_RegisterNatives.js) ```java var RevealNativeMethods = function() {   var pSize = Process.pointerSize;   var env = Java.vm.getEnv();   var RegisterNatives = 215, FindClassIndex = 6; // search "215" @ https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html   var jclassAddress2NameMap = {};   function getNativeAddress(idx) {     return env.handle.readPointer().add(idx * pSize).readPointer();   }   // intercepting FindClass to populate Map<address, jclass>   Interceptor.attach(getNativeAddress(FindClassIndex), {     onEnter: function(args) {       jclassAddress2NameMap[args[0]] = args[1].readCString();     }   });   // RegisterNative(jClass*, .., JNINativeMethod *methods[nMethods], uint nMethods) // https://android.googlesource.com/platform/libnativehelper/+/master/include_jni/jni.h#977   Interceptor.attach(getNativeAddress(RegisterNatives), {     onEnter: function(args) {       for (var i = 0, nMethods = parseInt(args[3]); i < nMethods; i++) { // parse JNINativeMethod struct         var structSize = pSize * 3; // = sizeof(JNINativeMethod), contain 3 pointer         var methodsPtr = ptr(args[2]);         var signature = methodsPtr.add(i * structSize + pSize).readPointer();         var fnPtr = methodsPtr.add(i * structSize + (pSize * 2)).readPointer();         var jClass = jclassAddress2NameMap[args[0]].split('/');         // send data back to python         send(JSON.stringify({           module: DebugSymbol.fromAddress(fnPtr)['moduleName'], // https://www.frida.re/docs/javascript-api/#debugsymbol           package: jClass.slice(0, -1).join('.'),           class: jClass[jClass.length - 1],           method: methodsPtr.readPointer().readCString(), // char* name           signature: signature.readCString(),           address: fnPtr         }));       }     }   }); } Java.perform(RevealNativeMethods); ``` 2. unidbg script: ```java! // dynamic linking, start analysis from JNI_Onload() public class natvive_call extends AbstractJni{ private final AndroidEmulator emulator; private final VM vm; private final Module module; natvive_call() { // Create a simulator instance. // It is recommended to fill in the process name according to the actual process name to avoid checking the process name. emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.sina.oasis").build(); //Get the simulator's memory operation interface final Memory memory = emulator.getMemory(); // Set up system library parsing memory.setLibraryResolver(new AndroidResolver(23)); // Create an Android virtual machine and pass in the APK. Unidbg can do some signature verification for us. vm = emulator.createDalvikVM(new File("lvzhou.apk")); DalvikModule dm = vm.loadLibrary(new File("liboasiscore.so"), true); // Get the handle of this SO module, which will be used later module = dm.getModule(); vm.setJni(this); // Setup JNI vm.setVerbose(true); // Print log dm.callJNI_OnLoad(emulator); // Calling JNI OnLoad }; public static void main(String[] args) { natvive_call test = new natvive_call(); } } ``` > 2nd param of `LoadLibrary` is init related functions (.init sections) are executed during the step of loading so into virtual memory (`forceCallInit` = true). The SO sample obfuscates or encrypts the string. This decryption process usually happens in the `.init_array` section and `JNI_OnLoad`. > > Incase you want direct call the specific function, set this param to false may help (:v reduce error) 3. [ExAndroidNativeEmu](https://github.com/maiyao1988/ExAndroidNativeEmu): ```python! import posixpath from androidemu.emulator import Emulator, logger from androidemu.java.classes.string import String # Initialize emulator emulator = Emulator( vfp_inst_set=True, vfs_root=posixpath.join(posixpath.dirname(__file__), "vfs") ) lib_module = emulator.load_library("liboasiscore.so") # find My module for module in emulator.modules: if "liboasiscore" in module.filename: base_address = module.base logger.info("base_address=> 0x%08x - %s" % (module.base, module.filename)) break # JNI_OnLoad emulator.call_symbol(lib_module, 'JNI_OnLoad', emulator.java_vm.address_ptr, 0x00) a1 = "aid=01A-khBWIm48A079Pz_DMW6PyZR8uyTumcCNm4e8awxyC2ANU.&cfrom=28B5295010&cuid=5999578300&noncestr=46274W9279Hr1X49A5X058z7ZVz024&platform=ANDROID&timestamp=1621437643609&ua=Xiaomi-MIX2S__oasis__3.5.8__Android__Android10&version=3.5.8&vid=1019013594003&wm=20004_90024" # Thumb mode result = emulator.call_native(module.base + 0xC364 + 1, emulator.java_vm.jni_env.address_ptr, 0x00, String(a1).getBytes(emulator, String("utf-8")), 0) print("result:"+ result._String__str) ``` > `Thumb mode` usaged in both frida or unidbg, which is the standard for most Android native libraries (LSB=1) - with dynamic linking method: ```java // declare package caller // method sign in class me.bhamza.hellosignjni.MainActivity private final DvmClass dvmMainActivity; this.dvmMainActivity = dalvikVM.resolveClass("me/bhamza/hellosignjni/MainActivity"); this.method_signature = "sign(Ljava/lang/String;)Ljava/lang/String;" ``` --- Each call takes a different block of memory as its argument, which is often a signal of decryption ![image](https://hackmd.io/_uploads/ByqBKWofgx.png) --- ## call native method ### passing argument ```java List<Object> list = new ArrayList<>(10); list.add(vm.getJNIEnv()); list.add(0); // String argument list.add(vm.addLocalObject(new StringObject(vm, "string argument"))); // Context argument Object custom = null; DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(custom); list.add(vm.addLocalObject(context)); // byte[] argument String input = "byte arr argument"; byte[] inputByte = input.getBytes(StandardCharsets.UTF_8); ByteArray inputByteArray = new ByteArray(vm, inputByte); list.add(vm.addLocalObject(inputByteArray)); // boolean list.add(0); // 0 - false, 1 - true ``` --- ## decrypt string in virbox: - native lib arm32: `l27f9768e_a32.so` - signature of native decrypt string method: `.method public static native F27f9768e_11(Ljava/lang/String;)Ljava/lang/String;` - java class declare native method: ```java package defpackage; import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; import java.io.InputStream; /* loaded from: C:\Users\REM\Desktop\VirBoxDynamicRestore\com.zhurenweile.app\dump1\com.zhurenweile.app-7a2e176000-7a2e45b000.bin */ public class m27f9768e { public static native void F27f9768e_00(int i, Object[] objArr); public static native boolean F27f9768e_01(int i, Object[] objArr); public static native byte F27f9768e_02(int i, Object[] objArr); public static native char F27f9768e_03(int i, Object[] objArr); public static native short F27f9768e_04(int i, Object[] objArr); public static native int F27f9768e_05(int i, Object[] objArr); public static native long F27f9768e_06(int i, Object[] objArr); public static native float F27f9768e_07(int i, Object[] objArr); public static native double F27f9768e_08(int i, Object[] objArr); public static native Object F27f9768e_09(int i, Object[] objArr); public static native void F27f9768e_10(Object obj); public static native String F27f9768e_11(String str); public static native AssetFileDescriptor F27f9768e_12(AssetManager assetManager, String str); public static native InputStream F27f9768e_13(Class cls, String str); public static native InputStream F27f9768e_14(ClassLoader classLoader, String str); static String F27f9768e_15(Object obj) { return obj != null ? "object value: " + obj.getClass().getName() + " - " + obj.toString() : "object value: null"; } // ... public static String decrypt(String str) { try { SecretKeySpec secretKeySpec = new SecretKeySpec(m27f9768e.F27f9768e_11("~`51535557595B5D5F61595B5D5F616365").getBytes(), "AES"); Cipher cipher = Cipher.getInstance("AES"); cipher.init(2, secretKeySpec); return new String(cipher.doFinal(Base64.getDecoder().decode(str))); } catch (Exception e) { return ""; } } } ``` - Some Encrypted Strings in code (for testing purpose): ```java sb.append(NRv6nmy82.decrypt(m27f9768e.F27f9768e_11("PE0E3E7F0B3B271A790B1C784043777E4486158F20278528291E428A901C4D4F4728389F422F2C3A9B4C573EA3"))); sb.append(NRv6nmy82.decrypt(m27f9768e.F27f9768e_11("vk590F29606013613344243D3348142822155541451D1B2F1A3A5378234F355A775C5A805F427F337932565481"))).append(NRv6nmy82.decrypt(m27f9768e.F27f9768e_11("R606617C527E735985216B66821B6D886F8D73796490662122")) + aliases.toString()); ``` [+] Unidbg solve script ```java! public class native_call extends AbstractJni { private final AndroidEmulator emulator; private final VM vm; private final Module module; native_call(){ // Create a simulator instance. // It is recommended to fill in the process name according to the actual process name to avoid checking the process name. emulator = AndroidEmulatorBuilder.for32Bit() .addBackendFactory(new Unicorn2Factory(true)) .setProcessName("com.zhurenweile.app") .build(); //Get the simulator's memory operation interface final Memory memory = emulator.getMemory(); // Set up system library parsing memory.setLibraryResolver(new AndroidResolver(23)); // Create an Android virtual machine and pass in the APK. Unidbg can do some signature verification for us. vm = emulator.createDalvikVM(new File("unidbg-android\\src\\test\\java\\com\\virbox\\com.zhurenweile.app.apk")); new AndroidModule(emulator, vm).register(memory); // Load target SO DalvikModule dm = vm.loadLibrary(new File("unidbg-android\\src\\test\\java\\com\\virbox\\l27f9768e_a32.so"), false); // Get the handle of this SO module, which will be used later module = dm.getModule(); vm.setJni(this); // Setup JNI vm.setVerbose(true); // Print log // dm.callJNI_OnLoad(emulator); // Calling JNI OnLoad } @Override public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) { switch (signature){ case "java/lang/String->intern()Ljava/lang/String;": String string = dvmObject.getValue().toString(); return new StringObject(vm, string.intern()); } return super.callObjectMethodV(vm, dvmObject, signature, vaList); } public String F27f9768e_11(String message){ // public static native String F27f9768e_11(String str); // args list List<Object> list = new ArrayList<>(10); // arg1 env list.add(vm.getJNIEnv()); // arg2 jobject/jclazz Generally not used, just fill in 0 list.add(0); DvmObject<?> dvm_message = ProxyDvmObject.createObject(this.vm, message); list.add(vm.addLocalObject(dvm_message)); Number number = module.callFunction(emulator, 0x1E2F88+1, list.toArray()); String result = vm.getObject(number.intValue()).getValue().toString(); return result; } public String decrypt_(String str) { try { SecretKeySpec secretKeySpec = new SecretKeySpec(F27f9768e_11("~`51535557595B5D5F61595B5D5F616365").getBytes(), "AES"); Cipher cipher = Cipher.getInstance("AES"); cipher.init(2, secretKeySpec); return new String(cipher.doFinal(Base64.getDecoder().decode(str))); } catch (Exception e) { return ""; } } public static void main(String[] args) { native_call test = new native_call(); System.out.println(test.decrypt_(test.F27f9768e_11("vk590F29606013613344243D3348142822155541451D1B2F1A3A5378234F355A775C5A805F427F337932565481"))); //> "; available aliases =" System.out.println(test.decrypt_(test.F27f9768e_11("PE0E3E7F0B3B271A790B1C784043777E4486158F20278528291E428A901C4D4F4728389F422F2C3A9B4C573EA3"))); //> "Missing key alias" } } ```