# Экспорт интерфейсов с помощью Frida

Эта функция позволяет вам экспортировать методы, определённые через Frida RPC, и вызывать их как расширения интерфейса FIRERPA. Вы можете написать собственный код для хуков, чтобы достичь максимального контроля над приложением. Эта функция требует знакомства с написанием скриптов-хуков для Frida. Если у вас уже есть готовые скрипты, вам потребуется немного их изменить, используя наши специальные методы.

```{attention}
Начиная с версии 9.0, встроенная версия Frida 17.x требует самостоятельной упаковки `frida-java-bridge` в скрипт, иначе возникнут ошибки, связанные с `Java not defined`. Это изменение было внесено официально командой Frida. Согласно их инструкциям, вам необходимо создать проект Node.js и подключить java bridge. Для получения подробной информации обратитесь к https://github.com/oleavr/frida-agent-example или используйте наш инструмент `tools/frida_script_generate.py` для повторной упаковки ваших существующих js-скриптов.
```

## Написание скрипта для экспорта

Вам необходимо написать скрипт в определённом формате. Имена экспортируемых функций должны соответствовать соглашению об именовании camelCase, то есть начинаться с маленькой буквы. Для определительных имён, таких как HTTP, в названии метода нельзя использовать полностью заглавные буквы. Например, имя `sendHTTPRequest` в скрипте экспорта должно быть записано как `sendHttpRequest`, а не с использованием заглавных букв HTTP. Ниже приведена общая структура, которой должен следовать скрипт.

```js
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 имеют следующее значение:

```js
return performRpcJVMCall(function() {
        // Выполняется в потоке JVM
})
```

Блок кода `performRpcJVMCall` означает, что ваш код будет выполняться в JVM. Внутри этого блока вы можете использовать функции, связанные с JVM, например, `Java.use` или другие операции, затрагивающие Java-уровень приложения.

```js
return performRpcJVMCallOnMain(function() {
        // Выполняется в UI-потоке
})
```

Блок кода `performRpcJVMCallOnMain` означает, что ваш код будет выполняться в JVM и в основном UI-потоке. Операции, связанные с UI или основным потоком, должны выполняться внутри этого блока для успешного завершения. При этом необходимо следить, чтобы ваш код не блокировал выполнение основного потока, иначе это может привести к зависанию или сбою приложения.

```js
return performRpcCall(function() {
        // Выполняется обычный JS-код
})
```

В блоке кода `performRpcCall` вы можете выполнять только базовый JS-код и не можете использовать логику Java-уровня.

```{attention}
Вам все еще может потребоваться обернуть весь блок кода в `Java.perform`, чтобы обеспечить корректное выполнение Java-логики.
```

Теперь, когда вы ознакомились с основной логикой написания нашего кода, вы можете адаптировать свой код под этот формат. Если вы просто хотите протестировать, вы можете скопировать этот код, чтобы начать. В дальнейшем мы будем использовать этот же код хука для объяснений.

## Внедрение скрипта для экспорта

Метод внедрения скрипта для экспорта здесь точно такой же, как и в разделе `Постоянные скрипты Frida`.

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

После вызова интерфейса для внедрения ваш скрипт должен быть внедрён. Вы можете выполнить следующий вызов, чтобы проверить, работает ли скрипт нормально.

```python
app.is_script_alive()
```

## Вызов экспортированных методов

Способ вызова экспортированных методов из скрипта очень похож на использование нативных интерфейсов FIRERPA. Пожалуйста, посмотрите на пример кода.

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

Для клонированных приложений вам необходимо получить экземпляр клонированного приложения.

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

Приведённый выше вызов эквивалентен следующему. Вы можете заметить, что `Func1` в названии метода изменилось на `_func1`. Оба варианта допустимы.

```python
app.example_func1("FIRE", "RPA")
```

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

## Вызов экспортированных методов (через HTTP-интерфейс)

Помимо поддержки вызовов через клиент, мы также поддерживаем вызовы через HTTP. Вы можете использовать следующий формат.

```{tip}
Начиная с версии 8.15, экспортированные методы также поддерживают вызовы по протоколу jsonrpc 2.0 (режим MultiCall пока не поддерживается).
```

Вы можете напрямую использовать `jsonrpclib` для вызовов, как показано ниже. Или, если вы знакомы с протоколом JsonRPC 2.0, вы можете написать свой собственный код для запросов. Это наиболее стандартизированная реализация протокола с самой полной документацией.

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

Конечно, вы все еще можете продолжать использовать предыдущий протокол вызова, который относительно прост, но менее стандартизирован.

```python
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` в ссылке — это UID клонированного приложения. По умолчанию он равен 0 и его можно не указывать. Если это клонированное приложение, здесь необходимо указать UID.

Приведённый выше код вызывает экспортированный интерфейс скрипта через HTTP. `com.android.settings` — это имя пакета приложения, а `exampleFunc1` — имя экспортированной функции. Параметр запроса `args` должен быть сериализован с помощью `json.dumps`. Список параметров может содержать несколько аргументов в зависимости от количества параметров вашего метода. Пустой список `[]` означает, что экспортированная функция не имеет параметров.

Если в вашем FIRERPA включена аутентификация по сертификату, вам необходимо использовать https для доступа и предоставить пароль сертификата.

```python
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-интерфейс используются определённые коды состояния, которые могут понадобиться вам для проверок. Коды и их значения следующие:

| Код состояния | Описание |
| ----------- | ----------- |
| 200 | Все в порядке |
| 410 | Скрипт не внедрен или не установлен |
| 500 | Ошибка скрипта или параметров |
| 400 | Неверные параметры |


## Устранение неполадок

Если при вызове интерфейса через API или HTTP вы сталкиваетесь с зависанием или тайм-аутом, скорее всего, это связано с тем, что ваше приложение находится в фоновом режиме и было принудительно приостановлено системой. Поэтому вам может потребоваться обеспечить, чтобы приложение всегда работало на переднем плане.