Как собрать логи с приложения на ios устройстве
Как собрать логи с приложения на ios устройстве
Сборка и анализ логов для iOS с помощью утилиты Console
Запись создана Viktor Akselrod в Общее · 6 июля, 2021
Здравствуйте.
В этой статье мы поговорим об отладке приложений на iOS с помощью логирования (часть материала будет актуальна и для Android).
Общие сведения.
Логирование позволяет получать информацию о работе вашего приложении даже когда оно запущено без отладчика, при фатальных падениях приложения и тд.
Особенно тема логирования актуальна для мобильных платформ, т.к. здесь, к сожалению, Delphi отладчик не может похвастаться такой же стабильностью и качеством, как отладчик для платформы Win32. Пошаговая отладка выливается в мучительное ожидание переходов, показываемые отладчиком значения могут не соответствовать действительности, либо вообще отсутствовать и тд.
Важно! Логирование не заменяет полностью отладчик, а является вспомогательным инструментом.
Условно механизм логирования можно разделить на источник логов и на утилиты для сборки и просмотра логов.
Источники логов.
Источником логов может быть как библиотека FGX Native, операционная система, так и само приложение (сообщения отправленные непосредственно разработчиком). На первые два варианта разработчик особо не может повлиять, поэтому более подробно остановимся на третьем варианте.
Основным методом класса является метод Log позволяющий отправить строку с указанным уровнем лога.
Уровни лога определяют степень важности того или иного события.
Вы можете определить минимальный уровень лога с помощью свойства MinimumLevel, чтобы уменьшить количество выводимых в лог сообщений
Для удобства у класса TfgLog есть набор одноименных методов, соответствующих каждому уровню лога. Например:
В дополнение, у каждого такого метода существует перегруженная версия, принимающая во втором аргументе список параметров для отправки форматированной строки:
Сборка и просмотр логов.
Для просмотра логов с iOS используется утилита, работающая на компьютере под управлением macOS под названием Console.
Для просмотра логов с Android используется утилита работающая на компьютере под управлением Windows под названием logcat.
Частично логирование в контексте Android было затронуто ранее в Уроке 1 «Настройка окружения, Hello World приложение и логирование».
В данной статье мы сосредоточимся на утилите Console.
Важно! Подразумевается, что вы имеете настроенную связку: компьютер под управлением macOS к которому подключено устройство с iOS.
Исходные данные: на устройстве с iOS установлено приложение под названием TestLogging, которое имеет только две кнопки, при нажатии на которые отправляются сообщения в системный лог.
Код выглядит следующим образом:
Главное окно состоит из меню навигации в левой части (1), тулбара с органами управления вверху (2) и списка логов, который занимают остальную клиентскую область окна (3).
Теперь выбираем в меню навигации слева устройство из списка Devices с которого мы хотим получать логи. В нашем случае это устройство с именем IPhone.
Теперь запускаем сбор логов с помощью кнопки тулбара Start или кликнув по ссылке Start streaming находящейся в центре списка логов.
После старта список логов будет быстро наполняться сообщениями.
Список логов представляет собой таблицу с настраиваемыми колонками. Вы можете настроить видимость колонок по своему усмотрению с помощью контекстного меню, вызываемого по клику на заголовки таблицы.
Теперь запустим наше тестовое приложение и последовательно нажмем обе кнопки.
Лог содежит тысячи записей, среди которых практически невозможно найти интересующие нас строки. Для решения этой задачи существуют фильтры, которые располагаются в правой части тулбара. Механизм фильтрация довольно гибкий и позволяет комбинировать несколько фильтров по разным колонкам с учетом выбранного способа сравнения (частичное или полное совпадение включая вариант с отрицанием).
Важно! Фильтрация осуществляется без учета регистра.
На скриншоте видно, как строки переданные самой библиотекой FGX Native, так и те, которые были отправлены после нажатия на кнопки (отмечены стрелками). Теперь вы можете изучать собранную информацию просматривая сообщения. При необходимости вы можете сохранить выделенные строки в буфер обмена, либо передать их в другое приложение к помощью кнопки тулбара Share.
Демистификация аварийных журналов iOS
Прежде чем отправить в AppStore ваше приложение, вы долго тестируете его, чтобы убедиться, что ваше приложение работает безупречно. Оно отлично работает на вашем устройстве, но после того, как приложение попало в App Store, некоторые пользователи сообщают, что оно «вылетает»!
Если вы похожи на меня, то вы хотите, чтобы ваше приложение было на пять с плюсом. Значит, вы возвращаетесь в свой код, чтобы исправить сбой… а куда надо смотреть?
Вот когда пригодятся аварийные журналы iOS. В большинстве случаев, вы получите очень подробную и полезную информацию о причинах аварии, это вроде обратной связи от хорошего учителя.
В этом уроке вы узнаете, как выглядят аварийные журналы, а также как получить аварийный журнал из iOS-устройства и iTunes Connect. Вы узнаете о символизации и о том, как вернуться от аварийного журнала назад, в код. Мы также займёмся отладкой приложения с ошибками, которые могут привести к сбою в определенных ситуациях.
Что это за аварийный журнал и где его взять?
Когда приложение «падает», то есть аварийно завершает свою работу на устройстве iOS, операционная система создает отчет о сбое или аварийный журнал. Этот журнал сохраняется на устройстве.
Вы можете найти много полезной информации в аварийном журнале, в том числе те причины, по которым приложение аварийно завершило работу. Как правило, там есть полная трассировка стека каждого исполняемого потока, так что вы сможете увидеть, что происходило в каждом потоке в момент аварии, а также определить поток, где произошла катастрофа.
Есть много способов, как получить аварийный журнал с устройства.
Устройство, которое синхронизируется с iTunes, хранит свои аварийные журналы на ПК. В зависимости от ОС, вот места, где их можно найти:
(5) Состояние потока
Этот раздел содержит значения в регистрах на момент сбоя. Обычно этот раздел не нужен, потому что трассировка потоков уже дала вам необходимую информацию, чтобы решить вашу проблему.
(6) Дамп памяти
В этом разделе перечислены все модули, которые были загружены в память на момент сбоя.
Демистификация с помощью символизации
Первый взгляд на трассировку потоков говорит о том, там нет смысла. Ты привык работать с именами функций и номерами строк, а не загадочными письменами, вроде этого:
Процесс преобразования этих шестнадцатеричных адресов исполняемого кода в имена методов и номера строк называется символизация (symbolification).
Когда вы получаете аварийный журнал от устройства используя Органайзер Xcode, то символизация автоматически происходит через несколько секунд. Строчка из журнала сбоя выше после символизации выглядит так:
Чтобы Xcode смог символизировать аварийный журнал, он должен иметь доступ к файлу приложения, который был загружено в App Store, и DSYM-файлу, который был создан при компиляции приложения. Тут должно быть точное соответствие версий, в противном случае аварийный журнал не может быть полностью символизирован.
То есть, важно сохранять каждую сборку, которую вы распространяете среди пользователей. Когда вы архивируете ваше приложение перед отправкой, Xcode сохраняет откомпилированный файл. Вы можете найти все архивы вашего приложения в Xcode Organizer, вкладка Archives.
Примечание: Вам нужно хранить и откомпилированный файл приложения и dSYM-файл, чтобы иметь возможность в полной мере символизировать отчеты о сбоях. Вы должны архивировать эти файлы для каждой сборки, которую вы выкладываете в iTunes Connect.
DSYM-файл и откомпилированный файл связаны друг с другом на уровне сборки, и последующая версия, даже из тех же самых исходных файлов, не будет работать с файлами из других сборок.
Если вы используете пункт меню Build and Archive, файлы будут сохранены в нужном месте автоматически.
Сбои, вызванные нехваткой памяти
Журнал сбоя, вызванного нехваткой памяти немного отличается от обычных аварийных журналов и мы рассмотрим его отдельно.
При нехватке памяти, система виртуальной памяти посылает сообщение об этом приложениям с просьбой освободить память. Такие сообщения направляются всем запущенным приложениям и процессам.
Если памяти всё равно не хватает, система может завершить фоновые процессы, чтобы уменьшить нагрузку на память. Если достаточный объем памяти был освобожден, ваше приложение будет продолжать работать и отчёт о нехватке памяти не будет сгенерирован. В противном случае, ваше приложение будет аварийно завершено iOS, и будет создан аварийный журнал.
В аварийных журналах, созданных при нехватке памяти, нет раздела с трассировкой потоков приложения. Вместо этого, есть отчёт об использовании памяти каждым процессом с указанием количества страниц памяти. (На момент написания документа – одна страница равняется 4 Кб.)
Пометка (jettisoned) рядом с названием процесса говорит о том, что процесс был завершён iOS, чтобы освободить память. Если такая пометка около вашего приложения, это означает, что ваше приложение было аварийно остановлено, так как использовало слишком много памяти.
Журнал сбоя, вызванного нехваткой памяти выглядит примерно так:
Когда ваше приложение прекращает работу из-за нехватки памяти, вам нужно исследовать то, как ваше приложение использует память и то как оно реагирует на предупреждения о нехватке памяти. Вы можете использовать утилиту Instruments, профили Allocations и Leaks, чтобы обнаружить утечки памяти. Если вы не знаете, как использовать эти инструменты, почитайте это руководство для начала.
И не забывайте о виртуальной памяти! Профили Leaks и Allocations утилиты Instruments не отслеживают графическую память. Вам необходимо при запуске профиля Allocations просмотреть данные профиля VM Tracker, чтобы узнать об использовании графической памяти.
Профиль VM Tracker по-умолчанию отключен. Для профилирования вашего приложения с VM Tracker, выберите строку с названием профиля VM Tracker в запущенной с профилем Allocations утилите Instrument, там поставьте флаг Automatic Snapshotting или просто нажмите кнопку Snapshot Now.
Как исследовать журнал аварийного останова, вызванного нехваткой памяти, мы рассмотрим ниже.
Коды исключений
Перед тем, как погрузиться в некоторые реальные аварийные журналы, поговорим ещё немного об интересной стороне аварийного журнала – о смешных кодах исключений.
Код исключения указывается в разделе № 3 (Исключение), см. приведенный выше пример. Есть несколько кодов исключений, которые могут возникнуть чаще, чем остальные.
Как правило, код исключения начинается с текста, потом одна или несколько шестнадцатеричных значений, которые являются процессор-специфичными кодами и которые могут дать вам информацию о характере сбоя. Эти коды могут сказать вам почему приложение аварийно остановлено, то ли из-за ошибки программирования, то ли из-за неверного доступа к памяти, то ли по какой-то другой причине.
Вот некоторые из наиболее распространенных кодов исключения:
Примечание: Помните, что принудительное прекращение приложения, находящегося в фоне, путём удаления его из списка задач не вызывает создания аварийного журнала. После того, как приложение было приостановлено, оно может быть прекращено операционной системой в любое время. И аварийный журнал создан не будет.
В омут головой!
Вы можете скачать исходный код приложения отсюда.
Сценарий 1. Плохой код на завтрак
Да ладно, не принимайте близко к сердцу! Как любой из этих комментариев даст вам подсказку? Взгляните лучше в аварийный журнал:
Это значит, что приложению не удалось уложиться при запуске в отведённое для этого время и сторожевой таймер операционной системы прекратил работу приложения. Круто! Вы нашли причину, но почему (и что ещё более важно, где) это происходит?
Смотрим дальше журнал. Трассировку потоков принято читать в обратном порядке, снизу вверх. Самый последний фрейм (25 фрейм: libdyld.dylib) – это первый вызов, затем фрейм 24, Rage Masters, main (main.m:16) и так далее.
Нам интересны фреймы, которые связаны с кодом вашего приложения. Так что игнорируйте системные библиотеки и фреймворки. Вот эта строчка нам интересна:
Приложение получило сбой в методе application:didFinishLaunchingWithOptions:, в 35-ой строке файла RMAppDelegate.m (RMAppDelegate.m: 35). Откройте Xcode и посмотрите на эту строку:
Да, вот оно! Синхронный вызов веб-сервиса? В главном потоке? В application:didFinishLaunchingWithOptions. Кто писал этот код?
Чтобы перенести вызов в другое место потребуются значительные изменения, поэтому, в данный момент, просто сделаем минимум изменений, просто, чтобы приложение аварийно не останавливалось при запуске. Вы всегда можете вернуться к этому вопросу и сделать это совсем правильно. Замените строку «плохого» кода (и три строки после неё) на асинхронную версию:
Сценарий 2. Где эта кнопка?
Пользователь пишет: «Я не могу отметить моего любимого персонажа закладкой. Когда я пытаюсь это сделать, приложение падает. «.
Другой пользователь: «Закладки не работают. Я вхожу в Подробную информацию, нажимаю на кнопку «Закладка» и БА-БАХ!»
Эти жалобы о многом не говорят, и существует куча причин, почему программа так себя ведёт. Посмотрим в аварийный журнал:
Обычно этого не происходит, так как если вы вызываете метод «foo» объекта «bar», то компилятор выдаст ошибку, что метод «foo» не существует. Но когда вы косвенно вызываете метод используя селектор, компилятор не сможет определить, существует или нет метод у объекта.
Вернёмся к аварийному журналу. Он говорит, что аварийный останов произошёл в потоке №0. Это значит, что у нас, скорей всего, ситуация, когда метод был вызван объектом главного потока, там где объект не реализует метод.
Если вы продолжите читать журнал трассировки, вы видите, что единственный вызов, связанный с вашим кодом – это фрейм 22, main.m: 16. Это не особенно помогло.
Посмотрим на вызовы фреймворков, и видим это:
Это не ваш код. Но по крайней мере, есть подтверждение, что был вызов нереализованного метода объекта.
Идём в RMDetailViewController.m, где реализована кнопка закладок. Найдём код, который делает закладку:
Тут всё выглядит нормально, так что проверим сториборд (XIB файл) и убедимся, что кнопки подключены правильно.
Вот оно! В MainStoryboard.storyboard, кнопка связана с bookmarkButtonPressed: вместо bookmarkButtonPressed (обратите внимание на двоеточие в конце, которое говорит о том, что у метода есть параметр). Чтобы это исправить, замените название метода на такой:
Конечно, вы можете просто удалить связь с неправильным методом в XIB-файле и связать событие к правильным методом. В любом случае сработает.
И вот ещё одна причина аварийного останова устранена.
Сценарий 3. Закладкой больше, закладкой меньше.
Ещё одна жалоба от пользователя: «Я не могу удалить закладку на персонажа из окна закладок. «. И ещё одно письмо о том же: «Если я пытаюсь удалить персонажа из закладок, приложение падает. «
К этому моменту, вы уже привыкли, что письма от пользователей не бывают полезными. К аварийным журналам!
Этот журнал очень похож на предыдущий аварийный журнал. Тут тоже SIGABRT исключение. Может тут та же причина: отправка сообщения объекту, у которого не реализован метод?
Давайте посмотрим трассировку, какие методы вызывались. Начните с нижней части. Последний вызов на ваш код в Rage Masters был в фрейме №6:
Посмотрим на фреймы дальше:
В фрейме №5, UITableView вызывает другой собственный метод, deleteRowsAtIndexPaths:withRowAnimation: а потом вызывается _endCellAnimationsWithContext:, который выглядит как внутренний метод Apple. Затем, происходит исключение фреймворка Foundation, handleFailureInMethod:object:file:lineNumber:description:.
Если собрать это вместе с жалобами пользователей, то это выглядит так, как будто вы имеете дело с ошибкой в процедуре удаления UITableView. Идём в Xcode. Вы знаете, куда идти? Может ли это сказать нам аварийный журнал? Смотрим строку №68 в RMBookmarksViewController.m:
Нашли, где проблема? Я буду ждать, пока вы не найдёте.
Кто-то забыл про источник данных! Код удаляет строку в представлении, но не меняет источник данных. Чтобы это исправить, замените код на следующий:
Вот так будет с каждой ошибкой! Бац! Бах! Бум!
Сценарий 4. Леденец
Письмо: «Мое приложение падает, когда персонаж лижет леденец. «. Другой пользователь: «Я нажимаю кнопку «Лизнуть леденец» несколько раз, а затем приложение вылетает!»
Вот аварийный журнал:
Этот журнал очень отличается от тех, что мы видели до сих пор!
Это аварийный журнал нехватки памяти iOS 6. Как уже говорилось ранее, аварийный журнал нехватки памяти отличается от других аварийных журналов, потому что он не указывает на конкретный файл или строку кода. Вместо этого, он рисует картину, сложившуюся в памяти устройства в момент ситуации, которая привела к аварийному останову.
Заголовок, впрочем, похож на заголовок обычного аварийного журнала: те же поля Incident Identifier, CrashReporter Key, Hardware Model, OS Version и другие.
При нехватке памяти iOS посылает предупреждение о низком уровне памяти активному приложению и завершает фоновые процессы. Если активное приложение продолжает увеличивать использование памяти, iOS завершает его.
Чтобы найти причину проблем с нехваткой памяти, необходимо профилировать приложение, используя утилиту Instruments. Если вы не знаете, как это делать, есть учебник. Вместо этого, мы решим проблему «в лоб», просто обработаем в нашей программе событие о нехватке памяти.
Перейдите в Xcode в RMLollipopLicker.m. Это там реализован контроллер представления лизания леденца. Взгляните на исходный код:
Когда пользователь нажимает кнопку запуска, приложение запускает в фоновом режиме процедуру lickLollipop несколько раз, а затем обновляет на экране количество лизаний. lickLollipop читает большую строку NSString из PLIST-файла и добавляет её в массив. Эти данные не критичны, и могут быть воссозданы, не влияя на работу пользователей.
И всё будет хорошо!
Что дальше?
Ура, вы прошли через все четыре сценария развития аварийных ситуаций! Ваше приложение работает намного лучше, и вы получили важные навыки отладки по ходу обучения.
Инструменты для снятия логов с Android / iOS-устройств. Чтение и разбор
Привет! Сегодня стартует наш четвертый митап для тестировщиков, QAчественное общение. До 18:00 МСК на него все еще можно зарегистрироваться. А пока мы начинаем выкладывать доклады с предыдущего митапа, и начинаем с Ольги, старшего QA-инженера в компании red_mad_robot. Поговорим про мобильные устройства и про снятие логов с этих мобильных устройств, почитаем их и разберем, как вообще с ними работать.
Что вообще такое логи мобильного устройства? Логи – это записи либо сообщения в виде текста. У нас в этом тексте записываются все действия пользователя или как отвечает система на действия пользователя, соответственно, вся та информация, что вы делаете, куда нажимаете на самом устройстве, в приложении – всё это пишется в логи.
Какие логи в принципе бывают? Разделим их на две группы. Первая – это Crash logs, они подразумевают под собой отдельный файл, куда сыпется только информация об экстренном завершении программы. И второй вариант – это просто логи, файл, который является журналом событий, в нём хранятся все системные записи и ответы устройства на действия пользователя.
Уровни логирования
Хочу заметить, что эти уровни логирования больше под Android-логи, потому что именно разделение на Error, Warn, Info, Debug и Verbose в основном вы можете увидеть именно на логах с Android. Плюс, такая же информация чаще всего будет в ваших серверных логах, в принципе, будет довольно полезно изучить их. Что примечательно, каждый уровень включает в себя предыдущий. Если мы возьмем Verbose, фильтрацию по нему, например, то мы будем получать логи со всех предыдущих уровней, то есть абсолютно все логи. Разберем каждый подробно.
Первый – это Error. Ошибки уровня Error – ошибки, которые говорят о работе системы, на них надо очень быстро реагировать и всегда сообщать о них разработчикам.
Например, SpannableStringBuilder – это ошибка приложения, которая говорит нам о том, что текстовое поле, то есть Span, наш элемент, он не может быть нулевым либо пустым. Второй вариант – это системная ошибка ZeroHung. Данная ошибка говорит о том, что у нас происходит утечка памяти, она может быть как от какого-то действия с приложением, так и от самого приложения.
Следующий вариант – Warning. Это тоже ошибки, которые говорят о каком-то неожиданном поведении, которые требуют внимания, но они не такие важные, как error. Например, ошибка из приложения: мы пытаемся декодировать видео в нужное нам качество, в нужный формат, и у нас на этом происходит ошибка. Второй вариант, например, BroadcastQueue. Это ошибка системная, ошибка работы какого-то виджета на вашем устройстве. У меня это был Android Huawei, мне от системы сыпятся такие ошибки.
Следующий уровень – Info. Это уровень логов, на котором нам приходят записи чисто информационного характера о работе системы. Например, в этот уровень будут приходить ваши запросы, которые отправляют приложения на сервер. То есть он будет выглядеть так: http start, здесь вы увидите, какие header’ы отправляются, какое тело отправляется, если оно есть, и так же будете получать ответ от сервера в таком формате: json, key, value, ключ, значение и так далее. Заканчиваться он будет как http end. Далее, системный вариант ошибки или системный вариант лога о том, что сейчас мы намереваемся выключить экран. То есть, эта запись появляется, когда мы просто блокируем экран телефона, и он гаснет. Это у нас падает в информацию.
Далее, уровень Debug. Это тот уровень сообщений, в котором передается информация о процессах отладки или шаги каких-то крупных процессов, то, на что разработчики хотели обратить пристальное внимание. Например, мы просто нажали на качель громкости. Здесь будет, конечно, более подробно внутри этого лога – если вы его поймаете, то увидите конкретно что произошло: мы увеличили звук, уменьшили звук и на какое количество. И второй вариант, например, у вас приложение работает по WebSocket, и вам надо понять, подключились вы вообще или нет. Соответственно, вот это сообщение о том, что коннект произошел (на экране «b$b: WebSocket connected»).
Следующий уровень Verbose. Это уровень самого низкого приоритета, там сыпятся вообще все логи, там будет какая-то дополнительная информация, которая не вошла в Info, например. К примеру, у нас всплывает окно, мы его закрываем, у нас WindowManager, и мы здесь видим, что-либо добавилось, либо все удалилось. Далее, вся информация о геолокации. Например, у нас есть LocationProvider. В более расширенном варианте там будет полностью писаться ваша геолокация вплоть до долготы и широты. И третий пример, тоже связанный со звуком, то есть какой у нас звук и насколько он громкий. То есть, например, volume 10 это у нас максимальный звук, и мы его увеличили до такого варианта (на экране «AudioManager: getStreamVolume streamType: 3 volume: 10»). Очень похож на Info, но, я бы сказала, что более подробная информация на него передается.
Чем снимать логи?
Android
Первый инструмент – это Android Studio, в частности, его утилита Logcat. Что надо для того, чтобы начать снимать логи через Android Studio? Первое, конечно же, необходимо перевести устройство в режим разработчика. В настройках вы ищете номер вашего билда или операционной системы, в зависимости от того, на каком устройстве вы собираетесь смотреть, оно меняется от производителей. Нажимаете около 10 раз на эту информацию, и у вас появляется сообщение «Не желаете ли вы перевести ваше устройство в режим разработчика?». Нажимаете «Ок», и ваш телефон уже не такой обычный. Далее, вам надо подключить это устройство по USB к вашему компьютеру, конечно же, установить на сам компьютер Android Studio, он устанавливается как на Windows, так и на MacOS, тут проблем никаких нет.
Открывая Android Studio, выбираем вкладку Logcat. Под цифрой 1 то, где найти это сокровенное слово. Нажимая на него, переходим в сообщения в реальном времени. Под цифрой 2 окно, где мы выбираем телефон, с которого будем снимать логи. Соответственно, если ничего не подключено либо ваш телефон не виден, здесь вы ничего не сможете выбрать. Под цифрой 3 интересный момент: если вы хотите полностью снимать все логи (системные и со всех приложений, которые у вас сыпятся), не выбирайте здесь ничего. Если вы выберете какое-то конкретное приложение, которое debug’ное, у вас будут показываться логи исключительно по нему. Рядом с цифрой 3 вы видите слово Verbose – это уровень того лога, который вы хотите видеть. То есть, если вы выберете Error, будут только Error’ы. И под цифрой 4 у вас поле поиска. Это то поле, где вы сможете фильтровать выдачу по приложению, по уровню, по какой-то утилите, которая вам нужна, соответственно, это у нас regular выражение, и там все довольно просто ищется по совпадению. Вариант второго скриншота – это я уже выбрала конкретную сборку, и мы видим, что у нас по этой сборке сыпятся логи.
Дальше у вас в терминале, примерно так же, как и в Android Studio, будут в режиме реального времени сыпаться логи. Разберем, как их читать. Под цифрой 1 будет дата и время, когда пришла запись. Под цифрой 2 – маленький столбец, где вы видите буквы V, D, E, I и так далее. Это как раз те самые уровни нашего логирования Debug, Verbose, Warning или Info. В графе 3 – названия – инструменты, утилиты или части ОС, откуда идет сообщение, и, соответственно, его расшифровка – что конкретно у нас происходит. Конечно, выглядит это так, что в Android’е это все намного удобнее и приятнее, легко можно фильтровать. В Terminal’е фильтровать будет уже посложнее, надо будет менять саму команду, добавлять ключи, которые бы фильтровали выдачу по уровню либо по отдельному приложению.
Открываем его, как мы видим, информация похожа на ту, что в Terminal, единственное, как вы прекратили команду, этот файл больше не обновляется до тех пор, пока вы снова не повторите эту команду. Опять же, в таблице 1 мы видим дату и время прихода сообщения, таблица 2 – уровень наших логов, в таблице 3 мы видим, от какой части системы у нас сыпятся данные, лог и его расшифровка.
Конечно же, первое, о чем я хотела бы рассказать, это xCode и встроенный для него симулятор. К сожалению, xCode – программа только для MacOS. Чтобы снять с симулятора логи, нужно установить xCode, зайти в меню, открыть Developer Tools и симулятор. Симулятор – дополнительная программа, которая позволяет воспроизводить работу системы, если у вас нет физического девайса.
В этот симулятор мы устанавливаем нужное нам приложение, выбираем, какой конкретно IPhone, его размеры, разрешение и операционную систему. И уже в симуляторе выбираем пункт «Debug» и «Open System Log». Это выглядит так: я выбираю в самом симуляторе папку Debug и подвкладку Open System Log. Он точно так же идет в режиме реального времени, но они (логи) выведены по-другому, не так, как на Android.
Мы видим, что тут уже нет уровня логирования, есть дата и время. Цифра 2 – сообщение, что мы вообще видим. Мы видим, с какого устройства была снята информация.
В моем случае это имя моего Mac. Видим дополнительную запись, с какого элемента системы это сообщение пришло и его расшифровка. Например, в логах IOS придётся поковыряться чуть поподробнее, чем в Android.
Второй инструмент тоже связан с xCode, но он идет по другой «дорожке». Это Devices and Simulator. Устанавливаем xCode, подключаем устройство по USB, тут уже важно, чтобы было реальное устройство. В самом xCode открываем вкладку Window, там выбираем подвкладку Devices and Simulator. Нажимаем у устройства «Open Console». На панели видим название нашего устройства, какая у него операционная система, модель, и правее этой кнопки нам интересно «Open Console».
Под цифрой 1 мы видим все приложения, которые дополнительно установлены на наши устройства, в колонке под цифрой 2 – версия этого устройства, которую разработчик указывает. Третье пишется URL нашего устройства. То есть, например, у вашего разработчика, у вашей компании, у вашего приложения есть свой URL, по которому он ходит. Соответственно, здесь он отражен.
Как это все выглядит: здесь довольно удобно отслеживать, как сыпятся логи. Они тоже сыпятся в реальном времени, и, если их никак не фильтровать, они будут постоянно падать, но здесь все удобно смотреть. У нас есть время этого сообщения, процесс – это с какой части системы, приложения пришло сообщение. В колонке «Сообщение» мы видим подробное описание того, что происходит, что не так, вся сервисная системная информация. Что интересно, именно в Devices and Simulator есть поиск, который позволяет фильтровать выдачу. То есть, справа сверху мы можем написать название нашего приложения, и у нас все отфильтруется по процессу. Также мы можем приостановить выдачу по кнопке, и тогда логи перестануть хаотично и беспорядочно сыпаться (чтобы удобнее искать то, что вы хотите найти на устройстве).
Как снимать логи с IOS на Windows
Есть приложение iMazing, которое ставится на Windows и MacOS. Подключаете устройство по USB и в меню выбираете «показать консоль устройства». В целом, это приложение платное, однако, снять логи с устройства можно на триальной версии, она никак не ограничивается. У нас открывается следующее окно: мы видим, какое устройство у нас подключено, мы видим аккаунт, и в меню мы видим как раз «показать консоль устройства».
Если мы на нее нажмем, увидим следующее. Первый квадрат – это дата и время, когда мы получили это сообщение, второй пункт – от кого, с какого устройства, так как это уже реальный девайс, он называется у нас «IP-040». Далее, пишется, с какой части системы прилетело сообщение и его описание. Под цифрой 3 мы видим поле поиска. Мы можем фильтровать эту выдачу, можем остановить поток входящих логов по кнопке «пауза» и отфильтровать ее в поле поиска. Это поможет вам сконцентрироваться на конкретном запущенном приложении. Также у iMazing можно эти логи сохранять по соответствующей кнопке.
SwiftyBeaver руководство для iOS: платформа логирования для Swift
Ошибки бывают разных форм и размеров. Некоторые ошибки милые и пушистые и их легко исправить, потому что вы можете запустить ваше приложение и пойти прямо к тому месту где происходит ошибка. Если вы счастливчик, Xcode покажет вам какая строка привела к падению вашего приложения.
Другие ошибки более зловещие. Они таятся в фоне и случаются когда вы менее всего этого ожидаете. Они ужасы ночных кошмаров и рекламных роликов Listerine.
В этом руководстве вы узнаете как отлаживать ваше приложения используя SwiftyBeaver и получите следующие полезные умения на этом пути:
Начало
Загрузите начальный проект. Этот архив содержит проект и все необходимые файлы для завершения урока.
Вы собираетесь создать приложение для отслеживания ваших детей. Если они отходят слишком далеко от дома, приложение будет предупреждать их.
Это приложение названо Ten PM (Вы знаете где ваши дети?).
Откройте TenPM.xcworkspace и посмотрите проект. Теперь откройте Main.storyboard и вы увидете два view controller’а внутри navigation controller’а:
View controller слева — это тот, который родители могут использовать, чтобы сказать приложению где находится «дом» а также задать максимальное расстояние, на которое дети могут отходить. Класс — TrackingSetupViewController, который запрашивает доступ к службам геолокации и сохраняет введенное расстояние для ограничения расстояния.
View controller справа — это тот, который будут использовать дети. Он отображает карту с зеленым кругом представляющим безопасную зону. Он также содержит блок текста ниже, чтобы сообщим им, находятся ли они в настоящее время в безопасной зоне или нет. Класс — TrackingViewController, в котором используется MKMapView отображающий допустимую для детей территорию.
И здесь еще несколько строк на заметку:
Используем начальное приложение
Запустите предложение на iOS симуляторе. Вы увидете view controller который представлен классом TrackingSetupViewController:
Перед тем как настроить отслеживание, вы должны быть уверены, что симулятор предоставляет данные о начальном местоположении. Чтобы сделать это выберете симулятор, затем на панели выберите Debug\Location\Apple.
Нажмите на кнопку SETUP TRACKING и приложение запросит разрешение чтобы начать сбор данных о местоположении. Поскольку этот экран для родителей там есть одно лишнее окно для подтверждения, что вы родитель и находитесь дома. Выберите Yes и затем Allow. Один раз приложение определит ваше местоположение и сохранит его как дом, чтобы использовать как центр безопасной для детей зоны.
Сейчас введите 1 в текстовое поле ниже как безопасное расстояние от дома. Это означает, что вы не хотите в дальнейшем быть дальше чем 1 километр от симулированной локации Apple.
Нажмите кнопку NEXT и вы попадете на представление поддерживаемое TrackingViewController. На этом экране есть одна аннотация с вашим текущим местоположение и зеленый круг обозначающий вашу безопасную зону.
Подумайте об этом, это приложение могло бы быть полезным для сотрудников Apple, несущих дорогие прототипы iPhone
Теперь когда все настроено, самое время притвориться, что сейчас 2011 год, вы получили прототип iPhone 4 и отправляетесь на прогулку. Давайте симулируем новое местоположение, вне безопасной зоны, в том месте где вы назначили встречу с репортером Gawker.
В меню iOS Simulator снова пройдите в Debug\Location, но в этот раз выберите Custom Location. Введите 37.3393 как широту и -122.0426 как долготу после чего нажмите OK.
Вы должны видеть, как синяя точка движется к новому местоположению и сообщение внизу экрана меняется чтобы сообщить вам, что вы на опасном расстоянии от дома.
Это краткое описание функциональности приложения Ten PM. Это довольно простое приложение, которое может быть легко расширено. Вы могли бы начать отправку уведомлений родителям когда их ребенок выходит за пределы безопасной зоны и т.д.
Но сегодня вы не собираетесь этого делать. Вместо этого вы будете использовать это приложение, чтобы имитировать некоторые неприятные фоновые ошибки, которые вы затем исправите с помощью SwiftyBeaver.
Установка SwiftyBeaver
Для начала установим SwiftyBeaver используя Cocoapods. Найдите корневую директорию проекта и найдите там Podfile. Откройте его в редакторе, который для вас наиболее удобен и добавьте следующую строку под строчкой # Pods for TenPM:
Откройте Terminal, перейдите в директорию с эти Podfile и введите следующую команду:
pod install
После минуты или может чуть больше установка SwiftyBeaver в ваш проект будет завершена. Теперь откройте AppDelegate.swift и добавьте следующую строку в подходящем месте в верхней части файла:
Скомпилируйте этот проект и вы увидете, что SwiftyBeaver теперь доступен в проекте потому что он был успешно скомилирован.
Примечание: это руководство по установке работает с Cocoapods и Swift 3. Более подробную инструкцию по установке вы можете найти на странице репозитория проекта SwiftyBeaver на GitHub.
Написание ваших первых логов со SwiftyBeaver
Фух. Вы готовы написать немного кода?
Загляните в AppDelegate.swift снова. Это место где вы сейчас настроите логирование для SwiftyBeaver. Вы заметили заготовку метод в стартовом проекте?
setupSwiftyBeaverLogging() вызывается каждый раз когда ваше приложение запускается, и как следует из названия вы будете его использовать для подготовки SwiftyBeaver к использованию. Перейдите к этому методу и добавьте следующее:
SwiftyBeaver это необычный инструмент логирования. Он имеет несколько входов названых Destinations, которые вы можете конфигурировать в соответствии с вашими нуждами.
Destination определяет где появляются ваши логи.
ConsoleDestination() создает Console Destination, который вы добавили как активный destination для SwiftyBeaver.
В метод application(_:didFinishLaunchingWithOptions:) добавьте следущую строку кода, после вызова метода setupSwiftyBeaverLogging():
Этот код отображает лог уровня info в консоли когда приложение запустится. Вы узнаете больше об уровнях логов чуть позже.
Запустите Ten PM снова. Проверьте консоль когда приложение запустится и вы увидете запись:
12:06:05.402 INFO AppDelegate.application():36 — Hello SwiftyBeaver Logging!
Круто! Вы ведете логирование в консоли с помощью SwiftyBeaver. Изобращение сердца относится к уровню логов, которые разъяснены ниже.
Краткое объяснение уровней логов
Я уверен, что на этом этапе вам интересно почему SwiftyBeaver заставляет использовать метод с именем info() вместо более понятных и логичных имен таких как log() или print().
Это связано с тем, что называется Log Levels.
Не все логи одинаково важны. Некоторые логи полезны для предоставления программисту дополнительной контекстной информации. Другие логи необходимы для более серьезных проблем, таких как ошибки и предупреждения. При изучении логов достаточно полезно иметь записи, которые классифицированы по уровню угрозы для приложения. Это помогает быстрее фильтровать менее важные сообщения, чтобы быстрее исправлять ошибки.
SwiftyBeaver придерживается договоренностей и использует эти 5 уровней логов:
Как записывать разные уровни логов со SwiftyBeaver
Также как вы использовали метод info() для логирования на уровне info, вы можете использовать четыре других метода: verbose(), debug(), warning(), и error() для логирования на остальных четырех уровнях.
Попробуйте. Вернитесь назад к методу application(_:didFinishLaunchingWithOptions:) и замените вызов info() на следующим кодом:
Теперь запустите ваше приложение. Вы должны увидеть другой цвет иконки сердца и другое сообщение на уровне debug.
14:48:14.155 DEBUG AppDelegate.application():36 — Look ma! I am logging to the DEBUG level.
Обратите внимание, что цвет иконки сменился на зеленый, чтобы обозначить debug уровень логов. Это одна из причин, почему SwiftyBeaver лучше, чем print() и NSLog(). Вы можете быстро просмотреть логи и найти сообщения на уровне, который вас интересует.
Примечание: старайтесь не злоупотреблять более высокими уровнями логирования. Предупреждения и ошибки должны быть зарезервированы для ситуаций требующих должного внимания.
Настройка SwiftyBeaver Crypto Cloud
Одна из крутейших особенностей SwiftyBeaver это возможность ведения логов напрямую в облаке. SwiftyBeaver имеется приложение для macOS, которое позволяет просматривать логи в реальном времени. Если вы когда-нибудь интересовались что же происходит в вашем приложении установленном на тысячи устройств, то теперь вы можете это знать.
Загрузите приложение SwiftyBeaver для Mac. Откройте его и заполните форму, чтобы создать аккаунт.
Далее вы будете перенаправлены напрямую в окно сохранения файла. Это немного странно, потому что они не сообщают для чего этот файл.
Назовите этот файл TenPMLogs, выберите любую локацию по своему усмотрению и нажмите Save.
Файл, которые в создали хранит логи для одного приложения, по этой причине вы и назвали его TenPMLogs. Когда вы открываете этот файл с помощью приложения SwiftyBeaver Mac App вы можете увидеть логи, связанные с приложением Ten PM. После сохранения файла вам будет предоставлен выбор. Вы можете зарегистрировать новое приложение или просмотреть логи из ранее зарегистрированного приложения. Вы продолжите на вкладке New App.
Кликните по кнопке Generate New App Credentials. Вы должны увидеть следующий экран, показывающий сгенерированные идентификатор и ключи для вашего приложения:
Теперь пора добавить еще один destination для логов используя только что созданные учетные данные безопасности Crypto Cloud. Оставьте это окно открытым и вернитесь к методу setupSwiftyBeaverLogging() в AppDelegate.swift.
Добавьте эти строки в низ этого метода, заменяя строки на соответствующие значение из приложения SwiftyBeaver Mac App:
Вернитесь в приложение SwiftyBeaver Mac App и кликнките по кнопке Connect. Теперь запустите приложение Ten PM.
Примечание: если вы сглупили как и я и нажали кнопку Connect до того как скопировали учетные данные в ваше приложение, вы можете кликнуть по кнопке настроек (шестеренка) в приложении SwiftyBeaver Mac App, чтобы посмотреть их после подключения.
Теперь проверьте это! Ваши логи появляются в приложении SwiftyBeaver Mac App. Если вы не видите записи сразу же, то не волнуйтесь. Иногда это занимает несколько минут прежде чем записи в журнале попадут в облако. В конечном итоге они все равно появятся. SwiftyBeaver также автоматически предоставляет одночасовое хранение для ваших логов если вы не перешли на платный аккаунт. Для большинства задач отладки это не будет проблемой. Просто стоит это отметить в случае если вы задумываетесь почему ваши более старые логи больше не видны.
Фильтрация логов по уровню логирования, избранным записям и минимальным уровням логирования
По настоящему классная вещь в SwiftyBeaver Mac App это возможность фильтровать логи по уровню логирования. Это значительно упрощает процесс копания в логах, чтобы найти причину критической ошибки.
Возможно вы уже заметили разные вкладки вверху. Каждая из этих вкладок представляет уровень логирования. Вы можете просматривать несколько уровней за раз или вы можете просматривать только предупреждения и ошибки.
Также вы можете пометить запись как избранную. Вы можете видеть все избранные записи по клику на звезду в левом меню.
Фильтрация по минимальному уровню логирования
Это еще одна особенность, которую вы действительно полюбите. SwiftyBeaver позволяет вам установить минимальный уровень для заданного destination. Если вы хотите использовать ваш Crypto Cloud аккаунт для серьезных предупреждений и ошибок вы можеет это сделать.
Для начала замените текущий код логирования в applicationDidFinishLaunching() на следующий:
Сейчас вы логируете сообщению на каждом уровне. Запустите приложение и вы должны видеть как все эти записи попадают в Crypto Cloud.
В методе setupSwiftyBeaverLogging() добавьте следующее перед тем как добавляете эту платформу в destination:
Запустите приложение снова. Посмотрите на новый вид вашей консоли Crypto Cloud.
Вы должны видеть только предупреждения и ошибки за последнее время. Никаких других записей не попадет в Crypto Cloud. Вы по прежнему видите все в консоли Xcode!
Примечание: Вы можете задать минимальный уровень для любого типа логирования (destination) в SwiftyBeaver. Вы можете создать несколько Crypto Cloud журналов для различных уровней логирования. SwiftyBeaver имеет большой простор для разных способов логирования.
Исправление труднодоступных ошибок
Было весело поговорить о логировани в Crypto Cloud, но у вас есть некоторые зловещие ошибки, которые вам нужно исправить. Ну или как минимум у вас есть зловещие ошибки, которые вы намерено смоделируете и затем исправите их.
Начните с очистки всех ранних записей. Удалите все логирование из application(_:didFinishLaunchingWithOptions:). Также удалите установку значения для platform.minLevel чтобы по умолчанию отображались все записи. Для этого теста вам нужно будет видеть все записи в логах.
Моделирование ошибки
Теперь вы готовы к использованию облачного логирования и это самое время чтобы смоделировать неприятный фоновый баг.
Откройте LocationTracker.swift и найдите там метод locationManager(_:didUpdateLocations:). Вставьте туда следующий код после объявления двух guard значений в верхней части метода:
Это довольно глупо, но здесь выпритворяетесь что где-то в LocationTracker есть ошибка, которая препятствует отслеживанию местоположения пользователя. Этот код здесь предотвращает уведомления о том, что пользователь вошел или покинул безопасную зону. Когда «ошибка» «отключена» этот код будет работать нормально.
Запустите приложение, чтобы убедиться в том, что «ошибка» есть.
Пока вы остаетесь в местоположении установленном ранее с помощью координат вы будете видеть ошибку. Не смотря на то, что вы явно находитесь за пределами безопасной зоны, в тексте сказано, что вы находитесь на безопасном расстоянии от дома.
Отслеживание ошибки с помощью SwiftyBeaver
Как теперь мы могли бы отследить эту ошибку с помощью SwiftyBeaver? Если вы получили отчеты об ошибках в неудавшемся отслеживании местположения, но не имеете никаких хороших предположений об ошибке добавьте логирование везде где только можно, чтобы получить как можно больше информации от пользователей.
Для начала импортируйте SwiftyBeaver в верхней части LocationTracker:
Далее добавьте запись вверху метода locationManager(_:didUpdateLocations:):
Теперь добавьте несколько строк внизу и вставьте эту строку прямо после объявления константы bug:
Далее добавьте информацию когда мы проверяем значение этой константы, но перед return:
И наконец, добавьте запись в конце метода locationManager(_:didUpdateLocations:):
Этой информации должно быть достаточно, чтобы начать вычислиение что же происходит в вашем коде. Вот как должно выглядеть все содержимое метода locationManager(_:didUpdateLocations:):
В симуляторе установите местположение в Apple также как делали это ранее. Теперь запустите приложение. Не смотря на то, что сейчас вам доступны логи в консоли Xcode проигнорируйте их и представьте, что вы отслеживаете записи в Crypto Cloud от удаленного пользователя.
И снова вы вышли за пределы вашей безопасной зоны без обновления текста.
Вы должны заметить следующие записи, повторяющиеся в SwiftyBeaver Crypto Cloud после того, как установите фильтр в ALL:
Воу, это действительно полезно! Если вы вернетесь к своему коду в классе LocationTracker то сможете сопоставить это с логами и вы увидете как далеко проходит выполнение вашего кода прежде чем оно остановится. Здесь это явно в if bug == true где была запись об ошибке была выведена.
Чтобы «исправить» эту «ошибку» просто установите константу bug в значение false где она объявляется в методе locationManager(_:didUpdateLocations:):
Запустите приложение. Смоделируйте начальную позицию в Apple со смещением за пределы безопасной зоны. В этот раз вы увидите предупреждение о безопасной зоне, которое сработало правильно.
Также вы должны видеть следующие записи в вашей Crypto Cloud консоли.
Это выглядит так как будто приложение миновало ошибку и снова успешно реагирует на изменения местоположения. Вы успешно исправили эту ошибку!
Безопасность iOS-приложений: гайд для новичков
Привет! Меня зовут Гриша, я работаю application security инженером в компании Wrike и отвечаю за безопасность наших мобильных приложений. В этой статье я расскажу про основы безопасности iOS-приложений. Текст будет полезен, если вы только начинаете интересоваться безопасностью мобильных приложений под iOS и хотите разобраться, как все устроено изнутри.
Disclaimer: Материал написан в образовательных целях, чтобы новички могли разобраться в принципах работы безопасности мобильных приложений. Используйте инструкции из статьи только на тестовых устройствах или же с разрешения владельца приложения (например, в рамках программы поиска уязвимостей).
Подготовка окружения
Для начала нужно подготовить окружение.
Вот что для этого необходимо:
Компьютер-хост. В идеале это должен быть MacOS, потому что с другой операционной системой возникнут сложности с установкой и запуском специализированного ПО.
Джейлбрейкнутый тестовый девайс с желаемой версией iOS. iOS симулятор, который поставляется в комплекте с Xcode, не подойдет, так как он предназначен для запуска приложений, собранных под x86 архитектуру. Релизные версии приложений, предназначенные для запуска на реальном девайсе, собираются под ARM. Поэтому приложения, загруженные из Apple App Store, не получится запустить в симуляторе iOS.
Сеть Wi-Fi, которая разрешает трафик от клиента к клиенту (или подход SSH через USB).
Это набор максимум: на самом деле можно работать и не на MacOS, и не на джейлбрейкнутом устройстве, но будут дополнительные сложности: отсутствие нужных инструментов, необходимость переподписывать приложение с использованием сертификата разработчика и т.д.
Джейлбрейк. Для тестирования желательно сделать джейлбрейк девайса.
Краткая инструкция выглядит так:
Найти подходящее тестовое устройство и сделать резервную копию.
Проверить, что для установленной версии iOS есть джейлбрейк.
Выбрать подходящий вариант (по этой ссылке можете почитать про сравнение между Tethered/Untethered).
Джейлбрейкнуть, следуя инструкции к выбранному способу: например, Checkra1n или Unc0ver.
Если хотите узнать подробно о том, как работают джейлбрейки, почитайте статью с техническим анализом эксплойта для checkm8 от Digital Security. Там много интересных подробностей.
Полезные приложения. Теперь на девайс можно поставить приложения, которые нельзя установить на iPhone без джейлбрейка. Для этого нужно установить Cydia. Установка будет отличаться в зависимости от выбранного джейлбрейка, просто следуйте инструкции.
Вот некоторые полезные приложения:
Обход обнаружения джейлбрейка (например, Liberty Lite).
Обход валидации SSL сертификатов / пиннинга (ssl-kill-switch2).
Приложение для установки неподписанных IPA файлов (например, AppSync Unified).
Прокси. Следующий обязательный шаг — это настройка прокси для перехвата трафика приложения на устройстве.
Логика этого процесса аналогична настройке перехвата для браузера:
Организуем доступность своего хоста (с запущенным прокси) для мобильного устройства: подключаем хост и девайс к одной Wi-Fi сети или используем SSH поверх USB.
Конфигурируем прокси в настройках мобильного устройства.
Запускаем перехватывающий прокси на компьютере-хосте.
Добавляем сертификат от прокси в доверенный на устройстве для перехвата HTTPS-трафика (подробную инструкцию для Burp Suite ищите по этой ссылке).
Перехват трафика мобильного приложения может быть полезен для увеличения поверхности атаки: он покажет новые хосты, сервисы, API, которыми пользуются только мобильные приложения. Разработчики могут уделять меньше внимания безопасности «внутренних» API, которые не видят пользователи. Возможно, будут какие-то ключи, параметры или заголовки, зашитые в код приложения и предоставляющие доступ к этим сервисам. А еще перехват трафика поможет лучше понять логику работы приложения.
IPA файл
Теперь нам нужно приложение для тестирования. Если мобильные приложения и находятся в скоупе для исследования по программе Bug Bounty, то максимум, что мы получим, — ссылку на официальный магазин приложений для платформы.
Мы можем попробовать перехватить трафик запущенного приложения и использовать разного рода инструменты, но для полноценного анализа желательно иметь IPA файл — аналог APK файла для Android. Чем ближе к оригинальному, тем лучше.
Находим IPA файл. Получить IPA файл можно несколькими способами:
Использовать приложения для управления устройством с компьютера (например, iTunes или Apple Configurator 2). Они скачивают приложения из App Store, а потом заливают на девайс. Но можно поймать момент, когда файл уже скачан на компьютер из App Store, но еще не залит на девайс, и скопировать его.
Установить приложение из App Store, а потом сдампить (например, через frida-ios-dump). Этот способ сработает только с джейлбрейкнутым девайсом, и в данном случае будут отсутствовать файлы с мета-информацией для App Store.
Использовать сайты с IPA файлами. Но там вы, скорее всего, найдете уже неоригинальный файл и исследовать его на безопасность будет не так интересно, но все еще полезно для использования.
Как получить IPA файл с помощью Apple Configurator 2:
Установить приложение на девайс.
Выбрать приложение в Apple Configurator 2, подключить девайс, начать обновление.
Отключить девайс после завершения шага загрузки приложения (опционально, так загруженный IPA файл дольше доступен в кеше приложения).
Забрать IPA на хосте по пути вида:
Что находится внутри IPA файла. Теперь файл нужно распаковать и посмотреть, что там внутри. Для IPA пакетов Apple использует LZFSE — алгоритм сжатия данных без потерь с открытым исходным кодом. Для распаковки нужен подходящий инструмент: например, unzip-lzfse.
Что находится внутри IPA файла:
Директория Payload — это все, что относится непосредственно к приложению.
Payload/Application.app — это скомпилированный код и статические ресурсы:
Info.plist — аналог Android-манифеста, который описывает свойства приложения для операционной системы, права, что приложение будет использовать (интернет или камеру и т.д.);
Основной исполняемый двоичный файл скомпилирован под ARM либо с использованием формата Mach-O, либо — fat binary;
Внешние библиотеки, фреймворки, плагины, ресурсы;
Информация о сборке для Apple. Например, embedded.mobileprovision с информацией о разработчике и приложении.
iTunesArtwork — иконка приложения для AppStore.
iTunesMetadata.plist — информация о приложении: жанр, возрастные ограничения, копирайты и т.д.
WatchKitSupport/WK — поддержка Apple Watch (если есть).
Файлы с расширением *.plist (property list) — это бинарные файлы, в которых хранятся сериализованные объекты. Открывать их удобнее всего в Xcode или любом hex-редакторе (например, 010 Editor с плагином BPlist.bt).
Посмотрим на информацию для App Store. Для примера возьмем приложение Wrike (файл iTunesMetadata.plist):
Содержимое файла iTunesMetadata.plist
Эта информация публично доступна в App Store. Недоступны только данные аккаунта Apple ID, от имени которого скачано.
UIRequiredDeviceCapabilities — связанные с устройством функции, необходимые приложению для работы.
Apple-id — каждое скачанное из App Store приложение «привязано» к вашему Apple ID.
SoftwareSupportedDeviceIds — какие устройства поддерживает это приложение: 1 — классические iPhone, 2 — iPod Touch, 4 — iPad, 9 — современные iPhone.
Разного рода мета-информация (авторские права, ограничения по возрасту, информация о разработчике и т.д.), которую можно найти в App Store.
Пример отображения информации о приложении в App Store
Теперь переходим к просмотру содержимого файла “Info.plist” (на примере приложения DVIA-2):
Содержимое файла Info.plist
Здесь можно увидеть информацию об основных правах, разрешениях, URL схемах и т.д.:
Camera Usage Description — разрешение на использование камеры с описанием того, для чего именно приложение будет её использовать.
NSAllowsArbitraryLoads — разрешает приложению использовать небезопасные HTTP-соединения.
Executable file — указывает на основной исполняемый файл, в данном случае — “DVIA-v2”.
URL Schemes — кастомная URL схема, зарегистрированная на устройстве и привязанная к приложению. Например, приложение может быть открыто через ссылку в браузере или в почтовом клиенте.
Информация об иконках, требуемых версиях iOS, поддерживаемых устройствах (UIDeviceFamily) и т.д.
Кастомные URL-схемы. Рассмотрим кастомные UPL-схемы отдельно, так как они могут быть потенциально опасными. Есть разные сценарии использования таких ссылок, но они могут стать хорошей точкой входа для того, чтобы в них что-то поместить и посмотреть на поведение приложения. Также поведение может быть интересно при эксплуатации XSS уязвимостей на мобильном девайсе.
Например, приложение DVIA-v2 поддерживает схемы “dvia://” и “dviaswift://”, и переход по ссылкам со схемой перенаправляет в приложение.
Перенаправление в приложение по ссылке с кастомной схемой
Приложение может не валидировать входные параметры с кастомной схемой, что приведет к проблемам с безопасностью. Например, вот ссылка на issue по Skype: по клику на ссылку происходил звонок.
Существуют и стандартные URL-схемы: “tel:”, “facetime:”, “facetime-audio:”, “sms:”, “mailto:”. При переходе по ссылкам с заданными схемами происходит перенаправление в соответствующее приложение на девайсе.
Файл embedded.mobileprovision. Приложению требуется файл профиля разработчика (embedded.mobileprovision) как для локальной разработки, так и для размещения в App Store. По-умолчанию он генерируется в Xcode и удаляется при публикации в App Store. В этом файле содержится информация о разработчике и его сертификат в формате PEM (см. DeveloperCertificates), что может быть интересно для сбора дополнительной информации. Однако получить такой файл можно только в том случае, если приложение было получено в обход App Store. Также такой файл может быть использован для переподписания приложения для его модификации и установки на устройство, см. Patching iOS Applications.
Содержимое файла embedded.mobileprovision
Исполняемый файл. Прежде чем приступать к реверсу исполняемого файла, можно попробовать собрать информацию простыми инструментами: вытащить строки, сделать class-dump и увидеть, что в нем есть какой-нибудь токен или секрет. А еще можно посмотреть, какие есть классы, увидеть следы механизмов обнаружения джейлбрейка и то, какие у приложения есть вызовы функций.
Поиск по слову jailbreak в выводе утилиты class-dump для приложения DVIA-v2
Поиск по слову secret в строковых константах приложения DVIA-v2
Дальнейший анализ возможен с помощью IDA Pro, Ghidra или других похожих инструментов.
Попробуем понять логику проверки девайса на джейлбрейк в приложении DVIA-v2:
Декомпилированный код проверки на джейлбрейк в Ghidra
Рассмотрим основные шаги:
Проверка существования определенных файлов с помощью NSFileManager fileExistsAtPath:
”/Applications/Cydia.app” — приложение Cydia (для установки сторонних приложений на джейлбрейкнутом девайсе);
“/Library/MobileSubstrate/MobileSubstrate.dylib” — зависимость, используемая во многих расширениях под джейлбрейк;
“/bin/bash” — наличие установленного Bash;
“/user/sbin/sshd” — проверка наличия SSH демона;
“/etc/apt” — файлы приложения Cydia.
Создание файла со строкой “This is a test” в приватной директории: “/private/jailbreak.txt”.
Попытка открыть приложение Cydia через ссылку с кастомной URL схемой: “cydia://package/com.example.package”. Используемое API: NSUrl URLWithString.
Защита бинарных файлов. Бинарные файлы могут быть защищены при распространении через App Store.
Рассмотрим возможные флаги, которые можно указать при сборке приложения для защиты бинарных файлов:
ASLR (Address space layout randomization, рандомизация адресного пространства) — флаг PIE.
Защита от Stack Smashing (флаг — fstack-protector-all). Приложения, которые используют «канарейки» (стандартный механизм обнаружения переполнения буфера на стеке), будут содержать _stack_chk_fail и _stack_chk_guard в исполняемом файле.
ARC (Automatic Reference Counting) — автоматический подсчет ссылок, _objc_release в исполняемом файле.
Флаг cryptid — отвечает за шифрование исполняемого файла. Значение 1 указывает, что приложение зашифровано. Для незашифрованных приложений значение cryptid равно 0.
Эти флаги можно проверить, используя команду otool, которая есть на Mac OS. Эта команда умеет отображать указанные части объектных файлов или библиотек.
Пример вывода команды otool
Исполняемые файлы приложений, которые распространяются через App Store, защищены и зашифрованы. Поэтому сделать анализ строковых констант и декомпилировать код не получится. Но загрузчик расшифровывает iOS- приложение и загружает его в память, когда оно запускается. Этим можно воспользоваться: например, используя frida-ios-dump, сдампить запущенное приложение.
Mobile Security Framework. Вручную прогонять все указанные инструменты для статического анализа и смотреть все флаги интересно только в первый раз, нужно сделать этот процесс быстрым и удобным. Mobile Security Framework — один из фреймворков, который может помочь. Это инструмент для тестирования на проникновение, анализа вредоносных программ и оценки безопасности мобильных приложений. Может выполнять статический и динамический анализ (под iOS есть только статический анализ). Удобно отображает дополнительную информацию о приложении.
Посмотрим на те же флаги для защиты бинарных файлов, но с красивым интерфейсом:
Отображение флагов защиты бинарных файлов в MobSF
Отображение требуемых разрешений и параметров безопасности для HTTP
Отображение кастомных URL схем в MobSF
Мы видим URL-схему, разрешение на использование HTTP-трафика, разрешения (permissions), которые могут быть опасны. Все уже собрано в один большой отчет, который можно выгрузить в PDF и изучить.
Установка и запуск
Мы сделали статический анализ. Теперь попробуем запустить приложение на джейлбрейкнутом девайсе и посмотреть, что оно делает.
Первая проблема, с которой мы сталкиваемся, — установка. Для пользователей есть один официальный способ это сделать — App Store. Для организаций существуют разные enterprise-решения, которые могут распространять приложение внутри компании в обход AppStore на девайсах, в которых уже включены MDM и т.д.
Нам это не нужно, поэтому попробуем поставить приложение (например, AppSync Unified), которое позволит устанавливать неподписанные файлы, файлы с невалидной подписью или с возможностью переподписать файл.
Самый простой вариант для этой задачи — Xcode (Window — Devices and Simulators) или Cydia Impactor (но в связи с последними изменениями от Apple у меня он не работает, вот тут есть информация про ошибки).
Пример установки приложения через Xcode (Window — Devices and Simulators)
Также неподписанное приложение можно установить, используя специальное приложение на девайсе. Например, через Filza: загрузить IPA на девайс (например, через SFTP), найти IPA файл и нажать “Install”.
Установка приложения с использованием Filza
Теперь попробуем запустить. При запуске приложения можно столкнутся с тем, что разработчики попытались заблокировать запуск на джейлбрейкнутом девайсе либо выдают предупреждения при каждом запуске или даже во время работы приложения.
Пример предупреждения о джейлбрейке
Один из простых способов обхода подобных предупреждений — использование специальных приложений (например, Liberty Lite), но это сработает только в случае простых механизмов обнаружения. Более сложные способы разберем в этой статье в разделе про инструменты динамической инструментализации.
Анализ трафика приложения
На предыдущих этапах мы уже настроили перехватывающий прокси, поэтому информацию об HTTP и HTTPS трафике сразу же сможем увидеть через Burp Suite:
Пример перехвата данных, отправленных приложением
С помощью анализа трафика мобильного приложения можно расширить поверхность атаки и найти больше входных точек. Иногда разработчики считают, что если мобильное приложение использует внутренний API, который не видят пользователи, то защита там может быть хуже, данные для аутентификации сохранены в коде приложения или аутентификация вовсе отсутствует.
SSL пиннинг. В качестве защиты перехвата HTTPS трафика в мобильных приложениях используется SSL пиннинг. В приложение добавляются заранее вычисленные пины — хэш-суммы от серверного сертификата или от отдельных его полей (например, SubjectPublicKeyInfo). Пины сохраняются в коде приложения. При обращениях к серверу приложение снова вычисляет пины сертификата и сравнивает со списком доверенных. Если пины не совпадают, значит сертификат подложный (например, как в случае с перехватывающим прокси) — соединение останавливается.
Для реализации SSL пиннинга существуют готовые решения:
NSURLSession — нужно писать самостоятельно свою реализацию защиты на основе данного API.
Разберем на примере TrustKit возможные варианты обхода SSL пиннинга. Например, конфигурация для TrustKit выглядит так:
Параметры конфигурации передаются в метод initSharedInstanceWithConfiguration при запуске приложения и инициализируют SSL пиннинг. При попытке установить соединение TrustKit проверит, что хотя бы один из указанных пинов совпадает с пинами, подсчитанными для сертификатов в цепочке сертификатов сервера.
Способы обойти данную проверку:
Перехватить вызов метода initSharedInstanceWithConfiguration и выставить TKSEnforcePinning в 0.
Перехватить вызов verifyPublicKeyPin и заменить на функцию, которая всегда возвращает 0 (=TSKTrustEvaluationSuccess).
Реализовать этот обход можно как используя готовые инструменты (например, ssl-kill-switch2), так и с помощью инструментов динамической интрументизации.
WebView. Если приложение использует WebView, то при тестировании стоит обратить особое внимание на конфигурацию и значения параметров. UIWebView (старая версия) и WKWebView предназначены для встраивания содержимого веб-страниц прямо в приложение, поддерживая CSS и JS. Также поддерживают базовую логику навигации в веб (переход вперед/назад/гиперссылки и т.д.). Начиная с iOS 8.0 и OS X 10.10, используйте WKWebView для добавления веб-содержимого в ваше приложение и не используйте UIWebView.
У WKWebView есть флаги, которые могут быть для нас интересны:
JavaScriptEnabled — включено ли исполнение JS для данного WebView.
JavaScriptCanOpenWindowsAutomatically — логическое значение, которое указывает, может ли JavaScript открывать окна без взаимодействия с пользователем.
AllowFileAccessFromFileURLs (для WKPreferences, по умолчанию false) — включает JavaScript, работающий в контексте URL-адреса схемы “file://”, для доступа к содержимому из других URL-адресов схемы “file://”.
IsFraudulentWebsiteWarningEnabled — логическое значение, которое указывает, отображаются ли предупреждения о мошенническом содержимом: например, о вредоносных программах или попытках фишинга.
Для экспериментов или отладки своего приложения удобно использовать Web Inspector в Safari (для этого приложение должно быть собрано с возможностью отладки). Включив Web Inspector на девайсе, мы сможем подключиться к WebView и посмотреть, что происходит: как выглядит HTML, выполнить какой-то JavaScript и т.д.
Подключение к DVIA-v2 через Web Inspector
Хранение данных на устройстве
Самый простой способ хранения данных на устройстве — NSUserDefaults. Это простое хранилище «ключ — значение». Там обычно хранится информация о настройках приложения, чтобы эти данные сохранялись между запусками. Хранилище NSUserDefaults нельзя использовать для хранения важной информации.
Безопасное хранение данных на iOS-девайсах должно быть реализовано с использованием Keychain. Это хранилище предназначено для хранения паролей, криптографических ключей, сертификатов и другой важной информации. Разработчику предоставляется API для работы с Keychain, при этом все важные операции вынесены в отдельную подсистему безопасности на уровне «железа» — Security Enclave.
Схема разделения зон ответственности между приложением, OS и Secure Enclave
У Keychain есть много параметров. При разработке приложения стоит внимательно подойти к тому, какие секреты с какими параметрами будут сохранены. Все параметры можно разделить на атрибуты доступности и параметры контроля доступа.
Атрибуты доступности указывают на то, когда данное значение может быть получено. Возможные модификаторы:
Системы тестирования iOS-приложений
С чего все начинается
Рассмотрим шаги, которые необходимо проделать разработчику iOS-приложения и тестеру, чтобы наладить между собой канал разработки, тестирования и получения отчетов без использования специализированных систем тестирования.
Доставка приложения тестерам
Тестеру необходимо, чтобы разрабатываемое Вами приложение было установлено на его устройстве. Как правило, делаете всю предварительную работу Вы сами: в лучшем случае Вы настроили автоматическую сборку c AdHoc-сертификатом, тестер в час X скачивает сборку и устанавливает ее на свое устройтво путем синхронизации через iTunes или с помощью Configuration Utility. В худшем случае Вы сами отправляете сборку письмом или загружая на определенный ресурс.
Тестирование
Тестер должен тщательно проверить приложение, отложить устройство и завести пару багов в баг-треккере и, если приложение вылетело в процессе тестирования, приложить парочку crash-файлов. Тестер может быть не один, и тогда количество созданных задач в треккере возрастет, если тестеры никак с собой не взаимодействуют. А такое бывает.
Доработка
Прочитав все, что создано после этого в баг-треккере, Вы приступаете к исправлению. И процесс повторяется до тех пор, пока вы все не исправите.
Проблемы такого подхода
На первом этапе проблемы — это все что находится между вашим финальным коммитом в репозиторий до установки приложения на устройство тестера. Ручные скачивания тестерами сборок, синхранизация с айтюнсом, установки через Configuration Utility, — все это рутина, которую нужно убрать.
На шаге тестирования проблема следующая. Тестер тратит время на создание задачи в треккере, прикладыванию к ней краш-лога. Крашлоги могут быть из-за одной ошибки, что может привести к дублирования задачи в треккере.
Уверен, что часть из вас так разрабатывает приложения прямо сейчас.
Что поможет?
За время развития современной индустрии мобильной разработки придумано ни одно средство, позволяющее свести к минимуму описанные выше затраты, если не убрать их полностью. Что если парой кликов «опубилковать тестерам» и «установить» приложение будет установлено на устройствах всех тестеров?
Такие решения есть.
К основным таким решениям на данный момент можно отнести TestFlight и HockeyApp.
Как начать работу
Тестерам в час X приходит письмо:
Нажав на Install, тестер начинает процесс установки приложения с сервера, после чего может начинать свою работу.
Пример того, что Вам нужно добавить в Ваш исходный код для минимальной и необходимой работы TestFlight:
Это все. Вам необходимо это вставить в метод UIApplicationDelegate:
Отличие систем
По упоминаниям в сети, пожалуй, самая популярная система — TestFlight. Но HockeyApp имеет ряд преимуществ перед TestFlight:
TestFlight самое популярное решение в данной нише. Может быть, из-за нагрузки, может быть, по другим причинам сервис бывает недоступен, особенно в конце весны, когда много разработчиков проголосовали в пользу HockeyApp ногами.
Надеюсь, удалось показать, как подобные системы позволяют полностью избавить вас от рутины, которая ест время вашей команды, упростить сбор информации во время тестирования, упростить создание задач в баг-треккере, сгруппировать одинаковые проблемы. В общем, сделать все, чтобы облегчить вашу жизнь.
Впервые столкнувшись с такими системами, люди обычно разводят руками и говорят: «Магия».
Запускаем iOS приложения из консоли на девайсе и симуляторе
В статье будет кратко описано, как собрать приложение консольными командами и запустить на реальном девайсе и симуляторе без какой-либо надобности открывать xcode для этого.
В общем, стоит начать с того, что в моей компании имеется отдел ios разработки в составе > 10 человек, которые работают над пачкой приложений. Для автоматизации рутинной работы нам пришлось развернуть CI сервер (пока самописный, ввиду особых исторических причин (ну как у всех), планируется миграция на jenkins). К рутинным вещам я отношу — сборку проектов, запуск тестов (если, конечно, вы их пишете), создание ипашек для тестеров и для выкладки в app store. В общем, хотелось чтобы по нажатию кнопочки или по хуку в гите всё это начинало работать. Пока у нас всё работает по нажатию кнопки разработчиком, про гит пока только в планах. В данной статье я только затрону тему компиляции проекта и упаковку его в ipa файла. Функционал запуска проектов на девайсах на стороне CI сервера еще находится в стадии разработки, а вот сам процесс упаковки уже давно в «бою», может кому пригодиться.
Перед компиляцией проекта необходимо установить apple doc generator github.com/tomaz/appledoc (там есть описание как установить).
Переходим к скаченному проекту. Корневой проект представляет собой сам ResearchKit framework, само тестовое приложение находится в другом месте, переходим туда:
Пробуем компилировать приложение. Собираем под симулятор, так как для сборки проекта под симулятор не нужны сертификаты/провижен файлы.
Билд падает, так как нет схемы ORKCatalog. Так как схема в оригинальном проекте не была помечена как shared, то после “слива” из гита, xcode еще ничего не знает об этой схеме, чтобы он ее снегирил, нужно просто открыть проект. Поэтому просто открываем проект:
После видим долгожданное ** BUILD SUCCEEDED **. Отлично, всё работает.
Запускаем проект на симуляторе
Компилируем под симулятор, архитектуры могут быть i386/x86_64. Через SYMROOT задаем путь результата сборки:
(Мысли вслух: когда писал статью, компиляция в режиме Release работала, перед публикацией статьи еще раз проверил все шаги и компиляция в этом режиме перестала работать, поэтому собираем в Debug, какие-то из последних коммитов это сломали.)
После успешной сборки мы получили ORKCatalog.app файл в build/Debug-iphonesimulator/. Осталось запустить это на симуляторе. Для этого будем использовать ios-sim утилиту github.com/phonegap/ios-sim. Пользоваться ей достаточно просто.
Получаем список доступных симуляторов:
Из предложенного списка я выбрал ‘iPhone-6-Plus’. Запускаем приложение на нем:
Если всё правильно сделали, то должен запуститься симулятор с приложением (для входа в режим ввода в консоли используйте ctrl + C).
Создание ipa файла и запуск на реальном девайсе
Тут немного сложнее, нам нужен mobileprovision файл для разработки (developer) и наличие сертификата на машине (p12 файл), при этом наличие аккаунта в xcode не нужно. При подписании/упаковки приложений из консоли нет необходимости добавлять аккаунт в xcode, это очень помогает, например, на CI сервере можно держать только p12 файлы.
Будем считать, что на вашей машине есть соответствующий сертификат. После генерируем developer mobileprovision через developer.apple.com в вашем аккаунте и скачиваем на машину (дадим ему название test.mobileprovision, а bundle id будет ru.habrahabr.test). После копируем его в директорию, где его сможет подхватить xcode:
Собираем архив под девайс (это архитектуры arm64/armv7):
Компиляция упадет, так как мы еще не прописали в приложении свой bundle id и не прилинковали mobileprovision, ошибка будет такого вида:
Будем передавать bundle id из консоли + нужно прописать его в Info.plist файле. Также через консоль будем передавать линк на наш mobileprovision. Plist файл находим по пути ORKCatalog/Supporting Files/Info.plist, в котором для ключа CFBundleIdentifier выставляем значение ru.habrahabr.test. Bundle id передаем через ключ со значением нашего ид PRODUCT_BUNDLE_IDENTIFIER=ru.habrahabr.test. Линк на mobileprovision передаем через ключ PROVISIONIG_PROFILE, со значние UUID, который прописан в mobileprovision.
Значение будет подобно 87b0df89-793a-4a0f-92bf-c5f9c35f1405. Снова собираем:
В итоге получим архив build/archive.xcarchive, который осталось упаковать в ipa. В xcode 7 появился новый метод упаковки, им и воспользуемся. Перед этим создадим конфиг файл options.plist с таким содержанием:
Пробуем собирать ipa:
Сборка падает, по логам можно понять, что что-то с entitlements:
По ошибке ясно, что приложение подписываем entitlements, значения в котором не соответствуют значениям в нашем mobileprovision, а именно com.apple.developer.healthkit. Ищем причину. Смотрим каким entitlements подписано приложение:
Видим, что всё ок, за исключением этого:
У нас в mobileprovision нет этой опции, нужно выяснить, откуда это взялось:
Поиск нам выдал ORKCatalog/Supporitng Files/ORKCatalog.entitlements. Смотрим что внутри:
Там только одно значение:
По логике нужно перезаводить mobileprovision, в который нужно добавить это значение, но мы ленивые и для теста это не обязательно, нам нужно просто переподписать приложение с entitlements без этого значения.
У нас есть как минимум два варианта:
1. Просто редактируем существующий entitlements (ORKCatalog/Supporitng Files/ORKCatalog.entitlements) и снова пересобираем (через archive).
2. Без пересборки, сами переподпишем ORKCatalog.app с нужным entitlements.
Выберем первый вариант как более простой. Поэтому из файла ORKCatalog/Supporitng Files/ORKCatalog.entitlements просто удаляем строки:
и снова пересобираем архив:
Видим долгожданное сообщение ** EXPORT SUCCEEDED **. Под build/dev-ipa/ появиться ipa файл, который будем устанавливать на девайс. Устанавливать на девайс будем с помощью ios-deploy github.com/phonegap/ios-deploy. Цепляем девайс к машине, получаем id девайса через:
Собираем ipa файл для маркета
Без использования xcode смогли собрать ipa файлы для тестов и на выкладку в app store. Всё это можно легко автоматизировать на CI сервере чтобы облегчить жизнь разработчикам.
Ломаем iOS-приложение. Часть 1
Вы хорошо поработали, и вот ваше приложение в App Store!
Disclaimer
Цель данного урока — не сделать вас хакером, а показать, как злоумышленники могут обвести вас вокруг пальца. В статье пропущена кое-какая инфа, необходимая для взлома реального приложения на девайсе. Будем мучить симулятор (кстати, это вроде бы даже законно [citation needed] ).
Disclaimer от переводчика: из оригинального текста убрано много «воды» и отсылок к Голливуду (и так длинно). Добавлен ряд ключевых пояснений.
Ни одно приложение не является безопасным! Если кто-то действительно захочет найти у вас уязвимости, то он их найдёт. Нет гарантированного способа предотвратить атаку. Разве что, не выпускать приложение. Но есть отличные способы помешать взломщикам. (По мнению автора, им надоест, и они пойдут спать искать более лёгкую добычу, ага. — Прим. пер.)
Перед тем, как читать дальше, вы должны примерно понимать, что такое терминал, а также Objective C и Cocoa (впрочем, особо много кода не будет).
Приступим
Нам понадобится:
1. Утилита class-dump-z;
2. Прокси для отладки по сети, например, Charles (триал-версия отличается надоедливыми сообщениями и работает максимум 30 минут за 1 сеанс). В комментах к исходнику статьи советуют альтернативу Чарльзу — Burpsuite.
Чтобы вы творчески подошли к процессу, предлагаю вам сценарий. Представьте: вышло новое приложение для айпада — «Собиратель мемов» (Meme Collector). Всем нравится. Но вам шепнули на ухо, что встроенные покупки вытянут из вас значительную сумму денег.
В общем, вы задумали получить платный контент (мемы) бесплатно. Есть несколько направлений, куда можно двигаться, мы поговорим о них — и о соответствующих методах защиты.
Что к чему? «Карта» приложения (application mapping)
Взглянем на приложение с высоты птичьего полёта! Что оно делает, с точки зрения пользователя? Какова его основная структура?
Откройте проект Meme Collector в своей любимой IDE или в Xcode (не реклама).
Мы будем запускать приложение в конфигурации Release.
Xcode: Product > Scheme > Edit scheme… (⌘ Build Configuration: Release.
AppCode: Run > Edit configurations… > Configuration: Release.
Запустите приложение на симуляторе iPad (⌘R). А теперь забудьте, что у вас есть исходники, вы — юзер. Откроется одно из двух:
Этот странный интерфейс позволяет «купить» мем по тапу на него, а также показывает число покупок данного мема и остаток денег на вашем счёте. От переводчика: нажав кнопку «Purchase Currency», я реально задумался: а не вводил ли я Apple ID на симуляторе?
…но вспомнил, что у меня к US-аккаунту не привязана кредитка. 🙂 Русский бы сказал цену в рублях.
В общем, всё просто. Есть «игровая валюта», и вы (типа взломщик) не хотите отдавать за неё реальные деньги. Наша цель — получить больше мемов, не нажимая кнопку «Purchase Currency».
Мы примерно поняли, что делает приложение, давайте заглянем глубже. Что ещё мы можем узнать?
Перейдите в папку симулятора iOS: (вместо 7.0.3 — ваша версия)
Здесь лежат все приложения, которые вы запускали в симуляторе. Их там может быть много. Как найти Meme Collector?
Вариант 1. Если вы только что запускали Meme Collector, то он лежит в папке с самой новой датой модификации. Просто перейдите в неё:
Вариант 2. Взять и найти:
Тут есть исполняемый файл, который так и называется: Meme Collector (без расширения). Посмотрим, на какие фреймворки (frameworks) и общие библиотеки (shared libraries) имеются в нём ссылки. Нам поможет стандартная утилита otool :
Мм, интересно, приложение использует фреймворк Store Kit — встроенные покупки? Ну-ка, идём дальше. Запускаем утилиту class-dump-z:
И откроем полученный файл в стандартном гуёвом редакторе:
Изучение дампа классов серьёзного проекта — чаще всего утомительное занятие. Но оно может дать потрясающую картину внутреннего устройства приложения!
Итак, мой юный следопыт! Найдите-ка мне все синглтоны в приложении.
Если встретите интересный синглтон, изучите все его методы.
(Видимо, «интересный» = «похоже, имеющий отношение к логике приложения». — Прим. пер.)
Точно нашли? Посмотрите у себя внимательнее. Их там около четырёх штук.
Взломщики часто делают то, что вы сейчас сделали — ищут определённые слова в дампе класса. Например, если они хотят узнать, есть ли в приложении логика, завязанная на джейлбрейк девайса, то поиск по словам ‘jailbroken’, ‘security’ и т.д. может дать быстрый ответ.
Как это предотвратить?
Есть две новости. Начну с хорошей.
Apple делает это за вас автоматически. Когда вы отправляете приложение в App Store, они шифруют ваши бинарники с использованием DRM-технологии под названием FairPlay. Если вы сдампите class-dump-z’ом зашифрованный бинарник, то получите… тарабарщину.
Плохая новость: обойти эту защиту довольно легко! Дело на 10 минут (вручную), а ещё есть средства автоматизировать сей процесс. В общем, будьте уверены, ваше приложение расшифруют и увидят названия всех ваших классов, методов, протоколов и т.д.
Plist-файлы: уязвимы!
Мы кое-что узнали о приложении. Теперь поищем всё, что плохо лежит. Разработчикам свойственно ошибаться. Злоумышленникам свойственно использовать эти ошибки в своих целях.
Посмотрим, какие plist’ы у нас есть?
(напомню, мы всё ещё находимся в папке бандла «Meme Collector.app»)
А вот какие — две штуки:
Посмотрим первый — Info.plist:
Ничего интересного, какая-то общая информация. Ладно. Ну-ка, а второй:
Вернёмся к симулятору. Уберём наше приложение из оперативной памяти (как говорят, «из многозадачности»).
Сочетание клавиш ⇧⌘H в симуляторе iOS соответствует нажатию кнопки Home. Нажмите это сочетание дважды, чтобы показать экран многозадачности, и смахните Meme Collector в никуда вверх.
Кажется, он не очень доволен, что его смахивают:
В дальнейшем, когда я буду говорить «перезапустите приложение», я буду иметь в виду именно эту операцию.
Снова запустите приложение. 1234 виртуальных тугриков в вашем распоряжении!
От переводчика: я ждал большей интриги. Не бывает так просто! Хоть свой тестовый проект пиши.
Пользовательские настройки: небезопасно!
Из терминала (мы по-прежнему находимся в Meme Collector.app, так?) откройте файл:
Несложное упражнение для вас: используя методы, о которых говорилось выше, модифицируйте NSUserDefaults так, чтобы получить кучу мемов из серии «Y U No …» бесплатно. Даже если вы уже обо всём догадались, советую сделать это для закрепления.
Но ведь злоумышленники могут получить доступ к plist-файлам, даже когда iOS-девайс заблокирован — так где же безопасно хранить данные? Одно из решений — хранить данные в NSUserDefaults в зашифрованном виде. В этом случае (и не только. — Прим. К.О.) нужно проверять на валидность данные, читаемые оттуда.
Связка ключей: лучшие рецепты
Связка ключей «повышает ставки» для хакера. Злоумышленники не смогут ничего стянуть, если устройство заблокировано.
Тем не менее, не стоит полностью полагаться на одну только Связку ключей! И вот почему. Связку ключей поддерживает Apple. (Ну вы уже всё поняли, да?) Информация в ней зашифрована паролем пользователя, который обычно является простым 4-значным цифровым кодом. А это значит, что атака брутфорсом займёт минут двадцать. Узнав пароль, легко сдампить всю связку ключей.
Что же делать? Некоторые общие рекомендации:
Сеть: тестирование на проникновение
Ещё хакеры любят наблюдать, как приложение взаимодействует с сетью. Самый тупой способ увидеть, происходит ли какая-то работа с сетью на устройстве, это поискать URL’ы в бинарнике.
Находясь в папке бандла (Meme Collector.app), наберите в терминале:
Стой, куда столько! Команда strings идёт по разделам бинарника и выводит все элементы данных, похожие на строки. Отфильтруем шум:
А, ну вот, одна строка:
Похоже, в какой-то момент приложение обращается к генератору мемов по этому урлу. Будучи хакером, вы бы хотели исследовать это дело дальше, изучив сетевой трафик приложения. Для этого нам понадобится сетевой монитор, перехватывающий все входящие и исходящие запросы.
Charles, упомянутый в начале статьи — хороший вариант для такого исследования. Скачайте его, если ещё не сделали это. Установите и запустите.
Проверьте, что Charles ловит сетевое взаимодействие с симулятора iOS (путём запуска «Карт», или в Safari набрать урл). Вы увидите, как в Charles побегут сетевые запросы. Если этого не произошло, убедитесь, что в меню Proxy > Mac OS X Proxy галочка установлена.
Выберите вкладку Response и затем внизу JSON. Ответ сервера, расшифрованный из JSON, представлен в виде красивой таблички:
Мы видим здесь множество строк вида «ключ-значение»: заголовок ( displayName ), описание ( description ), URL картинки ( imageUrl ) — в общем, некая информация по данному типу мемов «Four Bachelor Frog» из GET-параметра.
Похоже на то, что мы видели в приложении, правда? Двум другим картинкам в этот раз повезло меньше, они так и не дождались ответа от сервера (Charles сообщает об этом на вкладке Overview) и поэтому не отобразились в приложении.
При повторном запуске картинки могут браться из кэша симулятора, Charles лб этом не знает. Очистите кэш и перезапустите приложение.
Итак, с определённой долей вероятности делаем вывод: приложение берёт мемы с данного API и представляет их в виде платного контента. А что если попытаться изменить URL с целью приобрести какой-то новый контент, кроме этих трёх мемов? Непохоже, что тут есть проверка, действительно ли приложение получает с сервера то, что ожидал разработчик!
Вам уже надоели эти три мема? Ну-ка попробуем, можно ли отобразить и «купить» что-то новое, скажем, «Success Kid».
Выберите в меню Charles: Tools > Rewrite. Эта функция позволяет перехватывать входящие/исходящие запросы и модифицировать их по тем правилам, которые вы установите. Включите галочку Enable Rewrite. Правила группируются в «наборы» (Sets). Под списком Sets нажмите Add для добавления нового набора правил. По желанию, переименуйте (Name). Мы создали набор правил, но он пока пустой. Давайте добавим правило — в разделе Rules есть кнопка Add, нажмите её.
Открылось окно Rewrite Rule. Измените Type на «Modify Query Param» («Изменить параметр запроса») и заполните два поля:
Нажмите OK, OK. Перезапустите приложение… Success! Мы можем покупать контент, который ранее был недоступен.
Интересно: для этого нового мема указана конкретная цена. Откуда? Приложение должно было как-то определить стоимость, исходя из JSON-ответа.
Откройте вкладку Response и посмотрите на JSON, который возвращает сервер. Что может определить стоимость цены?
Попробуйте найти JSON-ключи, которые могут определить стоимость мема в приложении. Может быть, это generatorID, totalVotesScore, instancesCount, templatesCount или ranking. В качестве упражнения для вас: найдите тот ключ, который влияет на стоимость мема.
Теперь перезапустите приложение. Как только нам придёт ответ с сервера, сработает точка останова (breakpoint). Когда это произойдёт, щелкните на вкладке Edit Response, внизу выберите JSON:
Здесь вы можете вручную модифицировать JSON-ответ, который пойдёт в приложение. Поиграйтесь с этими параметрами и попробуйте определить, какие ключи влияют на цену, отображаемую в приложении. Изменив JSON-ответ, нажмите Execute (выполнить) для отправки ответа. Приложение делает три запроса к API, поэтому вам понадобится нажать Execute трижды.
Важно: действуйте быстро! AFNetworking имеет таймаут 30 секунд. Если вы перехватили ответ, но не успели внести изменения, AFNetworking вернёт ошибку таймаута запроса и выполнит соответствующий обработчик в коде (который в данном случае не делает ничего). Если у вас вышло время, перезапустите приложение и попробуйте снова.
Что дальше?
Вы открыли в себе хакерские способности и выполнили простейшие тесты на проникновение на примере файловой системы и сетевого взаимодействия конкретного приложения. Вы победили простые plist’ы и даже можете модифицировать ответы сервера.
Возможно, эти знания повысят безопасность вашего iOS-приложения… чуть-чуть. В следующей части мы углубимся гораздо дальше в недры приложения, будем изменять его функционал! А пока вы ждёте, когда я выложу перевод (до следующей пятницы), можно много чего попробовать на тему хранения данных в приложении:
Без джейлбрейка? Да ну?
Замечания по поводу перевода или неработающих примеров можно отправлять на почту dev @ x128.ru.
Ломаем iOS-приложение! Часть 2
В первой части мы изучили некоторые вопросы безопасности хранения и передачи данных. Теперь переходим к защите исполняемого кода. Мы будем модифицировать функционал iOS-приложения во время выполнения и проделаем реверс-инжиниринг. И снова, помните! Наша цель — не стать гадким взломщиком, а защитить ваше приложение и пользователей от злонамеренных действий. Для этого нужно понять, что может сделать взломщик.
Чтобы успешно пройти этот урок, вы должны представлять, что такое ассемблер. Автор статьи советует пройти туториал по ARM (на английском).
Вообще-то, чтобы понять смысл урока, уровень необходимых знаний — это пару минут погуглить по ходу дела. Ну вы ближе к концу статьи сами решите, нужно изучать ассемблер или нет. 🙂 — Прим. пер.
Начнём
Манипуляции со средой выполнения (runtime)
Откройте в терминале папку главного бандла ( Meme Collector.app ), установленного на симулятор iOS. Если вы затрудняетесь это сделать, загляните в первую часть.
Занимаем исходную позицию: симулятор запущен, приложение установлено, но не запущено.
В терминале наберите:
Дебаггер запущен, отлично. На следующей строчке мы видим приглашение от него: (lldb)
Набираем команду для дебаггера:
я не буду писать символы (lldb) в начале строки, чтобы вы ничего не перепутали при копировании
Команда attach служит для подключения к определённому процессу. Здесь мы просим LLDB, чтобы он подождал запуска нового процесса с названием » Meme Collector » и подключился к нему.
Итак, дебаггер ждёт. Перейдём к Симулятору iOS и выполним традиционное (по прошлой части урока) удаление приложения из многозадачности, и далее повторный запуск (запускать именно из симулятора, не из IDE) — далее будем называть это «перезапуск».
Если всё сделано правильно, LLDB начнёт вместе весело шагать с процессом в симуляторе. Отладчик подключится к процессу, приостановит его выполнение и скажет:
И приглашение для ввода новой команды: (lldb)
Выполните в терминале:
Названия методов чувствительны к регистру, поэтому вариант viewdidload не пройдёт.
Итак, LLDB сообщает нам, что он нашёл 15 подходящих мест для точек останова:
Отлично. Посмотрим, куда он их поставил. Вводим команду:
(Это сокращение от breakpoint list — если хотите, можете писать полную версию команды.)
Вернёмся к запуску нашего приложения! Введите команду:
«А тепе-е-ерь… пора нам позабавиться: а то я не играю!»
Мы остановили процесс на кадре (frame) класса ViewController (файл ViewController.m). Значит, у нас есть доступ к его переменным экземпляра (instance variables) и к методам. Круто? И ещё! Секция кода уже загружена в память. Следовательно, у нас есть доступ ко всем другим классам, включая — внимание! — синглтоны.
Мы вызвали метод! Отладчик выведет результат выполнения:
Если мы увидели ответ YES — значит, мы успешно «приобрели» виртуальную валюту. (Тут автор проболтался, это инсайдерская информация. Мы — взломщики — не должны её знать. — Прим. пер.)
А ещё LLDB повторяет предыдущую команду по нажатию Enter. Поэтому нажмите несколько раз Enter, чтобы ещё немного ограбить Михалкова:
Приобретение и так бесплатного контента никогда не было таким лёгким! Теперь пару раз введите команду
…чтобы закончились все breakpoint’ы, которые мы понаставили, и оцените результат в симуляторе:
Неплохо, да? Ну-ка посмотрим, что мы можем с этим сделать.
Чтобы приостановить выполнение приложения и снова вернуться в командную строку, переключитесь на терминал и там нажмите Ctrl+C. Отладчик LLDB снова готов выполнять наши команды.
Давайте пока закончим сеанс отладки: введите команду q и затем для подтверждения y :
Возвращаемся на сторону разработчика. Можно ли перехитрить желающих манипулировать вашим приложением через отладчик?
Защита от runtime-манипуляций
Добавьте 3 строчки в начало метода sharedManager класса MoneyManager :
Теперь метод должен выглядеть так:
Обратите внимание: этот макрос доступен только в конфигурации Release. Если вы следили за нами в части первой, вы уже должны были переключиться на релиз.
Xcode: Product > Scheme > Edit scheme… (⌘ Build Configuration: Release.
AppCode: Run > Edit configurations… > Configuration: Release.
А теперь запустите наше приложение из IDE (не забыли выбрать конфигурацию Release?)
Xcode: Run (⌘R)
AppCode: Debug (Ctrl+D)
Xcode автоматически подключает отладчик LLDB при выборе команды Run. Результат: остаток на счёте не отображается! Действительно, где-то там nil :
А в AppCode существуют две разные команды: команда Run не подключает отладчик, а команда Debug — подключает. Удобно.
Чтобы окончательно убедиться в том, что наша защита работает, проверьте: сможете ли вы сейчас купить что-то? MoneyManager недоступен — значит, не сможете.
Остановите приложение, нажав в IDE кнопку Stop (с квадратом). Также остановите дебаггер LLDB. Переключитесь на симулятор и запустите приложение оттуда. Приложение отобразит валюту, т.к. отладчик не подключен.
Как мы уже говорили, подключить отладчик к процессу можно не только при запуске, но вообще в произвольный момент времени. Выполните в терминале:
Вывод данной команды будет содержать список всех процессов, в имени которых встречается фраза «Meme Collector»:
Можно видеть, что вторая строчка соответствует папке приложения в симуляторе. Обратите внимание на номер этого процесса (второй столбец). В моём случае это число 2001.
LLDB запустится и сообщит об успешном подключении к процессу:
Когда LLDB запущен, попробуйте обратиться к синглтону MoneyManager :
И что же в этом описании?
Продолжим выполнение приложения командой:
Попробуйте легально пополнить свой счёт по кнопке «Purchase Currency». Ничего не выйдет! Ведь LLDB по-прежнему подключен к процессу.
Отключите отладчик от процесса: нажать Ctrl+C и затем ввести команду q. Кнопка «Purchase Currency» снова работает.
В дополнение к проверке на наличие отладчика, можно применить более жёсткий подход. Функция ptrace помогает по возможности сопротивляться подключению GDB / LLDB к вашему приложению.
Для этого вернитесь в IDE и откройте main.m. Добавьте один заголовочный файл:
И три строчки в начало функции main :
Теперь запустим приложение из IDE.
Xcode: приложение как будто не запускается. Что происходит? Мы видим на мгновение чёрный экран, который тут же исчезает — это приложение загружается в память и начинает выполняться. При этом Xcode хочет подключить к нему LLDB, но iOS не разрешает и завершает процесс отладчика. «Раз отладчик завершён, — думает Xcode, — приложение завершилось, поэтому остановлю-ка его выполнение». Последняя фраза звучит дико, но всё работает именно так. — Прим. пер.
AppCode: по команде Run (⌘R) приложение нормально запускается, а по команде Debug (Ctrl+D) «падает» подобно Xcode.
И из симулятора запускается корректно. Попробуйте теперь подключить к нему отладчик, как обсуждалось выше:
Это хорошее средство, чтобы остановить маленьких детей, начитавшихся хабра, от игр с вашим приложением. Но это не остановит бородатых хакеров. Они остановят ваш процесс при вызове функции ptrace и модифицируют её перед продолжением.
В общем, не чувствуйте себя слишком комфортно. Хакеры любят использовать Cycript — язык скриптов (напоминающий JavaScript) — именно для манипуляций над ObjC-приложениями во время выполнения. Защита от дебаггера, которую мы сделали, не защитит вас от Cycript. Помните, с чего мы начали разговор в предыдущей статье:
Ни одно приложение не является безопасным!
Препарируем бинарник
Прежде чем приступить к модификации бинарных файлов, давайте выясним, как его разобрать на части, и что к чему.
Я буду периодически ссылаться на конкретные адреса в бинарнике для иллюстрации определённых понятий. Если у вас не такая версия компилятора, как у меня (например, поставляемая с более новой Xcode), либо вы компилируете конфигурацию Debug вместо Release, либо сами вносили изменения в проект — адреса могут быть другими. Пусть это вас не смущает — просто следите за изложением, чтобы понять идею.
Формат исполняемых файлов в OS X и iOS называется Mach-O. Как правило, бинарник начинается с заголовка (header), содержащего всю информацию о том, где и какие данные есть в бинарнике. За этой информацией следуют команды загрузки (load commands), которые расскажут вам о разметке файла по сегментам. Кроме того, эти команды определяют специальные флаги: например, зашифрованы ли двоичные данные в файле.
В каждом сегменте (segment) есть одна или несколько секций (sections). Два вида секций стоит отметить:
Эта команда напечатает заголовок исполняемого двоичного файла «Meme Collector». Приблизительно так:
Заметим: файл имеет 25 команд загрузки ( cmds ), а занимают они 3372 байт ( sizeofcmds ). Посмотрим на эти команды:
(Перед этим можно очистить окно терминала, нажав ⌘K. Так будет удобнее листать. — Прим. пер.)
Вы получите много-много строк. Из этих строк (даже без предварительной подготовки) можно увидеть много интересного о порядке загрузки сегментов и секций в память. Но это исследование выходит за рамки данного туториала, оставим его наиболее любопытным читателям на самостоятельное изучение.
Здесь сдвиг секции __objc_classname равен 159942 байт (в десятичном представлении). На изображении ниже, в левой части — подчёркнуто красным.
Перейдите в терминал. Откройте новое окно терминала (⌘N) и из той же папки «Meme Collector.app» выполните:
Ну-ка, что у нас находится по адресу 159942? Имена классов! (Выделены красным.) Логично, мы же искали секцию __objc_classname :
Итак, мы видим, как команды загрузки (load commands) позволяют упорядочить тот хаос, который представляет из себя бинарный файл Mach-O. С этим знанием мы приступаем… тадааам! к модификации секции кода.
Тяжёлая артиллерия: дизассемблер и реверс-инжиниринг
Вы готовы пустить в ход серьёзные пушки? Наконец мы узнаем, как модифицировать бинарный файл приложения!
Вероятно, вы часто слышите в жизни фразу: приложение «взломано». Это значит, кто-то модифицировал приложение так, что оно работает… ммм… иначе, чем задумывал разработчик. Например, не просит зарегистрироваться. Поэтому мы (автор и переводчик) искренне надеемся, что наш труд послужит вам во благо. Только для защиты ваших приложений.
Скачайте IDA Demo и какой-нибудь HEX-редактор, например, Hex Fiend. IDA — это инструмент, к которому хакеры обращаются чаще всего при изучении бинарников. Это невероятно мощный дизассемблер, отладчик и декомпилятор. И полная версия стоит не так уж дорого.
Но если вы ещё не готовы купить программу, о которой услышали 15 секунд назад, IDA предлагает демонстрационную версию с ограниченным функционалом. В демо-версии ограничены типы ассемблерных файлов, которые можно в нём изучать. А также отключена возможность модификации кода.
Но в ней есть наш тип ассемблера x86. А все модификации мы будем делать вручную в другой программе — Hex Fiend.
Установите и запустите IDA. Нас приветствует Ада Лавлейс (Ada Lovelace) — первый в мире программист:
Нажмите кнопку Go. В терминале мы (да-да) всё ещё находимся в папке бандла ( Meme Collector.app ). Введите следующую команду, чтобы показать данную папку в Finder’е:
Не забудьте про точку в конце. Символ точки здесь означает «текущую папку».
Далее в открывшемся окне Finder: правой кнопкой мыши > Показать содержимое пакета:
(OS Mavericks в русском переводе называет бандл (bundle) «пакетом», но мне это название кажется неинформативным. — Прим. пер.)
Действительно, IDA определил, что этот бинарный файл является исполняемым файлом архитектуры i386.
Ваши настройки должны соответствовать тем, что изображены выше (думаю, вам не придётся ничего менять) — и нажимайте «Поехали!» OK. Дизассемблер разберёт файл по мелким кусочкам и составит его схему (mapping) — то, чем мы занимались выше, но… как сказать… более профессионально. 🙂
Если спросит «Objective-C 2.0 structures detected. Do you want to parse them and rename methods?» — отвечайте Yes. Если спросит что-то про «proximity view», отвечайте No.
(В моём случае оказалось недостаточно одного пробела, пришлось ещё Enter нажать раз-другой. Ну вы быстро разберётесь, что к чему. — Прим. пер.)
И откройте проект в Xcode или AppCode. В целях сокращения изложения, мы чуть-чуть подсмотрим в код.
Откройте MoneyManager.m и взгляните на метод buyObject:
Если бы эту проверку обойти («перепрыгнуть», в терминах ассемблера) — можно было бы купить всё, что угодно, тогда значение _money уже бы не рассматривалось как фактор при покупке.
Теперь найдём тот же код в дизассемблере. Вернитесь к IDA, щёлкните по любой функции на панели Functions (просто чтобы активировать эту панель) и затем нажмите Сtrl+F (или из меню: Edit > Quick Filter). Появится поле ввода для поиска нашей функции. Нам нужно найти buyObject:
Ага, нашли, дважды щёлкните на названии метода. IDA покажет окошко дизассемблера, которое отлично демонстрирует условный оператор и ветвление кода:
Даже если вы ничего не знаете из школьного курса ассемблера, из сравнения с исходным кодом buyObject: можно предположить, что зелёная стрелка «вправо» — именно то место, куда мы хотим хакерски пойти, там выполняется много действий. А краткий код под красной стрелкой «влево» больше похож на лаконичное » return nil «.
Здесь та же информация, но в линейном виде. Найдите номер строки с командой jnb (эта строка выделена):
В моём случае адрес оказался 0x00018D88. Напомню, у вас может быть любой другой адрес.
Откроется окно с нашим бинарником. Красиво, правда? Вот они — наши знакомые: __objc_classname и другие секции. Перед нами явно заголовок исполняемого файла.
Теперь наберите в терминале:
Здесь мы видим стартовый адрес секции ( addr ) 0x00002970, а сдвиг ( offset ) — 6512 (десятичное число). Вы можете взять IDA и наглядно убедиться, что стартовый адрес, с которого начинается код — именно 0x2970, для этого надо проскроллить (в «линейном» виде) на самый-самый верх. (Напоминаю, ваши конкретные значения могут отличаться, но смысл тот же).
Отлично! Время заняться арифметикой: нужно пересчитать смещение инструкции jnb (найденное для «текстовой» секции) в абсолютное значение внутри бинарного файла. Если вы попробуете изменить байты по адресу, найденному в IDA, вы наверняка где-то словите крэш, т.к. они не совпадают.
Чтобы нам не сильно отвлекаться, я приготовил для вас формулу:
Адрес команды jnb = 0x18D88 (из IDA)
Стартовый адрес текстовой секции = 0x2970 (из otool )
Сдвиг текстовой секции = десятичное 6512 (из otool )
Берём калькулятор, переключаем из меню: Вид > Для программиста… (не забудьте переключать на нужную систему счисления при вводе десятичных и 16-ричных чисел).
У меня получилось:
0x18D88 – 0x2970 + 6512 = 0x17D88
Хм, номера строк почему-то в десятичном виде. Ну ладно, пересчитаем: 0x17D88 = 97672, т.е. от позиции 97664 нужно отсчитать ещё 8 байт вправо. 8 байт = 16 шестнадцатиричных цифр = два 4-байтных слова. Видите, Hex Fiend группирует бинарный «текст» по словам:
Исправьте 0x73 на 0xEB (аккуратно: одно нажатие Backspace удаляет сразу 1 байт = два шестнадцатиричных символа). Сохраните (⌘S) и закройте файл. Откройте симулятор, удалите приложение из памяти и снова запустите (именно из симулятора, а не из IDE, чтобы не скомпилировалось заново). «Покупайте» мемы, пока у вас не закончатся деньги. Что произошло, когда вы попытались купить товар, который стоит дороже, чем у вас осталось «денег»?
Да, мы действительно выкинули проверку условия «есть ли у пользователя деньги?» Даже без денег, транзакция выполняется. И небольшой бонус: unsigned-значение _money «зацикливается», в силу особенностей представления чисел в памяти, вместо отрицательного становится чуть меньше, чем 10 32 (около 4 миллиардов).
Защита от реверс-инжиниринга
Как же нам защититься? Помните, я говорил: «ничто не безопасно». Это утверждение работает и здесь. Реверс-инжиниринг можно сильно затруднить, но вы не можете остановить взломщика, если он настроен серьёзно. Ваша единственная надежда — запутать злоумышленников настолько, что они бросят это дело и пойдут ломать другие приложения.
Один из способов — изменить имена важных классов и методов через препроцессор. Откройте проект в IDE и найдите в нём файл «Meme Collector-Prefix.pch». Добавьте в него строчку:
Этот код заменит все вхождения » MoneyManager » на название, которое покажется взломщикам менее интересным: » DS_UIColor_Theme «.
Данный подход должен применяться с большой осторожностью, чтобы ничего не сломать. Нужно убедиться на 100%, что выбранное новое имя больше нигде не встречается в вашем приложении. Иначе запутаете сами себя, с приложением начнут происходить необъяснимые вещи.
Снова откройте MoneyManager.m и добавьте следующую Си-функцию в начало:
Затем снова скомпилируйте приложение. Проверим существование данной функции в таблице символов. Из терминала:
Команда nm выводит таблицу символов, а grep фильтрует по имени функции. Вот, она тут есть:
Лёгкий способ удалить таблицу символов из iOS-приложения — в настройках проекта найти две опции: Deployment Postprocessing и Strip Linked Product, и выставить их в Yes:
Затем необходимо «очистить» проект (Xcode: Product > Clean или в AppCode: Run > Clean) и заново скомплировать. После чего перейдите в терминал и выполните ту же команду:
Что дальше?
Мы убедились, что злоумышленник может:
Безопасность iOS-приложения — это серьёзная тема. Можно ещё многому научиться. Пока мы только чуть-чуть поскребли по поверхности. Весь спектр возможностей отладчика и других инструментов для анализа кроется гораздо глубже. Если вам интересна эта тема, советую подумать о джейлбрейке тестового девайса. Файловая система даст богатую пищу для исследований.
Если у вас нет проблем с английским, обязательно ознакомьтесь с книгой Hacking and Securing iOS Applications (автор Jonathan Zdziarski). Хотя она слегка устарела (придётся погуглить на предмет изменений в механизме шифрования приложений Apple), но у автора статьи это одна из любимых книг по iOS и по безопасности.
Ещё пара книг:
Hacking: The Art of Exploitation, 2nd Edition by Jon Erickson
Mac OS X and iOS Internals: To the Apple’s Core by Jonathan Levin
Форумы:
http://www.woodmann.com
http://www.reddit.com/r/ReverseEngineering
Статья по инъекции кода:
http://blog.timac.org/?p=761
Автору можно писать в комменты, а по переводу пишите на почту dev @ x128.ru.
Взлом и внедрение своего кода в чужое iOS-приложение
В жизни хакеры не так всесильны и эффектны, как в голливудских фильмах. Но это не значит, что iOS-разработчик может вообще не думать о безопасности своего приложения. Пусть оно и не хранит тайны Пентагона, взломать его всё равно могут хотя бы для того, чтобы получить платные функции бесплатно.
На нашей конференции Mobius разработчик Мурад Татаев рассказывал о взломе iOS-приложений — и о том, что разработчики могут делать для защиты от него. А теперь мы расшифровали этот доклад (видеозапись также прилагаем). Далее повествование идёт от лица спикера.
Мы не будем говорить о защите сетевого слоя — о SSL Pinning и тому подобном, а рассмотрим только взлом приложения. Будет теоретическая часть, где мы разберемся с некоторыми понятиями, и практическая, где применим полученные знания.
Начнем с теории
Взлом
В моем понимании взлом — это изменение поведения приложения в интересах хакера. То есть приложение работает не так, как изначально задумывал программист. В основном поведение меняется в тех частях, что связаны с получением чего-то, обычно за деньги — хакер хочет получить это бесплатно.
Относительная безопасность
Относительная безопасность означает, что затраты на атаку превышают полученную в результате выгоду. Допустим, у вас есть погодное приложение, в котором нет внутренних покупок. Вы пользуетесь открытым API, вам нет смысла париться о безопасности. И в результате взлома вы ничего не потеряете.
Но если вдруг у вас что-то серьезнее, например, фоторедактор, в котором через встроенные покупки можно открыть дополнительные фильтры, то стоит задуматься о безопасности.
Нужно понимать, что абсолютной безопасности вы не добьетесь. Вы можете добиться некоторой «лучшей обезопашенности» по сравнению с тем, что было раньше.
Почему важно защищать приложение?
Чтобы это понять, нужно увидеть в этом смысл. И он в основном экономический, реже — репутационный.
Для примера возьмем YouTube Tools — твик для отключения рекламы в приложении YouTube. В чем проблема: из-за того, что вы не смотрите рекламу, YouTube недополучает денег. Разработчики YouTube знают об этом и часто борются с различными твиками.
Еще один пример — обход платного скачивания Minecraft. В этом случае особенно не защитишься, потому что пользователь скачает приложение, расшифрует его, выложит файл, и его скачают другие.
Но в двух предыдущих вариантах (YouTube и Plague Inc.) есть способы бороться с хакерами, причем их довольно много.
Как защищаться?
Это главный вопрос. Есть несколько вариантов, которые я знаю:
Совет: проверяйте, нет ли в интернете вашего взломанного приложения.
Проверка на джейлбрейк
Плюсы: легко и дешево использовать (достаточно скопировать и вставить код).
Минусы: во-первых, это очень легко пропатчить.
Во-вторых, страдают некоторые мирные пользователи, которые могли поставить джейлбрейк не чтобы получить что-то ваше бесплатно, а чтобы пользоваться различными другими твиками.
Есть и обратная сторона — джейлбрейк используется все реже, но есть и те, кто по-прежнему ставят его.
Проверка на дебаггер
Плюсы: легко использовать, сложнее патчить.
Это хорошо работает с отложенными ошибками. Если вы узнали, что пользователь запустил ваше приложение с подключенным дебаггером или у него джейлбрейк и так далее, вы можете конкретно в каких-то критичных местах не давать ему проходить дальше. Такую ошибку нужно показывать не сразу, а делать это позже, чтобы код проверки не был в очевидном месте.
Минусы: это тоже патчится.
Примерный код проверки выглядит так:
Мы создаем структуру для информации о процессе, задаем размер этой структуры и с помощью утилиты systemctl пытаемся запросить флаги для текущего процесса. Если у нас получилось запросить их — идем дальше и проверяем, активен ли флаг P_TRACED. При помощи этого флага производится дебаггинг в iOS.
Если флаг стоит не в null, то нас дебажат.
Обфускация кода
Есть различные обфускаторы, один из популярных, насколько я понял по GitHub — это SwiftShield.
Плюсы: код превращается в лабиринт, в нем вообще не разберешься. Особенно классные обфускаторы создают дополнительные методы, мусор, чтобы запутать хакера. Насчет Swift не уверен, но в C#, Java и Kotlin есть такие.
Минусы: иногда этот лабиринт сказывается и на вас. И еще крэш-логи приходится каждый раз расшифровывать.
На скриншоте — крэш-лог одного из приложений.
Над зеленой полосой — крэш-лог до расшифровки, ниже — расшифрованный крэш-лог. Крэш-лог нужно расшифровывать через утилиту, которая идет вместе со SwiftShield. Это можно автоматизировать: засунуть в CI дополнительный job, чтобы подтягивать все крэш-логи, прогонять их через утилиту и что-то делать с ними дальше. Но это дополнительная работа.
Еще один большой минус — нет готовых решений, годных для энтерпрайза. Они опенсорсные. Это не плохо, но нужно быть осторожным, подтягивая что-то опенсорсное в ваш проект, особенно если он связан с безопасностью и обфускацией.
Я за свою практику не сталкивался с тем, чтобы кто-то в iOS пользовался обфускатором кода, поэтому это очень редкая практика.
Strip Swift Symbols
Плюсы: легко и дешево использовать, удаляется часть информации о Swift-классах. И это включено по умолчанию, то есть Xcode думает о вашей безопасности.
Минусы: информацию о классе можно найти другим способом.
Зачем нужно включать Strip Swift Symbols?
Допустим, у нас есть стандартный класс ViewController, label, viewDidLoad и так далее. Прошу обратить особенное внимание на статичную функцию buildScreen, о ней мы еще поговорим.
Если вы оставили Swift Symbols и после этого прогнали бинарник вашего приложения через специальную утилиту dsdump, вот что появится в логе:
Ниже можно увидеть, что есть некоторые свифтовые методы, и по названиям методов и переменных вы можете примерно понять, что здесь происходит и важен ли для вас этот класс. Что самое интересное, здесь нет статического метода buildScreen. Статические методы находятся в другом месте.
Вот что будет, если включить эту функцию: как вы видите, данных о свифтовых методах просто нет. Есть все, что связано с Objective-C и бриджингом для Objective-C.
Валидация покупок на сервере
Это очень хорошая практика, которой придерживаются не все. Среди топ-100 приложений я находил немало тех, у которых валидация все еще производится на девайсе, и при помощи одного твика можно обойти валидацию (подмена ответа сервера).
Плюсы: безопасность.
Минусы: вам придется покупать хостинг.
А если вы будете валидировать на клиенте?
Плюс: вам не нужен сервер.
Минус: потенциальная потеря денег. Твик LocaliAPStore будет вашим худшим врагом.
В качестве примера могу привести одно приложение, связанное с фоторедактированием. Название я, к сожалению, не могу сказать. Это приложение как раз не использует валидацию покупок на сервере. Приложение классное, но при помощи одного твика вы можете получить всё бесплатно.
Еще есть одна RPG-игра, она тоже находится в топах, там тоже валидация покупок на девайсе. Даже у известного раннера про подземки и бег с поездами такая же проблема — валидация идет на девайсе.
Инструменты — чем я пользуюсь каждый день
Filza — файловый менеджер, в котором скрыто очень много функций. С его помощью вы можете поднять сервер, FileDAV и обмениваться файлами с компьютером.
Unc0ver — это джейлбрейк, которым я пользуюсь. Он поддерживает версии вплоть до iOS 13.5.
Но с iOS 13.5 я бы посоветовал быть осторожней, потому что встроенный браузер в приложении, Safari View Controller, работает плохо. Лично у меня, когда я открываю ссылки, появляется окно внутреннего браузера (Safari View Controller), которое абсолютно пустое и ничего не грузит.
Cydia — это менеджер пакетов для взломанных устройств.
Наверное, некоторые из вас слышали, что Cydia — всё, закрыта, но нет, она поддерживается и вполне живет. Здесь есть куча разных твиков. Мы будем использовать Cydia в основном для того, чтобы устанавливать различные инструменты на устройство. С Cydia вы можете скачать ту же самую Filza.
NewTerm — это мобильный терминал. Он тоже доступен из Cydia и тоже полезная штука.
FLEXing — твик для поверхностного анализа приложения на устройстве. При помощи него можно посмотреть интернет-трафик, ваши файлы и тому подобное. Снифер трафика обходит SSL Pinning.
Я так проверял трафик в Instagram, а там есть SSL Pinning,
Можно посмотреть объекты в куче, которые есть сейчас, и вызвать различные методы в этих объектах. Есть общий список классов для приложения, можно поверхностно посмотреть, что там есть. Можно посмотреть UserDefaults и, неожиданно, Keychain.
Keychain — это, грубо говоря, тот же самый UserDefaults, только зашифрованный вашим паролем, то есть пин-кодом от вашего устройства. Если вы введете пин-код, Keychain разблокируется, и все, что там лежит, можно посмотреть. Так что я бы вам советовал не хранить ничего незашифрованного в Keychain, потому что до этого можно добраться и что-то поменять.
Как делать не надо
В AppDelegate есть строка isUnlocked:
То есть разработчик добавил в AppDelegate переменную, с помощью которой он смотрит, куплено приложение или нет.
Естественно, эта переменная легко меняется: вы жмете кнопку i, дергаете свитч, нажимаете «ОК». Поменяли переменную, закрыли меню флексинга, и у вас полностью «купленное» приложение.
Ghidra
Классная штука, дизассемблер от АНБ США, здесь можно вставить шутку про слежку от спецслужб.
Переходим к практике
Мы будем практиковаться на тестовом проекте.
Как достать расшифрованное приложение?
Есть ограничения — не дампятся extensions. Но за все время, что я этим пользовался, мне extensions и не были нужны.
Обход проверки на джейлбрейк
Патчим бинарник. Тот проект, что вы скачали ранее, выглядит так: простой белый экран, на нем — label с текстом: либо Hacked, либо Safe and sound в зависимости от того, джейлбрейкнуто устройство или нет.
Если все сделали правильно, это будет выглядеть примерно так:
Как найти необходимый участок кода?
Так как у нас код не обфусцирован, нужно либо вручную, не давая методам слишком очевидные имена, либо утилитами обфусцировать код.
При помощи поиска ищем слово jail в настройках. Найдется пара методов. И если вы нажмете на один из них, как можно увидеть чуть выше, курсор прыгнет в нужную область памяти.
Патчим инструкцию
Здесь вы видите инструкцию bl (branch with link) — это по сути прыжок на функцию, которая ожидает, что из этой функции что-то вернется в регистре w0. Если мы знаем, в какой регистр вернется значение, мы можем выполнить команду mov (присваивание).
То есть в регистр w0 записывается как раз то, что возвращается с метода. Мы записываем 0 (то есть false), экспортируем бинарник. Так как мы не трогали остальные фреймворки, нам нужен только бинарник.
После этого все подписываем, собираем и устанавливаем на устройство.
Готово — вы пропатчили приложение.
Подпись и установка приложения
Необходимые предварительные требования:
Копируем необходимые файлы
Создаем Adhoc Provisioning Profile и копируем его в папку с приложением. Дальше выполняем код:
В итоге мы получим entitlements.plist, который нам нужен. Profile.plist уже не нужен. Система безопасности iOS построена вокруг этих entitlements.
security — утилита macOS, с помощью которой можно работать с сертификатами, паролями, keychains и всем, что связано с безопасностью.
После этого копируем Adhoc Provisioning Profile в контейнер приложения (папку Payload/*.app) и переименовываем его в embedded.mobileprovision. Это обязательно!
Находим ключ для подписи
Этот код ищет сертификат, при помощи которого можно подписать приложение. В моем случае я выбрал iPhone Distribution, можно выбрать Developer, разницы я не заметил. Она может возникнуть, когда вы будете пытаться залить это все в App Store. Но в локальном тесте разницы нет.
Подписываем файлы
Это самое интересное. Вводим codesign, не забываем подписывать фреймворки. В моем случае там лежали только dylib, если лежат еще и фреймворки — обговорим этот кейс позже.
Не забывайте сначала подписать основной бинарник, а потом все приложение. В таком порядке у меня все работало отлично.
И устанавливаем. Не забывайте, чтобы устройство было подключенным через кабель, иначе работать не будет.
Cтавим через ideviceinstaller, а не Xcode — ideviceinstaller пишет логи в консоль, и вы сможете понять, на каком шаге допустили ошибку. Это довольно сильно облегчает дебаг.
Инъекция фреймворков и подмена реализации методов
Дальше начинается самое интересное, то есть настоящий взлом. Необходимые инструменты:
Потренируемся!
Советую всем начинать с простого и только потом бежать взламывать пентагоны.
Приложение называется Mobius Conf. У вас есть 300 монет, можно добавить монеты, то есть их купить, но мы так делать не будем, либо можно что-то купить внутри приложения за 200 монет.
Скорее всего, большинству из вас код будет непонятен. Метод вызывается в момент. когда загружается ваш фреймворк. Стоит учитывать, что именно в этот момент приложения еще нет, оно только грузится.
Здесь нельзя сразу пытаться что-то менять. Именно поэтому здесь прописан dispatch_after. Через три секунды на экране появится алерт с текстом вроде «Вы это сделали, классно». Если вы скопировали, вставили, все заработало — прекрасно, это первый шаг в сторону взлома.
Поиграем с кодом
Cydia Substrate — это такой фреймворк для подмены реализации методов, другими словами, для хукания методов. Есть метод с изначальной реализацией, которую задумал программист, и есть ваш метод. Вы берете и подменяете эти методы. На этом фреймворке держатся все твики, которые лежат в Cydia.
Попробуем вызвать какую-нибудь функцию.
В навигаторе Xcode откройте папку Products. Вы можете открыть файл MobiusStep2.app в Finder, перейти в папку app через терминал, войти в app-контейнер и с помощью утилиты dsdump принтануть все символы, которые есть в приложении.
Все статичные методы в приложении тоже реализованы в виде символов.
Вводите ViewDidLoad или то, что вам интересно. Здесь есть два метода, я выбрал второй, вы можете выбрать первый — по сути, это одно и то же.
Копируете всю строку, в Xcode удаляете весь предыдущий код и вводите команду MSFindSymbol.
Что такое MS? Изначально CydiaSubstate называлась MobileSubstate, и MS — дань уважения этому. MSFindSymbol делает то же, что и dsdump — идет поиск символа, копируется отступ по памяти, дополнительные данные. Всё это нужно для того, чтобы позже можно было что-то менять по этому символу.
Можно что-то вызвать по этому символу:
Но имейте в виду, что в этом методе могут вызываться различные переменные, которые еще не инициализированы. И возможно, приложение будет просто крашиться. Поэтому будьте осторожны, и лучше всего через Ghidra заранее проверять метод, который вы хотите вызвать: посмотреть в интерпретаторе, не идут ли вызовы в какие-то локальные переменные, которые еще не инициализированы, и так далее.
Если вы так вызовете символ в этом проекте, вы увидите, что принтанется viewDidLoad. Из-за того, что вы сами вызвали viewDidLoad, и после этого вызвался «натуральный» метод, у вас в консоли продублируется текст viewDidLoad.
Давайте вызовем свой код после viewDidLoad.
Это уже что-то более боевое и интересное. Из предыдущего кода остается MSFindSymbol, но только вызов MSFindSymbol заменяется на MSHookFunction. Здесь оригинальный метод заменяется на засвизленный, то есть на наш.
В нашем методе мы сначала вызываем оригинальную имплементацию в виде Load, и после него идет наш код. Ничего оригинального не стал придумывать, просто через 2 секунды отображаю алерт.
Как насчет бесконечности денег?
Если вы будете бродить по символам, найдете интересный символ PurchaseManager spend. Из названия понятно, что этот метод будет вызван, если что-то нужно потратить, например, деньги. И этот метод можно захукать.
С помощью Ghidra можно понять и проверить, что этот метод на входе принимает аргумент в виде количества денег, и просто подменить оригинальную реализацию своей.
Разница в том, что мы не учитываем количество денег, которое потратил пользователь, — всегда передаем 1. Каждый раз, когда вы нажимаете кнопку покупки, единичка будет уходить. Можно передавать 0, и тогда у вас будет бесконечное количество денег (они не будут тратиться).
Потренировались? Будем инжектить в реальное приложение.
Здесь есть Foundation, Objective-C, UIKit, SwiftCore и так далее.
При помощи optool можно добавить свою дополнительную строку. Вводим код:
Добавится новая строка. Это можно проверить, вызвав команду:
Вы увидите, что в конце добавлена новая строка на загрузку нового фреймворка. Обязательно после фреймворка написать / и путь до бинарника, иначе ничего не подгрузится и не будет работать.
Не забудьте подписать фреймворк!
Не забудьте скопировать фреймворк в папку с фреймворками — та строка, что мы писали, магическим образом не будет искать по всему проекту — только в папке Frameworks.
Можно объединить все в один shell-файл, чтобы не вводить каждый раз несколько команд. Можно добавить еще одну команду для подписи сразу всех фреймворков.
Вы заинжектили свой фреймворк и свой код в чужое приложение.
Но! Хукать свифтовые методы можно только на взломанном устройстве.
Если хотите хукать не только на взломанных устройствах и карма смотрит на вас лицом, то вам повезет, и то приложение, которое вы будете исследовать, будет на Objective-C.
Это еще одна причина, почему я говорил, что писать надо на Swift. Вы сейчас увидите, почему не следует писать на Objective-С что-то безопасное.
Это приложение MobiusStep3. Здесь кнопка добавления денег пропала, есть только кнопка «Потратить». Наша цель — иметь бесконечное количество монет. В этом нам поможет Objective-C Runtime и тот же свизлинг методов.
Подготовка проекта
Их немного, и если присмотреться, можно заметить уязвимый метод spend. Мы его взламывали ранее, и он может принести потенциальные проблемы.
Будем свизлить как раз-таки его, здесь классический свизлинг методов. Находим класс по имени, находим селектор по имени и подменяем реализацию оригинального метода на неоригинальный.
Не забываем сохранять ссылку на предыдущую (оригинальную) реализацию. Можно заметить строку с переменной originalImplementation, так мы держим ссылку на оригинальную имплементацию. IMP — это сишная структура, просто так IMP-структуры не вызвать, приходится их кастить.
Внизу видно, как мы их закастили через swizzled_spend. id, SEL — это обязательно. Наконец, вы можете увидеть int в коде, который кастим. На следующей строке вызываем этот метод и передаем 0 или 1. Единица удобна для дебага.
Все работает без мам, пап, кредитов и Cydia Substrate. Причина — особенность гипердинамичного Objective-C Runtime, где связывание с методами происходит как можно позже.
Резюмируем
И помните — абсолютной безопасности не существует. Но можно сделать безопаснее, чем было раньше.
Если вам захочется обсудить с Мурадом его доклад, напишите ему:
Если вы дочитали досюда и вы не злонамеренный хакер (фу таким быть) — тогда, скорее всего, вы мобильный разработчик. Тогда обращаем ваше внимание, что следующий Mobius пройдет 22-25 ноября, и там будет много новых докладов. Хакерский колорит в их случае не обещаем, а вот актуальность для мобильных разработчиков — вполне.
Источники информации:
- http://habr.com/ru/post/167805/
- http://temofeev.ru/info/articles/instrumenty-dlya-snyatiya-logov-s-android-ios-ustroystv-chtenie-i-razbor/
- http://habr.com/ru/post/333852/
- http://habr.com/ru/company/wrike/blog/544754/
- http://habr.com/ru/company/luxoft/blog/148415/
- http://habr.com/ru/post/270041/
- http://habr.com/ru/post/199128/
- http://habr.com/ru/post/199130/
- http://habr.com/ru/company/jugru/blog/570220/