(Не)безопасность iOS-приложений (Часть 1)

(Не)безопасность iOS-приложений (Часть 1)

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

  Dominic Chell

1. Введение

За последний год консультанты MDSec провели возрастающее число оценок безопасности iOS-приложений и поддерживающей их архитектуры, в которых безопасность данных является первостепенной, в частности, приложений банковского сектора.

Смартфоны стали распространенными не только на потребительских рынках, но и в корпорациях. Они сочетают традиционные возможности мобильных устройств с функциональностью компьютеров. Увеличенная вычислительная мощность и память современных смартфонов привела к всплеску разработки мобильных приложений, поскольку разработчики пытаются использовать преимущество богатых возможностей платформы. Разработка мобильных приложений на самом деле теперь настолько популярна, что слоган торговой марки Apple, звучащий "There's an app for that" (И для этого есть приложение), граничит с реальностью.

В течение 2011 года можно было наблюдать рост спроса на оценки безопасности мобильных приложений, особенно приложений для платформ iOS и Android. Исследование рынка, проведенное компанией NetApplications [1], показало, что iOS-устройства контролируют примерно 52% глобального мобильного рынка.

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

2. Общие сведения

2.1. Перечень средств безопасности iOS

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

Ключевые средства безопасности платформы iOS сводятся к следующему:

  • Подпись кода
  • Общие средства борьбы с эксплоитами нативного языка
    • Рандомизация адресного пространства
    • Неисполняемая память
    • Защита от повреждения стека
  • Песочница уровня процессов
    • также известна как "ремень безопасности"
  • Шифрование неподвижных данных

Всеобъемлющий обзор данных средств можно найти в документе "Apple iOS 4 Security Evaluation", созданном Dino Dai Zovi [2], который дает значительную основу для материала, обсуждаемого в данном разделе.

Подпись кода

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

Для того, чтобы приложение смогло запуститься на устройстве, оно прежде должно быть подписано доверенным сертификатом. Разработчики могут устанавливать доверенные сертификаты на устройства через профиль инициализации, подписанный Apple. Профиль инициализации содержит встроенный сертификат разработчика и набор прав, которые он может выдавать приложениям. В законченных приложениях весь код должен быть подписан Apple, что выполняется в ходе процесса передачи приложения в AppStore. Данный процесс позволяет Apple в некоторой степени контролировать приложения и управлять API и функционалом, используемыми разработчиками. Например, Apple пытается пресекать приложения, использующие закрытый API или загружающие исполняемый код для последующей установки [3].

Средства борьбы с эксплоитами

Рандомизация адресного пространства (ASLR) [4] – средство безопасности, которое пытается увеличить сложность эксплуатации уязвимостей путем рандомизации (т. е. внесения случайности) отображения данных и кода на адресное пространство процесса. ASLR впервые появился в версии iOS 4.3 beta и с тех пор постепенно совершенствуется с каждым выпуском. Главной слабостью в реализации ASLR был недостаток переадресации dyld (динамического компоновщика). Этот недостаток был решен с выходом iOS 5.0. ASLR может быть применен к приложению в двух различных вариантах: полном либо частичном, в зависимости от того, было ли приложение скомпилировано с поддержкой PIE (выполнение, независящее от положения). В случае полного ASLR рандомизируются все области памяти приложения, и iOS загружает двоичный файл с поддержкой PIE по случайному адресу при каждом его запуске. Приложение с частичной поддержкой ASLR будет загружать основной двоичный файл по фиксированному адресу и использовать статическое расположение динамического компоновщика dyld. Для большего понимания данной информации мы рекомендуем ознакомиться с глубоким анализом ASLR в iOS, выполненным Стефаном Эссером [5].

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

В попытке дальнейшей борьбы с уязвимостями нативного языка iOS сочетает ASLR с реализацией политики неисполняемой памяти W^X, означающей, что страницы памяти не могут быть помечены как доступные для записи и выполнения одновременно. Часть данной политики заключается в том, что выполняемые страницы памяти, помеченные как доступные для записи, не могут быть позднее снова помечены как выполняемые. Это во многом походит на средства DEP (защиты от выполнения данных), реализованные в операционных системах для настольных компьютеров: Microsoft Windows, Linux и Mac OS X. Хотя отдельно взятую политику неисполняемой памяти можно тривиально обойти с помощью полезных нагрузок, основанных на возвратно-ориентированном программировании, сложность эксплуатации уязвимости значительно возрастает, при комбинации неисполняемой памяти с ASLR и обязательной подписью кода.

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

Песочница

Все сторонние приложения iOS запускаются в песочнице. Песочница – замкнутая программная среда, которая изолирует приложения не только от других приложений, но и от операционной системы. В то время как все приложения запускаются под пользователем ОС "mobile", каждое из них содержится в уникальной директории файловой системы, и разделение поддерживается расширением ядра "XNU Sandbox". Операции, которые могут быть выполнены в песочнице, управляются профилем ремня безопасности. Сторонним приложениям назначается профиль "container", который обычно ограничивает файловый доступ домашней директорией приложения, чтением и записью из адресной книги, а также имеет неограниченный доступ к исходящим сетевым соединениям за исключением сетевых сокетов launchd. Для дальнейшего чтения рекомендуется документ "The Apple Sandbox" [6].

Шифрование

По умолчанию все данные в файловой системе iOS шифруются блочным шифром AES с использованием ключа файловой системы, хранимого во flash-памяти. Файловая система зашифрована лишь в выключенном состоянии. Когда устройство включается, аппаратный криптоакселератор расшифровывает систему.

В дополнение к аппаратному шифрованию отдельные файлы и элементы цепочки ключей могут быть зашифрованы с помощью Data Protection API, который использует ключ, полученный из парольного кода устройства. Поэтому, когда устройство заблокировано, содержимое, зашифрованное DP API, будет недоступно до тех пор, пока не закэшируется в память. Сторонним приложениям, желающим зашифровать важные данные, также следует использовать для этого Data Protection API. Однако, следует рассмотреть как будут вести себя фоновые процессы, если неподвижные данные станут недоступны из-за блокировки устройства.

2.2 Обзор iOS-приложения

Сторонние приложения используют для взаимодействия с устройством Cocoa Touch API. Этот фреймворк предоставляет средства абстрагирования от ОС и написан на языке Objective-C, надмножестве языка C.

Разработка iOS-приложений может производиться с помощью свободно доступной интегрированной среды разработки XCode IDE для OS X. XCode предоставляет симулятор для компиляции и запуска приложений. Однако, следует отметить, что это скорее симулятор, чем эмулятор. Чтобы запустить приложение на не подвергшемся джейлбрейку устройстве, вы должны быть подписаны на программу разработчиков софта для iOS и иметь сертификат разработчика.

2.3. Предыдущие работы

Нам известны две заслуживающие внимания презентации по оценке безопасности iOS-приложений. Тем, кто занимается оценкой iOS-приложений, рекомендуется прочитать обе эти презентации:

  • "Auditing iPhone and iPad Applications" Ильи Шпрунделя [7]
  • "Secure Development on iOS" Дэвида Тиля [8]

3. Оценка по методу Черного Ящика

Введение

Организации нередко полагают, что внутренние работники некоторым образом защищены от атакующего, который не имеет доступа к исходному коду приложения. На практике атакующий может довольно естественно получить доступ к расшифрованному приложению, найти ключевые методы, которые оно содержит, внедрится в них во время выполнения и изменить переменные и поток выполнения. Этот процесс обычно состоит из следующих шагов.

3.1. Расшифровка двоичных файлов AppStore

Приложения с AppStore защищены по схеме двоичного шифрования Apple. Эти приложения расшифровываются во время выполнения mach-загрузчиком; по существу, восстановление расшифрованных файлов – относительно простой процесс. Снятие данного шифрования позволяет атакующему лучше понять то, как работает двоичный файл, внутреннюю структуру его классов и получить двоичный файл в подходящем для обратной инженерии виде.

Снять шифрование AppStore можно, позволив загрузчику расшифровать приложение, а затем использовав отладчик, чтобы выгрузить расшифрованный образ. Этот процесс был автоматизирован двумя приложениями Crackulous [9] и AppCrack, доступными через Cydia. Однако, процесс можно выполнить и вручную, используя GDB.

Зашифрованные двоичные файлы можно опознать по значению поля "cryptid" команды LC_ENCRYPTION_INFO [10], например:

mdsec-iPhone:/var/mobile/Applications/E938B6D0-9ADE-4CD6-83B8-
712D0549426D/99Bottles.app root# otool -l 99Bottles | grep -A 4
LC_ENCRYPTION_INFO
cmd LC_ENCRYPTION_INFO
cmdsize 20
cryptoff 4096
cryptsize 12288
cryptid 1

В некоторых случаях приложения могут компилироваться для нескольких архитектур (так называемые fat binaries). Архитектуры, для которых скомпилировано приложение, тоже можно узнать утилитой otool:

mdsec-iPhone:/var/mobile/Applications/68E3B644-9203-4B8F-A707-
A52E23B793B6/Kik.app root# otool -f Kik
Fat headers
fat_magic 0xcafebabe
nfat_arch 2
architecture
cputype 12
cpusubtype 6
capabilities 0x0
offset 4096
size 865152
align 2^12 (4096)
architecture 1
cputype 12
cpusubtype 9
capabilities 0x0
offset 872448
size 867488
align 2^12 (4096)

В вышеприведенном примере "cputype 12 with cpusubtype 6" соответствует ARM v6, а "cputype 12 with cpusubtype 9" – это ARM v7. Если нужно, двоичный файл можно облегчить до конкретной архитектуры с помощью lipo.

Чтобы получить расшифрованный сегмент приложения, сначала нам нужно позволить загрузчику запуститься. Это можно сделать, установив точки останова на методе "doModInitFunctions", который вызывается после того, как загрузятся все объекты:

mdsec-iPhone:/var/mobile/Applications/E938B6D0-9ADE-4CD6-83B8-
712D0549426D/99Bottles.app root# gdb --quiet -e ./99Bottles
Reading symbols for shared libraries . done
(gdb) set sharedlibrary load-rules ".*" ".*" none
(gdb) set inferior-auto-start-dyld off
(gdb) set sharedlibrary preload-libraries off
(gdb) rb doModInitFunctions
Breakpoint 1 at 0x2fe0ce36
<function, no debug info>
__dyld__ZN16ImageLoaderMachO18doModInitFunctionsERKN11ImageLoader11LinkContex
tE;
(gdb) r
Starting program: /private/var/mobile/Applications/E938B6D0-9ADE-4CD6-83B8-
712D0549426D/99Bottles.app/99Bottles

Breakpoint 1, 0x2fe0ce36 in
__dyld__ZN16ImageLoaderMachO18doModInitFunctionsERKN11ImageLoader11LinkContex
tE

()
(gdb)

На данном этапе загрузчик уже расшифровал приложение, и мы можем выгрузить text-сегменты прямо из памяти. Местоположение зашифрованного сегмента определяется значением cryptoff в команде LC_ENCRYPTION_INFO, которое дает смещение относительно заголовка. Зашифрованный сегмент в примере начинается со смещения 0x2000 (cryptoff равен 0x1000 = 4096 плюс начальный адрес 0x1000). Диапазон адресов выгружаемой памяти начинается с адреса начала зашифрованного сегмента и имеет размер, равный размеру зашифрованного сегмента, определяемому значением cryptsize(12288, 0x3000). Т. е., конечный адрес диапазона равен 0x5000 (0x2000 + 0x3000). Расшифрованный сегмент можно извлечь с помощью команды GDB "dump memory":

(gdb) dump memory 99bottles.dec 0x2000 (0x2000 + 0x3000)
(gdb) kill
Kill the program being debugged? (y or n) y
(gdb) q
mdsec-iPhone:/var/mobile/Applications/E938B6D0-9ADE-4CD6-83B8-
712D0549426D/99Bottles.app root# ls -al 99bottles.dec
-rw-r--r-- 1 root mobile 12288 Mar 4 16:31 99bottles.dec

Размер результирующего файла должен быть точно равен значению cryptsize. Расшифрованный раздел затем пишется в исходный двоичный файл, замещая исходный зашифрованный сегмент:

mdsec-iPhone:/var/mobile/Applications/E938B6D0-9ADE-4CD6-83B8-
712D0549426D/99Bottles.app root# dd seek=4096 bs=1 conv=notrunc
if=./99bottles.dec of=99Bottles
12288+0 records in
12288+0 records out
12288 bytes (12 kB) copied, 0.471737 s, 26.0 kB/s

Наконец, значение cryptid нужно установить в 0, чтобы отметить, что файл более не зашифрован и загрузчику не следует пытаться его расшифровать. Найдите с помощью vbindiff местоположение команды LC_ENCRYPTION_INFO. Для этого можно искать шестнадцатеричное значение 2100000014000000. Найдя это положение этой команды, можно обнулить значение cryptid, лежащее на 16 байт впереди размера команды (равного 0x21000000):

Рисунок 1 – шестнадцатеричное содержимое LC_ENCRYPTION_INFO

На данном этапе приложение должно быть расшифровано. Оно станет нормально запускаться, когда код будет переподписан.

3.2. Определение положения Position Independent Executable

Position Independent Executable (PIE) – средство борьбы против эксплоитов, которое позволяет приложению максимально использовать преимущество ASLR. Для этого приложение должно быть скомпилировано с флагом "–fPIE –pie". В XCode этот флаг может быть включен/отключен с помощью опции "Генерировать зависимый от положения код" в настройке генерации кода компилятором. Как ранее упоминалось, приложение, скомпилированное без PIE, будет загружать исполняемый код по фиксированному адресу. Рассмотрим следующий простой пример, который печатает адрес главной функции:

int main(int argc, const char* argv[])
{
NSLog(@"Main: %p\n", main);
return 0;
}

Скомпилировав данное приложение без PIE и запустив его на iPhone, мы увидим, что, несмотря на использование во всей системе ASLR, приложение загружается по фиксированному адресу:

mdsec-iPhone:~ root# for i in `seq 1 5`; do ./nopie-main;done
2012-03-01 16:56:17.772 nopie-main[8943:707] Main: 0x2f3d
2012-03-01 16:56:17.805 nopie-main[8944:707] Main: 0x2f3d
2012-03-01 16:56:17.837 nopie-main[8945:707] Main: 0x2f3d
2012-03-01 16:56:17.870 nopie-main[8946:707] Main: 0x2f3d
2012-03-01 16:56:17.905 nopie-main[8947:707] Main: 0x2f3d

Перекомпилировав то же приложение с PIE, мы увидим, что код главной функции теперь загружается по переменному адресу:

mdsec-iPhone:~ root# for i in `seq 1 5`; do ./pie-main;done
2012-03-01 16:57:32.175 pie-main[8949:707] Main: 0x2af39
2012-03-01 16:57:32.208 pie-main[8950:707] Main: 0x3bf39
2012-03-01 16:57:32.241 pie-main[8951:707] Main: 0x3f39
2012-03-01 16:57:32.277 pie-main[8952:707] Main: 0x8cf39
2012-03-01 16:57:32.310 pie-main[8953:707] Main: 0x30f39

При оценке по принципу черного ящика присутствие PIE можно проверить с помощью приложения otool, которое позволяет просматривать заголовок Mach-O. Например, сравнивая два представленных выше двоичных файла, мы можем с легкостью обнаружить PIE-приложение.

mdsec-iPhone:~ root# otool -hv pie-main nopie-main
pie-main:
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
MH_MAGIC ARM 9 0x00 EXECUTE 18 1948 NOUNDEFS
DYLDLINK TWOLEVEL PIE
nopie-main:
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
MH_MAGIC ARM 9 0x00 EXECUTE 18 1948 NOUNDEFS
DYLDLINK TWOLEVEL

В iOS 5 все встроенные приложения по умолчанию компилируются с PIE, однако на практике сторонние приложения как правило не пользуются преимуществами данного средства защиты [5].

3.3 Выявление использования защиты от повреждения стека

Как ранее отмечалось, приложения iOS во время компиляции могут применять защиту от повреждения стека. Это достигается установкой флага компилятора –fstack-protector-all, как показано ниже:

Рисунок 2 – Компиляция исходников в Xcode

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

При оценке по принципу черного ящика наличие стековых канареек можно обнаружить путем исследования таблицы символов двоичного файла. Если в приложении использована защита от повреждения стека, в ней будут присутствовать два неопределенных символа: "___stack_chk_fail" и "___stack_chk_guard". Таблица символов приложения может быть выгружена с помощью утилиты otool.

$ otool -I -v DummyApp | grep stack
0x00003fc4 14 ___stack_chk_fail
0x0000400c 14 ___stack_chk_fail
0x0000406c 15 ___stack_chk_guard

3.4. Выявление использования автоматического подсчета ссылок

Автоматический подсчет ссылок (ARC) был введен в iOS SDK версии 5.0, чтобы переместить ответственность за управление памятью с разработчика на компилятор. ARC также некоторым образом улучшает безопасность, поскольку уменьшает вероятность того, что разработчики внесут в программы уязвимость к повреждению памяти (особенно при использовании объекта после освобождения и при повторном освобождении объекта). См. раздел 5.2.

ARC в приложении можно включить с помощью XCode, путем установки опции компилятора "Objective-C Automatic Reference Counting" в значение "yes". Чтобы определить присутствие ARC при исследовании скомпилированного приложения по принципу черного ящика, исследователь может поискать наличие относящихся к ARC символов в таблице символов, как показано ниже:

$ otool -I -v DummyApp-ARC | grep "_objc_release"
0x00003fe8 181 _objc_release
0x00004030 181 _objc_release
$

Следующие символы свидетельствуют о наличии ARC:

  • _objc_retainAutoreleaseReturnValue
  • _objc_autoreleaseReturnValue
  • _objc_storeStrong
  • _objc_retain
  • _objc_release
  • _objc_retainAutoreleasedReturnValue

Во время компиляции ARC можно явно отключить для конкретных исходных файлов с помощью флага компилятора "-fno-objc-arc"

3.5. Исследование двоичного файла

В сегменте __OBJC расшифрованного двоичного файла есть уйма информации, которая может пригодиться реверс-инженеру. Сегмент __OBJC содержит подробности о внутренних классах, методах и переменных, используемых в приложении. Эта информация особенно полезна для понимания того, как функционирует приложение, а также для наложения заплаток и установке перехватчиков (хуков) во время выполнения.

Разбор сегмента __OBJC можно выполнить с помощью приложения class-dump-z [11]. Если, к примеру, пропустить ранее расшифрованное приложение "99 Bottles" через class-dump-z, получим следующее:

@interface BottleLayer : CALayer {
@private
BOOL flown;
}
@property(assign, nonatomic) BOOL flown;
-(void)drawInContext:(CGContextRef)context;
-(void)jiggle;
-(void)flyAway;
-(void)animationDidStop:(id)animation finished:(BOOL)finished;
-(void)dealloc;
@end
__attribute__((visibility("hidden")))
@interface RootViewController : UIViewController <UIActionSheetDelegate> {
@private
UILabel* numberDisplay;
NSMutableArray* marr;
Player* player;
UIView* wall;
BottleLayer* currentBottle;
NSArray* names;
NSArray* names10;
int count;
BOOL paused;

В показанном выше фрагменте кода class-dump-z обнаружил множество методов, включая "jiggle", "flyAway" and "drawInContext"; все их можно модифицировать и перехватывать во время выполнения.

3.6. Манипуляция во время выполнения

Установка в Objective-C перехватчиков во время выполнения – это мощный метод исследования и изменения поведения приложения. Наиболее распространенный метод установки перехватчика во время выполнения заключается в использовании MobileSubstrate [12], фреймворка для устройств, подвергшихся джейлбрейку. Он похож на Application Enhancer для OS X. MobileSubstrate входит по умолчанию во множество джейлбрейков для iOS и справляется с установкой перехватчиков в приложения, написанные не только на Objective-C, но также на C и C++.

Cycript [13] предоставляет язык программирования для создания моста между JavaScript и Objective-C из командной строки. Помимо соединения JavaScript и Objective-C, Cycript позволяет устанавливать перехватчики во время выполнения с помощью MobileSubstrate. Возможно одной из наиболее полезных возможностей Ccrypt является способность прикрепляться к запущенному процессу и манипулировать им. Например, cycript можно использовать для внедрения в запущенный процесс SpringBoard на подвергшемуся джейлбрейку устройстве, отключения необходимости ввода пароля и разблокировки устройства без ввода пароля:

mdsec-iPhone:~/Documents/Cracked root# cycript -p SpringBoard
cy# SBAwayController.messages['isPasswordProtected'] = function() {return NO;}
{}
cy# [SBAwayController.sharedAwayController unlockWithSound:1]
cy#

Для тех, кто собирается писать расширения к MobileSubstrate, iOSOpenDev предоставляет фантастические средства интеграции MobileSubstrate в XCode с помощью шаблонов XCode. iOSOpenDev [14] использует фреймворк CaptainHook для упрощения написания твиков к MobileSubstrate.

Пример: обход обнаружения джейлбрейка

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

@implementation AppSecurity
-(BOOL)isJailBroken
{
NSString *filePath = @"/Applications/Cydia.app";
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
return TRUE;
}
return FALSE;
}

Показанный выше метод может быть перехвачен и модифицирован с помощью твика к MobileSubstrate так, как указано ниже:

#import <Foundation/Foundation.h>
#import <CaptainHook/CaptainHook.h>
#include <notify.h>
@interface hookDummy : NSObject
@end
@implementation hookDummy
-(id)init
{
if ((self = [super init])){}
return self;
}
@end
@class AppSecurity;
CHDeclareClass(AppSecurity);
CHOptimizedMethod(0, self, BOOL, AppSecurity, isJailBroken)
{
NSLog(@"####### isJailBroken hooked");
return false;
}
CHConstructor
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
CHLoadLateClass(AppSecurity);
CHHook(0, AppSecurity, isJailBroken); // register hook
[pool drain];
}

Если после компиляции поместить данную библиотеку в папку DynamicLibraries, то она будет подгружаться при каждом запуске приложения на устройстве:

-rwxr-xr-x 1 root wheel 10912 Mar 8 10:15
/Library/MobileSubstrate/DynamicLibraries/hookDummy.dylib*
Mar 8 21:03:56 unknown DummyApp[1722] <Notice>: MS:Notice: Installing:
MDSec.DummyApp [DummyApp] (675.00)
Mar 8 21:03:56 unknown DummyApp[1722] <Notice>: MS:Notice: Loading:
/Library/MobileSubstrate/DynamicLibraries/Activator.dylib
Mar 8 21:03:56 unknown DummyApp[1722] <Notice>: MS:Notice: Loading:
/Library/MobileSubstrate/DynamicLibraries/hookDummy.dylib
Mar 8 21:03:56 unknown kernel[0] <Debug>: launchd[1722] Builtin profile:
container (sandbox)
Mar 8 21:03:56 unknown kernel[0] <Debug>: launchd[1722] Container:
/private/var/mobile/Applications/1F6A9800-DBD0-4831-A7C9-C4826C6F7EAD [69]
(sandbox)
Mar 8 21:03:57 unknown DummyApp[1722] <Warning>: ####### isJailBroken hooked

Библиотеку можно настроить на загрузку лишь в определенные приложения путем создания списка plist, содержащего bundle-идентификатор приложения, например:

Filter = {
Bundles = (MDSec.DummyApp);
};

Возьмем пример из реального мира: приложение CommBank Kaching реализует похожий метод для обнаружения устройств, подвергнувшихся джейлбрейку. Мы можем обнаружить соответствующие методы с помощью class-dump:

@interface RootViewController : /private/tmp/KIA_IPHONE_SOURCE/
<UIWebViewDelegate, DILDisplayView, UIAlertViewDelegate>
{
<snip>
- (BOOL)isJailbrokenDevice;

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

Рисунок 3 – Сообщение об ошибке в программе Kaching, связанное с обнаружением джейлбрейка

Следующий твик MobileSubstrate можно использовать для обхода данной защиты и использования приложения в обычном режиме:

@class RootViewController;
CHDeclareClass(RootViewController);
CHOptimizedMethod(0, self, BOOL, RootViewController, isJailbrokenDevice)
{
NSLog(@"####### isJailbrokenDevice hooked");
return false;
}
CHConstructor
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
CHLoadLateClass(RootViewController);
CHHook(0, RootViewController, isJailbrokenDevice);
[pool drain];
}

При повторном запуске приложение будет функционировать нормально, благодаря тому, что метод isJailBrokenDevice был перехвачен и модифицирован:

Mar 8 21:15:46 unknown KIA[1786] <Notice>: MS:Notice: Installing:
au.com.commbank.kaching [KIA] (675.00)
Mar 8 21:15:46 unknown KIA[1786] <Notice>: MS:Notice: Loading:
/Library/MobileSubstrate/DynamicLibraries/Activator.dylib
Mar 8 21:15:46 unknown kernel[0] <Debug>: launchd[1786] Builtin profile:
container (sandbox)
Mar 8 21:15:46 unknown kernel[0] <Debug>: launchd[1786] Container:
/private/var/mobile/Applications/63DC8037-5A2F-4C5C-ADDB-30AF3BF49449 [69]
(sandbox)
Mar 8 21:15:47 unknown KIA[1786] <Notice>: MS:Notice: Loading:
/Library/MobileSubstrate/DynamicLibraries/hookDummy.dylib
Mar 8 21:15:47 unknown securityd[1787] <Notice>: MS:Notice: Installing: (null)
[securityd] (675.00)
Mar 8 21:15:47 unknown securityd[1787] <Notice>: MS:Notice: Loading:
/Library/MobileSubstrate/DynamicLibraries/hookDummy.dylib
Mar 8 21:15:47 unknown KIA[1786] <Warning>: **** READ 60 LOG ENTRIES FROM
DISK
****
Mar 8 21:15:47 unknown KIA[1786] <Warning>: ####### isJailbrokenDevice hooked

3.7. Защита двоичных файлов

Разработчики, пытающиеся бороться против атак во время выполнения или увеличивать сложность реверс-инжиниринга, могут применить несколько стратегий, чтобы помешать атакующим. Однако, в общем случае следует принять тот факт, что не существует метода абсолютной защиты приложения, запускающегося на скомпрометированной ОС (например, подвергшейся джейлбрейку).

Один из наиболее распространенных подходов к защите во время выполнения – проверка целостности адресов и контрольных сумм классов, что позволяет определить, был ли код изменен или перехвачен. Это обычно достигается путем извлечения и проверки адреса класса. Существует метод _getMethodImplementation, который возвращает указатель на метод класса.

Рассмотрим следующую простую реализицию:

#import "security.h"
@implementation security
void * perform_sec_check()
{
void * addr = verify_address("AppSecurity", "isJailBroken");
fprintf(stderr, "\ncaddr = %p\n", addr);
if(addr != 0x25a9) take_evasive_action();
}
void * verify_address(const char * cname, const char * method)
{
id class = objc_lookUpClass(cname);
SEL selector = sel_registerName(method);
IMP imp = class_getMethodImplementation(class, selector);
return imp;
}
void * take_evasive_action() {
fprintf(stderr, "%s", "Tamper detected\n");
exit(-1);
}
@end

Показанный выше класс содержит простую реализацию обнаружения модификаций в ходе выполнения. Функция verify_address получает адрес указателя на метод AppSecurity:isJailBroken, взятый из более раннего примера. Данный адрес затем сравнивается с известным адресом, зашитым в код разработчиком. Если адрес отличается, значит возможно произошла модификация кода. В этом случае предпринимается необходимое действие.

Если запустить приложение без установки перехватчиков во время выполнения, обнаружение джейлбрейка выполняется в нормальном режиме:

caddr = 0x25a9
2012-04-18 20:51:51.580 DummyApp[595:707] ##### Sorry, you are running on a jailbroken
device

Выводимый адрес – ожидаемый адрес метода AppSecurity:isJailBroken. Если приложение запускается снова при загруженной библиотеке MobileSubstrate из предыдущего примера, адрес метода AppSecurity:isJailBroken изменяется:

caddr = 0x76ec5
Tamper detected

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

inline void * perform_sec_check()
{
void * addr = verify_address("AppSecurity", "isJailBroken");
fprintf(stderr, "\ncaddr = %p\n", addr);
if(addr != 0x25a9) take_evasive_action();
}

3.8. Заключение

Мы рассмотрели некоторые методы, которые можно применять в ходе оценки приложения для iOS по методу черного ящика. На самом деле возможно глубоко понять внутреннее устройство приложения даже если оно защищено шифрованием AppStore. Установка перехватчиков в реальном времени предоставляет мощные средства для взаимодействия с приложением, его оценки и модификации. В частности, это позволяет оценщику попасть внутрь приложения и использовать внутреннюю функциональность вроде API, иначе для проверки функциональности понадобилось бы прибегать к методам реверс-инжиниринга.

Что касается защиты, то разработчики, пытающиеся защитить свои приложения от модификации, могут использовать контрольные суммы и проверку адресов классов и методов во время выполнения. Эффективность такого рода защиты можно улучшить путем использования inline-функций. Везде, где это возможно, разработчикам следует пытаться переделать код так, чтобы повысить сложность ресерс-инжиниринга и уменьшить количество раскрываемой информации о структуре классов.

Устали от того, что Интернет знает о вас все?

Присоединяйтесь к нам и станьте невидимыми!