# 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/`&#x20;

   `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](https://gist.github.com/z3f1r/02ac6c700c5980a093e5da557af2d85f) (возможно, он немного недоотлажен).

### Перехват через цепочку: 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*](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)

```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()
```
