Schnittstellen mit Frida exportieren

Mit dieser Funktion können Sie von Frida RPC exportierte Methoden als FIRERPA-Schnittstellenerweiterungen aufrufen. Durch das Schreiben Ihres eigenen funktionalen Hook-Codes können Sie die ultimative Kontrolle über die APP erlangen. Diese Funktion erfordert, dass Sie mit dem Schreiben von Frida-Hook-Skripten vertraut sind. Wenn Sie bereits über Skripte verfügen, müssen Sie diese geringfügig anpassen, um unsere spezifischen Methoden zu verwenden.

Achtung

Ab Version 9.0 erfordert die von uns verwendete integrierte Frida-Version 17.x, dass Sie `frida-java-bridge` selbst in das Skript packen. Andernfalls treten Fehler wie `Java not defined` auf. Diese Änderung ist eine offizielle Änderung von Frida. Gemäß den offiziellen Änderungshinweisen müssen Sie ein neues Node.js-Projekt erstellen und die Java-Bridge importieren. Details finden Sie unter https://github.com/oleavr/frida-agent-example oder verwenden Sie unser bereitgestelltes Skript `tools/frida_script_generate.py`, um das ursprüngliche JS-Skript neu zu verpacken.

Schreiben des Export-Skripts

Sie müssen Skripte in einem bestimmten Format schreiben. Die Namen der exportierten Funktionen müssen den Namenskonventionen folgen, insbesondere camelCase mit einem kleinen Anfangsbuchstaben. Für definitorische Namen wie HTTP darf der Methodenname nicht in Großbuchstaben geschrieben werden. Zum Beispiel sollte der Name sendHTTPRequest im Export-Skript als sendHttpRequest und nicht mit dem großgeschriebenen HTTP geschrieben werden. Nachfolgend finden Sie die allgemeine Struktur, der ein Skript folgen sollte.

Java.perform(function() {
const String = Java.use("java.lang.String")
rpc.exports.exampleFunc1 =  function (a, b) {
        return performRpcJVMCall(function() {
                // Auf dem JVM-Thread ausführen
                return String.$new("Execute on JVM thread:" + a + b).toString()
        })
}
rpc.exports.exampleFunc2 = function (a, b) {
        return performRpcJVMCallOnMain(function() {
                // Auf dem Haupt-UI-Thread ausführen
                return String.$new("Execute on Main UI thread:" + a + b).toString()
        })
}
rpc.exports.exampleFunc3 = function (a, b) {
        return performRpcCall(function() {
                // Normalen JS-Code ausführen
                return a + b
        })
}})

Im obigen Beispielskript sehen Sie drei Methodendefinitionen. Der Codeblock mit dem Muster return performRpc ist zwingend erforderlich, da er sicherstellt, dass Ihr Wert korrekt zurückgegeben wird. Die Bedeutungen dieser drei verschiedenen performRpc-Aufruf-Codeblöcke sind wie folgt:

return performRpcJVMCall(function() {
        // Auf dem JVM-Thread ausführen
})

Der Codeblock performRpcJVMCall bedeutet, dass Ihr Code in der JVM ausgeführt wird. Innerhalb des Blocks können Sie JVM-bezogene Funktionen verwenden, wie z.B. Java.use oder andere Operationen, die die Java-Ebene der Anwendung betreffen.

return performRpcJVMCallOnMain(function() {
        // Auf dem UI-Thread ausführen
})

Der Codeblock performRpcJVMCallOnMain bedeutet, dass Ihr Code in der JVM und im Haupt-UI-Thread ausgeführt wird. Für Operationen, die die Benutzeroberfläche oder den Haupt-Thread betreffen, müssen Sie diese in diesem Block ausführen, um erfolgreich zu sein. Gleichzeitig müssen Sie darauf achten, dass Ihr Code die Ausführung des Haupt-Threads nicht blockiert, da dies dazu führen kann, dass die Anwendung nicht mehr reagiert oder sogar abstürzt.

return performRpcCall(function() {
        // Normalen JS-Code ausführen
})

Im Codeblock performRpcCall können Sie nur einfachen JS-Code ausführen; die Logik der Java-Ebene kann hier nicht verwendet werden.

Achtung

Möglicherweise müssen Sie den gesamten Codeblock weiterhin mit `Java.perform` umschließen, um sicherzustellen, dass die Java-Logik korrekt ausgeführt wird.

Nachdem Sie nun die grundlegende Logik zum Schreiben unseres Codes verstanden haben, können Sie Ihren Code an dieses Format anpassen. Wenn Sie es nur testen möchten, können Sie diesen Code auch direkt kopieren, um loszulegen. Wir werden diesen Hook-Code auch für die nachfolgende Erklärung verwenden.

Injizieren des Export-Skripts

Die Methode zum Injizieren des Export-Skripts ist hier tatsächlich dieselbe wie im Kapitel Persistente Frida-Skripte.

app = d.application("com.android.settings")
app.attach_script(script, runtime=ScriptRuntime.RUNTIME_QJS, standup=5)

Nach dem Aufruf der Injektionsschnittstelle sollte Ihr Skript injiziert sein. Sie können den folgenden Aufruf ausführen, um zu überprüfen, ob das Skript ordnungsgemäß funktioniert.

app.is_script_alive()

Aufrufen exportierter Methoden

Die Art und Weise, wie exportierte Methoden innerhalb des Skripts aufgerufen werden, ist der nativen Verwendung von FIRERPA-Schnittstellen sehr ähnlich. Bitte sehen Sie sich den Beispielcode an.

app = d.application("com.android.settings")
app.exampleFunc1("FIRE", "RPA")

Für Apps mit mehreren Instanzen (Multi-Instanz-Apps) müssen Sie die Instanz der jeweiligen App abrufen.

app = d.application("com.android.settings", user=UID)
app.exampleFunc1("FIRE", "RPA")

Die obige Aufrufmethode ist äquivalent zur nachfolgenden. Sie können feststellen, dass Func1 in der Methode zu _func1 wird. Beide Schreibweisen sind zulässig.

app.example_func1("FIRE", "RPA")
>>> app.example_func1("FIRE", "RPA")
'Hello World:FIRERPA'
>>> app.example_func2("FRI", "DA")
'Hello World:FRIDA'
>>> app.example_func3("FIRE", "RPA")
'FIRERPA'

Aufrufen exportierter Methoden (HTTP-Schnittstelle)

Neben der Unterstützung von clientseitigen Aufrufen unterstützen wir auch Aufrufe über HTTP. Sie können es im folgenden Format verwenden.

Tipp

Ab Version 8.15 unterstützen exportierte Methoden auch den Aufruf über das JSON-RPC 2.0-Protokoll (der MultiCall-Modus wird noch nicht unterstützt).

Sie können jsonrpclib wie unten gezeigt direkt für Aufrufe verwenden. Wenn Sie mit dem JSON-RPC 2.0-Protokoll vertraut sind, können Sie auch Ihren eigenen Code schreiben, um Anfragen zu stellen. Dies ist die standardisierteste Protokollimplementierung mit der umfassendsten verfügbaren Dokumentation.

import jsonrpclib
server = jsonrpclib.Server('http://192.168.0.2:65000/script/com.android.settings/0')
server.example_func1("FIRE", "RPA")

Natürlich können Sie auch weiterhin das folgende, frühere Aufrufprotokoll verwenden, das relativ einfach, aber weniger standardisiert ist.

import json
import requests
url = "http://192.168.0.2:65000/script/com.android.settings/exampleFunc1?user=0"
res = requests.post(url, data={"args": json.dumps(["FIRE", "RPA"])})
print (res.status_code, res.json()["result"])

Der Abfrageparameter user im Link ist die UID der App-Instanz. Der Standardwert ist 0 und muss nicht angegeben werden. Handelt es sich um eine App mit mehreren Instanzen, muss hier die UID angegeben werden.

Der obige Code ruft die exportierte Schnittstelle des Skripts über HTTP auf. com.android.settings ist der Paketname der Anwendung und exampleFunc1 ist der Name der exportierten Funktion. Der Anfrageparameter args muss mit json.dumps serialisiert werden. Die Parameterliste kann auch mehrere Parameter enthalten, abhängig von der Anzahl der Parameter Ihrer Methode. Die Angabe einer leeren Liste [] bedeutet, dass die exportierte Funktion keine Parameter hat.

Wenn für Ihre FIRERPA-Instanz Schnittstellenzertifikate aktiviert sind, müssen Sie über HTTPS zugreifen und das Zertifikatspasswort angeben.

headers = {"X-Token": "Zertifikatspasswort"}
res = requests.post(url, data={"args": json.dumps(["LAM", "DA"])}, headers=headers, verify=False)
print (res.status_code, res.json()["result"])

HTTP-Statuscodes

Beim Aufruf über die HTTP-Schnittstelle gibt es spezifische Statuscodes. Möglicherweise müssen Sie anhand dieser Codes Entscheidungen treffen. Die Statuscodes und ihre Bedeutungen sind wie folgt:

StatuscodeBeschreibung des Statuscodes
200Alles in Ordnung
410Skript nicht injiziert oder nicht installiert
500Skript- oder Parameterfehler
400Fehlerhafte Parameter

Fehlerbehebung

Wenn beim Aufruf der Schnittstelle über die API oder HTTP Probleme wie Einfrieren oder Zeitüberschreitungen auftreten, liegt dies höchstwahrscheinlich daran, dass Ihre Anwendung im Hintergrund läuft und vom System zwangsweise in den Ruhezustand versetzt wurde. Daher müssen Sie die APP möglicherweise ständig im Vordergrund ausführen.