Обхід SSL Pinning в Android-додатках, методи та інструменти

20 лютого 2025 4 хвилин Автор: George Droyd

У статті детально розглядаються сучасні підходи до обходу механізму SSL Pinning в Android-додатках. Пояснюємо, що SSL Pinning забезпечує додатковий рівень безпеки, перевіряючи сертифікат сервера, і що для аналізу такого захищеного трафіку необхідні root-права та спеціалізоване програмне забезпечення.

Вступ

SSL Pinning – це технологія, яка широко застосовується у програмах для захисту конфіденційних даних, що передаються між клієнтом і сервером. Під додатком розуміється будь-яке програмне забезпечення, незалежно від платформи – мобільне чи десктопне. Через домінування мобільних застосунків на ринку, як приклад розглядається Android.

Сучасний мережевий трафік майже повністю передається через захищений TLS-протокол, що забезпечує шифрування та перевірку довіри між пристроєм і сервером. В основі механізму лежить перевірка сертифіката сервера – якщо він відповідає вимогам безпеки, клієнтський пристрій встановлює захищене з’єднання та передає зашифровані дані.

Основне питання полягає в тому, чому пристрій довіряє певному серверному сертифікату і чи буде прийнято сертифікат, створений вручну.

Відповідь криється у сертифікатах довіри, які заздалегідь вбудовані в операційну систему і видані авторитетними сертифікаційними центрами (Certificate Authority, CA). Самостійно створений SSL-сертифікат не визнається надійним, оскільки його відсутність у списку довірених центрів сертифікації унеможливлює автоматичну перевірку легітимності.

Наступне логічне питання: чи можна створити власний CA-сертифікат, щоб обійти SSL-пінінг? Ця стаття — відповідь на це питання. Тому розглянемо такі питання:

  • Для чого потрібно це робити?

  • Як це взагалі працює?

  • Відкріплюємо SSL за допомогою Frida

  • Впроваджуємо свій CA

Для чого потрібно це робити?

У матеріалі розглядається тестування безпеки API мобільних застосунків та парсинг відкритих даних у рамках етичних досліджень. Багато онлайн-сервісів, зокрема інтернет-магазини, новинні портали та банківські установи, використовують окремі API для мобільних додатків, які можуть суттєво відрізнятися від API вебверсії.

Таке API спрощує роботу, оскільки:

  • Дані можуть подаватися у структурованому форматі (JSON).

  • Може бути відсутня CAPTCHA.

  • Запити можуть мати менший rate-limit або інші особливості, які слід вивчити чи протестувати на вразливості.

Однак, це зовсім не означає, що працювати з додатком буде легше. Він може використовувати різні механізми захисту, такі як:

  • Integrity Check – перевірка цілісності додатка, щоб виявити модифікації.

  • Proof of Work – додаткові обчислювальні задачі для уповільнення атак.

  • Root Checking – виявлення root-доступу та блокування роботи на рутованих пристроях.

  • Нативна криптографія – використання зашифрованих нативних бібліотек, які потрібно декомпілювати або реверсити.

Як це взагалі працює?

Існує бібліотека SSL OpenSSL, а для Android використовується її модифікована версія – BoringSSL, яка є форком OpenSSL та оптимізована під специфіку мобільної платформи. Саме ця бібліотека виконує всю логіку перевірки сертифікатів. У пам’яті процесу її можна знайти у вигляді файлу libssl.so.

Щоб перевірити це, необхідно спочатку визначити PID (process ID) будь-якого процесу, який використовує інтернет (за винятком браузера, оскільки він має особливості роботи з мережею, про що йдеться у розділі “Додатково”) Далі виконайте команду:

cat /proc/<pid>/maps | grep ssl

Результат на десктопному Linux виглядатиме приблизно так:

7f61cbbb8000-7f61cbbcb000 r--p 00000000 00:21 186853  /usr/lib64/libssl.so.3.2.2
7f61cbbcb000-7f61cbc5d000 r-xp 00013000 00:21 186853  /usr/lib64/libssl.so.3.2.2
7f61cbc5d000-7f61cbc80000 r--p 000a5000 00:21 186853  /usr/lib64/libssl.so.3.2.2
7f61cbc80000-7f61cbc8b000 r--p 000c8000 00:21 186853  /usr/lib64/libssl.so.3.2.2
7f61cbc8b000-7f61cbc8f000 rw-p 000d3000 00:21 186853  /usr/lib64/libssl.so.3.2.2

Це означає, що бібліотека libssl.so була завантажена з /usr/lib64/libssl.so.3.2.2.

У випадку з Android результат буде іншим:

/apex/com.android.conscrypt/lib/libssl.so

Ця бібліотека виконує ключову роль у реалізації SSL та перевірці сертифікатів у Android-додатках.

Проте програми не звертаються до її функцій безпосередньо. Натомість для роботи з SSL використовуються високорівневі бібліотеки, які вже інтегровані в середовище програмування та спрощують взаємодію з механізмами безпеки.

У випадку з Java це:

java.net.ssl

Ця бібліотека надає зручний API для роботи з TLS-з’єднаннями, а всі низькорівневі виклики до libssl.so виконуються у фоновому режимі. За своєю логікою, у бібліотеці має бути функція, яка визначає довіру до SSL-сертифіката – він або визнаний надійним, або ні.

Чи можливо змінити цю функцію так, щоб вона завжди приймала будь-які сертифікати?

Так, це можливо! Це один із методів, який має як свої переваги, так і недоліки.

Плюси:

  • Не потрібно возитися із власним CA сертифікатом.

  • Швидко можна відкріпити одну програму і бачити її трафік.

Мінуси:

  • Не працює з WebView та Flutter.

Тому, якщо вирішили взяти цей спосіб, переходьте до абзацу Відкріплюємо SSL за допомогою Frida, інакше переходьте до “Впроваджуємо свій CA”.

Відкріплюємо SSL за допомогою Frida

Frida – це потужний інструмент для зворотного інжинірингу, який дає змогу в реальному часі змінювати поведінку програм. Він широко використовується для аналізу та модифікації додатків, оскільки дозволяє вносити зміни в код без необхідності його перекомпіляції.

Завдяки Frida можна використовувати JavaScript для коригування логіки роботи програми, що ідеально підходить для відключення SSL Pinning та аналізу зашифрованого трафіку.

У бібліотеці OpenSSL є функції, які відповідають за валідацію сертифіката, такі як:

SSL_CTX_set_verify та SSL_set_verify.

Зосередимося на SSL_set_verify, оскільки саме вона відповідає за перевірку сертифіката сервера. Її сигнатура виглядає так:

void SSL_set_verify(SSL *ssl, int mode, SSL_verify_cb verify_callback);
  • Другий аргумент (mode) визначає режим перевірки сертифіката. Наприклад, значення SSL_VERIFY_PEER означає, що сертифікат сервера обов’язково повинен бути перевірений.

  • Третій аргумент (verify_callback) — це вказівник на функцію зворотного виклику, яка буде викликатися після верифікації сертифіката. Ця функція визначає, чи довіряти сертифікату.

Однак цей підхід зазвичай використовується для нативного коду або десктопних програм. У випадку Android механізм валідації працює інакше.

Android і BoringSSL

В Android використовується бібліотека BoringSSL, яка є форком OpenSSL та адаптована під потреби мобільної платформи.

Одна з ключових відмінностей BoringSSL – це додаткова функція SSL_set_custom_verify, яка застосовується в Java-бібліотеці conscrypt для перевірки сертифікатів. Вона дозволяє розробникам визначати власні правила валідації SSL-з’єднань, що може бути корисним для аналізу та модифікації роботи додатків.

Ця функція дозволяє задати власну логіку перевірки сертифікатів, яка виконується нативним кодом. Нижче наведено приклад із бібліотеки conscrypt, де викликається SSL_set_custom_verify:

static void NativeCrypto_SSL_set_verify(JNIEnv* env, jclass, jlong ssl_address,
                                        CONSCRYPT_UNUSED jobject ssl_holder, jint mode) {
    CHECK_ERROR_QUEUE_ON_RETURN;
    SSL* ssl = to_SSL(env, ssl_address, true);
    JNI_TRACE("ssl=%p NativeCrypto_SSL_set_verify mode=%x", ssl, mode);
    if (ssl == nullptr) {
        return;
    }
    SSL_set_custom_verify(ssl, static_cast<int>(mode), cert_verify_callback);
}

Хукаємо SSL_set_custom_verify

Тепер ми можемо перехопити функцію SSL_set_custom_verify за допомогою Frida і замінити її логіку так, щоб сертифікати завжди вважалися валідними. Ось код на JavaScript:

setTimeout(() => {
    // Спершу знаходимо модуль libssl.so
    const libssl = Process.getModuleByName("libssl.so");

    // Шукаємо функцію SSL_set_custom_verify.
    // getExportByName() повертає вказівник на неї
    const SSL_set_custom_verify_ptr = libssl.getExportByName("SSL_set_custom_verify");
    
    // Створюємо нативну функцію для цієї адреси, щоб мати
    // можливість викликати її
    // https://frida.re/docs/javascript-api/#nativefunction
    // https://android.googlesource.com/platform/external/boringssl/+/refs/heads/master/src/include/openssl/ssl.h#2648
    const SSL_set_custom_verify = new NativeFunction(SSL_set_custom_verify_ptr, 'void', ['pointer', 'int', 'pointer']);
    
    // Нативний callback, який завжди повертає 0
    // Це означає, що сертифікат вважається валідним
    // https://frida.re/docs/javascript-api/#nativecallback
    const alwaysTrue = new NativeCallback(() => {
        return 0;
    }, 'int', ['pointer', 'pointer']);
    
    // Замінюємо оригінальну SSL_set_custom_verify на нашу функцію,
    // яка викликає наш alwaysTrue callback
    Interceptor.replace(SSL_set_custom_verify_ptr, new NativeCallback((ctx, mode, cb) => {
        console.log("[+] SSL_set_custom_verify викликано");
        SSL_set_custom_verify(ctx, 1, alwaysTrue);
    }, 'void', ['pointer', 'int', 'pointer']));
}, 0);

Пояснення коду:

  1. Пошук функції. SSL_set_custom_verifyМи знаходимо її у модулі libssl.so за допомогою getExportByName.

  2. Створення NativeFunction. Ми створюємо NativeFunction для SSL_set_custom_verify, щоб мати можливість викликати її вручну.

  3. Нативний callback (alwaysTrue). Цей callback завжди повертає 0, що означає успішну перевірку сертифіката.

  4. Перехоплення і заміна функції. Ми використовуємо Interceptor.replace для заміни оригінальної функції на нашу, яка викликає SSL_set_custom_verify з нашим callback.

Аби запустити скрипт виконайте команду:

frida -U -l назва_вашого_файла.js -f назва.адроїд.пакета

Впроваджуємо свій CA

Особливістю WebView та Flutter є те, що вони не взаємодіють безпосередньо з libssl.so, а натомість самостійно виконують перевірку сертифікатів CA.

Наприклад, під час тестування WebView було завантажено додаток і здійснено спробу відкрити сторінку Google. Як показано на скріншоті, сайт не завантажується, а в терміналі mitmproxy з’являється помилка, яка вказує на те, що клієнт не довіряє сертифікату. Це підтверджує, що WebView має власні механізми перевірки SSL-з’єднань, які потребують альтернативних методів обходу.

В Android сертифікати довіри зберігаються в директорії /system/etc/security/cacerts/ у форматі hash.0. Щоб здійснювати перехоплення SSL-трафіку у WebView або Flutter, необхідно додати власний сертифікат у цю директорію.

Основна проблема полягає в тому, що починаючи з Android 10+, змонтувати /system без вимкнення dm-verity неможливо. Проте є альтернативний підхід – змонтувати tmpfs у /system/etc/security/cacerts/. Це дозволяє тимчасово змінити вміст каталогу та працювати із власними сертифікатами, але тільки до наступного перезавантаження пристрою.

Плюси цього способу:

  • Працює для всього трафіку на пристрої, включаючи WebView та Flutter.

  • Не потрібно втручатись в бібліотеки або програми безпосередньо.

Мінуси:

  • Можливий шум через непотрібні нам пакети, оскільки це буде стосуватись всього трафіку на пристрої.

Для цього способу рекомендується використовувати прозорі проксі у вашій програмі для перегляду трафіку. Особисто я використовую mitmproxy в режимі wireguard.

Додаємо власний CA сертифікат у /system/etc/security/cacerts

Крок 1. Підготовка CA сертифіката

Переконайтеся, що у вас є CA сертифікат. У нашому прикладі це сертифікат від mitmproxy:

➜ ls
mitmproxy-ca-cert.cer
➜ export CA_CERT=mitmproxy-ca-cert.cer

Крок 2. Конвертація сертифіката у формат PEM

Android використовує сертифікати у форматі PEM. Конвертуємо наш .cer файл у .pem:

➜ openssl x509 -in $CA_CERT -out certificate.pem
➜ ls
certificate.pem  mitmproxy-ca-cert.cer

Крок 3. Обчислення хешу сертифіката

Android зберігає сертифікати у каталозі /system/etc/security/cacerts/ у форматі hash.0. Обчислюємо хеш і перейменовуємо файл:

➜ mv certificate.pem $(openssl x509 -inform PEM -subject_hash_old -in certificate.pem | head -1).0
➜ ls
c8750f0d.0  mitmproxy-ca-cert.cer

Крок 4. Переміщення сертифіката на пристрій

Використовуйте adb, щоб скопіювати сертифікат у тимчасову директорію пристрою:

➜ adb push c8750f0d.0 /data/local/tmp
c8750f0d.0: 1 file pushed, 0 skipped. 3.4 MB/s (1172 bytes in 0.000s)

Додавання сертифіката в Android

1. Отримайте root-доступ на пристрої:

➜ adb shell
generic_x86_arm:/ $ su 
generic_x86_arm:/ # whoami
root

2. Створіть тимчасову директорію для сертифікатів:

mkdir -m 700 /data/local/tmp/certs

3. Скопіюйте існуючі сертифікати у тимчасову директорію:

cp /system/etc/security/cacerts/* /data/local/tmp/certs/

4. Створіть монтування tmpfs для каталогу cacerts:

mount -t tmpfs tmpfs /system/etc/security/cacerts/

5. Поверніть оригінальні сертифікати до tmpfs:

mv /data/local/tmp/certs/* /system/etc/security/cacerts/

6. Скопіюйте ваш новий сертифікат у cacerts:

mv /data/local/tmp/c8750f0d.0 /system/etc/security/cacerts/

7. Оновіть права доступу та SELinux контекст:

chown root:root /system/etc/security/cacerts/*
chmod 644 /system/etc/security/cacerts/*
chcon u:object_r:system_file:s0 /system/etc/security/cacerts/*

Важливо! Після перезавантаження пристрою всі внесені зміни будуть скинуті, тому процедуру потрібно повторити знову.

Після додавання сертифіката в /system/etc/security/cacerts/ він буде розпізнаний системою як довірений, що дозволить WebView коректно працювати із перехопленим трафіком.

Додатково

Браузери не піддаються стандартному методу відключення SSL Pinning через Frida, оскільки вони мають власну реалізацію логіки перевірки сертифікатів.

Хоча WebView базується на Chromium, його імплементацію можна переглянути у Chromium Repository, що дає змогу знайти потенційні способи обходу.

Деякі програми можуть не довіряти встановленому CA-сертифікату, навіть якщо він доданий у систему. Це може бути наслідком Certificate Transparency, через що застосунок ігнорує непідтверджені сертифікати. У такому випадку необхідне зворотне інжинірингування додатка для виявлення логіки перевірки та її модифікації.

Якщо жоден із методів не працює, але застосунок використовує libssl.so, можна змінити підхід: відключити перехоплення трафіку та замість цього перехоплювати виклики функцій SSL_read і SSL_write, що дозволить отримати розшифровані дані безпосередньо під час їх обробки.

Висновок

SSL Pinning – це ключовий механізм безпеки, який широко використовується в сучасних мобільних додатках для захисту переданих даних. У статті було розглянуто кілька способів обходу цього механізму для тестування безпеки додатків, зокрема:

  • Перехоплення та модифікація функцій перевірки сертифікатів за допомогою Frida

  • Додавання власного CA-сертифіката для роботи з WebView і Flutter

Кожен із методів має свої переваги та обмеження, тому вибір оптимального підходу залежить від конкретного сценарію.

Важливо пам’ятати про етичні аспекти тестування безпеки та використовувати ці знання виключно для зміцнення захисту мобільних додатків.

Інші статті по темі
Блог підписниківОсвіта
Читати далі
Атака Slowloris: методи, вразливості та захист веб-серверів
У цій статті ми розбираємо особливості атаки Slowloris, її механізм роботи та вплив на сервери. Також розглядаються способи захисту, які дозволяють мінімізувати ризики та ефективно протистояти подібним загрозам.
226
Знайшли помилку?
Якщо ви знайшли помилку, зробіть скріншот і надішліть його боту.