В данной статье я немного расскажу о том, как c помощью фреймворка AndHook можно перенаправлять вызовы функций в native-библиотеках. Можно перехватывать вызовы как публичных (экспортируемых функций), так и непубличные, напрямую по их адресу. Подробнее о перенаправлении можно почитать тут, и на странице фреймворка.
В качестве примера будет рассмотрен случай с внедрением своей библиотеки. Однако данный фреймворк также позволяет работать и без пересбора приложения с помощью xposed.
В данной статья я буду использовать Visual Studio и BatchApkTool для Windows. Вместо Visual Studio можно использовать Android Studio, или вообще компилировать через gcc или clang (вариант для продвинутых). Вместо BatchApkTool можно использовать apktool. Подробно по поводу работы с BatchApkTool я останавливаться не буду, т.к. разобраться с работой в программе не трудно.
Для начала нужно разобрать АПК с помощью BatchApkTool или apktool.
Теперь скачаем библиотеку и заголовочный файл со страницы проекта, для этого нужно перейти по этой ссылке и этой ссылке и скачать библиотеку для вашей платформы. (Я не нашел чем отличаются версия Compat от обычной, лично я использовал обычную версию. Возможно, подскажут в комментариях)
В программе Visual Studio Installer должны быть установлены следущие компоненты:
Остальные по желанию.
Обратите внимание на содержание спойлера выше, если не выполнить предыдущий этап, то либо не будет нужного шаблона, либо решение не скомпилируется.
Нужно выбрать шаблон "Динамическая общая библиотека (Android)", и затем ввести название.
После того как проект будет создан нужно его настроить для этого нужно щелкнуть правой кнопкой мыши вот сюда:
и открыть свойства. В свойствах проекта выбираем Конфигурация: Все конфигурации, Платформа: <Ваша целевоя платформа>, Целевой уровень API можно выбрать 14 для ARM и X86, а для ARM64 и X64 уже нужен 21 уровень.
Также по желанию можно поменять компилятор.
Затем во вкладке Компановщик->Ввод в поле дополнительные зависимости нужно указать путь к скачанной нами библиотке и библиотеке, функции которой мы хотим перехватывать:
Обратите внимание на то, что мы можем делать один проект для нескольких платформ, поэтому, если вы собироаетесь компилировать для нескольких платформ, то нужно переключить на другую платформу и выбрать библиотеку для другой платформы.
Теперь нужно добавить заголовочный файл. Нужно перейти в папку с решением.
Теперь нужно скопировать скачанный ранее заголовочный файл в эту папку и добавить его в проект. Для того чтобы добавить файл в проект можно перетащить его из папки на обозреватель решений.
Затем нужно подключит заголовочный файл, добавив в начало нашего .cpp файла строку
#include "AndHook.h"
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/