Обхід SSL-pinning в iOS застосунку

3 хв. читання

Вступ

У попередній статті ми розглядали як сніфити трафік вашого мобільного застосунку з 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 для:

Другий сервер зберігає безпосередньо JSON зі списком наших покупок. Аби отримати сертифікати у бажаному форматі, я використовував Mozilla Firefox.

У браузері переходимо за посиланням https://www.dropbox.com. Натискаємо на символ замка в адресному рядку:

dropbox

dropbox-2

Натискаємо More Information, обираємоSecurity -> View Certificate.

view-certificate

Далі обираємо Details та знаходимо кінцевий сертифікат у полі Certificate Hierarchy.

certificate-hierarchy

Натискаємо Export та зберігаємо у форматі DER.

save-as

Повторюємо те ж саме для uc9b17f7c7fce374f5e5efd0a422.dl.dropboxusercontent.com.

Зверніть увагу: для контент-сервера Dropbox (*.dl.dropboxusercontent.com) використовується wildcard сертифікат. Це значить, що сертифікат, який ми вилучили для сервера uc9b17f7c7fce374f5e5efd0a422 підійде і для будь-яких інших *.dl.dropboxusercontent.com серверів Dropbox.

В результаті маємо два файли з сертифікатами:

  • dropboxcom.crt
  • dldropboxusercontentcom.crt

Додаємо їх до проекту iOS застосунку FoodSniffer.

certificates

Далі додаємо 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 сертифікат не входить до списку дозволених сертифікатів. Користувач побачить наступну помилку:

error

Хакери подолані!

Однак, існує невелика проблема. Як мені, розробнику, контролювати 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();
  1. За повним ім'ям функції знаходимо вказівник на ValidateTrustCertificateList у бінарному файлі застосунку.
  2. Обгортаємо вказівник у NativeFunction обгортку, вказуючи тип параметру та вихідного значення функції.
  3. Змінюємо імплементацію функції ValidateTrustCertificateList на таку, що завжди повертає 1 (тобто true).

Весь скрипт розміщено у файлі {sourceroot}/fridascrpts/killCertPinnig.js.

Однією з проблем є отримання повного імені функції _T016FoodSnifferFrida0A15ListAPIConsumerC024validateTrustCertificateD0SbSo03SecG0CF.

Я використовував наступну техніку:

  • Створив у застосунку додатковий таргет FoodSnifferFrida
  • Під'єднав до нього бібліотеку FridaGadget.dylib. Докладніше про підключення бібліотеки можна дізнатися тут
  • Запустив на симуляторі застосунок FoodSniffer
  • Використав наведену команду для пошуку повного імені функції validateTrustCertificateList

frida-trace -R -f re.frida.Gadget -i "validateTrust"

  • Отримав результат у такому вигляді:

result

Наостанок використав його у killCertPinnig.js.

Дізнатися чому у функції наприкінці вийшло таке «дивне» ім'я та що означають T016 та 0A15 можна за посиланням.

Відключення SSL-pinning

Запустимо FoodSniffer з вимкненим SSL-pinnig!

  • Запустимо Charles Proxy.
  • Запустимо таргет FoodSnifferFrida у симуляторі проекту Xcode. Ми повинні побачити білий екран. Застосунок очікує підключення Frida.

white-screen

  • Запустимо Frida для виконання скрипта killCertPinnig.js:

    frida -R -f re.frida.Gadget -l ./fridascrpts/killCertPinnig.js

  • Дочекаємось під'єднання до iOS застосунку

connecting

  • Продовжимо роботу застосунку командою %resume

resume

  • Тепер ми маємо побачити список продуктів

food-list

  • Та JSON у Charles Proxy

location

json

Профіт!

Висновок

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 версій застосунків.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.6K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація