- PVSM.RU - https://www.pvsm.ru -
В нашей статье мы покажем, как Intel Tamper Protection Toolkit позволяет защитить критически важные участки кода и ценные данные в утилите шифрования Scrypt [1] против статического/динамического реверс-инженеринга и изменений. Scrypt – наиболее новая и безопасная функция выработки ключа по паролю, широко применяемая на практике. Однако существует угроза фальсификации параметров функции scrypt, что приведет к появлению уязвимостей пароля пользователя. Инструментарий позволяет уменьшить эти угрозы. Мы определяем модель угроз для рассматриваемого приложения, объясняем как провести его рефакторинг для дальнейшей защиты, учитывая особенности применения инструмента Tamper Protection к приложению.
Основной целью этой статьи является демонстрация возможностей Intel Tamper Protection Toolkit по защите от атак на критически важные участки кода и ценные данные, находящиеся в реальных приложениях. Инструментарий позволяет противодействовать статическому и динамическому реверс-инженерингу путем обфускации и препятствовать внесению изменений в защищаемое приложение путем контроля целостности во время выполнения.
Здесь мы рассмотрим только один компонент инструментария, называемый iprot, который используется для обфускации, и применим его к утилите шифрования Scrypt версии 1.1.6. Утилита представляет собой простую функцию scrypt выработки ключа на основе введенного пароля. Выбор в пользу нее был сделан по нескольким причинам. Во-первых, код ее функций содержит часто используемые приложениями возможности: операции чтения и записи файлов, распределение памяти, криптографические функции и системные вызовы. Во-вторых, она включает специфичный математический аппарат. В-третьих, утилита достаточно компактна, но позволяет при этом показать широкий круг проблем, с которыми могут столкнуться разработчики на практике в процессе защиты их собственных приложений. Наконец, scrypt является современной и безопасной функцией выработки ключа, которая активно используется на практике, например, новое шифрование диска, базирующееся на scrypt, встроено в Android 4.4.
Рассмотрим пример исходного кода для функции sensitive, который представлен в Листинге ниже, и скомпилируем для него динамическую библиотеку.
#define MODIFIER (0xF00D)
int __declspec(dllexport) sensitive(const int value) {
int result = 0;
int i;
for (i = 0; i < value; i++) {
result += MODIFIER;
}
return result;
}
Исходный код функции sensitive
Сделаем реверс-инжениринг получившейся библиотеки, используя IDA Pro. Рисунок демонстрирует порядок исполнения кода с логикой и данными для вычисления. Таким образом хакер сможет легко увидеть значение MODIFIER в коде и поменять его.
Порядок исполнения в дизассемблированном коде
Различные приемы обфускации кода могут помочь спрятать детали реализации, усложнить реверс-инжениринг и предотвратить изменения кода. Обфускация кода – это процесс преобразования кода в такой, для которого сложно сделать реверс-инжениринг и понять логику и данные программы, при этом код будет иметь прежнее функциональное назначение. Обфускация используется, чтобы избежать кражи секретных данных в коде, их изменений и защитить интеллектуальную собственность разработчика.
Intel Tamper Protection Toolkit – это продукт, который используется для обфускации кода и проверки целостности приложения во время выполнения для исполняемых файлов под ОС Microsoft Windows* и Android*. Применяя Tamper Protection Toolkit, можно защитить ценный код и данные в приложении от статического и динамического реверс-инженеринга и внесения изменений. Исполняемые файлы, защищенные с помощью инструмента, не требуют специальных загрузчиков или дополнительного программного обеспечения и могут быть запущены на любом процессоре Intel.
Инструментарий Intel Tamper Protection Toolkit Beta можно скачать здесь [2].
В этой статье мы будем использовать следующие компоненты Intel Tamper Protection Toolkit, для того чтобы обфусцировать критические участки кода и защитить утилиту шифрования от возможных атак:
Обфускатор получает на вход динамическую библиотеку (.dll) и список экспортных функций. На выходе получается динамическая библиотека с обфусцированными экспортными функциями. Начиная с их адресов, код поданной на вход динамической библиотеки разбирается и преобразуется в специальное внутреннее представление. Ветвления, переходы и вызовы, если они достижимы, также разбираются и преобразуются. Для того, чтобы код можно было обфусцировать, следует учитывать несколько ограничений. В коде не может быть низкоуровневой работы с памятью, внешних недостижимых вызовов функций, непрямых переходов и глобальных переменных.
В этой статье мы рассказываем, какие подводные камни встречались при внесении изменений в код с целью его обфускации и каким образом можно обойти возникшие трудности.
Обфусцируем динамическую библиотеку, рассмотренную в предыдущей секции, используя iprot:
iprot sensitive.dll sensitive -o sensitive_obf.dll
Попробуем сделать реверс инжениринг обфусцированного кода, используя IDA Pro.
sensitive PROC NEAR
jmp ?_001
?_001: push ebp
push eax
call ?_002
?_002 LABEL NEAR
pop eax
lea eax, [eax+0FECH]
mov dword ptr [eax], 608469404
mov dword ptr [eax+4H], 2308
mov dword ptr [eax+8H], -443981824
mov dword ptr [eax+0CH], 1633409
mov dword ptr [eax+10H], -477560832
mov dword ptr [eax+14H], 15484359
mov dword ptr [eax+18H], -1929379840
mov dword ptr [eax+1CH], -1048448
<….>
Дизассемблированный обфусцированный код
Мы можем легко заметить, как отличается обфусцированный код от первоначального. IDA Pro не смог показать схему для порядка исполнения обфусцированного кода, и значение MODIFIER исчезло. Также обфусцированный код защищен от статического и динамического изменения.
Функции выработки ключа по паролю (англ. PBKDF) используются для преобразования введенного пользователем пароля в ключ (бинарный набор данных), который может использоваться в криптографических алгоритмах. PBKDF очень важная составляющая защиты приложений, потому что введенный пользователем пароль небезопасно использовать в криптографическом алгоритме ввиду его недостаточной энтропии. Эти функции широко используются при защите приложений, например, криптографические ключи получаются из паролей в PGP системах для шифрования/дешифрования данных на диске. Также, операционные системы используют эти функции для проверки пользовательского пароля (аутентификации).
В общем случае математическое выражение для PBKDF имеет следующий вид:
y=F(P, S, d, t1, …, tn)
где y – выработанный функцией ключ, P – пароль, S – соль, d – длина вырабатываемого ключа и t1,…,tn – параметры, определяемые количеством аппаратных ресурсов, таких как тактовая частота процессора, объем оперативной памяти, требуемой для вычисления функции. Соль S служит для создания разных ключей при заданном пароле. Параметры t1,…,tn играют роль определения аппаратных ресурсов, потребляемых для вычисления функции, и могут быть настроены таким образом, чтобы усложнить ее вычисление и добавить дополнительную защиту против атаки грубой силой (brute-force), использующей распараллеливание на аппаратном уровне на обычных GPU.
Схема использования PBKDF
Существует два способа восстановления пользовательского пароля:
Для первого случая Intel Tamper Protection Toolkit поможет предотвратить утечку ключа путем сокрытия кода, выполняемого для его выработки и в дальнейшем использующего выработанный ключ.
Второй случай Intel Tamper Protection Toolkit не может предотвратить, но поможет проверить, что злоумышленник не изменил параметры, используемые для генерации ключа, на небезопасные.
Приведем примеры функций выработки ключа по паролю, используемые на практике:
Современной и наиболее безопасной функцией, разработанной Колином Персивалем (Colin Percival), является scrypt. Она имеет следующую математическую формулу:
y=F(P, S, d, N, r, p),
где y – выработанный функцией ключ, d – длина вырабатываемого ключа, P – пользовательский пароль, S – соль, p, r и N – параметры для установки процессорного времени и объема оперативной памяти, требуемой для выработки ключа. Значения параметров N, r, p, d могут быть открытыми и обычно они хранятся вместе с ключом или с зашифрованными данными.
В зависимости от значений параметров N, r, p выработка одного и того же ключа может потребовать разного количества процессорного времени и объема памяти. Например, если параметры запрашивают ~100 мс и ~20 МБ, то атака грубой силой на обычном GPU против функции scrypt будет не такой эффективной, как против PBKDF2, требующей малый объем оперативной памяти и позволяющей выполнять параллельные вычисления для разных паролей на GPU.
Утилита шифрования Scrypt использует алгоритм AES в режиме CTR и выработанный функцией scrypt по пользовательскому паролю ключ для работы с входными файлами. Она содержит обязательные и дополнительные параметры для запуска.
Обязательными являются:
Дополнительные параметры:
Например, запуск утилиты командой
scrypt enc infile -t 0.1 -M 20971520
потребует 100мс процессорного времени и 20МБ оперативной памяти для выработки ключа. Такие значения параметров усложнят распараллеливание перебора при атаке грубой силой.
Рисунок ниже представляет работу утилиты Scrypt в случае, когда пользователь ввел имя входного файла для шифрования, пароль и параметры, определяющие требуемые аппаратные ресурсы.
Дадим описание шагов, выполняемых утилитой при шифровании:
Схема шифрования утилитой Scrypt
Анализируя работу утилиты в режиме шифрования, мы определим модель угроз. Значения параметров N, r, p, соли и выработанный ключ, получаемые на промежуточных шагах, являются критически важными данными и требуют защиты от изменений в реальном времени. Например, в режиме отладки злоумышленник может установить другие значения параметров N, r, p с целью ослабления сопротивляемости выработанного ключа атаке грубой силой.
Рисунок ниже иллюстрирует процесс расшифрования, когда пользователь ввел имя входного файла с зашифрованным текстом, N, r, p, солью, кодами аутентификации и пароль.
Дадим описание шагов, выполняемых утилитой при расшифровании:
Схема расшифрования утилитой Scrypt
Целью работы является защита утилиты шифрования Scrypt под ОС Windows с помощью Tamper Protection toolkit. Исходная версия утилиты написана под ОС Linux, поэтому первой задачей становится ее портирование под ОС Windows.
Платформо-зависимый код будет размещен между следующими условными директивами:
#if defined(WIN_TP)
// Код под ОС Windows
#else
// Код под ОС Linux
#endif // defined(WIN_TP)
Директива препроцессора WIN_TP отделяет предназначенный для ОС Windows код. WIN_TP должен быть определен для сборки под ОС Windows, в противном случае будет выбран для сборки код под ОС Linux.
Мы используем среду разработки Microsoft* Visual Studio 2013 для сборки и отладки утилиты. Существуют отличия между некоторыми объектами ОС Windows и ОС Linux, такими как процесс, поток, управление памятью и файлами, инфраструктурами сервисов, пользовательскими интерфейсами и так далее. Мы должны были учитывать все эти различия при портировании утилиты. Опишем их ниже.
/* ... RLIMIT_DATA... */
#if defined(WIN_TP)
rl.rlim_cur = 0xFFFFFFFF;
rl.rlim_max = 0xFFFFFFFF;
if((uint64_t)rl.rlim_cur < memrlimit) {
memrlimit = rl.rlim_cur;
}
#else
if (getrlimit(RLIMIT_DATA, &rl))
return (1);
if ((rl.rlim_cur != RLIM_INFINITY) &&
((uint64_t)rl.rlim_cur < memrlimit))
memrlimit = rl.rlim_cur;
#endif // defined(WIN_TP)
#if defined(WIN_TP)
static __inline uint32_t
#else
static inline uint32_t
#endif // WIN_TP
be32dec(const void *pp);
/* В случае чтения из терминала, пробуем выключить вывод символов */
#if defined(WIN_TP)
if ((usingtty = _isatty(_fileno(readfrom))) != 0) {
GetConsoleMode(hStdin, &mode);
if (usingtty)
mode &= ~ENABLE_ECHO_INPUT;
else
mode |= ENABLE_ECHO_INPUT;
SetConsoleMode(hStdin, mode);
}
#else
if ((usingtty = isatty(fileno(readfrom))) != 0) {
if (tcgetattr(fileno(readfrom), &term_old)) {
warn("Cannot read terminal settings");
goto err1;
}
memcpy(&term, &term_old, sizeof(struct termios));
term.c_lflag = (term.c_lflag & ~ECHO) | ECHONL;
if (tcsetattr(fileno(readfrom), TCSANOW, &term)) {
warn("Cannot set terminal settings");
goto err1;
}
}
#endif // defined(WIN_TP)
#if defined(WIN_TP)
uint8_t i = 0;
for (i = 0; i < buflen; i++, buf++)
{
_rdrand32_step(buf);
}
#else
/* Открываем /dev/urandom. */
if ((fd = open("/dev/urandom", O_RDONLY)) == -1)
goto err0;
/* Читаем байты, пока не заполним buffer. */
while (buflen > 0) {
if ((lenread = read(fd, buf, buflen)) == -1)
goto err1;
/* Случайный поток не должен закончиться, пока не заполнен buffer. */
if (lenread == 0)
goto err1;
/* Заполнение по частям */
buf += lenread;
buflen -= lenread;
}
/* Закрытие генератора случайного потока */
while (close(fd) == -1) {
if (errno != EINTR)
goto err0;
}
#endif // defined(WIN_TP)
Теперь мы произведем рефакторинг кода утилиты для защиты всех важных данных, определенных в нашей модели угроз. Защита таких данных достигается путем обфускации кода с помощью инструмента iprot, обфусцирующего компилятора из набора. Будем также придерживаться принципа разумности и обфусцировать только те функции, которые создают, обрабатывают и используют важные данные.
Нам уже известно, что обфускатор принимает на вход динамическую библиотеку и вырабатывает бинарный файл, содержащий только защищенные функции, указанные в командной строке. Поэтому мы поместим все функции, работающие с важными данными внутрь динамической библиотеки для дальнейшей ее обфускации. Оставшиеся функции, такие как разбор аргументов командной строки, чтение пароля, мы оставим в незащищенном виде в основном исполняемом файле.
Новая структура защищаемой утилиты представлена на рисунке ниже. Утилита разделена на две части: главный исполняемый файл и динамическую библиотеку, которая будет обфусцирована. Главный исполняемый файл отвечает за разбор аргументов командной строки, чтение пароля и загрузку входного файла в память. Динамическая библиотека содержит экспортные функции, такие как scryptenc_file, scryptdec_file, которые работают с важными данными (N, r, p, соль).
Структура ключевых данных, используемых в динамической библиотеке, называется контекстом Scrypt и содержит проверочную информацию HMAC для параметров функции scrypt: N, r, p и соль. Информация HMAC в контексте используется для проверки целостности контролируемых параметров доверенными функциями, такими как scrypt_ctx_enc_init, scrypt_ctx_dec_init, scryptenc_file и scryptdec_file, которые были добавлены в результате рефакторинга кода. Эти доверенные функции будут устойчивы к изменениям, так как мы намеренно их обфусцируем с помощью инструмента. Две новые функции scrypt_ctx_enc_init и scrypt_ctx_dec_init потребовались для инициализации контекста функции scrypt для каждого из режимов: шифрование, расшифрование.
Архитектура защищенной утилиты Scrypt
Дадим подробное описание к рисунку, как работает утилита в каждом из режимов: шифрование и расшифрование.
Шифрование:
Расшифрование:
В защищенной утилите мы заменяем OpenSSL реализацию алгоритма AES в режиме CTR и функцию вычисления кода аутентификации на аналогичные функции из библиотеки Intel Tamper Protection Toolkit crypto library. В отличие от OpenSSL, crypto library удовлетворяет всем ограничениям на исходный код и может быть обфусцирована с помощью инструмента iprot и использована с обфусцируемым кодом без изменений. Алгоритм AES вызывается внутри функций scryptenc_file и scryptdec_file для шифрования/расшифрования входного файла и использует выработанный по паролю ключ. Функция вычисления кода аутентификации вызывается в экспортных функциях (scrypt_ctx_enc_init, scrypt_ctx_dec_init, scryptenc_file и scryptdec_file) для проверки целостности данных контекста scrypt перед их использованием. В защищенной утилите все экспортные функции динамической библиотеки обфусцированы с помощью iprot.
Tamper Protection помогает нам достичь цели уменьшения угроз. Наше решение представляет собой переработанную утилиту с обфусцированной с помощью iprot динамической библиотекой. Решение устойчиво к атакам, определенным ранее и это может быть доказано: контекст scrypt может быть обновлено только через экспортные функции, потому что они содержат собственный ключ HMAC для перерасчета значения HMAC в контексте. Также эти функции и проверочные данные HMAC защищены от внесения изменений и реверс-инженеринга с помощью обфускатора. В дополнение, другие важные данные, такие как вырабатываемый функцией scrypt ключ, защищены, поскольку выработка происходит внутри обфусцированных экспортных функциях scryptenc_file и scryptdec_file. обфусцирующий компилятор iprot вырабатывает код, который является самомодифицирующимся во время выполнения и защищенным от внесения изменений и отладки.
Рассмотрим как функция scrypt_ctx_enc_init защищает контекст scrypt. Основной исполняемый файл с помощью указателя buf_p указывает который раз вызывается функция scrypt_ctx_enc_init. Если указатель пуст (значение равно null), то функция вызывается певый раз, в противном случае второй раз. Во время первого вызова происходит инициализация параметров scrypt, вычисляется HMAC и возвращается количество требуемой памяти для вычислений функции scrypt. Все это иллюстрирует следующий код.
// Первое выполнение: возвращение объема памяти для вычисления функции scrypt
if (buf_p == NULL) {
// Подбор параметров scrypt и инициализация окружения
// <...>
// Вычисление HMAC
itp_res = itpHMACSHA256Message((unsigned char *)ctx_p, sizeof(scrypt_ctx)-sizeof(ctx_p->hmac),
hmac_key, sizeof(hmac_key),
ctx_p->hmac, sizeof(ctx_p->hmac));
*buf_size_p = (r << 7) * (p + (uint32_t)N) + (r << 8) + 253;
}
Во время второго вызова buf_p указывает на выделенную память, переданную в функцию scrypt_ctx_enc_init. Используя значение HMAC, функция проверяет целостность контекста, чтобы удостовериться, что никакие данные не были изменены между первым и вторым вызовом функции. После этого она инициализирует адрес внутри контекста, используя указатель buf_p, и перерасчитывает значение HMAC для измененного контекста. Код, выполняемый при повторном вызове, приведен ниже.
// Второе выполнение: память для функции scrypt выделена
if (buf_p != NULL) {
// Проверка HMAC
itp_res = itpHMACSHA256Message(
(unsigned char *)ctx_p, sizeof(scrypt_ctx)-sizeof(ctx_p->hmac),
hmac_key, sizeof(hmac_key),
hmac_value, sizeof(hmac_value));
if (memcmp(hmac_value, ctx_p->hmac, sizeof(hmac_value)) != 0) {
return -1;
}
// Инициализация указателей буферов для вычислений scrypt:
// ctx_p->addrs.B0 = …
// Перерасчет HMAC
itp_res = itpHMACSHA256Message(
(unsigned char *)ctx_p, sizeof(scrypt_ctx)-sizeof(ctx_p->hmac),
hmac_key, sizeof(hmac_key),
ctx_p->hmac, sizeof(ctx_p->hmac));
}
Мы уже знаем, что обфускатор накладывает некоторые ограничения на исходный код, чтобы его можно было обфусцировать: не должно быть релокаций и непрямых переходов (англ. indirect jump) в коде. Конструкции языка C содержащие глобальные переменные, системные вызовы и стандартные С-функции, могут порождать релокации и непрямые переходы. Код выше содержит одну стандартную С-функцию memcmp, что делает код необфусцируемым с помощью iprot. По этой причине мы реализуем несколько собственных стандартных С-функций, таких как memcmp, memset, memmove, используемых в утилите. Также мы заменим все глобальные переменные в динамической библиотеке на локальные и позаботимся о том, чтобы данные инициализировались на стеке.
Кроме того, мы столкнулись с проблемой обфускации кода, содержащего значения типа double, которая не описана в документации к инструменту. Например, в коде ниже показано, что функция pickparams для ограничения числа операций salsa20/8 использует тип переменной double со значением 32768. Это значение не инициализируется на стеке и компилятор располагает его в сегменте данных исполняемого файла, что генерирует в коде релокацию.
double opslimit;
#if defined(WIN_TP)
// unsigned char d_32768[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x40};
unsigned char d_32768[sizeof(double)];
d_32768[0] = 0x00;
d_32768[1] = 0x00;
d_32768[2] = 0x00;
d_32768[3] = 0x00;
d_32768[4] = 0x00;
d_32768[5] = 0x00;
d_32768[6] = 0xE0;
d_32768[7] = 0x40;
double *var_32768_p = (double *) d_32768;
#endif
/* Минимальное количество повторений операции salsa20/8. */
#if defined(WIN_TP)
if (opslimit < *var_32768_p)
opslimit = *var_32768_p;
#else
if (opslimit < 32768)
opslimit = 32768;
#endif
Мы устранили эту проблему просто инициализировав на стеке нужную последовательность байт в 16-ричном виде, представляющую требуемое значение типа double, и создали указатель типа double на адрес этой последовательности. Возможно, некоторые небольшие утилиты наподобие double2hex могут помочь разработчикам получить 16-ричное представление для значений double и могут быть использованы как вспомогательный инструмент.
Для обфускации динамической библиотеки с помощью iprot, мы используем следующую команду:
iprot scrypt-dll.dll scryptenc_file scryptdec_file scrypt_ctx_enc_init scrypt_ctx_dec_init -c 512 -d 2600 -o scrypt_obf.dll
Интерфейс защищенной утилиты не изменился. Сравним необфусцированный и обфусцированный код. Дизассемблерный код, приведенный ниже, показывает существенное различие между ними.
|
|
В результате обфускации производительность утилиты понизилась и размер библиотеки увеличился. Обфускатор позволяет разработчикам выбирать между большей безопасностью и большей производительностью с помощью опций: размер ячейки и расстояние между точками мутаций. В нашем случае обфускатором используются 512-байтные ячейки и 2600-байтные расстояния мутаций. Ячейкой называется подпоследовательность инструкций из оригинального исполняемого файла. Ячейки в обфусцированном коде зашифрованы до тех пор, пока не потребуется выполнить код, хранящийся в них. После расшифровывания ячейки и полного выполнения содержащегося в ней кода, она зашифровывается обратно.
Исходный код утилиты, защищаемой с помощью Intel Tamper Protection Toolkit, скоро появится на Github.
Мы благодарим Рагхудип Каннавара за идею защиты утилиты шифрования Scrypt и Андрея Сомсикова за многочисленные полезные обсуждения.
Авторы: Роман Казанцев, Денис Катеринский, Таддеус Летнес
{Roman.Kazanstev, Denis.Katerinskiy, Thaddeus.C.Letnes}@intel.com
Автор: Intel
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/109563
Ссылки в тексте:
[1] Scrypt: https://github.com/Tarsnap/scrypt/tree/1.1.6
[2] здесь: https://software.intel.com/en-us/tamper-protection
[3] getopt_port: https://github.com/kimgr/getopt_port/
[4] Freebsd sources on github: https://github.com/lattera/freebsd
[5] Источник: https://habrahabr.ru/post/274045/
Нажмите здесь для печати.