Игривый Xamarin. Изучаем и взламываем мобильное приложение на С#
Life-Hack [Жизнь-Взлом]/ХакингПрограммировать для Android можно не только на Java или Kotlin. Разработчики на С# имеют возможность создавать мобильные приложения с помощью платформы Xamarin. Сегодня мы поговорим о том, как исследовать такие приложения и как при необходимости их можно взломать.
Попалось мне недавно в руки мобильное приложение для Android, которое работало не совсем так, как хотелось бы. Значит, нужно хорошенько покопаться в его потрошках!
Сказано — сделано: берем свежую версию GDA, открываем наш подопытный APK и видим, что выглядит он как‑то уж слишком подозрительно. Классы всех activity содержат примерно одинаковый шаблонный код такого типа:
public class MainActivity extends BaseActivity
{
private ArrayList refList;
public static final String __md_methods;
static {
MainActivity.__md_methods = "n_onCreate:\(Landroid/os/Bundle;\)V:GetOnCreate_Landroid_os_Bundle_Handler
.....
_ILandroid_os_Bundle_Handler:Android.Locations.ILocationListenerInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\n";
Runtime.register("Megaprogram.Activity.MainActivity, Megaprogram", MainActivity.class, MainActivity.__md_methods);
}
public void MainActivity(){
super();
if (this.getClass() == MainActivity.class) {
Object[] objArray = new Object[0];
TypeManager.Activate("Megaprogram.Activity.MainActivity, Megaprogram", "", this, objArray);
}
return;
Это наводит на мысли, что нам попался неправильный APK. Возможно, какой‑то фреймворк... Если поменять расширение .apk
на .zip
, то в глаза бросается необычная для обычных мобильных приложений папка assemblies
, содержащая кучу DLL-библиотек.
Поскольку многие библиотеки содержат в названии слово Xamarin, становится понятным, откуда взялось такое однообразие — основной код программы написан на С# и располагается в библиотеке DLL, а на Java написаны лишь шаблонные куски кода, предназначенные для связи между средой выполнения Mono и виртуальной машиной среды выполнения Android (ART).
Ну да ладно, в сторону теорию, пора браться за дело.
ВЫБИРАЕМ ИНСТРУМЕНТ
Для работы с .Net я использую три инструмента.
- dotPeek от JetBrains. Позволяет декомпилировать и исследовать файлы
.dll
и.exe
. У данного продукта самая, на мой взгляд, удобная навигация по декомпилированному коду, так что, если говорить только об исследовании алгоритма, этот инструмент самый удобный.
dnSpy — позволяет декомпилировать, редактировать, компилировать и отлаживать сборки .Net. Следует отметить, что все функции, кроме декомпиляции, работают далеко не всегда, многое зависит от конкретной ситуации. В моем случае компиляция не заработала: софтина не смогла связать пространства имен Mono и Android.
Simple-assembly-explorer — достаточно старый, но от этого не утративший актуальности софт. Позволяет декомпилировать .dll
и .exe
в код на С# или CIL (Common Intermediate Language — «высокоуровневый ассемблер» виртуальной машины .NET. Промежуточный язык, разработанный фирмой Microsoft для платформы .NET Framework). Самая полезная возможность — этот инструмент умеет компилировать код на CIL, что позволяет без особого труда вносить изменения в исследуемые файлы.
Для распаковки и запаковки APK я решил использовать 7z вместо стандартного для таких случаев apktool. Ниже я объясню почему.
РАЗБИРАЕМ APK
Сначала я попытался использовать для распаковки APK известную программу apktool, но при запаковке возникла проблема из‑за того, что apktool «не знает» такой тип файлов, как .dll
, не считает его стандартным для APK. Стандартными считаются только файлы с именами из следующего массива:
private final static String[] APK_STANDARD_ALL_FILENAMES = new String[] {
"classes.dex", "AndroidManifest.xml", "resources.arsc", "res", "lib", "libs", "assets", "META-INF" };.
При сборке APK все неизвестные файлы сжимаются (тип сжатия DEFLATED), а Xamarin надеется увидеть свои DLL несжатыми (STORED) и от разочарования не может нормально прочитать их. Сначала возникла мысль исправить и пересобрать apktool, но потом я решил поступить проще: распаковывать и запаковывать файлы обычным архиватором, без сжатия. Ведь декодирование манифеста или получение smali-кода мне в данной задаче не требуется, а зачем тогда усложнять себе жизнь без необходимости?
Итак, распаковываем:
7z.exe x program.apk -oprogram_apk
После распаковки, помимо привычных для APK файлов, получаем каталог assemblies
с кучей dll
. Из них нас интересует одна библиотека, имя которой совпадает с именем приложения. Ее‑то мы и будем потрошить.
ПАТЧИМ .NET
Анализ DLL и поиск места для внесения правок выходит за рамки сегодняшней статьи, так как мыслям на эту тему будет тесно даже в книге. Остановлюсь лишь на технических моментах. Как я писал выше, dnSpy отказался компилировать исправленную библиотеку, поэтому пришлось прибегнуть к помощи Simple-assembly-explorer (SAE).
Допустим, нам необходимо, чтобы данная функция всегда возвращала true
:
protected bool IsOnline()
{
ConnectivityManager connectivityManager = (ConnectivityManager)this.GetSystemService#0x0a0001d5("connectivity");
if (connectivityManager == null)
{
return false;
}
NetworkInfo activeNetworkInfo = connectivityManager.get_ActiveNetworkInfo#0x0a0002ad();
return activeNetworkInfo != null && activeNetworkInfo.get_IsConnected#0x0a0002ae();
}
Поскольку правки можно вносить только в IL-код, в окне SAE переключаемся на вкладку Details, где наблюдаем такую картину:
0 L_0000: ldarg.0
1 L_0001: ldstr "connectivity"
2 L_0006: callvirt Java.Lang.Object Android.Content.Context::GetSystemService(System.String)
3 L_000b: castclass Android.Net.ConnectivityManager
4 L_0010: stloc.0
5 L_0011: ldloc.0
6 L_0012: brtrue.s 9 -> ldloc.0
7 L_0014: ldc.i4.0
8 L_0015: ret
9 L_0016: ldloc.0
10 L_0017: callvirt Android.Net.NetworkInfo Android.Net.ConnectivityManager::get_ActiveNetworkInfo()
11 L_001c: stloc.1
12 L_001d: ldloc.1
13 L_001e: brfalse.s 17 -> ldc.i4.0
14 L_0020: ldloc.1
15 L_0021: callvirt System.Boolean Android.Net.NetworkInfo::get_IsConnected()
16 L_0026: ret
17 L_0027: ldc.i4.0
18 L_0028: ret
Тут есть два пути.
- Подойти к делу основательно, фундаментально, изучить язык CIL и написать необходимый код самостоятельно.
- Написать нужную функцию на C# и скомпилировать в CIL, получив тем самым нужный код автоматически.
Я выбрал второй вариант — это гораздо быстрее. Более того, немножко погуглив, можно обнаружить очень полезный сайт sharplab.io, на котором весьма удобно конвертировать код из C# в CIL.
Итак, вводим в левой вкладке следующее:
bool function() {
return true;
}
и справа среди кучи лишнего получаем:
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: stloc.0
IL_0003: br.s IL_0005
IL_0005: ldloc.0
IL_0006: ret
Вставляем полученный код в библиотеку с помощью Simple assembly explorer, не забывая при этом сохранить измененную DLL. Если мы ничего не напутали и нигде не ошиблись, то пора собирать новый APK.
СОБИРАЕМ APK ОБРАТНО
Для сборки, как я уже писал выше, будем использовать 7z в режиме без сжатия. Полученный таким образом APK будет большего размера, чем исходный, ну да размер не главное:
7z.exe a -tzip -mx0 -r0 program_patched_unalign_unsigned.apk .\program_apk\*.*
Небольшое пояснение: -tzip
— формат архива, -mx0
— отсутствие сжатия, -r0
— рекурсивный обход всех подкаталогов.
Да, перед сборкой лучше удалить каталог META-INF
, содержащий старую подпись. Он не нужен, ведь нам придется подписывать APK самостоятельно. Затем нужно создать сертификат для подписи и поместить его в хранилище. Если у тебя уже есть сертификат, то этот шаг можно пропустить. Создаем сертификат с помощью утилиты keytool из состава JDK:
"c:\Android\Android Studio\jre\bin\keytool.exe" -genkey -v -keystore keys.keystore -alias key -keyalg RSA -keysize 2048 -validity 10000
Она задаст стандартные вопросы:
Enter keystore password:
Re-enter new password:
What is your first and last name?
[Unknown]: x
What is the name of your organizational unit?
[Unknown]: x
What is the name of your organization?
[Unknown]: x
What is the name of your City or Locality?
[Unknown]: x
What is the name of your State or Province?
[Unknown]: x
What is the two-letter country code for this unit?
[Unknown]: x
Is CN=x, OU=x, O=x, L=x, ST=x, C=x correct?
[no]: yes
Generating 2 048 bit RSA key pair and self-signed certificate (SHA256withRSA) wi
th a validity of 10 000 days
for: CN=x, OU=x, O=x, L=x, ST=x, C=x
[Storing keys.keystore]
Ну и после этого можно переходить к подписыванию:
jarsigner.exe -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore keys.keystore -signedjar program_pathed_unalign.apk program_pathed_unalign_unsigned.apk key
В результате будет создан почти готовый к установке файл program_pathed_unalign.apk
. «Почти» — потому что перед использованием его следует выровнять программой zipalign из состава build-tools SDK Android. Данная процедура гарантирует, что все несжатые файлы в архиве выровнены относительно начала файла. Это позволяет получить доступ к файлам напрямую, без необходимости копирования данных в ОЗУ, что уменьшит использование памяти твоим приложением.
Итак, ровняем:
zipalign.exe -v 4 program_pathed_unalign.apk program_pathed.apk
После этого можно смело устанавливать программу на телефон или эмулятор и приступать к ее тестированию.
ВЫВОДЫ
Как видишь, Xamarin’овские сборки ничуть не сложнее для анализа, чем родные приложения ОС Android, надо лишь учесть некоторые тонкости при сборке APK.