Ein in Frida basierendes Tool zur Verfolgung der JNI -API in Android -Apps.
Native Bibliotheken, die innerhalb von Android -Apps enthalten sind, verwenden häufig die JNI -API, um die Android -Laufzeit zu nutzen. Das Nachverfolgen dieser Anrufe durch manuelle Reverse Engineering kann ein langsamer und schmerzhafter Prozess sein. jnitrace arbeitet als dynamisches Analyse-Tracing-Tool ähnlich wie Frida-Trace oder Strace, aber für die JNI.
Der einfachste Weg, um mit jnitrace zu laufen, besteht darin, mit PIP zu installieren:
pip install jnitrace
Nach einer PIP -Installation ist es einfach, jnitrace auszuführen:
jnitrace -l libnative-lib.so com.example.myapplication
jnitrace erfordert mindestens zwei Parameter, um eine Spur auszuführen:
-l libnative-lib.so -wird verwendet, um die Bibliotheken zu verfolgen. Dieses Argument kann mehrmals verwendet werden oder * kann verwendet werden, um alle Bibliotheken zu verfolgen. Zum Beispiel -l libnative-lib.so -l libanother-lib.so oder -l * .com.example.myapplication - ist das Android -Paket zu verfolgen. Dieses Paket muss bereits auf dem Gerät installiert sein.Optionale Argumente sind unten aufgeführt:
-R <host>:<port> - wird verwendet, um den Netzwerkstandort des Remote -Frida -Servers anzugeben. Wenn A: nicht näher bezeichnet wird, wird Localhost: 27042 von DeeauT verwendet.-m <spawn|attach> - wird verwendet, um den zu verwendenden Frida -Anhangsmechanismus anzugeben. Es kann entweder laichen oder befestigt werden. Spawn ist die Standard- und empfohlene Option.-b <fuzzy|accurate|none> - wird verwendet, um die Backtraces -Ausgabe zu steuern. Standardmäßig wird jnitrace den Backtracer im accurate Modus ausführen. Diese Option kann in den fuzzy -Modus geändert oder verwendet werden, um die Backtrace mithilfe der Option none zu stoppen. In den Frida -Dokumenten finden Sie eine Erklärung zu den Unterschieden.-i <regex> - wird verwendet, um die Methodennamen anzugeben, die verfolgt werden sollten. Dies kann hilfreich sein, um das Geräusch in besonders großen JNI -Apps zu reduzieren. Die Option kann mehrmals geliefert werden. Zum Beispiel, -i Get -i RegisterNatives nur JNI -Methoden einbeziehen, die GET oder Registernativen in ihren Namen enthalten.-e <regex> - wird verwendet, um die Methodamen anzugeben, die in der Spur ignoriert werden sollten. Dies kann hilfreich sein, um das Geräusch in besonders großen JNI -Apps zu reduzieren. Die Option kann mehrmals geliefert werden. Zum Beispiel würde -e ^Find -e GetEnv aus den Ergebnissen ausschließen, die alle JNI -Methodennamen finden, die Getenv finden oder enthalten.-I <string> - wird verwendet, um die Exporte aus einer Bibliothek festzulegen, die verfolgt werden sollte. Dies ist nützlich für Bibliotheken, in denen Sie nur eine kleine Anzahl von Methoden verfolgen möchten. Die Funktionen, die Jnitrace als exportiert berücksichtigt, sind alle Funktionen, die direkt von der Java -Seite aufgerufen werden können, die als solche Methoden enthält, die unter Verwendung von Registernativen gebunden sind. Die Option kann mehrmals geliefert werden. Zum Beispiel konnte -I stringFromJNI -I nativeMethod([B)V verwendet werden, um einen Export aus der Bibliothek mit dem Namen Java_com_nativetest_MainActivity_stringFromJNI und eine Methode mit Registernamen mit der Signatur von nativeMethod([B)V .-E <string> wird verwendet, um die Exporte aus einer Bibliothek anzugeben, die nicht verfolgt werden sollte. Dies ist nützlich für Bibliotheken, in denen Sie eine Gruppe geschäftiger native Anrufe haben, die Sie ignorieren möchten. Die Funktionen, die Jnitrace als exportiert berücksichtigt, sind alle Funktionen, die direkt von der Java -Seite aufgerufen werden können, die als solche Methoden enthält, die unter Verwendung von Registernativen gebunden sind. Die Option kann mehrmals geliefert werden. Zum Beispiel würde -E JNI_OnLoad -E nativeMethod aus der verfolgung den JNI_OnLoad function call und alle Methoden mit dem Namen nativeMethod ausschließen.-o path/output.json - wird verwendet, um einen Ausgangsweg anzugeben, in dem jnitrace alle verfolgten Daten speichert. Die Informationen werden im JSON-Format gespeichert, um eine spätere Nachbearbeitung der Trace-Daten zu ermöglichen.-p path/to/script.js - Der bereitgestellte Pfad wird verwendet, um ein Frida -Skript in den Zielprozess zu laden, bevor das jnitrace -Skript geladen wurde. Dies kann zur Besiegung von Anti-Frida- oder Anti-Debugging-Code verwendet werden, bevor jnitrace beginnt.-a path/to/script.js - Der bereitgestellte Pfad wird verwendet, um das Frida -Skript in den Zielprozess zu laden, nachdem jnitrace geladen wurde.--hide-data -Wird verwendet, um die in der Konsole angezeigte Ausgangsmenge zu verringern. Mit dieser Option werden zusätzliche Daten ausgeblendet, die als Hexdumps oder als Stringentherapie angezeigt werden.--ignore-env -Verwenden Sie diese Option mithilfe dieser Option alle Anrufe, die die App mithilfe der JNIENV-Struktur erstellt.--ignore-vm -Mit dieser Option werden alle Aufrufe ausgeblendet, die die App mithilfe der Javavm-Struktur erstellt.--aux <name=(string|bool|int)value> -verwendet, um benutzerdefinierte Parameter beim Lebenden einer Anwendung zu übergeben. Zum Beispiel wird --aux='uid=(int)10' die Anwendung für Benutzer 10 anstelle von Standardbenutzer 0 erzeugt.Notiz
Denken Sie daran, Frida-Server muss vor dem Ausführen jnitrace ausgeführt werden. Wenn die Standardanweisungen zur Installation von Frida befolgt wurden, startet der folgende Befehl den Server, der für jnitrace bereit ist:
adb shell /data/local/tmp/frida-server
Der Motor, der Jnitrace versorgt, ist als separates Projekt erhältlich. Mit diesem Projekt können Sie JNitrace importieren, um einzelne JNI -API -Aufrufe zu verfolgen, in einer Methode, die mit der Verwendung des Frida Interceptor zu Funktionen und Adressen bekannt ist.
import { JNIInterceptor } from "jnitrace-engine" ;
JNIInterceptor . attach ( "FindClass" , {
onEnter ( args ) {
console . log ( "FindClass method called" ) ;
this . className = Memory . readCString ( args [ 1 ] ) ;
} ,
onLeave ( retval ) {
console . log ( "tLoading Class:" , this . className ) ;
console . log ( "tClass ID:" , retval . get ( ) ) ;
}
} ) ;Weitere Informationen: https://github.com/chame1eon/jnitrace-engine
Nach dem Erstellen jnitrace aus der Quelle muss der node zuerst installiert werden. Nach der Installation node müssen die folgenden Befehle ausgeführt werden:
npm installnpm run watch npm run watch wird frida-compile im Hintergrund ausgeführt, der die Quelle in die Ausgabedatei erstellt, build/jnitrace.js . jnitrace.py lädt standardmäßig von build/jnitrace.js , sodass keine anderen Änderungen erforderlich sind, um die Updates auszuführen.
Wie bei Frida-Trace wird die Ausgabe basierend auf dem API-Aufruf-Thread gefärbt.
Unmittelbar unter der Thread -ID in der Anzeige befindet sich der Name der JNI -API -Methode. Methodennamen übereinstimmen genau mit denen, die in der Header -Datei jni.h zu sehen sind.
Nachfolgende Zeilen enthalten eine Liste von Argumenten, die durch a |- angegeben sind. Nach den |- Zeichen sind der Argumentyp, gefolgt vom Argumentwert. Für JMethods, JFields und Jclasses wird der Java -Typ in lockigen Zahnspangen angezeigt. Dies hängt davon ab, dass jnitrace die ursprüngliche Methode, das Feld oder die Klassen -Lookup gesehen hat. Bei allen Methoden, die Puffer übergeben, extrahiert jnitrace die Puffer aus den Argumenten und zeigt sie als Hexdump unter dem Argumentwert an.
Die Rückgabeteile werden unten in der Liste als |= angezeigt und sind für Hohlraummethoden nicht vorhanden.
Wenn die Backtrace aktiviert ist, wird unter dem Methodenaufruf eine Frida -Backtrace angezeigt. Bitte beachten Sie, dass die Fuzzy -Backtrace gemäß den Frida -Dokumenten nicht immer genau ist und die genaue Backtrace möglicherweise nur begrenzte Ergebnisse liefern.
Ziel dieses Projekts war es, ein Tool zu erstellen, mit dem JNI -API -Anrufe für die meisten Android -Anwendungen effizient nachverfolgen könnten.
Leider überlastet der einfachste Ansatz, an allen Funktionszeiger in der JNIENV -Struktur zu binden, die Anwendung. Es führt zu einem Absturz, der auf der bloßen Anzahl von Funktionsaufrufen anderer nicht verwandter Bibliotheken basiert, die ebenfalls dieselben Funktionen in libart.so verwenden.
Um mit dieser Leistungsbarriere umzugehen, erstellt jnitrace einen Schatten Jnienv, den es den Bibliotheken liefern kann, die es verfolgen möchte. Dieser JNIENV enthält eine Reihe von Funktionen Trampolinen, die die JNI -API -Aufrufe durch einige benutzerdefinierte Frida Nativecallbacks abprallen, um den Eingang und die Ausgabe dieser Funktionen zu verfolgen.
Die generische Frida -API bietet eine hervorragende Arbeit, um eine Plattform für den Aufbau dieser Funktionen Trampoline mit minimalem Aufwand zu bieten. Dieser einfache Ansatz funktioniert jedoch nicht für die gesamte Jnienv -API. Das Hauptproblem bei der Verfolgung aller Methoden ist die Verwendung von variadischen Argumenten in der API. Es ist nicht möglich, den NATiveCallback für diese Funktionen im Voraus zu erstellen, da er vorher nicht alle verschiedenen Kombinationen von Java -Methoden bekannt ist, die aufgerufen werden.
Die Lösung besteht darin, den Prozess für Aufrufe zum GetMethodID zu überwachen oder GetStaticMethodID zu überwinden, der zur Suche nach Methodenkennern aus der Laufzeit verwendet wird. Sobald jnitrace eine jmethodID -Lookup sieht, gibt es eine bekannte Zuordnung von ID zur Methodensignatur. Später, wenn ein JNI -Java -Methodenaufruf getätigt wird, wird ein anfänglicher Nativcallback verwendet, um die Methoden -ID im Aufruf zu extrahieren. Diese Methodensignatur wird dann analysiert, um die Methodenargumente zu extrahieren. Sobald jnitrace die Argumente in der Methode extrahiert hat, kann es dynamisch einen NATiveCallback für diese Methode erstellen. Dieser neue NATiveCallback wird zurückgegeben und ein bisschen architekturspezifische Shellcode befasst sich mit der Einrichtung des Stacks und der Register, damit dieser Aufruf erfolgreich ausgeführt wird. Diese NATiveCallbacks für bestimmte Methoden werden zwischengespeichert, damit der Rückruf effizienter ausgeführt wird, wenn eine Methode mehrfach aufgerufen wird.
Der andere Ort, an dem ein einfacher Nativcallback nicht ausreicht, um die Argumente aus einem Methodenaufruf zu extrahieren, ist für Anrufe mit einem VA_ARGS -Zeiger als endgültiges Argument. In diesem Fall verwendet jnitrace einen Code, um die Argumente aus dem angegebenen Zeiger zu extrahieren. Auch dies ist Architekturspezifisch.
Alle in diesen Funktionsaufrufen nachgegebenen Daten werden an die Python -Konsolenanwendung gesendet, die sie formatiert und dem Benutzer anzeigt.
Die meisten Tests dieses Tools wurden an einem Android X86_64 -Emulator durchgeführt, das Marshmallow ausführt. Alle Probleme, die auf einem anderen Gerät ausgeführt wurden, stellen Sie bitte ein Problem ein, aber wenn möglich, wird empfohlen, auf einem ähnlichen Emulator auszuführen.
Für alle Probleme, die mit jnitrace ausgeführt wurden, erstellen Sie bitte ein Problem auf GitHub. Bitte geben Sie die folgenden Informationen in die eingereichte Ausgabe ein: