Если вы разработчик ios приложений, то скорее всего, тема пиратства вам знакома, болезненна и малоприятна. Надеюсь, вам будет интересно разобраться как ей препятствовать и что нужно делать, что бы не увидеть свое приложение в репозитории hackulo.us через час после релиза в appstore.
Для начала, рассмотрим, что предприняла Apple для нашей с вами защиты. Называется этот DRM — FairPlay и в его компетенцию входит полное шифрование исполняемого файла, что по замыслу яблока, должно препятствовать реверс-инжинирингу и несанкционированному копированию. В целом, со своими задачами система справляется, но только до того момента, пока наше приложение не попадет на iУстройство. Дело в том, что процессор не умеет выполнять шифрованные инструкции, поэтому операционная система после загрузки файла в память и непосредственно перед его исполнением, производит его полную расшифровку. Вот именно эту особенность и эксплуатирует весь кракерский софт. Дожидаясь когда приложение будет полностью расшифровано в памяти и готово к исполнению, кракерская утилита дампит его образ в файл.
Для наглядности, покажу как это можно сделать с помощью gdb:
Затем, меняем пару служебных полей в заголовке исполняемого файла mach-o и Info.plist. Вот и все. Наше приложение готово выйти в свободное плавание. Но, самое неприятное, что с релизом полностью автоматизированных утилит, таких как Crackulous, проделать эту операцию может любой школьник, даже не имея малейшего представления о том, как все устроено.
Естественно, такое положение вещей не могло оставить сообщество разработчиков равнодушными. Можно легко нагуглить с десяток решений, которые помогут справиться со школьниками, но совершенно беспомощны против даже начинающего кракера. Почему? Давайте рассмотрим подробнее. Основная идея проверок заключается в том, что бы отследить изменения в бандле приложения. Например, разное время создания исполняемого файла и Info.plist, или размер Info.plist отличается от эталонного, или отсутствует директория _CodeSignature. Как и положено приложениям для ios, все эти проверки написаны на ObjC c использованием фреймворков.
Попробуйте открыть исполняемый файл вашего приложения в текстовом редакторе, а еще лучше через консольную утилиту strings. Видите? Все что вы используете, лежит как на ладони. Достаточно загрузить бинарник в IDA и посмотреть, кто и где в коде использует все эти NSBundle, NSDate и NSFileSize. В каком месте используются «грозные» предупреждения Crack Detected и т.д. Обнаружив проверку, взломщику совсем не составит труда изменить 1-2 байта так, что бы проверка всегда возвращала удобное значение.
Недостаток всех этих способов заключается в том, что они очень легко находятся статическим анализом, потому что используют фреймворки, методы и классы ObjC. Значит, нам нужен такой способ, который нельзя было бы найти по общим шаблонам, написанный на чистом С, без использования классов и фреймворков. Вот лучшее из того, что мне показал гугл:
Идея заключается в том, что в заголовке mach-o исполняемого файла есть флаг cryptid в секции LC_ENCRYPTION_INFO, отображающий зашифрован бинарник или нет. На основе его значения делается вывод о происхождении данного приложения.
Несмотря на то, что приведенный выше код достаточно сложно обнаружить статическим анализом, злоумышленник может запустить наше приложение на своем джейлбрейкнутом телефоне под gdb. И в режиме пошагового выполнения он будет в состоянии распутать любую нашу защиту в рекордный промежуток времени!
К счастью, Apple позволяет нам запрещать такую трассировку:
После ее исполнения, любые попытки отлаживать наше приложение, будут сегфолтить, «посягнувший на святое», процесс. Можно сказать, что на этом фронте мы одержали полную и безоговорочную победу!
Но не спешите радоваться, поскольку ptrace — это функция, по сути, врапер, а не настоящий системный вызов, ее легко найти и обезвредить, например так:
Как же быть? Отказываться от fancy api и делать системный вызов своими руками!
Адепты С могут вспомнить о:
Но, во-первых syscall — функция, с которой можно поступить так же как и с ptrace, а во-вторых, это часть private api и за ее использование программу могут не пропустить цензоры.
Так что, нам придется делать системный вызов без посредников, прямиком на ассемблере, благо это совсем не страшно:
А вот найти и обезвредить эти инструкции будет несоизмеримо сложнее, особенно если таких вызовов будет 5-10 и они будут раскиданы по всему коду программы.
Венцом нашей защиты должна быть проверка целостности исполняемого файла, необходимо проверять контрольную сумму секции с кодом (__text). Смысл в том, что даже изменив один байт в этой секции, изменится и контрольная сумма, соответственно мы отследим попытку модифицировать файл! Поскольку в штатных условиях наш файл зашифрован и нет смысла пытаться прочитать его содержимое, мы поступим как взломщики и прочитаем секцию, после того как операционная система загрузит и расшифрует ее сама. Необходимую информацию о смещении и длине можно узнать с помощью:
Интересующий нас код находится начиная с уже знакомого нам адреса 0x2000, а длина соответственно 0x96980.
Шаблон этой проверки может быть, например, таким:
Обратите внимание на атрибут always_inline, он позволяет в разных местах программы размещать не просто вызов к этой функции, а непосредственно ее код. Таким образом, если мы разместим эту проверку в нескольких местах, то взломщику придется найти и обезвредить их все. И пока он этого не сделает, любая попытка модифицировать исполняемый файл будет пресекаться. В идеале, стоит разместить ее в каждом вашем методе или функции. Чтение из памяти — очень быстрый процесс, так что, на производительности ее частое использование никак не отразится. А перспектива выпиливать ее из всего кода должна оттолкнуть даже самого целеустремленного взломщика.
Защита — это противодействие нападению. Противодействуя одной атаке, мы не защищенны от другой. Поэтому, эффективной защитой может быть только комплекс проверок. Я постарался осветить необходимый минимум. И надеюсь, этот материал вдохновит читателя к собственным поискам и исследованиям. Удачи вам! И пусть уровень ваших продаж не омрачают романтики с большой дороги!
хорошую инструкцию на тему Patching Iphone Application с примерами из жизни можно взять тут.
В поисках легкой наживы
Для начала, рассмотрим, что предприняла Apple для нашей с вами защиты. Называется этот DRM — FairPlay и в его компетенцию входит полное шифрование исполняемого файла, что по замыслу яблока, должно препятствовать реверс-инжинирингу и несанкционированному копированию. В целом, со своими задачами система справляется, но только до того момента, пока наше приложение не попадет на iУстройство. Дело в том, что процессор не умеет выполнять шифрованные инструкции, поэтому операционная система после загрузки файла в память и непосредственно перед его исполнением, производит его полную расшифровку. Вот именно эту особенность и эксплуатирует весь кракерский софт. Дожидаясь когда приложение будет полностью расшифровано в памяти и готово к исполнению, кракерская утилита дампит его образ в файл.
Для наглядности, покажу как это можно сделать с помощью gdb:
break main # установим точку прерывания в самое начало
command 1 # и когда она будет достигнута, дампим расшифрованное содержимое в файл output.bin
dump memory output.bin 0x2000 cryptsize # cryptsize берется через otool -l в секции LC_ENCRYPTION_INFO
kill
quit
end
start
Затем, меняем пару служебных полей в заголовке исполняемого файла mach-o и Info.plist. Вот и все. Наше приложение готово выйти в свободное плавание. Но, самое неприятное, что с релизом полностью автоматизированных утилит, таких как Crackulous, проделать эту операцию может любой школьник, даже не имея малейшего представления о том, как все устроено.
Естественно, такое положение вещей не могло оставить сообщество разработчиков равнодушными. Можно легко нагуглить с десяток решений, которые помогут справиться со школьниками, но совершенно беспомощны против даже начинающего кракера. Почему? Давайте рассмотрим подробнее. Основная идея проверок заключается в том, что бы отследить изменения в бандле приложения. Например, разное время создания исполняемого файла и Info.plist, или размер Info.plist отличается от эталонного, или отсутствует директория _CodeSignature. Как и положено приложениям для ios, все эти проверки написаны на ObjC c использованием фреймворков.
Попробуйте открыть исполняемый файл вашего приложения в текстовом редакторе, а еще лучше через консольную утилиту strings. Видите? Все что вы используете, лежит как на ладони. Достаточно загрузить бинарник в IDA и посмотреть, кто и где в коде использует все эти NSBundle, NSDate и NSFileSize. В каком месте используются «грозные» предупреждения Crack Detected и т.д. Обнаружив проверку, взломщику совсем не составит труда изменить 1-2 байта так, что бы проверка всегда возвращала удобное значение.
Недостаток всех этих способов заключается в том, что они очень легко находятся статическим анализом, потому что используют фреймворки, методы и классы ObjC. Значит, нам нужен такой способ, который нельзя было бы найти по общим шаблонам, написанный на чистом С, без использования классов и фреймворков. Вот лучшее из того, что мне показал гугл:
#import <mach-o/dyld.h>
#define LC_ENCRYPTION_INFO 0x21
struct encryption_info_command {
uint32_t cmd;
uint32_t cmdsize;
uint32_t cryptoff;
uint32_t cryptsize;
uint32_t cryptid;
};
static BOOL is_encrypted () {
// в оригинальном коде этот адрес искали с помощью dladdr(main, &dlinfo), но на iphone это константа,
// так что, нет смысла лишний раз светиться использованием dl-функций
struct mach_header *header = 0x1000;
struct load_command *cmd = (struct load_command *) (header+1);
for (uint32_t i = 0; cmd != NULL && i < header->ncmds; i++) {
/* Encryption info segment */
if (cmd->cmd == LC_ENCRYPTION_INFO) {
struct encryption_info_command *crypt_cmd = (struct encryption_info_command *) cmd;
/* Check if binary encryption is enabled */
if (crypt_cmd->cryptid < 1) {
/* Disabled, probably pirated */
return NO;
}
/* Probably not pirated? */
return YES;
}
cmd = (struct load_command *) ((uint8_t *) cmd + cmd->cmdsize);
}
/* Encryption info not found */
return NO;
}
Идея заключается в том, что в заголовке mach-o исполняемого файла есть флаг cryptid в секции LC_ENCRYPTION_INFO, отображающий зашифрован бинарник или нет. На основе его значения делается вывод о происхождении данного приложения.
На этом месте, все хорошие идеи в паблике заканчиваются, дальше только приват
Несмотря на то, что приведенный выше код достаточно сложно обнаружить статическим анализом, злоумышленник может запустить наше приложение на своем джейлбрейкнутом телефоне под gdb. И в режиме пошагового выполнения он будет в состоянии распутать любую нашу защиту в рекордный промежуток времени!
К счастью, Apple позволяет нам запрещать такую трассировку:
ptrace(PT_DENY_ATTACH, 0, 0, 0);
После ее исполнения, любые попытки отлаживать наше приложение, будут сегфолтить, «посягнувший на святое», процесс. Можно сказать, что на этом фронте мы одержали полную и безоговорочную победу!
Но не спешите радоваться, поскольку ptrace — это функция, по сути, врапер, а не настоящий системный вызов, ее легко найти и обезвредить, например так:
break ptrace # останавливаемся в функции ptrace
commands 1
return # и просто возвращаемся из нее, естественно без выполнения самого вызова
continue
end
run
Как же быть? Отказываться от fancy api и делать системный вызов своими руками!
Адепты С могут вспомнить о:
#include <sys/syscall.h>
syscall(SYS_ptrace, PT_DENY_ATTACH, 0, 0, 0);
Но, во-первых syscall — функция, с которой можно поступить так же как и с ptrace, а во-вторых, это часть private api и за ее использование программу могут не пропустить цензоры.
Так что, нам придется делать системный вызов без посредников, прямиком на ассемблере, благо это совсем не страшно:
asm {
mov r0, #31 // PT_DENY_ATTACH
mov r1, #0
mov r2, #0
mov r3, #0
mov ip, #26 // SYS_ptrace
svc #0x80 // собственно сам вызов
}
А вот найти и обезвредить эти инструкции будет несоизмеримо сложнее, особенно если таких вызовов будет 5-10 и они будут раскиданы по всему коду программы.
Венцом нашей защиты должна быть проверка целостности исполняемого файла, необходимо проверять контрольную сумму секции с кодом (__text). Смысл в том, что даже изменив один байт в этой секции, изменится и контрольная сумма, соответственно мы отследим попытку модифицировать файл! Поскольку в штатных условиях наш файл зашифрован и нет смысла пытаться прочитать его содержимое, мы поступим как взломщики и прочитаем секцию, после того как операционная система загрузит и расшифрует ее сама. Необходимую информацию о смещении и длине можно узнать с помощью:
otool -l myPrecious.app/binary
...
Section
sectname __text
segname __TEXT
addr 0x00002000
size 0x00096980
offset 4096
align 2^2 (4)
reloff 0
nreloc 0
flags 0x80000400
reserved1 0
reserved2 0
...
Интересующий нас код находится начиная с уже знакомого нам адреса 0x2000, а длина соответственно 0x96980.
Шаблон этой проверки может быть, например, таким:
__attribute__((always_inline)) void my_secret_checksum() {
u_char *buf = 0x2000;
u_int res;
for (int i = 0; i < 0x96980; i++) {
res += buf[i] ..... //алгоритм подсчета контрольной суммы crc/adler и тд
}
if (res != ...) {
// Achtung! Partizanen!
}
}
Обратите внимание на атрибут always_inline, он позволяет в разных местах программы размещать не просто вызов к этой функции, а непосредственно ее код. Таким образом, если мы разместим эту проверку в нескольких местах, то взломщику придется найти и обезвредить их все. И пока он этого не сделает, любая попытка модифицировать исполняемый файл будет пресекаться. В идеале, стоит разместить ее в каждом вашем методе или функции. Чтение из памяти — очень быстрый процесс, так что, на производительности ее частое использование никак не отразится. А перспектива выпиливать ее из всего кода должна оттолкнуть даже самого целеустремленного взломщика.
Послесловие
Защита — это противодействие нападению. Противодействуя одной атаке, мы не защищенны от другой. Поэтому, эффективной защитой может быть только комплекс проверок. Я постарался осветить необходимый минимум. И надеюсь, этот материал вдохновит читателя к собственным поискам и исследованиям. Удачи вам! И пусть уровень ваших продаж не омрачают романтики с большой дороги!
Литература
хорошую инструкцию на тему Patching Iphone Application с примерами из жизни можно взять тут.