что такое ssl pinning в вк
Использование Xposed для обхода SSL Pinning на Android
Есть различные подходы к анализу защищенности приложений, но рано или поздно все упирается в изучение взаимодействия приложения с API. Именно этот этап дает больше всего информации о работе программы, об используемых функциях и собираемых данных. Но что делать, если приложение защищено SSL Pinning? Давайте посмотрим, что можно сделать в такой ситуации.
Перед началом
SSL Pinning в мобильных приложениях реализуют, внедряя сертификат SSL в саму программу. При открытии защищенного соединения приложение не обращается в хранилище устройства, а использует свой сертификат. Это сразу же устраняет возможность направить трафик на Burp и анализировать его. Ведь для того, чтобы увидеть трафик SSL, нужно внедрить на устройство Burp CA, который бы подтвердил, что созданный Burp сервер валиден и ему можно доверять.
SSL Pinning — это не панацея, очень часто появляются заметки про обход защиты на банковских приложениях или вообще про уязвимости в самом SSL. Но если защита построена правильно, то это создает огромные проблемы для исследователя.
Для работы 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, вы легко обойдете все возможные методы защиты на стороне клиента.
Анализ трафика Android-приложений: обход certificate pinning без реверс-инжиниринга
Иногда нужно исследовать работу бэкенда мобильного приложения. Хорошо, если создатели приложения не заморачивались и все запросы уходят по «голому» HTTP. А что, если приложение для запросов использует HTTPS, и отказывается принимать сертификат вашего корневого удостоверяющего центра, который вы заботливо внедрили в хранилище операционной системы? Конечно, можно поискать запросы в декомпилированом приложении или с помощью реверс-инжиниринга вообще отключить применение шифрования, но хотелось бы способ попроще.
Что такое certificate pinning?
Даже при использовании HTTPS пользователь не защищен от атак «Человек посередине», потому что при инициализации соединения злоумышленник может подменить сертификат сервера на свой. Трафик при этом будет доступен злоумышленнику.
Справиться с такой атакой поможет certificate pinning. Эта защитная мера заключается в том, что разработчик «зашивает» в приложение доверенный сертификат. При установке защищенного соединения приложение проверяет, что сертификат, посылаемый сервером, совпадает с (или подписан) сертификатом из хранилища приложения.
Обход certificate pinning
В качестве подопытного выберем приложение Uber. Для анализа HTTP-трафика будем использовать Burp Suite. Также нам понадобится JDK и Android SDK (я использую все последней версии). Из Android SDK нам понадобится только утилита zipalign, так что при желании можно не скачивать весь SDK, а найти ее на просторах интернета.
Заранее облегчим себе жизнь, добавив следующие пути к нужным утилитам в переменную окружения PATH:
Открываем Burp, заходим в Proxy – Options – Add и добавляем Proxy Listener на интерфейсе, который будет доступен подопытному Android-устройству (или эмулятору). На устройстве в свою очередь настраиваем используемую Wi-Fi сеть на использование только что включенного прокси.
Скачаем apk-файл через apkpure.com, установим приложение на устройство и попытаемся войти в свой аккаунт – приложение зависнет на этапе аутентификации.
В логах Burp Suite (вкладка Alerts) при этом мы увидим множественные отчеты о неудачных SSL-рукопожатиях. Обратите внимание на первую строчку – именно через сервер cn-geo1.uber.com в моем случае осуществляется аутентификация, поэтому и не удается войти в приложение.
Дело в том, что Burp Suite при перехвате HTTPS-соединений (а мы помним, что все соединения устройства проксируются через него) подменяет сертификат веб-сервера на свой, который, естественно, не входит в список доверенных. Чтобы устройство доверяло сертификату, выполняем следующие действия. В Burp заходим в Proxy – Options и нажимаем Import/export CA certificate. Далее в диалоге выбираем Export Certificate. Копируем сертификат на устройство, переходим в Настройки – Безопасность – Установить сертификаты и устанавливаем наш сертификат в качестве сертификата для VPN и приложений.
Опять пытаемся войти в свой аккаунт. Сейчас приложение Uber сообщит нам только о неудачной попытке аутентификации – значит прогресс есть, осталось только обойти certificate pinning.
Откроем приложение в вашем любимом архиваторе как zip-архив. В папке res/raw можно заметить файл с говорящим названием ssl_pinning_certs_bk146.bks.
По его расширению можно понять, что Uber использует хранилище ключей в формате BouncyCastle (BKS). Из-за этого нельзя просто заменить сертификат в приложении. Сначала нужно сгенерировать BKS-хранилище. Для этого качаем jar для работы с BKS.
Теперь генерируем BKS-хранилище, которое будет содержать наш сертификат:
На вопрос о доверии сертификату отвечаем «yes». Опять открываем apk в архиваторе и заменяем оригинальное хранилище на наше (сохраняем при этом оригинальное название).
Но на этом все не заканчивается. Каждый apk должен быть подписан сертификатом разработчика. К счастью, это делается не для обеспечения безопасности, а для идентификации приложений, поэтому для наших исследовательских целей мы вполне можем использовать и недоверенный сертификат.
Удаляем из apk папку META-INF со старой подписью приложения и приступаем к генерации новой.
Создаем хранилище ключей и генерируем в нем ключ для подписи apk:
Подписываем только что сгенерированным ключом наш APK:
Теперь осталось выровнять данные в архиве по четырехбайтной границе:
Откручивание SSL пиннинга в Android приложениях
Проблемы перехвата трафика
В процессе тестирования на проникновение мобильных приложений на Android часто необходимо выяснить, каким образом приложение общается с сервером, с какими адресами происходит взаимодействие, как выглядят запросы, какие данные передаются. Но не всегда это удается сделать.
В наше время для взаимодействия компонентов веб-приложений используется протокол HTTPS, в основе которого лежат протоколы HTTP и TLS. Просто так перехватить трафик приложения не выйдет, т.к. он зашифрован. Можно, конечно, использовать прокси сервер, который с помощью своего сертификата сможет расшифровать трафик приложения и увидеть все запросы. Однако и средства защиты приложений не стоят на месте. Многие мобильные приложения используют SSL Pinning.
SSL Pinning – это внедрение SSL сертификата, который используется на сервере в код мобильного приложения. Таким образом, приложение не использует хранилище сертификатов устройства и не будет работать с сертификатом, который мы ему подсунули.
Ошибка, которая возникает в случае использования Burp suite для перехвата трафика с помощью собственного сертификата
Способы защиты приложений
Для того чтобы понять, как обходить защиту, необходимо сначала разобраться, какими средствами эта защита осуществляется. Для этого существуют несколько способов.
Для данного способа необходимо добавить файл сертификата в файлы приложения, затем создать KeyStore и добавить в него наш сертификат.
После этого создаем сам TrustManager, который будет работать с нашим KeyStore, в котором находится нужный сертификат.
Далее создается SSLContext, который использует наш TrustManager. Затем указываем для URLConnection SocketFactory из созданного SSLContext.
Основная суть этого метода в том, что мы используем непосредственно сам файл сертификата в проекте, а затем создаем TrustManager, который будет работать только с этим сертификатом.
Данная реализация работает непосредственно с API на достаточно низком уровне, поэтому этот способ надо использовать осторожно.
Вторым способом является использование библиотеки OkHttp. В ней есть удобный CertificatePinner, который с помощью своего конструктора закрепляет за доменом определенный fingerprint сертификата.
Этот fingerprint высчитывается из сертификата, с которым должно работать приложение, а затем пишется непосредственно в код.
Можно указать несколько сертификатов для разных доменов.
Network Security Configuration
С версии Android 7.0 стала возможна настройка конфигурации сети. В папке res/xml/ создается файл network_security_config.xml, в котором прописываются правила и указываются fingerprints, как в случае с OkHttp.
Затем в AndroidManifest.xml прописывается использование данного файла в качестве android:networkSecurityConfig.
Этот способ позволяет редактировать сертификаты, не меняя исходный код приложения.
Способы обхода механизмов защиты
Frida – инструмент для динамического внедрения кода в приложение. Утилита прямо в процессе работы приложения способна отслеживать вызовы методов приложения, модифицировать методы приложения, обращаться к памяти выполняемого приложения и многое другое.
Для обхода SSL Pinning с помощью Frida есть несколько готовых скриптов, создающих кастомный TrustManager, который будет доверять нашему сертификату. Рассмотрим скрипт.
CertificateFactory и X509Certificate нужны для создания экземпляра сертификата из считанного файла.
FileInputStream и BufferedInputStream используются для считывания файла сертификата.
KeyStore – наш кастомный KeyStore, в который положим наш сертификат.
TrustManagerFactory создает экземпляр TrustManager, который будет работать с нашим KeyStore.
SSLcontext – реализация протокола SSL, которая выступает как factory для sslSocketFactory.
Считываем сертификат cert-der.crt, который мы предварительно поместили на устройство, и создаем экземпляр этого сертификата.
Создаем keyStore, в который складываем наш сертификат.
Создаем trustManager, который доверяет keyStore с нашим сертификатом.
Вот здесь начинается настоящее использование Frida. Данный фрагмент перезаписывает метод SSLContext. Теперь при вызове приложением метода сначала будет вызываться информационный лог в консоль, затем выполнится исходный метод, который мы перезаписываем. Но он вызовется не просто так. Вместо второго параметра, который будет передан в функцию, новая функция подставляет кастомный TrustManager с нашим сертификатом. И в конце снова информационный лог в консоль о том, что все прошло удачно.
Таким образом, данный скрипт готовит TrustManager, а затем использует его в методе вместо того, который передается (переменные a и с передаются в вызов исходной функции, а вот переменная b заменяется на кастомный TrustManager).
Frida позволяет переопределять методы, выполнять собственные скрипты. Поэтому с помощью Frida можно обойти защиту, реализованную через OkHttp CertificatePinner.
Главное преимущество этого метода – отсутствие необходимости патчить исходный код apk и пересобирать приложение. А так как нам не нужно пересобирать приложение, значит этому способу не помешают методы защиты приложения от пересборки.
С другой стороны, для данного метода необходимо подключить телефон к устройству с Frida, установить frida-server на телефон, а это не всегда удобно.
Изменение исходного кода и пересборка apk
В случае, если мы хотим один раз произвести обход средств защиты и получить приложение, в котором не будет SSL Pinning, необходимо изменить сам код приложения.
Для декомпиляции можно воспользоваться удобной утилитой apktool. Также для декомпиляции работы с разобранным apk, а затем и для сборки можно использовать удобное расширение для Visual Studio Code ApkLab.
Чтобы не копаться в трудночитаемых исходных кодах smali, вышеперечисленные утилиты умеют преобразовывать их в Java-код, который уже гораздо удобнее изучать. Также утилиты предоставляют функциональность деобфускации, которая очень помогает в изучении исходного кода.
ApkLab предоставляет также автоматизированный поиск необходимых участков кода. Рассмотрим что и почему он изменяет.
В первом файле утилита закомментировала исполнение следующих методов: checkClientTrusted, checkServerTrusted, getAcceptedIssuers.
По названиям методов можно предположить, что первый проверяет, является ли клиент доверенным, второй проверяет, является ли сервер доверенным, а третий получает лист доверенных сертификатов. Но не будем гадать и перейдем к Java-коду данных методов.
Теперь мы можем уже с пониманием рассмотреть, почему утилита закомментировала эти фрагменты. Первые два метода внутри ничем не отличаются и вызывают новый метод mo9499a (такое название из-за деобфускации), который слишком большой, чтобы вставлять его здесь. Но, судя по документации, этот метод строит цепочку сертификатов до корневого сертификата, а потом проверяет, можно ли ему доверять. В данном методе при ошибке будет выброшено исключение. Но мы закомментировали вызов данной функции, поэтому никаких проверок сертификат проходить не будет, и поэтому исключений мы не получим.
Метод getAcceptedIssuers должен возвращать массив доверенных сертификатов. Но дальнейшая реализация и использование данного метода работает таким образом, что если мы вернем пустой массив, то программа будет доверять любому сертификату. Именно это и сделала утилита: закомментировала код, в котором происходит получение сертификатов из keyStore (это происходит в методе m7931a), а вместо этого просто возвращается пустой массив.
Автоматический редактор кода заменяет эти фрагменты, т.к. они используются в проверке сертификата. Поэтому, если необходимо самому изменить какие-то фрагменты проверки, сначала лучше изучить, как работают те или иные методы, а затем уже вручную менять их в smali. К тому же обычно не приходится писать много кода вместо методов. В рассмотренном выше примере не делается ничего сложнее комментирования основной функциональности и создания каких-либо переменных-заглушек.
Также можно рассмотреть OkHttp CertificatePinner. В обзоре реализации был представлен фрагмент кода, где в OkHttpClient передается CertificatePinner. Найдя в smali соответствующий метод, можно убрать строчку, в которой CertificatePinner складывается в поле объекта OkHttpClient.
Изменение NSC (Network Security Configuration)
Самый простой способ обхода SSL Pinning.
В случае, если в приложении присутствует NSC, необходимо его изменить. В файле NSC могут быть прописаны base-config и domain-config.
В base-config может быть указано, каким сертификатам приложение может доверять.
В domain-config настраиваются домены.
Для обхода защиты достаточно лишь убрать весь блок .
ApkLab также умеет делать автоматическое изменение NSC файла.
В случае отсутствия файла в проекте утилита создает его с указанным выше содержимым, а также прописывает его в AndroidManifest.xml.
Подмена файла сертификата
Поэтому можно просто положить туда свой сертификат вместо того, который установили туда разработчики.
В связи с тем, что SSL Pinning может быть реализован множеством разных способов, от различных библиотек, до кастомных реализаций привязки сертификата, задача его обхода не всегда простая и очевидная. Каждый раз, рассматривая новое приложение, нужно изучить, какие методы используются, а затем уже применять соответствующие способы обхода, которые тоже не всегда удобны и работоспособны.
Frida достаточно сильный инструмент, но возникает неудобство в виде постоянного подключения телефона к устройству, предварительной конфигурации и прочего. А если в интернете нет готового скрипта для нужных целей, то написание собственного требует хорошего понимания работы Frida.
Непосредственный патчинг apk хорош тем, что его можно сделать всего раз и на выходе будет приложение без SSL Pinning. Однако на этом пути может помешать защита приложения от пересборки. Конечно и ее можно попытаться обойти, раз мы имеем исходный код приложения, но это потребует больше времени и сил.
Изменение Network Security Configuration достаточно простой и рабочий способ, но редко когда приложения ограничиваются только этим способом защиты. Поэтому знание обхода этого способа SSL Pinning необходимо, но недостаточно.
Подмена KeyStore с доверенным сертификатом в файлах приложения тоже легко реализуется. Но опять же данный способ может не сработать в случае, если приложение при запуске проверяет, не было ли каких-либо изменений в файлах приложения (например, высчитывает чек-сумму).
Таблица 1. Область применения
Способ обхода
Способы защиты, которые можно обойти