Интересные статьи об Apple, приложениях для iPhone и iPad, iTunes

Ломаем iOS-приложение. Часть 1

Ломаем iOS-приложение. Часть 1

Вы хорошо поработали, и вот ваше приложение в App Store!

  • Храните учётные записи юзеров?
  • Используете встроенные покупки?
  • Не хотите показывать своё ноу-хау?


Повод задуматься о безопасности кода и данных! Мы будем искать уязвимости в тестовом приложении. В этой статье поговорим о безопасности данных, а в следующей — перейдём к коду.

Disclaimer


Цель данного урока — не сделать вас хакером, а показать, как злоумышленники могут обвести вас вокруг пальца. В статье пропущена кое-какая инфа, необходимая для взлома реального приложения на девайсе. Будем мучить симулятор (кстати, это вроде бы даже законно[citation needed]).

Disclaimer от переводчика: из оригинального текста убрано много «воды» и отсылок к Голливуду (и так длинно). Добавлен ряд ключевых пояснений.

Итак


Ни одно приложение не является безопасным! Если кто-то действительно захочет найти у вас уязвимости, то он их найдёт. Нет гарантированного способа предотвратить атаку. Разве что, не выпускать приложение. Но есть отличные способы помешать взломщикам. (По мнению автора, им надоест, и они пойдут спать искать более лёгкую добычу, ага. — Прим. пер.)

Перед тем, как читать дальше, вы должны примерно понимать, что такое терминал, а также Objective C и Cocoa (впрочем, особо много кода не будет).

Приступим


Нам понадобится:
1. Утилита class-dump-z;
2. Прокси для отладки по сети, например, Charles (триал-версия отличается надоедливыми сообщениями и работает максимум 30 минут за 1 сеанс). В комментах к исходнику статьи советуют альтернативу Чарльзу — Burpsuite.

Чтобы вы творчески подошли к процессу, предлагаю вам сценарий. Представьте: вышло новое приложение для айпада — «Собиратель мемов» (Meme Collector). Всем нравится. Но вам шепнули на ухо, что встроенные покупки вытянут из вас значительную сумму денег.



В общем, вы задумали получить платный контент (мемы) бесплатно. Есть несколько направлений, куда можно двигаться, мы поговорим о них — и о соответствующих методах защиты.

Небольшое упрощение

Из-за длины этого урока мы изначально допустили некоторые упрощения в тестовом проекте. Например, «покупка» игровой валюты представляет из себя не настоящий in-app purchase, а фэйковый запрос к StoreKit (всё происходит локально на девайсе).

Что к чему? «Карта» приложения (application mapping)


Взглянем на приложение с высоты птичьего полёта! Что оно делает, с точки зрения пользователя? Какова его основная структура?

Откройте проект Meme Collector в своей любимой IDE или в Xcode (не реклама).



Мы будем запускать приложение в конфигурации Release.
Xcode: Product > Scheme > Edit scheme… (⌘<) — слева выбрать Run…, справа вкладка Info > Build Configuration: Release.
AppCode: Run > Edit configurations… > Configuration: Release.

Запустите приложение на симуляторе iPad (⌘R). А теперь забудьте, что у вас есть исходники, вы — юзер. Откроется одно из двух:

У вас вариант слева?

Это хабраэффект или проблема с соединением. Мемы берутся с API memegenerator.net — проверьте, что этот урл выдаёт {"success":true,"result":[список мемов]}. Во время перевода статьи у них были проблемы с сервером. Если есть желание избавить проект от зависимости от данного API, велкам на гитхаб.


Этот странный интерфейс позволяет «купить» мем по тапу на него, а также показывает число покупок данного мема и остаток денег на вашем счёте. От переводчика: нажав кнопку «Purchase Currency», я реально задумался: а не вводил ли я Apple ID на симуляторе?



…но вспомнил, что у меня к US-аккаунту не привязана кредитка. :) Русский бы сказал цену в рублях.

В общем, всё просто. Есть «игровая валюта», и вы (типа взломщик) не хотите отдавать за неё реальные деньги. Наша цель — получить больше мемов, не нажимая кнопку «Purchase Currency».

Мы примерно поняли, что делает приложение, давайте заглянем глубже. Что ещё мы можем узнать?

Упомянутая утилита class-dump-z отобразит нам все объявления (declarations), которые она сможет достать из исполняемого файла. Скачайте последнюю версию утилиты (например, 0.2a), распакуйте архив. В терминале перейдите в папку class-dump-z_0.2a/mac_x86/. Там есть исполняемый файл class-dump-z, установите его, например, скопировав в /usr/bin/:

<code class="bash">sudo cp class-dump-z /usr/bin/

Перейдите в папку симулятора iOS: (вместо 7.0.3 — ваша версия)

<code class="bash">cd ~/Library/Application\ Support/iPhone\ Simulator/7.0.3/Applications/

Здесь лежат все приложения, которые вы запускали в симуляторе. Их там может быть много. Как найти Meme Collector?

Вариант 1. Если вы только что запускали Meme Collector, то он лежит в папке с самой новой датой модификации. Просто перейдите в неё:

<code class="bash">cd `ls -tr | tail -1`

Это что за магия?

  • Команда ls выдаёт содержимое папки. Флаг "-tr" сортирует по времени: самая новая папка будет последней в списке.
  • Весь этот список поступает на вход команды tail. Она берёт некоторый объём данных с конца. В данном случае — ровно одну строчку (флаг "-n 1" или просто "-1").
  • Далее команда cd переходит в эту самую новую папку.

Вариант 2. Взять и найти:

<code class="bash">find . -name "Meme Collector"


В моём случае папка называется 9A72F266-8851-4A25-84E4-9CF8EFF95CD4 — далее будем называть её просто «папкой приложения». В ней лежит:

  • Meme Collector.app — главный бандл (main application bundle)
  • ещё что-то :)

Бандл — это папка (именно папка, да, с расширением .app), в которой есть один или несколько исполняемых файлов плюс ресурсы к ним (картинки, звуки, цифровая подпись, в общем, что угодно, даже другие бандлы… read more). Перейдём в папку основного бандла:

<code class="bash">cd Meme Collector.app

Тут есть исполняемый файл, который так и называется: Meme Collector (без расширения). Посмотрим, на какие фреймворки (frameworks) и общие библиотеки (shared libraries) имеются в нём ссылки. Нам поможет стандартная утилита otool:

<code class="bash">otool -L "Meme Collector"

И вот что видим:

<code class="bash">Meme Collector:
	/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration (compatibility version 1.0.0, current version 615.0.0)
	/System/Library/Frameworks/MobileCoreServices.framework/MobileCoreServices (compatibility version 1.0.0, current version 51.0.0)
	/System/Library/Frameworks/Security.framework/Security (compatibility version 1.0.0, current version 1.0.0)
	/System/Library/Frameworks/StoreKit.framework/StoreKit (compatibility version 1.0.0, current version 1.0.0)
	/System/Library/Frameworks/QuartzCore.framework/QuartzCore (compatibility version 1.2.0, current version 1.8.0)
	/System/Library/Frameworks/UIKit.framework/UIKit (compatibility version 1.0.0, current version 2903.23.0)
	/System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1047.22.0)
	/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics (compatibility version 64.0.0, current version 600.0.0)
	/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 227.0.0)
	/usr/lib/libSystem.dylib (compatibility version 1.0.0, current version 111.0.0)

Мм, интересно, приложение использует фреймворк Store Kit — встроенные покупки? Ну-ка, идём дальше. Запускаем утилиту class-dump-z:

<code class="bash">class-dump-z "Meme Collector" > ClassDump.txt

И откроем полученный файл в стандартном гуёвом редакторе:

<code class="bash">open ClassDump.txt

Ого! Хотя нет, по сравнению с примером из оригинальной статьи (Xcode 4, iOS 6 SDK) появились неприятные моменты типа XXUnknownSuperclass, но всё же! Мы видим не только интерфейсную часть, но и объявления закрытых (private) методов, свойств, протоколов.

Изучение дампа классов серьёзного проекта — чаще всего утомительное занятие. Но оно может дать потрясающую картину внутреннего устройства приложения!

Итак, мой юный следопыт! Найдите-ка мне все синглтоны в приложении.

Подсказка

В сигнлтонах часто встречаются объявления методов класса с ключевыми словами: manager, shared, store.


Если встретите интересный синглтон, изучите все его методы.
(Видимо, «интересный» = «похоже, имеющий отношение к логике приложения». — Прим. пер.)

Решение

Точно нашли? Посмотрите у себя внимательнее. Их там около четырёх штук.

Да, все.

  • AFNetworking
  • MemeManager — интересный синглтон! Он содержит:
    <code class="cpp">@property(retain, nonatomic) AFHTTPClient* client;
    @property(readonly, assign, nonatomic) NSMutableArray* memes;
    +(id)sharedManager;
    -(void)memePurchased:(int)purchased;
    -(void)getMemeInformation; // кстати, это private-метод
    
  • MoneyManager — тоже интересный синглтон:
    <code class="cpp">@property(readonly, assign, nonatomic) NSNumber* money;
    +(id)sharedManager;
    -(BOOL)saveState; // кстати, это private-метод
    -(void)loadState; // кстати, это private-метод
    -(BOOL)purchaseCurrency;
    -(BOOL)buyObject:(id)object;
    
  • SVProgressHUD


Взломщики часто делают то, что вы сейчас сделали — ищут определённые слова в дампе класса. Например, если они хотят узнать, есть ли в приложении логика, завязанная на джейлбрейк девайса, то поиск по словам 'jailbroken', 'security' и т.д. может дать быстрый ответ.

Как это предотвратить?


Есть две новости. Начну с хорошей.

Apple делает это за вас автоматически. Когда вы отправляете приложение в App Store, они шифруют ваши бинарники с использованием DRM-технологии под названием FairPlay. Если вы сдампите class-dump-z'ом зашифрованный бинарник, то получите… тарабарщину.

Плохая новость: обойти эту защиту довольно легко! Дело на 10 минут (вручную), а ещё есть средства автоматизировать сей процесс. В общем, будьте уверены, ваше приложение расшифруют и увидят названия всех ваших классов, методов, протоколов и т.д.

Plist-файлы: уязвимы!


Мы кое-что узнали о приложении. Теперь поищем всё, что плохо лежит. Разработчикам свойственно ошибаться. Злоумышленникам свойственно использовать эти ошибки в своих целях.

Ошибки… Начнём с самых глупых! Например, как вам идея: использовать файл .plist для хранения критической информации? Взломщики всегда смотрят plist'ы: их легко просмотреть, а их изучение может дать ключ к внутренней логике приложения. (Plist = property list, текстовый файл с сериализованными объектами).

Посмотрим, какие plist'ы у нас есть?
(напомню, мы всё ещё находимся в папке бандла «Meme Collector.app»)

<code class="bash">ls *.plist

А вот какие — две штуки:

<code class="bash">Info.plist           MoneyDataStore.plist

Посмотрим первый — Info.plist:

<code class="bash">plutil -p Info.plist

Результат:

<code class="bash">{
  "DTSDKName" => "iphonesimulator7.0"
  "CFBundleName" => "Meme Collector"
  "CFBundleDevelopmentRegion" => "en"
  "CFBundleVersion" => "1.0"
  "DTPlatformName" => "iphonesimulator"
…и ещё куча всего…

Ничего интересного, какая-то общая информация. Ладно. Ну-ка, а второй:

<code class="bash">plutil -p MoneyDataStore.plist

И видим:

<code class="bash">{
  "userCurrency" => 108
}

Прикол! Этот .plist содержит ключ userCurrency с тем же значением, которое вы только что видели на экране симулятора. Значит ли это, что приложение хранит деньги в .plist'е? Есть только один способ узнать!

<code class="bash">open MoneyDataStore.plist


Измените значение userCurrency, например, на 1234. Сохраните и закройте файл .plist.

Вернёмся к симулятору. Уберём наше приложение из оперативной памяти (как говорят, «из многозадачности»).

На всякий случай инструкция.

Сочетание клавиш ⇧⌘H в симуляторе iOS соответствует нажатию кнопки Home. Нажмите это сочетание дважды, чтобы показать экран многозадачности, и смахните Meme Collector в никуда вверх.

Кажется, он не очень доволен, что его смахивают:

В дальнейшем, когда я буду говорить «перезапустите приложение», я буду иметь в виду именно эту операцию.

Снова запустите приложение. 1234 виртуальных тугриков в вашем распоряжении!
От переводчика: я ждал большей интриги. Не бывает так просто! Хоть свой тестовый проект пиши.

Пользовательские настройки: небезопасно!


Ну вы поняли? Хранить важную информацию в .plist — не самая лучшая идея. А теперь угадайте: что представляют из себя пользовательские настройки (NSUserDefaults)? Ага, тоже plist! Физически он лежит по адресу {App Directory}/Library/Preferences/{Bundle Identifier}.plist.

Это как раз то место, где разработчики часто ошибочно чувствуют себя в безопасности. Не только фрилансеры: крупные корпорации время от времени попадают в эту ловушку. Бесчисленное множество приложений хранят важные данные в NSUserDefaults. Посмотрим, что нам здесь приготовил Meme Collector?

Из терминала (мы по-прежнему находимся в Meme Collector.app, так?) откройте файл:

<code class="bash">open ../Library/Preferences/com.selander.Meme-Collector.plist


Несложное упражнение для вас: используя методы, о которых говорилось выше, модифицируйте NSUserDefaults так, чтобы получить кучу мемов из серии «Y U No …» бесплатно. Даже если вы уже обо всём догадались, советую сделать это для закрепления.



Но ведь злоумышленники могут получить доступ к plist-файлам, даже когда iOS-девайс заблокирован — так где же безопасно хранить данные? Одно из решений — хранить данные в NSUserDefaults в зашифрованном виде. В этом случае (и не только. — Прим. К.О.) нужно проверять на валидность данные, читаемые оттуда.

Связка ключей: лучшие рецепты


Другое возможное решение — перенос важных данных из .plist'ов в Связку ключей iOS (Keychain). Как это сделать — написано, например, здесь.

Связка ключей «повышает ставки» для хакера. Злоумышленники не смогут ничего стянуть, если устройство заблокировано.

Тем не менее, не стоит полностью полагаться на одну только Связку ключей! И вот почему. Связку ключей поддерживает Apple. (Ну вы уже всё поняли, да?) Информация в ней зашифрована паролем пользователя, который обычно является простым 4-значным цифровым кодом. А это значит, что атака брутфорсом займёт минут двадцать. Узнав пароль, легко сдампить всю связку ключей.

Что же делать? Некоторые общие рекомендации:

  • Шифруйте данные! Связка ключей вроде как безопасна, но это приоритетная цель для хакеров, поэтому её ломают в первую очередь. (Про джейлбрейкнутые устройства вообще нечего говорить — для них существуют утилиты, показывающие содержимое Связки ключей.) Усложните жизнь взломщикам хотя бы ненамного: шифруйте данные с помощью CommonCrypto API, который входит в состав стандартного Security Framework (пример).
  • Не прописывайте ключ шифрования в приложении. Длинная строка в секции данных представляет потенциальный интерес для хакера. И вообще: если закрытый ключ прописан в приложении, злоумышленник может выложить его в сеть, скомпрометировав данные всех пользователей приложения! Обязательно генерировать уникальный ключ шифрования на каждом устройстве.
  • Контролируйте код! Конкретно — следите за возможностью хакера использовать ваш код в своих целях. Ваш метод шифрования/дешифрования может быть самым прекрасным решением. Но хакеры возьмут отладчик и применят ваш метод дешифрования к вашим же зашифрованным данным. Вы увидите это во второй части данного туториала (следующий пост).
  • Это точно нужно хранить? Раз злоумышленник может искать, модифицировать и выполнять ваши бинарники — просто спросите себя: эту информацию действительно нужно хранить на девайсе?

Сеть: тестирование на проникновение


Ещё хакеры любят наблюдать, как приложение взаимодействует с сетью. Самый тупой способ увидеть, происходит ли какая-то работа с сетью на устройстве, это поискать URL'ы в бинарнике.

Находясь в папке бандла (Meme Collector.app), наберите в терминале:

<code class="bash">strings "Meme Collector"

Стой, куда столько! Команда strings идёт по разделам бинарника и выводит все элементы данных, похожие на строки. Отфильтруем шум:

<code class="bash">strings "Meme Collector" | grep http

А, ну вот, одна строка:

<code class="bash">http://version1.api.memegenerator.net/Generator_Select_ByUrlNameOrGeneratorID


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

Charles, упомянутый в начале статьи — хороший вариант для такого исследования. Скачайте его, если ещё не сделали это. Установите и запустите.

Проверьте, что Charles ловит сетевое взаимодействие с симулятора iOS (путём запуска «Карт», или в Safari набрать урл). Вы увидите, как в Charles побегут сетевые запросы. Если этого не произошло, убедитесь, что в меню Proxy > Mac OS X Proxy галочка установлена.

Кстати, Charles отлично перехватывает SSL-трафик.

Мы не будем это делать, потому что не увидели HTTPS-урлов на выходе команды strings. Но этот шаг потребуется для других приложений, которые могут использовать HTTPS. В меню Proxy > Proxy Settings… > SSL нужно включить SSL-прокси и добавить домены, по которым требуется перехватывать (и расшифровывать) HTTPS-трафик. Пока вы это не сделаете, SSL будет выглядеть примерно так:


При работающем Charles перезапустите Meme Collector. После запуска вы должны увидеть три запроса к http://version1.api.memegenerator.net, нажав на треугольник слева от урла (см. ниже). Раскроются три запроса, отличающиеся GET-параметрами. Например, у первого один GET-параметр: urlName=Foul-Bachelor-Frog — это видно на вкладке Request.

Выберите вкладку Response и затем внизу JSON. Ответ сервера, расшифрованный из JSON, представлен в виде красивой таблички:



Мы видим здесь множество строк вида «ключ-значение»: заголовок (displayName), описание (description), URL картинки (imageUrl) — в общем, некая информация по данному типу мемов «Four Bachelor Frog» из GET-параметра.

Что происходит дальше? Приложение трижды делает одно и то же: HTTP-запрос по урлу картинки, пытается скачать её с cdn.memegenerator.co, получает 301-й редирект на cdn.memegenerator.net и качает оттуда.



Похоже на то, что мы видели в приложении, правда? Двум другим картинкам в этот раз повезло меньше, они так и не дождались ответа от сервера (Charles сообщает об этом на вкладке Overview) и поэтому не отобразились в приложении.

А я вообще не вижу, чтобы запрашивались картинки!

При повторном запуске картинки могут браться из кэша симулятора, Charles лб этом не знает. Очистите кэш и перезапустите приложение.

<code class="bash">rm -R Library/Caches/



Итак, с определённой долей вероятности делаем вывод: приложение берёт мемы с данного API и представляет их в виде платного контента. А что если попытаться изменить URL с целью приобрести какой-то новый контент, кроме этих трёх мемов? Непохоже, что тут есть проверка, действительно ли приложение получает с сервера то, что ожидал разработчик!

Вам уже надоели эти три мема? Ну-ка попробуем, можно ли отобразить и «купить» что-то новое, скажем, «Success Kid».

Выберите в меню Charles: Tools > Rewrite. Эта функция позволяет перехватывать входящие/исходящие запросы и модифицировать их по тем правилам, которые вы установите. Включите галочку Enable Rewrite. Правила группируются в «наборы» (Sets). Под списком Sets нажмите Add для добавления нового набора правил. По желанию, переименуйте (Name). Мы создали набор правил, но он пока пустой. Давайте добавим правило — в разделе Rules есть кнопка Add, нажмите её.



Открылось окно Rewrite Rule. Измените Type на «Modify Query Param» («Изменить параметр запроса») и заполните два поля:

  • Match > Name: urlName
  • Replace > Value: success-kid



Нажмите OK, OK. Перезапустите приложение… Success! Мы можем покупать контент, который ранее был недоступен.



Интересно: для этого нового мема указана конкретная цена. Откуда? Приложение должно было как-то определить стоимость, исходя из JSON-ответа.

Откройте вкладку Response и посмотрите на JSON, который возвращает сервер. Что может определить стоимость цены?

Попробуйте найти JSON-ключи, которые могут определить стоимость мема в приложении. Может быть, это generatorID, totalVotesScore, instancesCount, templatesCount или ranking. В качестве упражнения для вас: найдите тот ключ, который влияет на стоимость мема.

Чтобы сделать это, перейдите к Proxy > Breakpoints. Нажмите Enable Breakpoints и нажмите Add, чтобы добавить новую точку останова. Появится окно Edit breakpoint, введите в нём следующие данные:

  • Protocol: http
  • Host: version1.api.memegenerator.net
  • опция Response включена



Теперь перезапустите приложение. Как только нам придёт ответ с сервера, сработает точка останова (breakpoint). Когда это произойдёт, щелкните на вкладке Edit Response, внизу выберите JSON:



Здесь вы можете вручную модифицировать JSON-ответ, который пойдёт в приложение. Поиграйтесь с этими параметрами и попробуйте определить, какие ключи влияют на цену, отображаемую в приложении. Изменив JSON-ответ, нажмите Execute (выполнить) для отправки ответа. Приложение делает три запроса к API, поэтому вам понадобится нажать Execute трижды.

Нашли, какой ключ влияет на цену?

Это ranking. Чем больше ranking, тем меньше цена.


Важно: действуйте быстро! AFNetworking имеет таймаут 30 секунд. Если вы перехватили ответ, но не успели внести изменения, AFNetworking вернёт ошибку таймаута запроса и выполнит соответствующий обработчик в коде (который в данном случае не делает ничего). Если у вас вышло время, перезапустите приложение и попробуйте снова.

Что дальше?

Вы открыли в себе хакерские способности и выполнили простейшие тесты на проникновение на примере файловой системы и сетевого взаимодействия конкретного приложения. Вы победили простые plist'ы и даже можете модифицировать ответы сервера.

Возможно, эти знания повысят безопасность вашего iOS-приложения… чуть-чуть. В следующей части мы углубимся гораздо дальше в недры приложения, будем изменять его функционал! А пока вы ждёте, когда я выложу перевод (до следующей пятницы), можно много чего попробовать на тему хранения данных в приложении:

  • Попробуйте такие средства, как iExplorer. iExplorer позволяет изучить содержимое файлов, которые используются вашими реальными приложениями. Посмотрите, что они там хранят.

    Без джейлбрейка? Да ну?

    Серьёзно!

  • Настройте SSL-прокси в Charles и проверьте его на реальных приложениях. Ну-ка, что отсылает это приложение с вашего девайса, тихо сидя в углу с милой UI'ыбкой? Вероятно, вы удивитесь. А наблюдение за сетевым взаимодействием приложений-конкурентов может обеспечить потрясающее понимание того, как они решили проблемы сетевого взаимодействия, схожие с вашими.
  • Подтяните свои знания по ассемблеру iOS (ARM). Автор советует этот туториал (на английском). Следующая часть нашего урока будет гораздо более насыщенной! Готовьтесь.



Замечания по поводу перевода или неработающих примеров можно отправлять на почту dev@x128.ru.

Автор: x256