## 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:

## 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×tamp=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

---
## 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"
}
}
```