Android: SSL Unpinning

apk-mitm

JS-программа, которая перепатчивает апк, в том числе и для обхода пиннинга и доверие сертам пользовательским https://github.com/shroudedcode/apk-mitm#apk-mitm

Objection

android sslpinnning disable

Как на новых android

В Android, начиная с 7 версии, сертификаты пользователя не подходят для перехвата трафика. Надо их добавлять в хранилище доверенных сертификатов (как и в Apple, только делается это нетривиально слегка): https://blog.ropnop.com/configuring-burp-suite-with-android-nougat/

При добавлении сертификата первым способом, может возникнуть проблема, что файловая система находится в режиме read-only. В этом случае:

mount -o rw,remount /system
добавляем сертификат
mount -o ro,remount /system

Настройка на эмуляторе

Выбираем образ с поддержкой Google API (но не Google Play) и далее по инструкции, что выше, или, если так будет понятнее, то вот красивая статья на medium: https://secabit.medium.com/how-to-configure-burp-proxy-with-an-android-emulator-31b483237053

Кратко (серт уже подготовлен):

$ adb root
$ adb remount
$ adb push 9a5ba575.0 /system/etc/security/cacerts
$ adb shell "chmod 664 /system/etc/security/cacerts/9a5ba575.0"
$ adb reboot

В “Settings -> Security -> Trusted Credentials” должен появится серт PortSwigger.

Далее, делаем снапшот эмулятора и пользуемся на здоровье!

Создание сертификатов, совместимых с системными сертификатами Android

Имя серта:
openssl x509 -in <some.cer> -subject_hash_old

Составные части:
Серт:
openssl x509 -in <some.cer>

Серт в текстовом виде:
openssl x509 -in <some.cer> -noout -text

Fingerprint:
openssl x509 -in <some.cer> -noout -fingerprint -sha1

Или все разом:
openssl x509 -in FiddlerRoot.cer -inform DER -text -subject_hash_old -sha1 -fingerprint
Главное, потом расположиить в правильном порядке

Системные сертификаты на Android хранятся здесь: /system/etc/security/cacerts

Пользовательские сертификаты хранятся здесь: /data/misc/user/0/cacerts-added На более старых устройствах их можно найти здесь: /data/misc/keychain/cacerts-added

  1. Экспортируем сертификат из перехватываюжего прокси (Burp Suite; CER|DER)

  2. openssl x509 -inform DER -in cacert.der -out cacert.pem

  3. openssl x509 -inform PEM -subject_hash_old -in cacert.pem |head -1

  4. mv cacert.pem *.0

  5. mv /sdcard/*.0 /system/etc/security/cacerts/

    chmod 644 /system/etc/security/cacerts/*.0

  6. *chgrp - на root если вдруг группа не root стоит: chgrp root <file>

  7. *chown root <file> - если вдруг пользователь-владелец не root

  8. adb reboot

    В trusted creds появляется наш сертификат

Или все вместе одним скриптом — zcc.py (возможно, он немного недоотлажен).

Перехват через цепочку: android -> mitmproxy -> burp

Запускаем burp: localhost:<port_burp> Запускаем mitmproxy: mitmproxy -p <port_mitmproxy> --mode upstream:localhost:<port_burp> --ssl-insecure На телефоне: set proxy with <port_mitmproxy>

Из-за чего такие финтеля: не получается поставить серта бурпа на телефоне в системные. Серт mitmproxy становится норм!

Патчинг на примере Uber

1 Инструменты: JDK, Android SDK (Tool: zipalign) Добавить их в PATH: C:\path\to\jdk\bin %USERPROFILE%\AppData\Local\Android\sdk\build-tools\23.0.2

2 Ставим Burp на прослушивание, в WiFi настройках телефона ставим наш прокси. Запускаем приложение - на аутентификации виснет.

3 Что случилось: Дело в том, что Burp Suite при перехвате HTTPs-соединений (а мы помним, что все соединения устройства проксируются через него) подменяет сертификат веб-сервера на свой, который, естественно, не входит в список доверенных. Круто! -> прокидываем наш сертификат на устройство в список доверенных сертификатов.

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

5 Откроем Uber.apk как ZIP-архив. В /res/raw лежит ssl_pinning_certs_bk146.bks. По его расширению можно понять, что Uber использует хранилище ключей в формате BouncyCastle (BKS). Из-за этого нельзя просто заменить сертификат в приложении. Сначала нужно сгенерировать BKS-хранилище. Для этого качаем JAR (https://www.bouncycastle.org/latest_releases.html; bcprov-ext-jdk.jar) для работы с BKS. Пример команды: keytool -import -v -trustcacerts -alias mybks -file FiddlerRoot.cer -keystore ssl_pinning_certs_bk146.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath bcprov-ext-jdk15on-160.jar -storepass spassword

Копируем полученное хранилище в архив *.apk

6 Теперь надо подписать приложение Удаляем из 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

Сконвертировать сертификат в системный для андроида (Python)

import os
import argparse
from binascii import hexlify
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
import OpenSSL.crypto


"""
script create cert: <hash>.0
android> # mount -o rw,remount /system
android> # mv <hash>.0 /system/etc/security/cacerts/
android> # chmod 644 /system/etc/security/cacerts/<hash>.0
android> # mount -o ro,remount /system
"""


def arg_parse():
    parser = argparse.ArgumentParser(description='Convert certificate -> Android System cert')

    parser.add_argument('-c', '--cert', help='input cert', default='', required=True)
    parser.add_argument('-o', '--output', help='output directory', default='.', required=False)

    args = parser.parse_args()

    return args


def md5(data):
    digest = hashes.Hash(hashes.MD5(), backend=default_backend())
    digest.update(data)
    return digest.finalize()


class CertConverter:
    def __init__(self, cert_path, output_dir='.'):
        self.cert_path = cert_path
        self.output_dir = output_dir

    def convert(self):
        with open(self.cert_path, 'rb') as cert_file:
            cert_data_pem = cert_file.read()
        if cert_data_pem[:2] == b'\x30\x82':
            file_type = OpenSSL.crypto.FILETYPE_ASN1
        else:
            file_type = OpenSSL.crypto.FILETYPE_PEM
        cert = OpenSSL.crypto.load_certificate(
            file_type,
            cert_data_pem
        )
        if file_type == OpenSSL.crypto.FILETYPE_ASN1:
            cert_data_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert).decode()  # DER->PEM
        else:
            cert_data_pem = cert_data_pem.decode()
        cert_text = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_TEXT, cert).decode()
        cert_fingerprint_sha1 = cert.digest("sha1").decode()
        cert_subject_der = cert.get_subject().der()
        cert_subject_hash_old = hexlify(md5(cert_subject_der)[:4][::-1]).decode()

        out_cert_name = f'{self.output_dir}/{cert_subject_hash_old}.0'
        with open(out_cert_name, 'w') as android_system_cert:
            android_system_cert.write(f'{cert_data_pem}{cert_text}SHA1 Fingerprint={cert_fingerprint_sha1}')

        return self


if __name__ == "__main__":
    args = arg_parse()
    if not os.path.isfile(args.cert):
        print(f'{args.cert}: File not found')
        exit(-1)
    if not os.path.isdir(args.output):
        print(f'{args.output}: Directory not found')
        exit(-2)
    converter = CertConverter(args.cert, output_dir=args.output).convert()

Last updated

Was this helpful?