Обход SSL Pinning в iOS-приложении

в 16:19, , рубрики: charlesproxy, ios приложения, iOS разработка, ssl pinning, анализ трафика, Блог компании DataArt, разработка мобильных приложений, разработка под iOS, снифферинг, тестирование ios приложений, Тестирование мобильных приложений

Обход SSL Pinning в iOS-приложении - 1

Привет, меня зовут Андрей Батутин, я Senior iOS Developer в DataArt. В предыдущей статье мы говорили, как можно сниффить трафик нашего мобильного приложения с помощью HTTPS-прокси. В этой обсудим, как обходить SSL Pinning. На всякий случай, рекомендую прочитать первую статью, если вы ее еще не читали: это понадобится для понимания приведенного ниже текста.

Собственно, на практике SSL Pinning применяют, чтобы описанный способ инспекции и модификации трафика мобильного приложения не был доступен плохим парням или любопытному шефу.

Что такое SSL Pinning

В предыдущей статье мы установили на мобильное устройство Charles Root Certificate, что позволило нашему Charles Proxy принимать, расшифровывать, показывать нам трафик, зашифровывать его обратно и отправлять на Dropbox.

Если я как разработчик мобильного приложения хочу, чтобы мой трафик мог инспектировать только мой сервер и никто другой, даже если этот другой установил на устройство свой SSL-сертификат, я могу воспользоваться SSL Pinning.

Его суть сводится к тому, что во время SSL-хендшейка клиент проверяет полученный от сервера сертификат.

В этой статье рассматривается самый простой в реализации способ SSL Pinning с помощью разрешенного списка сертификатов, зашитых в приложение (whitelisting).

Больше о типах SSL Pinning можно почитать здесь.

Реализация SSL Pinning в FoodSniffer

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

Второй сервер хранит сам JSON со списком наших покупок.

Чтобы получить сертификаты в нужном формате, я использовал Mozila Firefox.

Открываем в браузере dropbox.com.

Нажимаем на символ замка в адресной строке.

Обход SSL Pinning в iOS-приложении - 2

Обход SSL Pinning в iOS-приложении - 3

Нажимаем More Information, выбираем Security -> View Certificate.

Обход SSL Pinning в iOS-приложении - 4

Затем выбираем Details и находим конечный сертификат в Certificate Hierarchy.

Обход SSL Pinning в iOS-приложении - 5

Нажимаем Export и сохраняем в формате DER.

Обход SSL Pinning в iOS-приложении - 6

Повторяем ту же процедуру для uc9b17f7c7fce374f5e5efd0a422.dl.dropboxusercontent.com.

Примечание
Для контент-сервера Dropbox (*.dl.dropboxusercontent.com) используется wildcard-сертификат. Значит, сертификат, который вы извлекли для uc9b17f7c7fce374f5e5efd0a422 сервера, будет подходить и для любых других *.dl.dropboxusercontent.com серверов Dropbox.

В результате у меня получилось два файла с сертификатами:

dropboxcom.crt,
dldropboxusercontentcom.crt,

которые я добавил в проект iOS-приложения FoodSniffer.

Обход SSL Pinning в iOS-приложении - 7

Затем я добавил extention для FoodListAPIConsumer-класса, в котором и проверяю полученный от сервера сертификат. Для этого я ищу его в списке разрешенных сертификатов, обрабатывая Authentication Challenge-делегат NSURLSessionDelegate-протокола:

extension FoodListAPIConsumer {
    
	func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
   	 
    	guard let trust = challenge.protectionSpace.serverTrust else {
        	completionHandler(.cancelAuthenticationChallenge, nil)
        	return
    	}
   	 
    	let credential = URLCredential(trust: trust)
   	 
    	if (validateTrustCertificateList(trust)) {
        	completionHandler(.useCredential, credential)
    	} else {
        	completionHandler(.cancelAuthenticationChallenge, nil)
    	}
	}
    
	func validateTrustCertificateList(_ trust:SecTrust) -> Bool{
   	 
    	for index in 0..<SecTrustGetCertificateCount(trust) {
        	if let certificate = SecTrustGetCertificateAtIndex(trust, index){
            	let serverCertificateData = SecCertificateCopyData(certificate) as Data
            	if ( certificates.contains(serverCertificateData) ){
                	return true
            	}
        	}
    	}
   	 
    	return false
	}
}

В массиве certificates у меня хранятся Data представления моих разрешенных сертификатов.

Теперь при работающем Charles Proxy приложение будет разрывать связь с ним по причине того, что Charles-сертификат не входит в список разрешенных. Пользователь будет видеть следующую ошибку:

Обход SSL Pinning в iOS-приложении - 8

Хакеры повержены!

Но теперь есть одна маленькая проблема — как мне-разработчику мониторить HTTPS-трафик своего же приложения?

Frida

Один из вариантов — отключить SSL Pinning с помощью dynamic code injection фреймворка Frida.

Идея в том, чтобы в процессе разработки приложения метод validateTrustCertificateList всегда возвращал true.

Этого, конечно, можно добится и без dynamic code injection, например, используя #if targetEnvironment(simulator) условие для отключения SSL Pinning на симуляторе, но это слишком просто.

С помощью Frida мы сможем написать скрипт на JavaScript (ловко, правда?), в котором подменим имплементацию validateTrustCertificateList на такую, что всегда возвращает true.
И этот скрипт будет впрыскиваться в приложение уже на этапе исполнения.

Как работает Frida на iOS, вы можете почитать здесь.

Установка Frida (взято отсюда).

sudo pip install frida-tools

Frida-скрипт

Непосредственный скрипт для подмены validateTrustCertificateList функции выглядит так:

// Are we debugging it?
DEBUG = true;

function main() {
    // 1
    var ValidateTrustCertificateList_prt = Module.findExportByName(null, "_T016FoodSnifferFrida0A15ListAPIConsumerC024validateTrustCertificateD0SbSo03SecG0CF");
    if (ValidateTrustCertificateList_prt == null) {
   	 console.log("[!] FoodSniffer!validateTrustCertificateList(...) not found!");
   	 return;
    }
    // 2
    var ValidateTrustCertificateList = new NativeFunction(ValidateTrustCertificateList_prt, "int", ["pointer"]);
    // 3
    Interceptor.replace(ValidateTrustCertificateList_prt, new NativeCallback(function(trust) {
   	 
   	 if (DEBUG) console.log("[*] ValidateTrustCertificateList(...) hit!");
   	 return 1;

    }, "int", ["pointer"]));
    console.log("[*] ValidateTrustCertificateList(...) hooked. SSL pinnig is disabled.");    

}

// Run the script
main();

  1. Мы находим по полному имени функции указатель на validateTrustCertificateList в бинарнике приложения.
  2. Оборачиваем указатель в NativeFunction-обертку, указывая тип параметра и выходного значения функции.
  3. Заменяем имплементация функции validateTrustCertificateList такой, что всегда возвращает 1 (т. е. true).

Весь скрипт лежит в {source_root}/fridascrpts/killCertPinnig.js.

Одина из проблем — как было получено полное имя функции _T016FoodSnifferFrida0A15ListAPIConsumerC024validateTrustCertificateD0SbSo03SecG0CF

Для этого я использовал следующую технику.

  • Создал в приложении дополнительный таргет FoodSnifferFrida.
  • Подключил к нему библиотеку FridaGadget.dylib, которую взял здесь. Подробнее процедура подключения библиотеки описана здесь.
  • Запустил на симуляторе приложение FoodSniffer.
  • Использовал данную команду для поиска полного имени функции validateTrustCertificateList:
    frida-trace -R -f re.frida.Gadget -i "*validateTrust*"
  • Получил его в виде:
    Обход SSL Pinning в iOS-приложении - 9

А затем использовал его в killCertPinnig.js.

Почему такое «странное» имя вышло у функции в конечном итоге и что значат все эти T016 и 0A15, можно посмотреть здесь.

Убийство SSL Pinning

Теперь наконец запустим FoodSniffer с отключенным SSL Pinnig!

Запустим Charles Proxy.

Запустим таргет FoodSnifferFrida в Xcode-проекте в симуляторе. Мы должны увидеть просто белый экран. Приложение ждет, пока к нему подключится Frida.
Обход SSL Pinning в iOS-приложении - 10

Запустим Frida для исполнения killCertPinnig.js скрипта:
frida -R -f re.frida.Gadget -l ./fridascrpts/killCertPinnig.js

Дождемся подключения к iOS-приложению:

Обход SSL Pinning в iOS-приложении - 11

Продолжим работу приложения с помощью команды %resume:

Обход SSL Pinning в iOS-приложении - 12

Теперь мы должны увидеть список продовольствия в приложении:

Обход SSL Pinning в iOS-приложении - 13

И JSON в Charles Proxy:

Обход SSL Pinning в iOS-приложении - 14

Профит!

Вывод

Frida — это как Wireshark для бинарников. Она работает на iOS, Android, Linux, Windows-платформах. Этот фреймворк позволяет отслеживать вызовы методов и функций — и системных, и пользовательских. А еще подменять значения параметров, возвращаемых значений и имплементации функций.

Обход SSL Pinning в условиях процесса разработки с помощью Frida может показаться немного overkill. Меня он привлекает тем, что мне не надо иметь в самом приложении специфичной логики для отладки и разработки приложения. Такая логика загромождает код и при некорректной имплементации может просочиться в релизную версию сборки (макросы, привет вам!).

Кроме того, Frida применима и для Android. Что дает мне возможность облегчить жизнь всей своей команде и обеспечить плавный процесс разработки всей линейки продукта.
Frida позиционирует себя как black box process code injection tool. С ней возможно, не меняя непосредственный код iOS-приложения, добавлять в runtime логирование вызовов методов, что может быть незаменимо при отладке сложных и редких багов.

Автор: DataArt

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js