Анализ трафика Android-приложений: обход certificate pinning без реверс-инжиниринга

    Иногда нужно исследовать работу бэкенда мобильного приложения. Хорошо, если создатели приложения не заморачивались и все запросы уходят по «голому» HTTP. А что, если приложение для запросов использует HTTPS, и отказывается принимать сертификат вашего корневого удостоверяющего центра, который вы заботливо внедрили в хранилище операционной системы? Конечно, можно поискать запросы в декомпилированом приложении или с помощью реверс-инжиниринга вообще отключить применение шифрования, но хотелось бы способ попроще.

    image

    Что такое certificate pinning?


    Даже при использовании HTTPS пользователь не защищен от атак «Человек посередине», потому что при инициализации соединения злоумышленник может подменить сертификат сервера на свой. Трафик при этом будет доступен злоумышленнику.

    Справиться с такой атакой поможет certificate pinning. Эта защитная мера заключается в том, что разработчик «зашивает» в приложение доверенный сертификат. При установке защищенного соединения приложение проверяет, что сертификат, посылаемый сервером, совпадает с (или подписан) сертификатом из хранилища приложения.

    Обход certificate pinning


    В качестве подопытного выберем приложение Uber. Для анализа HTTP-трафика будем использовать Burp Suite. Также нам понадобится JDK и Android SDK (я использую все последней версии). Из Android SDK нам понадобится только утилита zipalign, так что при желании можно не скачивать весь SDK, а найти ее на просторах интернета.
    Заранее облегчим себе жизнь, добавив следующие пути к нужным утилитам в переменную окружения PATH:

    C:\path\to\jdk\bin
    %USERPROFILE%\AppData\Local\Android\sdk\build-tools\23.0.2

    Открываем Burp, заходим в Proxy – Options – Add и добавляем Proxy Listener на интерфейсе, который будет доступен подопытному Android-устройству (или эмулятору). На устройстве в свою очередь настраиваем используемую Wi-Fi сеть на использование только что включенного прокси.

    Скачаем apk-файл через apkpure.com, установим приложение на устройство и попытаемся войти в свой аккаунт – приложение зависнет на этапе аутентификации.

    image

    В логах Burp Suite (вкладка Alerts) при этом мы увидим множественные отчеты о неудачных SSL-рукопожатиях. Обратите внимание на первую строчку – именно через сервер cn-geo1.uber.com в моем случае осуществляется аутентификация, поэтому и не удается войти в приложение.

    image

    Дело в том, что Burp Suite при перехвате HTTPS-соединений (а мы помним, что все соединения устройства проксируются через него) подменяет сертификат веб-сервера на свой, который, естественно, не входит в список доверенных. Чтобы устройство доверяло сертификату, выполняем следующие действия. В Burp заходим в Proxy – Options и нажимаем Import/export CA certificate. Далее в диалоге выбираем Export Certificate. Копируем сертификат на устройство, переходим в Настройки – Безопасность – Установить сертификаты и устанавливаем наш сертификат в качестве сертификата для VPN и приложений.

    image

    Опять пытаемся войти в свой аккаунт. Сейчас приложение Uber сообщит нам только о неудачной попытке аутентификации – значит прогресс есть, осталось только обойти certificate pinning.

    Откроем приложение в вашем любимом архиваторе как zip-архив. В папке res/raw можно заметить файл с говорящим названием ssl_pinning_certs_bk146.bks.

    image

    По его расширению можно понять, что Uber использует хранилище ключей в формате BouncyCastle (BKS). Из-за этого нельзя просто заменить сертификат в приложении. Сначала нужно сгенерировать BKS-хранилище. Для этого качаем jar для работы с BKS.

    Теперь генерируем BKS-хранилище, которое будет содержать наш сертификат:

    keytool -import -v -trustcacerts -alias mybks -file c:/path/to/burp.crt -keystore ssl_pinning_certs_bk146.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath c:/path/to/bcprov-jdk15on-154.jar -storepass spassword

    На вопрос о доверии сертификату отвечаем «yes». Опять открываем apk в архиваторе и заменяем оригинальное хранилище на наше (сохраняем при этом оригинальное название).

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

    Удаляем из apk папку META-INF со старой подписью приложения и приступаем к генерации новой.
    Создаем хранилище ключей и генерируем в нем ключ для подписи apk:

    keytool -genkey -keystore mykeys.keystore -storepass spassword -alias mykey1 -keypass kpassword1 -dname "CN=ololo O=HackAndroid C=RU" -validity 10000 -sigalg MD5withRSA -keyalg RSA -keysize 1024

    Подписываем только что сгенерированным ключом наш APK:

    jarsigner -sigalg MD5withRSA -digestalg SHA1  -keystore mykeys.keystore -storepass spassword -keypass kpassword1 Uber.apk mykey1

    Теперь осталось выровнять данные в архиве по четырехбайтной границе:

    zipalign -f 4 Uber.apk Uber.apk_zipal.apk

    Готово, удаляем с устройства старое приложение, устанавливаем новое и пытаемся войти в свой аккаунт. Если раньше попытка приложения связаться с cn-geo1.uber.com прерывалось на рукопожатии, сейчас можно просматривать и, при желании, модифицировать трафик.

    image

    Спасибо за внимание!
    • +25
    • 13,5k
    • 9
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 9
    • 0
      В скором времени инструкцию надо будет расширить.

      В Android Nougat приложения перестанут доверять пользовательским сертификатам если это отдельно не указано в network security configuration:
      https://developer.android.com/preview/api-overview.html?hl=en#default_trusted_ca
      • 0
        Есть другой способ. Можно установить на своём сервере Nginx, настроить его пробрасывать трафик с http на https хоста с API и поменять везде в приложении адреса с https на http.

        Для приложения Uber такой способ работает.
        • 0
          можно поподробней? не понял Вашей схемы
          • 0

            Прописываем в nginx следующее:


            server {
                listen 0.0.0.0:80;
                server_name test.alexbers.com;
            
                location / {
                        proxy_pass         https://cn-dca1.uber.com/;
                        proxy_redirect     off;
                }      
            }

            Это заставит его пробрасывать соединения с 80 порта нашего сервера на 443 порт сервера убер. Затем меняем все адреса вида https://cn-dca1.uber.com/ в приложении на адреса вида http://alexbers.com/.


            Теперь приложение будет использовать http вместо https.

            • 0
              ваш метод хороший, спору нет. Но вот телодвижений и затраченного времени и ресурсов больше.
          • 0
            Да, это если можно поменять…

            Например с приложением КиноПоиск такой фокус не прокатит — декомпилировать что-бы поменять адрес не выходит, а без декомпиляции я не могу поменять ничего в classes.dex
            • 0
              для этого есть xposed. декомпилируем до smali, через grep находим все адреса и вперед. Правда убер обфусцирует весь код при каждой компиляции с рандомным выбором начала алфавита, поэтому придется шерстить все классы на bestmatch, что требует нормального телефона с минимум 2 Гб памяти и хорошим процессором, иначе будет тормозить.
              • 0
                Писать модули для xposed — это для меня уже сложно.
          • 0
            и вообще в тэги хорош бы добавить uber, а то искал публикации на эту тему и не нашел. планирую написать статеечку как выяснять водителям заранее куда поедет пассажир

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