Существует большое количество приложений на С# созданы с помощью платформы Xamarin. В этой статье рассмотрим способы анализа и взлома подобных приложений.
Еще по теме: Взлом протектора .NET Reactor
Как взломать приложение на C# созданное в Xamarin
Недавно в мои руки попало интересное мобильное приложение на C# созданное в Xamarin, которое к моему сожалению работало не так, как хотелось бы мне. Хороший повод распотрошить приложение, подумал я.
Статья написана в образовательных целях. Мы не призываем к взлому программ, а пытаемся показать и привлечь внимание разработчиков, к тому, насколько уязвимы могут быть приложения.
Для этого дела отлично подойдет последняя версия GDA.
Итак, открываем APK-файл приложения и видим, что выглядит он как‑то очень странно. Все классы activity имеют примерно одинаковый шаблонный код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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. Утилита может декомпилировать и анализировать файлы .exe и .dll. Утилита имеет самую удобную навигацию по декомпилированному коду, так что, если говорить только об анализе алгоритма, данная тулза самая удобная.
dnSpy — может декомпилировать, редактировать, компилировать и отлаживать сборки .Net. Стоит отметить, что весь функционал, кроме декомпиляции, работает далеко не всегда, все зависит от конкретной ситуации. Лично в моем случае компиляция не хотела работать: dnSpy не смог связать пространства имен Mono и Android.
Simple-assembly-explorer — это довольно древний но от этого не утративший свою актуальности инструмент. Утилита умеет декомпилировать .exe и .dll в код на С# или CIL. Самая важная функция — инструмент может компилировать код на CIL, что дает возможность без труда вносить изменения в анализируемые файлы.
Для распаковки и сборки файла APK я буду использовать архиватор 7z вместо используемого для подобных случаев apktool. Далее я объясню почему.
Распакова APK
В начале я попробовал использовать для распаковки APK популярную софтину apktool, но при сборке появилась загвоздка из‑за того, что apktool «не знает» что такое .dll и не считает его стандартным для файла APK. Стандартными считаются исключительно файлы с именами из следующего массива:
1 2 |
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-кода мне в данном конкретном случае не нужно, а зачем тогда искать на попу приключения?
Итак, начинаем распаковку:
1 |
7z.exe x program.apk -oprogram_apk |
После распаковки, кроме привычных для APK файлов, видим папку assemblies с файлами dll. Из всех этих файлов нас интересует только одна библиотека, название которой совпадает с названием нашего приложения. Ее‑то мы и будем вскрывать и исследовать.
Патчинг .Net
Анализ DLL и поиск места для внесения правок выходит за рамки сегодняшней статьи, так как мыслям на эту тему будет тесно даже в книге. Остановлюсь лишь на технических моментах. Как я писал выше, dnSpy отказался компилировать исправленную библиотеку, поэтому пришлось прибегнуть к помощи Simple-assembly-explorer (SAE).
Допустим, нам нужно, чтобы данная функция всегда возвращала true:
1 2 3 4 5 6 7 8 9 10 |
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, где видим следующую картину:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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.
Итак, вводим в левой вкладке следующее:
1 2 3 |
bool function() { return true; } |
и справа среди кучи лишнего получаем:
1 2 3 4 5 6 |
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 будет большего размера, чем исходный, ну да размер не главное:
1 |
7z.exe a -tzip -mx0 -r0 program_patched_unalign_unsigned.apk .\program_apk\*.* |
Небольшое пояснение:
- -tzip — формат архива,
- -mx0 — отсутствие сжатия,
- -r0 — рекурсивный обход всех подкаталогов
Да, перед сборкой лучше удалить каталог META-INF, содержащий старую подпись. Он не нужен, ведь нам придется подписывать APK самостоятельно. Затем нужно создать сертификат для подписи и поместить его в хранилище.
Если у вас уже есть сертификат, то этот шаг можно пропустить. Создаем сертификат с помощью утилиты keytool из состава JDK:
1 |
"c:\Android\Android Studio\jre\bin\keytool.exe" -genkey -v -keystore keys.keystore -alias key -keyalg RSA -keysize 2048 -validity 10000 |
Она задаст стандартные вопросы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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] |
Ну и после этого можно переходить к подписыванию:
1 |
jarsigner.exe -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore keys.keystore -signedjar program_pathed_unalign.apk program_pathed_unalign_unsigned.apk key |
В результате будет создан почти готовый к установке файл
1 |
program_pathed_unalign.apk |
«Почти» — потому что перед использованием его следует выровнять программой zipalign из состава build-tools SDK Android. Данная процедура гарантирует, что все несжатые файлы в архиве выровнены относительно начала файла.
Это позволяет получить доступ к файлам напрямую, без необходимости копирования данных в ОЗУ, что уменьшит использование памяти вашим приложением.
Итак, ровняем:
1 |
zipalign.exe -v 4 program_pathed_unalign.apk program_pathed.apk |
После этого можно смело устанавливать программу на телефон или эмулятор и приступать к ее тестированию.
Заключение
Как видите, приложениея C# в сборке Xamarin ничуть не сложнее для анализа, чем родные приложения ОС Android, надо лишь учесть некоторые тонкости при сборке APK.
Полезные ссылки: