Вступ
У попередній статті ми розглядали як сніфити трафік вашого мобільного застосунку з HTTPS-proxy. Раджу ознайомитись зі статтею аби краще зрозуміти подальший матеріал.
За допомогою SSL Pinning даний спосіб інспекції та модифікації трафіку мобільного застосунку не буде доступний поганим хлопцям та вашому допитливому шефу.
Що таке SSL Pinning
Раніше ми вже встановили Charles Root Certificate на мобільний пристрій. Таким чином, це дозволило нашому Charles Proxy приймати, розшифровувати та демонструвати трафік, а потім зашифровувати та відправляти на Dropbox.
Якщо я, як розробник мобільного застосунку, прагну, щоб мій трафік міг інспектувати ніхто, крім мого сервера, навіть встановивши на пристрій власний SSL сертифікат, то один з варіантів — SSL Pinning.
Основна ідея SSL Pinning полягає у тому, що під час SSL хендшейку клієнт перевіряє отриманий від сервера сертифікат, та порівнює його з еталонним сертифікатом, що міститься у самому застосунку.
У статті я огляну найпростіший в реалізації спосіб SSL Pinning за допомогою списку дозволених сертифікатів, вбудованих у застосунок (whitelisting). Дізнатися більше про типи SSL Pinning можна тут.
Реалізація SSL Pinning у FoodSniffer
З повним кодом проекту можна ознайомитись у репозиторії.
Спершу необхідно отримати два сертифікати від Dropbox у форматі DER 2 для:
- www.dropbox.com
- uc9b17f7c7fce374f5e5efd0a422.dl.dropboxusercontent.com
Другий сервер зберігає безпосередньо JSON зі списком наших покупок. Аби отримати сертифікати у бажаному форматі, я використовував Mozilla Firefox.
У браузері переходимо за посиланням https://www.dropbox.com. Натискаємо на символ замка в адресному рядку:
Натискаємо More Information, обираємоSecurity -> View Certificate.
Далі обираємо Details та знаходимо кінцевий сертифікат у полі Certificate Hierarchy.
Натискаємо Export та зберігаємо у форматі DER.
Повторюємо те ж саме для uc9b17f7c7fce374f5e5efd0a422.dl.dropboxusercontent.com.
Зверніть увагу: для контент-сервера Dropbox (*.dl.dropboxusercontent.com) використовується wildcard сертифікат. Це значить, що сертифікат, який ми вилучили для сервера uc9b17f7c7fce374f5e5efd0a422 підійде і для будь-яких інших *.dl.dropboxusercontent.com серверів Dropbox.
В результаті маємо два файли з сертифікатами:
- dropboxcom.crt
- dldropboxusercontentcom.crt
Додаємо їх до проекту iOS застосунку FoodSniffer.
Далі додаємо 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 сертифікат не входить до списку дозволених сертифікатів. Користувач побачить наступну помилку:
Хакери подолані!
Однак, існує невелика проблема. Як мені, розробнику, контролювати 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.");
}
// Запускаємо скрипт
main();
- За повним ім'ям функції знаходимо вказівник на
ValidateTrustCertificateList
у бінарному файлі застосунку. - Обгортаємо вказівник у
NativeFunction
обгортку, вказуючи тип параметру та вихідного значення функції. - Змінюємо імплементацію функції
ValidateTrustCertificateList
на таку, що завжди повертає 1 (тобтоtrue
).
Весь скрипт розміщено у файлі {sourceroot}/fridascrpts/killCertPinnig.js.
Однією з проблем є отримання повного імені функції _T016FoodSnifferFrida0A15ListAPIConsumerC024validateTrustCertificateD0SbSo03SecG0CF
.
Я використовував наступну техніку:
- Створив у застосунку додатковий таргет
FoodSnifferFrida
- Під'єднав до нього бібліотеку FridaGadget.dylib. Докладніше про підключення бібліотеки можна дізнатися тут
- Запустив на симуляторі застосунок FoodSniffer
- Використав наведену команду для пошуку повного імені функції
validateTrustCertificateList
frida-trace -R -f re.frida.Gadget -i "validateTrust"
- Отримав результат у такому вигляді:
Наостанок використав його у killCertPinnig.js.
Дізнатися чому у функції наприкінці вийшло таке «дивне» ім'я та що означають T016 та 0A15 можна за посиланням.
Відключення SSL-pinning
Запустимо FoodSniffer з вимкненим SSL-pinnig!
- Запустимо Charles Proxy.
- Запустимо таргет
FoodSnifferFrida
у симуляторі проекту Xcode. Ми повинні побачити білий екран. Застосунок очікує підключення Frida.
-
Запустимо Frida для виконання скрипта killCertPinnig.js:
frida -R -f re.frida.Gadget -l ./fridascrpts/killCertPinnig.js
-
Дочекаємось під'єднання до iOS застосунку
- Продовжимо роботу застосунку командою %resume
- Тепер ми маємо побачити список продуктів
- Та JSON у Charles Proxy
Профіт!
Висновок
Frida — щось на зразок Wireshark для бінарників. Підтримується на платформах iOS, Android, Linux, Windows. Фреймворк дозволяє відстежувати виклики методів та функцій, як системних, так і користувацьких. Також існує можливість підміняти параметри, значення, що повертаються, та імплементацію функцій.
Обхід SSL-pinning за допомогою Frida в умовах процесу розробки може на перший погляд здатися трохи overkill-ом. Особисто мене він приваблює тим, що відпадає необхідність мати специфічну логіку у коді для налагодження та розробки застосунку. Усе це перевантажує код та може просочитися до релізної версії збірки при некоректній імплементації (Привіт, макроси!)
Окрім того, Frida застосовуються також і в Android, що полегшує життя усієї команди та забезпечує плавну розробку повної лінійки продукту.
Frida позиціонує себе як black box process code injection tool. З нею існує можливість, не змінюючи безпосередньо код IOS застосунку, додавати до runtime логування викликів методів, що є незамінним при налагодженні складних та рідкісних багів.
П.С. А у наступній статті ми розглянемо, як підключати Frida до ad-hoc та Testflight версій застосунків.
Ще немає коментарів