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*,
jsize);
jclass (*FindClass)(JNIEnv*, const char*);
/*<skipped> */
jint (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*,
jint);
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[] =
{
{
"createSubprocess",
"(ILjava/lang/String;[Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;[I)Ljava/io/FileDescriptor;",
(void*)&Exec_createSubprocess
},
{ "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
JNI_OnLoad
. Apt recent blackhat talk : i.blackhat.com/us-18/Thu-August-9/… – sudhackar Aug 11 '18 at 7:58