Использование Xposed для обхода SSL Pinning на Android

Использование Xposed для обхода SSL Pinning на Android

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

Еще по теме: Способы обхода SSL Pinning Android

Перед началом

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

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

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

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

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

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

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

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

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

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

Реверс APK

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

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

Я специально использовал OkHttp, так как эта библиотека лежит в основе других библиотек для работы с API, например Retrofit 2.
Найдем класс Main, в данном случае это com.loony.mitmdemo.Demo. В onCreate видим создание OnClickListener. Выше от него, через v1, передается как аргумент Demo$1. Посмотрим, что реализует этот класс.
В конце функции onClick вызывается асинхронная задача, которая носит очевидное имя SendRequest. Перейдем в com/loony/mitmdemo/Demo$SendRequest. Здесь мы видим множество обращений к okhttp3. Значит, мы не ошиблись в предположении.

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

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

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

Атака

Итак, мы знаем, как реализуется общение, у нас есть представление о том, что мы хотим перехватить и что получить. Кроме того, мы хотим как-то просматривать эти данные и иметь возможность их изменить до отправки или получения. Для реализации этого я написал свой API, но можно было пойти дальше и подключиться к Burp API.

Базовый процесс работы с Xposed и создания модулей уже был описан на множестве ресурсов. Если вам интересно, можете ознакомиться со статьей Xposed Development tutorial.

MultiDex

Прежде чем приступить, я хочу показать интересный трюк — перехват функций в приложении с MultiDex. Не зная о нем, я потратил несколько дней впустую. Дело в том, что приложение сразу не загружает весь код в память, вместо этого он разбивается на файлы dex. Если вы пытаетесь перехватить функцию, которая находится во втором или даже в третьем dex, то ее нужно сначала подгрузить в память. Сделать это можно таким образом.

Мы перехватываем запуск приложения и получаем экземпляр ClassLoader. Дальше, перед тем как поставить хук на функцию, нужно загрузить класс, в котором она находится.

Проверка вектора атаки

Для начала посмотрим, что мы не ошиблись с выводами, и добавим хук, который просто что-то выведет в консоль. Здесь мы перехватываем не Call, а RealCall, так как Call — это interface и у нас нет возможности перехватить его, но мы можем перехватить его наследников. Наследника Call я нашел по полученному исходному коду, после apktool.

При нажатии на кнопку в консоли печатается Yeah — мы на верном пути. Функция execute возвращает Response, поэтому добавим перехват afterHookedMethod. До вызова функции есть возможность перехватить Request, а после вызова функции — Response.

Получение данных

Мы нашли функцию, которая отправляет данные, что дальше? Для начала откроем исходники okhttp3 на GitHub и посмотрим, как выглядит RealCall.java. Видим, что есть возможность получить Request, если из функции execute вызвать код this.request(). Добавим эту строку в beforeHookedMethod.

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

Итак, мы получили экземпляр объекта Request, и теперь хотелось бы сделать преобразование с Object в экземпляр Request в коде хука.

К сожалению, мы не можем просто преобразовать его в экземпляр объекта, потому что он создан в другой среде и наш okhttp3.Request отличается от того, который есть в приложении. Придется получать данные при помощи вызова функций и чтения значений переменных через Xposed. Открываем Request.java на GitHub и ищем функции и переменные, которые могут содержать интересную для нас информацию: тип запроса, хедер, путь, данные.

В моем случае код не обфусцирован, поэтому я могу использовать названия функций, как на GitHub. В противоположном случае необходимо в коде, прошедшем реверс, найти соответствующую функцию, так как некоторые имена могут отличаться из-за обфускации. Я решил сразу складывать данные в JSONObject, это понадобится дальше. Также я кодирую данные от body в Base64, потому что функция возвращает массив байтов. В таком виде мы не можем корректно выводить информацию в терминал.

Обрати внимание, что в коде есть преобразование Object в String[]. Это можно делать для всех простых типов: String,int,long и других.

Проделаем похожую процедуру с Response.

Теперь у нас есть два JSONObject, которые содержат данные для анализа. Выведем в консоль то, что получилось собрать.

Посмотрим, что содержится в body, распаковав его с помощью Base64.

Минуточку, здесь явно ошибка! Особо внимательные читатели заметят, что имя ресурса написано неправильно. Но как быть, если нам нужно что-то поменять перед отправкой?

Подмена данных

Для подмены я буду отправлять собранные данные на свой API. Можно было бы обойтись без этого, но каждый раз, когда вы вносите изменение в модуль Xposed, вам необходимо перезагружать устройство. Если же написать выполнение команд от C&C-центра, то можно избежать этих проблем.

Добавим отправку собранных данных на наш endpoint. Код отправки использует стандартный POST Request. На сервере данные меняются и отправляются обратно. Теперь нужно их вставить в оригинальный Request.

Теперь попробуем запустить приложение и отправить запрос. В консоли мы видим, что наш запрос поменялся и на экране отображается «Congratulations!».

Итого

Мне эта идея уже не раз помогала найти проблемы и обойти некоторые уровни защиты. Можно ли от нее защититься? Безусловно, да. Можно добавить блокировку приложения или клиента, если был найден Xposed, добавлять подписи к данным, которые отправляются. Можно не доверять любым запросам от клиента, даже если они идут по защищенному соединению.

Тем не менее, имея в своем наборе такой мощный инструмент, как Xposed или Frida, вы легко обойдете все возможные методы защиты на стороне клиента.

Полезные ссылки:

ВКонтакте
OK
Telegram
WhatsApp
Viber

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *