2016 год ещё не кончился, но продолжает радовать нас крутыми продуктами по обработке изображений. Сначала все болели FaceSwap, потом появился MSQRD, теперь у нас есть Prisma. Ещё больше радости/гордости, конечно, от того, что последние 2 продукта — наши, родные. MSQRD делают ребята из Беларусии, Prisma же вообще родом из Москвы. Логично, что у любого популярного продукта сразу начинают плодитьяся конкуренты. Призме в этом плане повезло больше всех — благодаря стечению некоторых обстоятельств, основным конкурентом призме стали Mail.ru Group, которые почти сразу выпустил аж 2 похожих продукта со схожими функциями: Vinci (от команды vk.com) и Artisto (от команды my.com).
А лично мне стало интересно посмотреть на эти «клоны» изнутри. Зачем мне всё это и к каким выводам я пришёл — об это я рассказал на roem.ru, повторяться не вижу смысла. На Хабре же я бы хотел поделиться техникой детального анализа приложений для iOS на примере Prisma.
Что нам предстоит? Во-первых, мы узнаем, что есть приложение для iOS и из чего оно состоит, какую информацию можно оттуда извлечь. Во-вторых, я расскажу как снифать траффик client-server приложений, даже если их авторы этого очень сильно не хотят. По факту я не расскажу вам ничего нового, я не придумал никакого ноу-хау, это просто вектор известных техник и умений на приложения. Но будет интересно. Погнали.
iOS-приложение, IPA-файл
iOS-приложение представляет собой .ipa-файл. По факту это zip-архив и может открываться любым архиватором (да да, mobilz обещал научить ломать приложения, а на самом деле покажет, как пользоваться архиватором). Сам .ipa-файл проще всего получить с помощью iTunes — в разделе «Программы» есть вкладка AppStore, которая является аналогом AppStore в iPhone. Соответственно, вам потребуется аккаунт (AppleID); загрузив приложение с помощью iTunes мы можем перейти в директорию к нему.
Далее .ipa-файл, как я уже говорил, открывается любым архиватором. Внутри мы найдём помимо прочего директорию Payload и файл iTunesMetadata.plist. В Payload находится наше приложение, а точнее директория с расширением .app, которую MacOS будет пытаться запустить, но нам достаточно просто открыть содержимое. iTunesMetadata содержит метаинформацию из AppStore. Какой аккаунт скачал приложение, в каком разделе приложение находится и т.д. и т. п. Ничего интересного в нём для нашего анализа нет, идём непосредственно к .app. Разберём сразу на конкретном приложении, Prisma 2.3 — Payload/Prisma.app.
В разных проектах мы можем увидеть разную структуру, но всегда точно будет Info.plist (Payload/Prisma.app/Info.plist). Это основные настройки приложения, такие как минимальная версия для запуска, поддерживаемые ориентации, поддержка iPad и прочее. Тут уже интереснее:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>DTPlatformVersion</key>
<string>9.3</string>
<key>DTSDKName</key>
<string>iphoneos9.3</string>
<key>CFBundleName</key>
<string>prisma</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
<key>CFBundleIcons</key>
<dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>AppIcon29x29</string>
<string>AppIcon40x40</string>
<string>AppIcon60x60</string>
</array>
</dict>
</dict>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>CFBundleDisplayName</key>
<string>Prisma</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>instagram</string>
<string>fb</string>
<string>fbauth2</string>
<string>fbshareextension</string>
<string>fbapi</string>
<string>fb-profile-expression-platform</string>
<string>vk</string>
<string>vk-share</string>
<string>vkauthorize</string>
</array>
<key>DTSDKBuild</key>
<string>13E230</string>
<key>CFBundleShortVersionString</key>
<string>2.3</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>iPhoneOS</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>Pushwoosh_APPID</key>
<string>46F12-BE2E4</string>
<key>BuildMachineOSBuild</key>
<string>15G31</string>
<key>DTPlatformBuild</key>
<string>13E230</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>DTXcodeBuild</key>
<string>7D1014</string>
<key>CFBundleVersion</key>
<string>40</string>
<key>UIStatusBarHidden</key>
<true/>
<key>FacebookAppID</key>
<string>582433738573752</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
</array>
<key>Fabric</key>
<dict>
<key>Kits</key>
<array>
<dict>
<key>KitName</key>
<string>Crashlytics</string>
<key>KitInfo</key>
<dict/>
</dict>
</array>
<key>APIKey</key>
<string>8e17945e7d29d1c775f321348caef29075f5ab9a</string>
</dict>
<key>FacebookDisplayName</key>
<string>Prisma.AI</string>
<key>CFBundleIdentifier</key>
<string>com.prisma-ai.app</string>
<key>DTXcode</key>
<string>0731</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>vk.com</key>
<dict>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
<key>cdninstagram.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
<key>CFBundleExecutable</key>
<string>prisma</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>DTPlatformName</key>
<string>iphoneos</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>fb582433738573752</string>
</array>
</dict>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>vk5530956</string>
</array>
</dict>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>prisma</string>
</array>
</dict>
</array>
</dict>
</plist>
Про ключи вы можете почитать подробнее в официальной документации, нас же интересуют лишь некоторые из них.
Во-первых, мы можем добыть API-ключи для сторонних продуктов (вроде crashlytics), идентификаторы групп/страниц в vk/facebook. Во-вторых, мы можем точно знать детальные настройки урлов, куда ходит приложение:
LSApplicationQueriesSchemes
<key>LSApplicationQueriesSchemes</key>
<array>
<string>instagram</string>
<string>fb</string>
<string>fbauth2</string>
<string>fbshareextension</string>
<string>fbapi</string>
<string>fb-profile-expression-platform</string>
<string>vk</string>
<string>vk-share</string>
<string>vkauthorize</string>
</array>
Видим, что приложение захочет работать с инстаграмом, фейсбуком и вконтактом. Эта информация никаким образом, конечно, не поможет «сломать» приложение, но даст нам дополнительную информацию.
NSAppTransportSecurity
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>vk.com</key>
<dict>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
<key>cdninstagram.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
Если не ошибаюсь, флаг появился с 9-ой версии iOS. Рассказывает нам, куда приложение будет ломиться по http-протоколу (https доступен любой домен). Т.е. при всём желании приложение не сможет обратиться на http, если не настроен NSAppTransportSecurity.
Ключ в целом тоже нам особо ничего не даёт, кроме как информации. Но курочка по зёрнышку. Видим, что приложение хочет ломиться по http на vk.com и cdninstagram.com. Ок.
CFBundleURLTypes
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>fb582433738573752</string>
</array>
</dict>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>vk5530956</string>
</array>
</dict>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>prisma</string>
</array>
</dict>
</array>
Пожалуй, самый интересный из бесполезных ключей. Опять же, он нам ничего особо полезного не даёт, но рассказывает о зарегистрированных урлах на этом конкретном приложении. Например, если в мобильном сафари вы наберёте fb582433738573752:// вас перекинет в приложение (если оно установлено, конечно). Данную информацию, повторюсь, тоже можно отнести к условно-бесполезной. Но пару раз я натыкался на приложения, где помимо стандартных урлов соцсетей я находил урлы типа «app-admin» или «app-dev». При переходе на которые можно было получить скрытые настройки приложения. В одном приложении я получил редакторский доступ к одному изданию, в котором можно было публиковать статьи, пихать их на главную, удалять, менять местами итп.
Далее нас интересует директория Frameworks: Payload/Prisma.app/Frameworks
В ней мы найдём ещё немного бесполезной информации об используемых внешних фреймворках и SDK.
Подробнее я напишу чуть ниже про каждый фреймворк, но заранее могу сказать, что, например, отсюда можно тоже выцепить интересную информацию. Среди прочих фреймворков видим FLAnimatedImage, который работает с анимированными GIF — невольно напрашивается вывод, что Prisma всё же будет и с видео.
Также в метафайлах часто можно найти какой-то мусор, забытый разработчиками. Часто я нахожу README.md, .gitignore, лицензии и прочее. Опять же, Prisma хороший пример, т.к. с версии 2.3 в ней появилась просто забытая разработчиками фотка одного из друзей. Те, кто докопаются, прошу не выкладывать имя человека в комментах, он очень просил.
Также находим USERTrustRSAAddTrustCA.cer — уже интересней. Насколько я понял это запароленный сертификат, аналоговый брутфорс профита не дал.
Непосредственно в бинарнике часто находятся скрытые plist, которые могут быть интересными. Также, если приложение собрано на webview-технологиях (типа Cordova), мы найдём config.xml кордовы и, собственно, исходник приложения. Например, приложение Sworkit, которое помимо прочего предлагает пользователям оплатить дополнительные видеоуроки, уже имеет их в исходниках в удобном .mp4-формате. Хочешь удобно — плати. Хочешь неудобно, но бесплатно — читай на Хабре эту статью.
Пожалуй, это основное, что можно добыть из пакета с приложением. Точнее основное, что удалось добыть мне. Уверен, вы ребята умные и найдёте ещё больше полезного — пишите в комментарии и мы добавим это в статью. Но, опять же, всё индивидуально. В некоторых приложениях можно найти очень многое, в том числе и целиком исходники.
Ну и чуть подробнее о самом приложении. Призма (2.3) 17.6Мб. Нативный swift, поддержка русского и английского языка. Фреймворки:
Alamofire.framework — http-клиент
AlamofireImage.framework
AlamofireNetworkActivityIndicator.framework
Bolts.framework — вспомогательный набор инструментов для разработчика
FBSDKCoreKit.framework — facebook
FBSDKShareKit.framework — facebook
FLAnimatedImage.framework — библиотека для работы с видео
KeychainAccess.framework — враппер авторизации. Чаще всего используется для работы с touchid
Obfuscator.framework — обфускатор, тут, думаю, объяснять не нужно
PINCache.framework — key/value хранилище для больших объектов с поддержкой работы в разных потоках.
PINRemoteImage.framework — модуль picache
pop.framework — библиотека для работы с анимацией. Чаще всего используется для UI-анимации.
RHBOrientationObjC.framework — работа с акселерометром, точнее, с ориентацией устройства.
SDWebImage.framework — ещё один http_клиент/кешер для изображений
SwiftyJSON.framework — удобная работа с json
Swinject.framework — DI паттерн разработки
VK_ios_sdk.framework — vk.com
Что в итоге мы имеем? Мы собрали кучу информации о работе приложения и понимаем, чего от него ожидать. Мы нашли некий сертификат и сохранили его себе. Мы узнали, что нас скоро ждёт видео и нашли классную фотку друга основателя Prisma. Двигаемся дальше.
Сниффаем HTTP
Prisma и тут оказалась хорошим примером для статьи. Если те же Artisto и Vinci ходят по голому http и нет труда их сниффать, Prisma ходит по https с проверкой подлинности сертификата. И тут начинаются танцы с бубнами. Но давайте по порядку.
1. Для начала нам нужен http(s)-прокси. Я пользуюсь Charles, он достаточно простой и функциональный.
2. Нам нужно iOS-устройство. Эмуляторы не подойдут.
3. Нам нужна одна сеть между устройствами. Самое простое — Wi-Fi.
На своём терминале запускаем прокси, заодно включаем и https-прокси. На устройстве, соответственно, в настройках Wi-Fi-сети прописываем руками прокси (IP нашего терминала и порт):
Дальше чаще всего достаточно подсунуть в iOS свой сертификат. Как это сделать хорошо написано на том же сайте Charles. Но в случае с Prisma этого сделать не получилось — разработчики не лыком шиты и проверяют подлинность сертификата. Но делается это средствами устройства, а мы тоже хитрюги те ещё. Однако для того, чтобы заставить iOS не проверять сертификат на подлинность, нам потребуется jailbreak.
Действуйте можно было сделать до версии iOS 9.3.3, и то на свой страх и риск — как вариант, используйте тематические ресурсы и внимательно читайте комментарии. В частности, некоторый софт «типа для джаилбрейка» может попросить AppleID вместе с паролем, что чревато исчезновением данных и денег денег с привязанной карты. Все эти тонкости подробно описаны.
Я не буду описывать как делал это я, т.к. разлочка сильно отличается в зависимости от версий устройства и iOS. Единственное, зачем нам это надо в данном случае — https://github.com/nabla-c0d3/ssl-kill-switch2/releases — крайняя версия ssl kill switch. С помощью Cydia (опять же, всю информацию можно найти в сети) ставим любой просмоторщик файлов, например iFile. И с помощью него заливаем .deb-файл последнего релиза ssl kill switch. Работать ssl kill switch после перезагрузки телефона. Важно не забыть его выключить после всех манипуляций, т.к. в противном случае мы рискуем, ведь наше устройство больше не проверяет подлинность ssl.
Прокси включён, проверка сертификата выключена, погнали смотреть на приложение. Первый запуск — как видим, приложение сначала собирает настройки. Откуда же оно их берёт? Дёргается https://cdn.neuralprisma.com/config.json обычным GET`ом, там стандартные настройки, не интересно. Потом дёргается api3.neuralprisma.com/styles POST-ом с телом
{
"codes": ["public"]
}
На выходе список фильтров. Уже интересней, поигрался с массивом [«public»]. Пытаясь подставить туда что-то вроде «dev», «new» и прочего, профита я не получил, но может у кого из вас получится. Я рекомендую использовать для этого Postman.
Ладно, идём дальше. Следующий запрос при загрузке фото опять POST на урл api3.neuralprisma.com/upload/image
И всё было в моей жизни хорошо, пока я не увидел заголовок prisma-image-sign с base64 от бинарного md5. Жизнь боль. Все мои мечты о том, что я сейчас перехвачу траффик призмы и научусь делать то же самое, что и приложение, но просто по http… рухнули. Что это значит? Таким образом разработчики защищаются от таких как я. Посылая фотку по http, приложение также вычисляет какой-то хеш с какой-нибудь солью и добавляет этот хэш в заголовок. Как сгенерить хэш знает только приложение и сервер. Фотка отправляется на сервак, сервак генерит хеш от фотки по тому же алгоритму, сверяет хэши, если они отличаются — значит запрос подделан. Есть способ обойти это, если вы сильны в ассемблере. Дизассм бинарника + анализ его на тему генерации этого заголовка может дать нам алгоритм. Но учитывая, что там md5 (минимум) + base64, на это может уйти много времени. Ну и не забываем, что всё усложняется ещё и наличием фреймворка Obfuscator.framework. В общем, как я уже писал, жизнь боль.
Вся дальнейшая работа приложения достаточно простая. Картинка отправляется на сервак, в ответ получаем некое имя изображения. При выборе стиля шлются запросы с этим именем приложения + именем стиля, на выходе профит. А ведь победа была так близка.
Ну да ладно, давайте не будем отчаиваться и разберём возможность перехвата API на примере другого подобного приложения Vinci. Как уже говорил, там всё ходит по голому http, поэтому нам даже не придётся коверкать наше устройство. Просто прописываем прокси сервер и смотрим что куда ходит при работе с приложением. Все запросы можно эмулировать в Postman, о котором я уже писал, или реализовать на каком-нибудь серверном языке.
Всё достаточно просто. При запуске первым запросом Vinci собирает доступные стили, далее регистрирует девайс, нам это пофиг. Затем я загрузил фото, отправляется POST-запрос на /preload с фото, с любым фото, в ответ мы получаем некий хэш нашего фото:
Ну и далее, как видно, получаем уже готовое изображение, обращаясь по урлу http://vinci.camera/process/2_gNmHxDdthLsmPtuXGxRzQnKjbbspfO/21, где 2_gNmHxDdthLsmPtuXGxRzQnKjbbspfO
хеш фото, а 21 — номер фильтра, которые можно добыть из http://vinci.camera/list
Вот и всё. Итого, чему мы научились сегодня? Мы научились собирать информацию о приложении по метафайлам самого приложения и научились сниффать трафик приложения, даже если всё API ходит по https с проверкой подлинности сертификата. Мы научились собирать информацию из API: что, куда, зачем, а также я показал некоторые «тупики».
За сим не буду отвлекать вас от увлекательных анализов приложений, если кто что интересное найдёт, скидывайте в комменты, вместе посмеёмся.
Кстати, интересный факт. Те же SQLinj. В вебе их сейчас уже сложно встретить, любой разработчик понимает всю опасность инъекций. Но вот мобильные разработчики, которые часто приходят в мобильную разработку не из веба (а даже когда из веба — доверяют своим API, типа «да кто его знает наше API-то, только наше приложение!») частенько забывают про веселье, что может их ждать при полном доступе к базе удалённым пользователем.
Автор: mobilz