Экспортируемые интерфейсы Frida¶
С помощью этой функции вы можете вызывать методы, экспортируемые через Frida RPC, как расширение интерфейса FIRERPA. Написав собственные функциональные Hook-скрипты, вы можете получить полный контроль над приложением, экспортировать подписи и т. д. Требуется знание написания скриптов Frida.
Внимание
Начиная с версии 9.0, встроенный Frida 17.x требует, чтобы вы самостоятельно упаковывали frida-java-bridge в скрипт, иначе возникнут ошибки, связанные с Java not defined; это изменение является официальным изменением Frida. Согласно официальным примечаниям к изменениям, вам необходимо создать проект Node.js и подключить frida-java-bridge. Подробнее см.: https://github.com/oleavr/frida-agent-example , либо используйте предоставленный нами pack_frida_script.py для упаковки js-скрипта.
Написание экспортируемого скрипта¶
Вам необходимо написать скрипт определённого формата. Имена экспортируемых функций должны соответствовать соглашению об именовании: необходимо использовать camelCase с первой строчной буквой. Для аббревиатур, таких как HTTP, имя метода не должно записываться полностью заглавными буквами. Например, sendHTTPRequest в экспортируемом скрипте следует записывать как sendHttpRequest. Ниже приведена общая структура, которой должен следовать скрипт.
Java.perform(function() {
const String = Java.use("java.lang.String")
rpc.exports.exampleFunc1 = function (a, b) {
return performRpcJVMCall(function() {
return String.$new("Execute on JVM thread:" + a + b).toString()
})
}
rpc.exports.exampleFunc2 = function (a, b) {
return performRpcJVMCallOnMain(function() {
return String.$new("Execute on Main UI thread:" + a + b).toString()
})
}
rpc.exports.exampleFunc3 = function (a, b) {
return performRpcCall(function() {
return a + b
})
})
В приведённом выше примере скрипта вы видите три определения методов. Блоки кода в стиле return performRpc обязательны, чтобы гарантировать корректный возврат вашего значения. Эти три разных варианта вызова performRpc означают следующее:
return performRpcJVMCall(function() {
// Выполняется в потоке JVM
})
Блок performRpcJVMCall означает выполнение вашего кода в JVM; внутри блока вы можете использовать функции, связанные с JVM, например Java.use или другие операции, затрагивающие Java-слой приложения.
return performRpcJVMCallOnMain(function() {
// Выполняется в UI-потоке
})
Блок performRpcJVMCallOnMain означает выполнение вашего кода в главном UI-потоке JVM. Для операций, связанных с UI или главным потоком, их необходимо выполнять в этом блоке, чтобы они завершились успешно. При этом следите, чтобы ваш код не блокировал главный поток, иначе приложение может перестать отвечать или даже завершиться с ошибкой.
return performRpcCall(function() {
// Выполняется обычный JS-код
})
Блок performRpcCall можно использовать только для выполнения обычного JS-кода; логику Java-слоя в нём использовать нельзя.
Внимание
Вам, вероятно, по-прежнему нужно использовать Java.perform для обёртывания всего блока кода, чтобы обеспечить корректное выполнение Java-логики.
Теперь вы понимаете основную логику написания кода. Вы можете адаптировать свой код, обернув его в этот формат. Если вы просто хотите протестировать, можно напрямую скопировать этот код. Ниже мы также будем объяснять на основе этого Hook-кода.
Внедрение экспортируемого скрипта¶
Метод внедрения экспортируемого скрипта здесь полностью совпадает с методом из раздела Постоянные Frida-скрипты.
app = d.application("com.android.settings")
app.attach_script(script, runtime=ScriptRuntime.RUNTIME_QJS, standup=5)
После вызова интерфейса внедрения скрипт уже внедрён. Вы можете проверить, работает ли скрипт, с помощью следующего вызова.
app.is_script_alive()
Вызов экспортируемых методов¶
Вызов экспортированных методов внутри скрипта очень похож на обычное использование интерфейсов FIRERPA.
app = d.application("com.android.settings")
app.exampleFunc1("FIRE", "RPA")
Для приложений с несколькими экземплярами необходимо получить соответствующий экземпляр приложения:
app = d.application("com.android.settings", user=UID)
app.exampleFunc1("FIRE", "RPA")
Эти два способа вызова эквивалентны. Вы увидите, что Func1 в имени метода превращается в _func1; допустимы оба варианта.
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'
Вызов экспортируемых методов (HTTP-интерфейс)¶
Помимо вызовов из клиента, мы также поддерживаем вызовы по HTTP. Использовать можно следующим образом.
Вы можете использовать jsonrpclib для вызова, как показано ниже; либо, если вы знакомы с протоколом JSON-RPC 2.0, можете самостоятельно написать код для отправки запросов — это наиболее стандартизированная реализация протокола с самой полной документацией.
import jsonrpclib
server = jsonrpclib.Server('http://192.168.0.2:65000/script/com.android.settings/0')
server.example_func1("FIRE", "RPA")
Конечно, вы также можете продолжать использовать прежний протокол вызова — он относительно прост, но менее стандартизирован.
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"])
Параметр запроса user в URL обозначает UID приложения, по умолчанию равен 0. Для приложений с несколькими экземплярами необходимо указывать UID.
Приведённый выше код вызывает экспортированный интерфейс скрипта по HTTP. com.android.settings — это имя пакета приложения, exampleFunc1 — имя экспортируемой функции. Параметр запроса args обязательно должен быть сериализован с помощью json.dumps. Список аргументов поддерживает несколько значений в зависимости от количества параметров метода; передача пустого списка [] означает, что у экспортируемой функции нет аргументов.
Если в FIRERPA включён сертификат интерфейса, необходимо использовать HTTPS и указывать пароль сертификата.
headers = {"X-Token": "пароль сертификата"}
res = requests.post(url, data={"args": json.dumps(["LAM", "DA"])}, headers=headers, verify=False)
print(res.status_code, res.json()["result"])
HTTP-коды состояния¶
При вызове через HTTP-интерфейс возвращаются определённые коды состояния, по которым можно судить о статусе.
Поиск и устранение неполадок¶
Если при вызове интерфейса через API или HTTP происходит зависание или тайм-аут, скорее всего, приложение находится в фоновом режиме и было принудительно переведено системой в спящий режим. Поэтому необходимо, чтобы приложение всегда работало на переднем плане.