Ci sono due modi generali in cui puoi dichiarare le funzioni JNI
.
Il primo è il modo più ovvio in cui la funzione JNI
deve seguire una convenzione di denominazione specifica come JNIEXPORT void JNICALL Java_com_app_foo_bar
. Puoi facilmente identificare tali funzioni usando readelf
.
L'altro modo non così ovvio è usare RegisterNatives
. Qui le tue funzioni possono avere qualsiasi firma e inoltre non devono essere esportate dalla libreria condivisa. In genere, dovresti chiamare RegisterNatives
da JNI_OnLoad
per registrare le funzioni native in Java Run-time.
Per il tuo binario libcms.so
, utilizza il secondo metodo.
RegisterNatives
ha il seguente prototipo
jint RegisterNatives (JNIEnv * env, jclass clazz, const JNINativeMethod *, jint Methods);
Se analizzi il codice di JNI_OnLoad
ti imbatterai in una chiamata RegisterNatives
come sotto.
Il terzo argomento punta a un array di strutture JNINativeMethod
che è dichiarato come
typedef struct {char * name; char * signature; void * fnPtr;} JNINativeMethod;
Il primo membro è un puntatore a una stringa con terminazione nulla che denota il nome della funzione. Tuttavia, nel tuo caso tutti i nomi e le firme sono crittografati.
Questi sono decrittografati in fase di esecuzione dalla famiglia di funzioni .datadiv_decodeXXXXXXXX
. La sezione .init_array
contiene puntatori a queste funzioni di decrittazione, il che implica che verranno chiamate all'avvio.
Tuttavia, non è tutto. Il binario utilizza anche Appiattimento flusso di controllo offuscamento, quindi il percorso di esecuzione potrebbe non essere facilmente distinguibile come mostrato di seguito.
Per analizzare il binario è meglio ricorrere a tecniche di analisi dinamica utilizzando uno strumento come Frida.
Ulteriori letture: