My goal is to see what a native method m does. It doesn't have a direct counterpart in the so file, but I've found the JNI_OnLoad function's ARM assembly code. It starts with

AAAAAAAA <JNI_OnLoad@@Base>:

where AAAAAAAA is some hex number. (Correct me if I'm wrong.) I want to find calls to the RegisterNatives function. I've seen some function calls, like

bl AAAA <JNI_OnLoad@@Base+0xBB>

where AAAA and BB are hex numbers. (Are these function calls indeed?) I can't determine which, if any, are calls to RegisterNatives. How do I go about finding one, or determine that none exists?


Oh, it's possible that JNI_OnLoad calls func1, and func1 calls func2, and so on, and only at the very end RegisterNatives gets called.

I have obtained the C code, using the Hopper Disassembler. Is there something distinctive about the RegisterNatives function? I find a tree of functions to explore.

  • 1
    It may be possible that the code is packed/encrypted and gets unpacked/decrypted in JNI_OnLoad. Apt recent blackhat talk : i.blackhat.com/us-18/Thu-August-9/…
    – sudhackar
    Aug 11, 2018 at 7:58
  • @sudhackar: Is there anything new in the PPT?
    – Zirui Wang
    Aug 11, 2018 at 11:08

1 Answer 1


RegisterNatives is not a function that you find in the binary you're analyzing. It is implemented in the system Java runtime and is called indirectly by a pointer. The JNI_OnLoad function has the folliwing prototype (from jni.h):

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved);

Where vm is a pointer to a function table:

 * JNI invocation interface.
struct JNIInvokeInterface {
    void*       reserved0;
    void*       reserved1;
    void*       reserved2;

    jint        (*DestroyJavaVM)(JavaVM*);
    jint        (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);
    jint        (*DetachCurrentThread)(JavaVM*);
    jint        (*GetEnv)(JavaVM*, void**, jint);
    jint        (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);
typedef const struct JNIInvokeInterface* JavaVM;

However, RegisterNatives itself is a method in another function table:

struct JNINativeInterface {
    void*       reserved0;
    void*       reserved1;
    void*       reserved2;
    void*       reserved3;

    jint        (*GetVersion)(JNIEnv *);

    jclass      (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*,
    jclass      (*FindClass)(JNIEnv*, const char*);

   /*<skipped> */

    jint        (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*,
    jint        (*UnregisterNatives)(JNIEnv*, jclass);

   /*<skipped> */

The typical way of calling RegisterNatives from JNI_OnLoad would be to retrieve a pointer to a JNIEnv by calling vm->GetEnv(), then calling the RegisterNatives pointer in the JNIEnv struct, e.g.:

(*vm)->GetEnv(vm, (void **)&env, 0x10004); class = (*env)->FindClass(env, "org/gaeproxy/Exec"); (*env_)->RegisterNatives(env, class, &methods, 4);

where methods is an array of JNINativeMethod structs describing names, prototypes, and pointers to native implementations of the methods.

JNINativeMethod methods[] =
  { "waitFor", "(I)I", (void *)&Exec_waitFor },
  { "close", "(Ljava/io/FileDescriptor;)V", (void *)&Exec_close },
  { "hangupProcessGroup", "(I)V", (void *)&Exec_hangupProcessGroup }

There is no sure-fire indicator that immediately point out this call in disassembly, but here are some hints for ARM 32-bit:

  • the call is indirect, i.e. usually a BLX rN, where rN is loaded from a pointer with offset.

  • one of the arguments to the call (third, so loaded in R2) is the method table which may look obvious (pointers to method names and prototypes).Note that especially in malware strings may be obfuscated, so don't expect to always see this in cleartext, but it should be visible at runtime/debug time.

  • offset of the RegisterNatives pointer in the JNIEnv struct is fixed. for ARM EABI it is 0x35C, so if you see this constant it's a pretty solid hint. However, once again this constant may be obfuscated and not visible in initial code.

For example, here is the call site of the above snippet:

 LDR     R0, [R5] ; <- dereferencing env from a previous GetEnv call
 LDR     R2, =(methods - 0x15AC)
 MOVS    R3, #0x35C ; <- offset 0x35C
 LDR     R4, [R0,R3] ; load RegisterNatives pointer
 ADD     R2, PC          ; methods
 MOVS    R0, R5 ; env
 MOVS    R3, #4 ; number of methods
 BLX     R4       ;<- call
 CMP     R0, #0 ; check return value
  • Man, You deserve a beer. This happens when compiling with fvisibility=hidden, right ? any IDA plugin known to map method+signature to address ?
    – whoopdedoo
    Aug 13, 2018 at 21:02
  • @IddoE visibility has nothing to do with this since the target function is not linked by name. Not sure what you mean by the second question, maybe ask a new one with details.
    – Igor Skochinsky
    Aug 13, 2018 at 21:05
  • stackoverflow.com/questions/51811348/… should I open one here ?
    – whoopdedoo
    Aug 13, 2018 at 21:37
  • @IgorSkochinsky: No, it doesn't have 0x35c. That's what I said: "a tree of functions to explore." Plus encryption maybe?
    – Zirui Wang
    Aug 15, 2018 at 3:06
  • I do mention possibility of obfuscation in case you did not notice. My answer is for the general case, not your specific sample (which I don’t have)
    – Igor Skochinsky
    Aug 15, 2018 at 7:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.