Игривый Xamarin. Изучаем и взламываем мобильное приложение на С#

Игривый 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 я исполь­зую три инс­тру­мен­та.

  1. dotPeek от JetBrains. Поз­воля­ет деком­пилиро­вать и иссле­довать фай­лы .dll и .exe. У дан­ного про­дук­та самая, на мой взгляд, удоб­ная навига­ция по деком­пилиро­ван­ному коду, так что, если говорить толь­ко об иссле­дова­нии алго­рит­ма, этот инс­тру­мент самый удоб­ный.
Глав­ное окно dotPeek

dnSpy — поз­воля­ет деком­пилиро­вать, редак­тировать, ком­пилиро­вать и отла­живать сбор­ки .Net. Сле­дует отме­тить, что все фун­кции, кро­ме деком­пиляции, работа­ют далеко не всег­да, мно­гое зависит от кон­крет­ной ситу­ации. В моем слу­чае ком­пиляция не зарабо­тала: соф­тина не смог­ла свя­зать прос­транс­тва имен Mono и Android.

Глав­ное окно dnSpy

Simple-assembly-explorer — дос­таточ­но ста­рый, но от это­го не утра­тив­ший акту­аль­нос­ти софт. Поз­воля­ет деком­пилиро­вать .dll и .exe в код на С# или CIL (Common Intermediate Language — «высоко­уров­невый ассем­блер» вир­туаль­ной машины .NET. Про­межу­точ­ный язык, раз­работан­ный фир­мой Microsoft для плат­формы .NET Framework). Самая полез­ная воз­можность — этот инс­тру­мент уме­ет ком­пилиро­вать код на CIL, что поз­воля­ет без осо­бого тру­да вно­сить изме­нения в иссле­дуемые фай­лы.

Глав­ное окно Simple assembly explorer

Для рас­паков­ки и запаков­ки 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

Тут есть два пути.

  1. По­дой­ти к делу осно­ватель­но, фун­дамен­таль­но, изу­чить язык CIL и написать необ­ходимый код самос­тоятель­но.
  2. На­писать нуж­ную фун­кцию на 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.

Источник


Report Page