Перенаправление функций в native-библиотеках на Android

от автора

В данной статье я немного расскажу о том, как c помощью фреймворка AndHook можно перенаправлять вызовы функций в native-библиотеках. Можно перехватывать вызовы как публичных (экспортируемых функций), так и непубличные, напрямую по их адресу. Подробнее о перенаправлении можно почитать тут, и на странице фреймворка.

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

В данной статья я буду использовать Visual Studio и BatchApkTool для Windows. Вместо Visual Studio можно использовать Android Studio, или вообще компилировать через gcc или clang (вариант для продвинутых). Вместо BatchApkTool можно использовать apktool. Подробно по поводу работы с BatchApkTool я останавливаться не буду, т.к. разобраться с работой в программе не трудно.

Для начала нужно разобрать АПК с помощью BatchApkTool или apktool.

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

Настройка Visual Studio для работы с NDK

В программе Visual Studio Installer должны быть установлены следущие компоненты:


Остальные по желанию.

Cоздание нового проекта в Visual Studio

Обратите внимание на содержание спойлера выше, если не выполнить предыдущий этап, то либо не будет нужного шаблона, либо решение не скомпилируется.

Нужно выбрать шаблон "Динамическая общая библиотека (Android)", и затем ввести название.

Выглядит примерно так

После того как проект будет создан нужно его настроить для этого нужно щелкнуть правой кнопкой мыши вот сюда:

и открыть свойства. В свойствах проекта выбираем Конфигурация: Все конфигурации, Платформа: <Ваша целевоя платформа>, Целевой уровень API можно выбрать 14 для ARM и X86, а для ARM64 и X64 уже нужен 21 уровень.
Также по желанию можно поменять компилятор.
Затем во вкладке Компановщик->Ввод в поле дополнительные зависимости нужно указать путь к скачанной нами библиотке и библиотеке, функции которой мы хотим перехватывать:

Пример


Обратите внимание на то, что мы можем делать один проект для нескольких платформ, поэтому, если вы собироаетесь компилировать для нескольких платформ, то нужно переключить на другую платформу и выбрать библиотеку для другой платформы.

Теперь нужно добавить заголовочный файл. Нужно перейти в папку с решением.

Как это сделать

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

Или так

Затем нужно подключит заголовочный файл, добавив в начало нашего .cpp файла строку

#include "AndHook.h"

Создадим фунцию JNI_OnLoad, в котором мы будем регистрировать наши хуки

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {     JNIEnv* env = NULL;     jint result = -1;     LOGD("JNI_OnLoad start!");      if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) {         LOGE("JNI_OnLoad! GetEnv failed");         return -1;     }      RegisterHooks();      result = JNI_VERSION_1_4;     LOGD("JNI_OnLoad! finished!");     return result; }

Непосредственно сама функция RegisterHooks:

// Для определения адреса удобно использовать директиву #define #ifdef __arm__ #define Test_Offset 0x1234 #elif __aarch64__ #define Test_Offset 0x1235 #elif __i386__ #define Test_Offset 0x1236 #elif __x86_64__ #define Test_Offset 0x1237 #endif void (*Old_Test)(); void Test() {     LOGD("Test!");     // Do something     Old_Test(); } void (*Old_Test2)(); void Test2() {     LOGD("Test2!");     Old_Test2(); // Здесь мы вызываем оригинальную функцию Test2 } void RegisterHooks() {     const void* libil2cpp = AKGetImageByName("libil2cpp.so"); // Тут указываем имя целевой библиотеки.     if (libil2cpp == NULL) {         LOGW("AKGetImageByName return null!");         return;     }      // Перехват непубличной функции по ее адресу     // Для реверс-инженеров адрес можно получить в IDA/Ghidra     // В случае с il2cpp-игрой адрес можно получить программой https://github.com/Perfare/Il2CppDumper     AKHookFunction(AKFindAnonymity(libil2cpp, Test_Offset), reinterpret_cast<void*>(&Test), reinterpret_cast<void**>(&Old_Test));     // Здесь мы ищем функцию по адресу Test_Offset в файле libil2cpp и заменяем ее на нашу функцию Test, объявленную выше. Старая функция будет доступна по указателю Old_Test.      // Перехват публичной функции     AKHookFunction(AKFindSymbol(libil2cpp, "Test2"), reinterpret_cast<void*>(&Test2), reinterpret_cast<void**>(&Old_Test2));      // Также мы можем вызывать непубличные функции не перехватывая их:     void (*Test3)();     Test3 = reinterpret_cast<void (*)()>(AKFindAnonymity(libil2cpp, 0x12345));     Test3();      // Еще мы можим патчить оригинальную библиотеку     const uint8_t data[] = { 0x00, 0xF0, 0x20, 0xE3 }; // Инструкция NOP     AKPatchMemory(reinterpret_cast<const void*>(AKFindAnonymity(libil2cpp, 0x123456)), reinterpret_cast<const void*>(&data), 4);     // Здесь мы патчим инструкцию по адресу 0x123456      AKCloseImage(libil2cpp); }

Также для патчинга под несколько архитектур удобно использовать #define

Пример

#ifdef __arm__ #define Patch_Offset 0x1234 #define Patch_Data { 0x00, 0xF0, 0x20, 0xE3 } #elif __aarch64__ #define Patch_Offset 0x1234 #define Patch_Data { 0x1F, 0x20, 0x03, 0xD5 } #endif  const uint8_t data[] = Patch_Data; const size_t len = sizeof(data) / sizeof(uint8_t); AKPatchMemory(reinterpret_cast<const void*>(AKFindAnonymity(libil2cpp, Patch_Offset)), reinterpret_cast<const void*>(&data), len);

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

Выбрать целевую платформу

Собрать проект

Если платформ несколько повторить пункты 1-2.

Теперь необходимо скопировать нашу библиотеку и библиотеку AndHook в папку lib нашего приложения для каждой из архитектур. (Скомпилированнаяя библиотека будет в папке с решением).

Теперь для того чтобы наша библиотека загружалась нужно отредактировать приложение. Для этого нужно найти MainActivity нашего приложения. В начало метода onCreate в MainActivity добавим следующие строки:

const-string v0, "SharedObject1" invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

Где вместо SharedObject1 имя вашей библиотеки без lib и .so
Для il2cpp-игр вместо MainActivity добавляем в метод onCreate в файле smali/com/unity3d/player/UnityPlayerActivity.smali.

Далее рекомпилируем приложение.

Полезные ссылки:
https://github.com/asLody/AndHook/
http://armconverter.com/
http://armconverter.com/hextoarm/

ссылка на оригинал статьи https://habr.com/ru/post/496316/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *