Атака на приложение. Используем Xposed, чтобы обойти SSLPinning на Android

Есть разные подходы к анализу защищенности приложений, но рано или поздно все упирается в изучение взаимодействия программы с API. Именно этот этап дает больше всего информации о работе приложения, об используемых функциях и собираемых данных. Но что, если приложение защищено SSLPinning и защита реализована на уровне? Давай посмотрим, что можно сделать в этом случае.

Вся информация предоставлена исключительно в ознакомительных целях. Ни редакция, ни автор не несут ответственности за любой возможный вред, причиненный материалами данной статьи.

Прежде чем начать

SSLPinning в мобильных приложениях реализуют, внедряя сертификат SSL в саму программу. При открытии защищенного соединения приложение не обращается в хранилище устройства, а использует свой сертификат. Это сразу же устраняет возможность направить трафик на Burp и анализировать его. Ведь для того, чтобы увидеть трафик SSL, нужно внедрить на устройство Burp CA, который бы подтвердил, что созданный Burp сервер валиден и ему можно доверять.

SSLPinning — это не панацея, очень часто появляются заметки про обход защиты на банковских приложениях или вообще про уязвимости в самом SSL. Но если защита построена правильно, то это создает огромные проблемы для исследователя.

Xposed представляет собой фреймворк, который внедряется в Zygote. Это происходит при старте системы, дальше от Zygote делается .fork(), что копирует Xposed во все запущенные процессы. Сам фреймворк предоставляет возможность внедрить любой код перед функцией и после нее. Можно изменить входящие параметры, заменить функцию, прочитать данные, вызвать внутренние функции и многое другое. На самом деле на описания и демонстрацию всех возможностей Xposed уйдет не одна статья. В общем, если ты раньше с ним не работал, рекомендую ознакомиться.

Для работы Xposed нам понадобится рутованный девайс. Для демонстрации атаки возьмем простое мобильное приложение, которое использует одну из самых часто встречающихся сетевых библиотек. В этом приложении отсутствует защита от SSLUnpinning, так как описанная мной атака не пытается атаковать сертификат и сетевое общение, а нацелена на перехват данных до покрытия их SSL. Для демонстрации серверной стороны атаки и бэкенда мобильного приложения используем быстрое решение в виде Python и Flask. Все исходники ты можешь найти на моем GitHub.

Вернемся к проблеме перехвата трафика SSL. С помощью Xposed можно попытаться отключить проверку сертификата, например «затереть» его для программы. Но предположим, атакуемое приложение хорошо защищено, бэкенд проверяет валидность защиты, детектирует попытки перехвата или проксирования трафика. Что делать в таком случае? Сдаться и заняться другим приложением? А что, если перехватить трафик еще до того, как он станет сетевым?

С этого вопроса началось мое исследование.

В статье я буду работать с Android и Xposed, но подобного результата можно добиться с помощью фреймворка Frida, который доступен и на других ОС.

Сбор информации

Для начала попробуем запустить приложение. Видим на экране кнопку SEND и текстовое приглашение ее нажать. Нажимаем — надпись меняется сначала на «Wait…», а после отображается «Sorry, not today». Скорее всего, отправляется запрос, который не проходит проверку на стороне сервера. Давай посмотрим, что происходит внутри приложения.

Реверс APK

Отреверсим приложение, чтобы понять, какие библиотеки используются внутри.

$ apktool d app-debug.apk 

Открываем проект в Android Studio и смотрим, что есть в smali. Сразу видим okhttp3.

Я специально использовал OkHttp, так как эта библиотека лежит в основе других библиотек для работы с API, например Retrofit 2.

.method protected onCreate(Landroid/os/Bundle;)V  .line 34 iget-object v0, p0, Lcom/loony/mitmdemo/Demo;->sendButton:Landroid/widget/Button;  new-instance v1, Lcom/loony/mitmdemo/Demo$1;  invoke-direct {v1, p0}, Lcom/loony/mitmdemo/Demo$1;-><init>(Lcom/loony/mitmdemo/Demo;)V  invoke-virtual {v0, v1}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V 

Найдем класс Main, в данном случае это com.loony.mitmdemo.Demo. В onCreate видим создание OnClickListener. Выше от него, через v1, передается как аргумент Demo$1. Посмотрим, что реализует этот класс.

.method public onClick(Landroid/view/View;)V  .line 41 .local v0, "sr":Lcom/loony/mitmdemo/Demo$SendRequest; const-string v1, "test"  filled-new-array {v1}, [Ljava/lang/String;  move-result-object v1  invoke-virtual {v0, v1}, Lcom/loony/mitmdemo/Demo$SendRequest;->execute([Ljava/lang/Object;)Landroid/os/AsyncTask; 

В конце функции onClick вызывается асинхронная задача, которая носит очевидное имя SendRequest. Перейдем в com/loony/mitmdemo/Demo$SendRequest. Здесь мы видим множество обращений к okhttp3. Значит, мы не ошиблись в предположении.

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

Посмотрим на стандартное применение okhttp3 в проектах Android.

OkHttpClient client = new OkHttpClient(); RequestBody body = RequestBody.create(JSON, requestJson.toString()); Request request = new Request.Builder()         .url(url)         .post(body)         .build();  Response response = client.newCall(request).execute(); 

Наиболее выгодным здесь будет перехват execute. Почему? Эта функция возвращает Response и, очевидно, отправляет Request. Это значит, что, перехватив эту функцию, мы получим возможность изменить Request до отправки и получить Response до возвращения в основную функцию.

Автор записи: coolsergey