company_banner

Защита утилиты шифрования Scrypt с помощью Intel® Tamper Protection Toolkit

    В нашей статье мы покажем, как Intel Tamper Protection Toolkit позволяет защитить критически важные участки кода и ценные данные в утилите шифрования Scrypt против статического/динамического реверс-инженеринга и изменений. 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


    Intel Tamper Protection Toolkit – это продукт, который используется для обфускации кода и проверки целостности приложения во время выполнения для исполняемых файлов под ОС Microsoft Windows* и Android*. Применяя Tamper Protection Toolkit, можно защитить ценный код и данные в приложении от статического и динамического реверс-инженеринга и внесения изменений. Исполняемые файлы, защищенные с помощью инструмента, не требуют специальных загрузчиков или дополнительного программного обеспечения и могут быть запущены на любом процессоре Intel.
    Инструментарий Intel Tamper Protection Toolkit Beta можно скачать здесь.
    В этой статье мы будем использовать следующие компоненты Intel Tamper Protection Toolkit, для того чтобы обфусцировать критические участки кода и защитить утилиту шифрования от возможных атак:
    • iprot – обфускатор, создающий самомодифицирующийся и самозашифровывающийся код;
    • crypto library – библиотека с набором базовых криптографических операций: алгоритмы безопасного хеширования, коды аутентификации (проверки подлинности) сообщений и симметричные шифры.

    Обфускатор получает на вход динамическую библиотеку (.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

    Существует два способа восстановления пользовательского пароля:
    1. Злоумышленник восстанавливает пароль, используя выработанный ключ, который получен им в результате утечки;
    2. Злоумышленник восстанавливает пароль, используя зашифрованные или подписанные выработанным ключом данные.

    Для первого случая Intel Tamper Protection Toolkit поможет предотвратить утечку ключа путем сокрытия кода, выполняемого для его выработки и в дальнейшем использующего выработанный ключ.
    Второй случай Intel Tamper Protection Toolkit не может предотвратить, но поможет проверить, что злоумышленник не изменил параметры, используемые для генерации ключа, на небезопасные.
    Приведем примеры функций выработки ключа по паролю, используемые на практике:
    • Password-based Key Derivation Function (PBKDF2). Это функция вида y=F(P, S, c), где c – количество итераций для регулирования процессорного времени, требуемого на вычисление функции F для любых P, S. PBKDF2 может быть реализована для систем с очень маленьким объемом оперативной памяти, что делает атаку грубой силы с помощью GPU очень эффективной. Не смотря на это многие продукты продолжают использовать PBKDF2.
    • bcrypt. Эта функция является более стойкой к подобному типу атак с помощью GPU, так как использует больший фиксированный объем оперативной памяти.

    Современной и наиболее безопасной функцией, разработанной Колином Персивалем (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


    Утилита шифрования Scrypt использует алгоритм AES в режиме CTR и выработанный функцией scrypt по пользовательскому паролю ключ для работы с входными файлами. Она содержит обязательные и дополнительные параметры для запуска.
    Обязательными являются:
    • пароль, который используется функцией scrypt для выработки ключа;
    • режим: шифрование или расшифрование;
    • имя входного файла.

    Дополнительные параметры:
    • -t время в секундах, требуемое для выработки ключа;
    • -m доля объема оперативной памяти, используемая для выработки ключа;
    • -M количество байт оперативной памяти, используемой для выработки ключа;
    • имя выходного файла.

    Например, запуск утилиты командой

    scrypt enc infile -t 0.1 -M 20971520

    потребует 100мс процессорного времени и 20МБ оперативной памяти для выработки ключа. Такие значения параметров усложнят распараллеливание перебора при атаке грубой силой.
    Рисунок ниже представляет работу утилиты Scrypt в случае, когда пользователь ввел имя входного файла для шифрования, пароль и параметры, определяющие требуемые аппаратные ресурсы.
    Дадим описание шагов, выполняемых утилитой при шифровании:
    1. Scrypt Сбор и преобразование параметров. Программа подбирает параметры процессорного времени и объема оперативной памяти, требуемые для выработки ключа и преобразует их в параметры, воспринимаемые функцией scrypt.
    2. Scrypt Выработка ключа. Функция scrypt вырабатывает по пользовательскому паролю и параметрам N, r, p, рассчитанным на предыдущем шаге, 64-байтный ключ. Младшие 32 байта ключа dk1 используются для вычисления кода аутентификации для параметров N, r, p, соли и зашифрованных данных. Таким образом, в процессе расшифрования можно проверить правильность введенного пароля и целостность зашифрованных данных. Старшие 32 байта ключа dk2 используются для шифрования входного файла алгоритмом AES в режиме CTR.
    3. Вычисление кода аутентификации для параметров scrypt. На этом шаге вычисляется код аутентификации (проверки подлинности) для параметров N, r, p и соли, используемый для выработки ключа.
    4. OpenSSL шифрование 32-байтными блоками AES в режиме CTR. Шифрование входного сообщения с dk2, используя 32-байтный шифр AES в режиме CTR.
    5. Вычисление кода аутентификации для зашифрованных данных. Наконец, код аутентификации вычисляется для шифрования данных, используя dk1 для обеспечения целостности. Выходной файл содержит зашифрованные данные, параметры N, r, p, соль, использованные при шифровании и коды аутентификации, которые обеспечивают целостность зашифрованных данных и параметров.



    Схема шифрования утилитой Scrypt

    Возможные угрозы


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

    Рисунок ниже иллюстрирует процесс расшифрования, когда пользователь ввел имя входного файла с зашифрованным текстом, N, r, p, солью, кодами аутентификации и пароль.
    Дадим описание шагов, выполняемых утилитой при расшифровании:
    1. Scrypt Установка параметров. Входной файл для расшифрования содержит зашифрованные данные, коды аутентификации hmac1, hmac2 и параметры N, r, p, соль, использованные при шифровании. На этом шаге эти параметры вычитываются из входного файла и передаются в функцию выработки ключа.
    2. Scrypt Выработка ключа. Функция scrypt вырабатывает ключ для пароля и параметров N, r, p, соль, полученных на предыдущем шаге. Младшие 32 байта и старшие 32 байта этого ключа обозначены на рисунке dk1 и dk2 соответственно.
    3. Scrypt Проверка целостности параметров и пароля. Целостность N, r, p, соли и корректность пароля проверяются с помощью кода аутентификации. Для проверки корректности пароля утилита вычисляет код аутентификации для параметров N, r, p, соли, используя dk1, и сравнивает полученное значение со значением hmac1. Если они совпадают, значит пароль верен.
    4. Проверка целостности зашифрованных данных. Для проверки того, что зашифрованные данные не были изменены, вычисляется код аутентификации для данных, используя dk1, и сравнивается со значением hmac2. Если они совпадают, то данные не были испорчены и могут быть расшифрованы на следующем шаге.
    5. OpenSSL 32-байтное блочное расшифрование алгоритмом AES в режиме CTR. Наконец, данные расшифровываются с использованием 32-байтного блочного алгоритма AES в режиме CTR с использованием dk2. Выходной файл содержит расшифрованные данные.



    Схема расшифрования утилитой Scrypt

    Портирование утилиты под Windows


    Целью работы является защита утилиты шифрования 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, такими как процесс, поток, управление памятью и файлами, инфраструктурами сервисов, пользовательскими интерфейсами и так далее. Мы должны были учитывать все эти различия при портировании утилиты. Опишем их ниже.
    • Утилита использует функцию getopt() для разбора аргументов командной строки. Список доступных аргументов программы приведен выше. Функция getopt() находится в заголовочном файле unitstd.h согласно набору стандартов POSIX. Мы используем реализацию get_opt() из открытого проекта getopt_port. Для этого добавим файлы getopt.h и getopt.c из проекта getopt_port в наш проект.
    • Оставшаяся функция gettimeofday(), объявленная в POSIX API, используется утилитой для измерения salsa opps и подсчета числа операций в секунду salsa20/8, выполненных на пользовательской платформе. Метрика salsa opps используется утилитой для подбора более безопасных значений параметров N, r, и p, так что алгоритм scrypt выполняет операции salsa20/8 минимальное количество раз, которое позволяет избежать атаки перебором. Мы добавили реализацию функции gettimeofday() в файл scryptenc_cpuperf.c.
    • Перед запуском алгоритма конфигурации утилита запрашивает у операционной системы количество доступной оперативной памяти, которая будет захвачена вызовом функции getrlimit(RLIMIT_DATA, …) из набора POSIX для выработки ключа. В ОС Windows жесткий и нежесткий лимиты для максимального размера сегмента данных процесса (инициализированные и неинициализированные данные и куча) установим равными 4ГБ. Все это показано в коде ниже.

      /* ... 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)
      
    • Дополнительно, для компилятора MSVS была добавлена директива для определения inline функций в файле sysendian.h.

      #if defined(WIN_TP)
      static __inline uint32_t
      #else
      static inline uint32_t
      #endif  // WIN_TP
      be32dec(const void *pp);
      
    • Мы портировали функцию tarsnap_readpass(…) для выполнения скрытого ввода пароля в терминале. Функция отключает отображение символов в окне терминала и маскирует пароль пробельными символами. Пароль сохраняется в выделенном в памяти буфере и отправляется в следующие функции конфигурации Scrypt и выработки ключа.

      /* В случае чтения из терминала, пробуем выключить вывод символов */
      #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)
      
    • Оригинальная функция getsalt() для получения псевдослучайной последовательности выполняет чтение специального файла /dev/urandom, входящего в состав ОС Unix. На Windows мы используем инструкцию rdrand() из аппаратного генератора случайных чисел, доступного на чипах семейства Intel Xeon и Core, начиная с Ivy Bridge. Стандартная функция C для генерации псевдослучайной последовательности намеренно не используется, так как в этом случае не может быть обфусцирована функция getsalt() с помощью инструмента обфускации Tamper Protection. Функция getsalt() должна быть защищена обфускатором от статического и динамического внесения изменений и реверс-инженеринга, так как соль, производимая этой функцией категоризирована нами в 3 разделе как защищаемый объект. Ниже приведены изменения, внесенные в код, для получения соли.

      #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)
      


    Защита утилиты с помощью Intel Tamper Protection Toolkit


    Теперь мы произведем рефакторинг кода утилиты для защиты всех важных данных, определенных в нашей модели угроз. Защита таких данных достигается путем обфускации кода с помощью инструмента 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

    Дадим подробное описание к рисунку, как работает утилита в каждом из режимов: шифрование и расшифрование.
    Шифрование:
    1. Утилита использует функцию getopt() для разбора аргументов командной строки. Список аргументов приведен выше.
    2. Входной файл для шифрования/расшифрования и пароль считываются в выделенный буфер памяти.
    3. В основном исполняемом файле вызывается функция scrypt_ctx_enc_init для инициализации контекста функции scrypt с вычислением подходящих безопасных значений параметров (N, r, p и соли), учитывая переданные значения аргументов maxmem, maxmemfrac и maxtime, которые задают требуемое процессорное время и объем оперативной памяти для выработки ключа. В конце этого вызова используется ключ HMAC (значение хеш-суммы) для контекста, которое частично инициализировано и требует адрес выделенной памяти, используемой scrypt для вычислений. Один аргумент передается по ссылке, куда функция возвращает количество байт, требуемых алгоритму scrypt для установки параметров.
    4. Утилита в основном исполняемом файле динамически выделяет память, размер которой был возвращен функцией инициализации.
    5. Для шифрования функция scrypt_ctx_enc_init повторно вызывается из основного исполняемого файла. Функция проверяет целостность контекста scrypt, используя значение HMAC. Если проверка целостности пройдена, то заполняются значения адресов в контексте из пространства выделенной на предыдущем шаге памяти для вычислений scrypt и обновляется HMAC. Чтение файла и динамическое распределение памяти перемещено в основной исполняемый файл, чтобы избежать появления необфусцируемого кода в динамической библиотеке. Код, который содержит системные вызовы и стандартные С-функции, генерирующие непрямые переходы и перераспределение памяти, не могут быть обфусцированы.
    6. Выполняется вызов экспортной функции шифрования scryptenc_file, использующей введенный пароль. Функция проверяет целостность контекста функции scrypt с параметрами (N, r, p и соль), используемыми для выработки ключа. Если проерка пройдена, то вызывается алгоритм scrypt для выработки ключа. Выработанный ключ затем используется для шифрования. Экспортная форма функции имеет тот же выход, что и оригинальная функция утилиты scrypt. Это означает, что выход имеет то же значение хеша, используемое для проверки целостности зашифрованных данных и правильности введенного пароля в процессе расшифрования.

    Расшифрование:
    1. Утилита использует функцию getopt() для разбора аргументов командной строки.
    2. Входной файл для шифрования/расшифрования и пароль считываются в выделенный буфер памяти.
    3. В основном исполняемом файле вызывается функция scrypt_ctx_dec_init для проверки, что полученные из зашифрованного файла параметры корректны и функция выработки ключа может быть вычислена с доступным объемом оперативной памяти и процессорным временем. Один аргумент передается по ссылке, куда функция возвращает количество байт, требуемых алгоритму scrypt для установки параметров.
    4. Утилита в основном исполняемом файле динамически выделяет память, размер которой был возвращен функцией инициализации.
    5. Для расшифрования функция scrypt_ctx_dec_init повторно вызывается из основного исполняемого файла. Она выполняет действия, аналогичные функции шифрования.
    6. Выполняется вызов экспортной функции расшифрования scryptdec_file, использующей введенный пароль. Функция проверяет целостность контекста функции scrypt с параметрами (N, r, p и соли), используемыми для выработки ключа. Если провекра пройдена, то вызывается алгоритм 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
    

    Интерфейс защищенной утилиты не изменился. Сравним необфусцированный и обфусцированный код. Дизассемблерный код, приведенный ниже, показывает существенное различие между ними.

    # необфускированный код
    scrypt_ctx_enc_init PROC NEAR
            push    ebp
            mov     ebp, esp
            sub     esp, 100
            mov     dword ptr [ebp-4H], 0
            mov     eax, 1
            imul    ecx, eax, 0
            mov     byte ptr [ebp+ecx-1CH], 1
            mov     edx, 1
            shl     edx, 0
            mov     byte ptr [ebp+edx-1CH], 2
            mov     eax, 1
            shl     eax, 1
            mov     byte ptr [ebp+eax-1CH], 3
            mov     ecx, 1
    <…>
    

    # обфускированный код с параметрами по умолчанию
    scrypt_ctx_enc_init PROC NEAR
            mov     ebp, esp
            sub     esp, 100
            mov     dword ptr [ebp-4H], 0
            mov     eax, 1
            imul    ecx, eax, 0
            mov     byte ptr [ebp+ecx-1CH], 1
            push    eax
            pop     eax
            lea     eax, [eax+3FFFD3H]
            mov     dword ptr [eax], 608469404
            mov     dword ptr [eax+4H], -124000508
            mov     dword ptr [eax+8H], -443981569
            mov     dword ptr [eax+0CH], 1633409
            mov     dword ptr [eax+10H], -477560832
    <…>
    

    В результате обфускации производительность утилиты понизилась и размер библиотеки увеличился. Обфускатор позволяет разработчикам выбирать между большей безопасностью и большей производительностью с помощью опций: размер ячейки и расстояние между точками мутаций. В нашем случае обфускатором используются 512-байтные ячейки и 2600-байтные расстояния мутаций. Ячейкой называется подпоследовательность инструкций из оригинального исполняемого файла. Ячейки в обфусцированном коде зашифрованы до тех пор, пока не потребуется выполнить код, хранящийся в них. После расшифровывания ячейки и полного выполнения содержащегося в ней кода, она зашифровывается обратно.
    Исходный код утилиты, защищаемой с помощью Intel Tamper Protection Toolkit, скоро появится на Github.

    Благодарности


    Мы благодарим Рагхудип Каннавара за идею защиты утилиты шифрования Scrypt и Андрея Сомсикова за многочисленные полезные обсуждения.

    Ссылки


    1. K. Grasman. getopt_port on github
    2. C. Percival. The scrypt encryption utility
    3. C. Percival. “Stronger key derivation via sequential memory-hard functions”.
    4. C. Percival, S. Josefsson (2012-09-17). “The scrypt Password-Based Key Derivation Function”. IETF.
    5. N. Provos, D. Mazieres, J. Talan Sutton 2012 (1999). “A Future-Adaptable Password Scheme”. Proceedings of 1999 USENIX Annual Technical Conference: 81–92.
    6. W. Shawn. Freebsd sources on github

    Авторы: Роман Казанцев, Денис Катеринский, Таддеус Летнес
    {Roman.Kazanstev, Denis.Katerinskiy, Thaddeus.C.Letnes}@intel.com
    Intel 194,90
    Компания
    Поделиться публикацией
    Комментарии 17
    • 0
      А в чем заключается отличие используемой технологии от почившего в бозе Armadillo (Software Passport) и почему она лучше? То о чем говорится в статье это по сути технология наномитов. У Армадилло было слежение за кодом программы из другого потока и расшифровка частей программы по мере необходимости.
      • –1
        Да, наша технология похожа на технологию наномитов. Наверное наномиты предшественники технологии, используемой в Tamper Protection. Во-первых, программист, использующий Armadillo на стадии написания кода, отмечает участки кода вручную в исходном коде программы, которые будут защищены. Что касается iprot, процедура обфускации более автоматизированна, для этого достаточно задать параметры "-c" и "-d". Во-вторых, как мне кажется у Armadillo обфусцированный код он был незашифрованный, а просто туда вставлялся мусор, который затруднял дизассемблирование. Код, защищенный с помощью iprot, зашифрованный и дешифруется в run-time в памяти. В-третьих, iprot обфускация не вставляет отладочные инструкции int3, но защищает от них, т.к. в случае измененного кода дешифрации будет неверна, что приведет к неверному исполнению кода и сбою. Хороший вопрос!
      • 0
        noth
        • 0
          Нужно копать в сторону VmProt, вот где технологии реальные. А эти домашние обфускаторы так, серьезному крякеру на пару часов
          • 0
            Ваше утверждение неаргументрировано.
            • 0
              Если удалось кому-то подломать другие обфускаторы, то это еще ни о чем не говорит. Одна продвинутая американская группа ресечеров пыталась разгадать crackme для iprot, ей понадобилось чуть более двух месяцев. Я очень надеюсь, что мы в ближайшем будущем создадим еще один crackme для всеобщей публики.
              • –1
                iprot во-первых бета, во-вторых еще нигде не используется толком. Вы погодите, обретёте популярность и появятся iprot-стрипперы и иже с ними. Тем более 1 раз уже сломали. VmProtector даже за деньги ломать народ не берется, а он на рынке уже много лет
                • 0
                  В скором времени к выходу готовится голд версия. Наша технология составит хорошую конкуренцию по качеству защиты и цене Arxan'у и вашему VmProtect'у. Считаю, что два месяца работы продвинутой группы ресечеров наоборот говорит о хорошем уровне защиты, это не пара часов/дней/недель работы хакера, сидящего в кресле.
                  • 0
                    К сожалению, vmProtect никакой не мой, просто я его помню еще с тех времен, когда он был бесплатным на сайте политеха. А насчет вашего протектора — посмотрим, имхо эта тема уже загибается, настала эра облаков
            • НЛО прилетело и опубликовало эту надпись здесь
              • 0
                По-видимому вы не понимаете статью, либо по причине отсутствия фундаментальных знаний в области криптографии, либо по причине непонимания сущности DRM систем и их назначения. Наверное первое. Потому что в нашей статье мы привели реальный пример, где может понадобится DRM, выделили критичные данные в программе и объяснили почему, здесь нужен DRm, а также рассказали как можно организовать DRM использую обфускатор iprot. Но это одна из концепций использования нашего продукта: создание контекста критичных данных, обоспечения их целостности, возможности работы с ним только изDRM функций. Контекст также может быть зашифрован и подписан, просто в нашем случае данные открытые. DRM это trusted computing, когда мы обеспечиваем работу с критичными данными только из проверенного кода.
                • НЛО прилетело и опубликовало эту надпись здесь
                  • 0
                    Ivan_83, любому специалисту очевидно, что Вы пишите откровенную ерунду.
                    Прежде чем в подобном стиле высказываться, рекомендую хотя бы на базовом уровне ознакомиться с определениями, понятиями и положением вещей в области информационной и компьютерной безопасности.
                    А чтобы наконец узнать «какая польза от DRM», советую почитать ту же статью «Digital rights management» на Википедии, там все расписано очень подробно.
                    • НЛО прилетело и опубликовало эту надпись здесь
                      • –2
                        Ivan_83, у нас тут в Интеле принято к людям относиться по-человечески. И конкретно Вас никто никак не называл.

                        А Ваше резко негативное отношение к DRM инструментам становится понятным после Вашей же фразы:

                        Особенно мне не нравится огораживания в железе, потому что я не хочу бороться со своим собственным железом за то чтобы я мог запустить что мне вздумается.

                        Вы пользуетесь лицензионным ПО или пиратским?
                        Пиратский софт действительно так и работает. Если бы Вы использовали лицензионный софт, то «бороться с собственным железом» не приходилось бы. По крайней мере, у меня ни разу не возникло серьезных проблем с лицензионным софтом, по которым техподдержка компаний не смогла бы помочь (в том числе и просто вернуть средства или предоставить новую исправленную версию продукта бесплатно).

                        Тогда дальнейшую Вашу фразу уже понятно как следует интерпретировать:

                        Польза дрм — она только правообладателям, по сути это инструмент для отжима бабла[, которое я не намерен платить правообладателям за их труд, так как можно те же продукты бесплатно скачать на варезных порталах].


                        И в данном случае боретесь Вы, в первую очередь, за пиратство в сети, что, кстати, является уголовно наказуемым деянием. И делаете это по понятным причинам: чем сильнее будут развиты DRM инструменты, тем меньше продуктов будет взломано и тем меньше их возможно будет скачать с варезников.

                        А если Вам так не нравится политика
                        анального огораживания всего и вся внутри компьютера и внутри программ от юзера и других программ

                        то кто запрещает Вам перейти на свободно распространяемое программное обеспечение?
                        • НЛО прилетело и опубликовало эту надпись здесь
                • +3
                  Ivan_83, категорически не соглашусь с Вами.
                  В наше время вопросы безопасности в компьютерных системах стоят довольно остро. Их пытаются решать на разных уровнях, правда, с переменным успехом.

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

                  Относительно зловредных программ — они были, есть и будут. Наличие или отсутствие каких-либо DRM систем не повлияют на эту ситуацию. Связано это в первую очередь с методами детектирования средствами защиты, которые ориентируются до сих пор на сигнатурный анализ. Хотя и есть попытки создания более сильных систем, они пока не доказали своей эффективности и являются лишь маркетинговой приманкой к продукту. Я лично проверял, что большинство разрекламированных продуктов (по понятным причинам не буду их называть) перестают детектировать зловреда сразу после того, как заменяется 1-2 байта сигнатуры, что не сказывается на поведении вредоносного кода в целом.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое