Руслан Рахметов, Security Vision
При обеспечении информационной безопасности использование методов криптографической защиты не всегда возможно по техническим и организационным причинам, а использование патентного права для юридической защиты предполагает публикацию описания работы изобретения и технической документации, что не всегда желательно. Техники обфускации (запутывания) используются защитниками и атакующими для сокрытия информации и усложнения анализа (реверс-инжиниринга) систем, при этом задачей обфускации является не гарантированная защита систем от несанкционированного доступа и анализа, а повышение ресурсозатрат настолько, чтобы сделать такой анализ экономически нецелесообразным для предполагаемого противника. В предыдущей части статьи мы обсудили основы обфускации и некоторые её виды, а в данной публикации поговорим о применении обфускации в программном и аппаратном обеспечении.
1. Аппаратная обфускация.
При создании сложного изделия частные компании и правительства затрачивают значительные бюджеты и усилия конструкторов, проектировщиков, архитекторов, в особенности при разработке передовых образцов. Подобные продукты привлекают интерес конкурентов и зарубежных спецслужб, которые пытаются получить инновационные продукты и затем провести процесс обратной разработки (аппаратного реверс-инжиниринга, англ. hardware reverse engineering): понять структуру изделия и принцип функционирования, создать 3D-модель устройства, воссоздать конструкторскую документацию, и в конечном итоге обеспечить процесс самостоятельного создания аналогичного или улучшенного изделия, его компонентов и запасных деталей, иногда без ведома правообладателя. Подобный подход используется в промышленности (металлургии, автопроме, авиастроении), медицине, микроэлектронике. Обратное проектирование микросхем осложнено достаточно малыми размерами устройств и их многослойностью, поэтому используются методы декапсуляции микросхем (вскрытие корпуса и получение доступа к чипу) и травления слоёв чипа кислотами, применяются сканирующие электронные микроскопы (Scanning Electron Microscopy, SEM) и микроскопы с фокусированным ионным пучком (Focused Ion Beam, FIB), цена которых может достигать сотен тысяч и даже миллионов долларов, а также новейшие методы птихографической рентгеновской ламинографии (англ. ptychographic X-ray laminography, PyXL). Разумеется, создатели оригинальных (исходных) изделий и правообладатели всеми силами стараются предотвратить такое несанкционированное копирование устройств и промышленный шпионаж – например, в микроэлектронике используются методы защиты от удаления корпуса и от послойного восстановления топологии чипа, методы экранирования элементов микросхемы (в частности, модулей EEPROM) и методы разрушения компонентов чипа при определенном внешнем физическом воздействии, применяются защищённые типы памяти (MRAM, Antifuse ROM). Разработчики могут наносить незаметные, уникальные, отслеживаемые и взломостойкие водяные знаки на изделия в цифровом (данные о разработчике записываются в ROM компонентов), аналоговом (незаметные изменения напряжения/тока), структурном (расположение компонентов в устройстве) видах. Кроме хищения ноу-хау и дальнейшего производства контрафакта, обратное проектирование может быть использовано для выявления внедренных в компоненты микросхем закладок – однако на микропроцессорном уровне такие атаки пока изучались лишь в лабораторных условиях. Но, несмотря на отсутствие широко известных фактов внедрения имплантов непосредственно в микропроцессоры на аппаратном уровне «in the wild», бэкдоры всё же встраиваются либо в BIOS/UEFI и в прошивки устройств, либо функционируют под видом уязвимостей и недекларированных возможностей в различных технологиях и компонентах микросхем.
Аппаратные бэкдоры близки по функционалу к аппаратным вирусам-троянам (англ. Hardware Trojan) и «аварийным выключателям» (англ. Kill Switch). Бэкдоры целенаправленно встраиваются самим разработчиком микросхемы (по собственной инициативе или по указанию государственного органа), а аппаратные вирусы-трояны внедряются в микросхемы сторонними атакующими – это может быть компания-разработчик САПР для создания дизайна чипа, поставщик IP-блоков (intellectual property core, сложнофункциональные блоки для проектирования микросхем) или фабрика, на которой непосредственно выпускаются чипы. Согласно принятой таксономии, аппаратные трояны могут быть заложены на различных этапах создания микросхемы (определение спецификации, разработка архитектуры, производство, тестирование, сборка/корпусирование/монтаж) и функционировать на различных уровнях абстракции, активироваться по внешнему/внутреннему триггеру, размещаться в различных компонентах микросхемы (процессор, память, внутренние периферийные устройства, источник питания, генератор тактовых импульсов) и выполнять различные несанкционированные действия – изменять функционал микросхемы, снижать её производительность, приводить к отказу в обслуживании, передавать информацию или ключи шифрования вовне через сторонние или скрытые каналы. В свою очередь, аварийные выключатели предназначены для блокирования работы микроэлектронного устройства по внешней команде или при выполнении определенных условий: по аналогии с автоматическим блокированием смартфона при попытке воровства, разработчики или спецслужбы могут встроить подобный функционал в различные изделия для их удаленного отключения. В целом, внедрение аппаратных троянов и аварийных выключателей, т.е. реализация атак на цепочки поставок микроэлектронных устройств и электронной компонентной базы, крайне ресурсоёмко и доступно только нарушителям с высоким уровнем возможностей – спецслужбам, научным и правительственным учреждениям.
2. Программная обфускация.
Защита программ от анализа может использоваться добросовестными разработчиками для ряда целей: например, для защиты интеллектуальной собственности и авторских прав, для предотвращения изучения, клонирования, взлома программы и её незаконного/нелицензионного использования, для предотвращения вмешательства в работу программы, в том числе со стороны ВПО. Атакующие также стремятся защищать свои образцы ВПО от исследования вирусными аналитиками, от автоматического анализа антивирусами и песочницами. В обоих случаях защита может достигаться за счет обфусцирования – программа продолжит работать (возможно, с меньшей скоростью), но её анализ и исследование будут осложнены. Обфускации может подвергаться исходный код, байт-код и скомпилированные бинарные файлы, а для понимания механизма обфускации нужно учитывать, что языки программирования разделяются на компилируемые, интерпретируемые и гибридные:
1) Компилируемые: исходный код (текст) программы передаётся программе-компилятору, которая создаёт бинарный исполняемый файл, состоящий из заголовков, машинного кода (инструкций), данных. Такой файл можно запустить только в определенной ОС и процессорной архитектуре, в зависимости от использованного компилятора (например, .exe-файл для запуска в Windows-среде на ПК с архитектурой x86-64). Подобная компиляция называется также предварительной AOT-компиляцией (Ahead-of-Time compilation).
2) Интерпретируемые: исходный код программы передаётся на исполнение программе-интерпретатору (например, команды в терминале Linux выполняются интерпретатором Bash). В некоторых интерпретируемых языках исходный код программы сначала компилируется в байт-код (bytecode, промежуточный/переносной архитектурно-независимый код) и уже затем этот байт-код исполняется интерпретатором – такой подход обеспечивает кроссплатформенную совместимость, при которой один и тот же исходный код программы может быть запущен на различных ОС и процессорных архитектурах. Например, текст программы на Python, сохраненный в формате .py, компилируется в байт-код в формате .pyc и затем исполняется интерпретатором CPython, который работает на всех популярных типах ОС. Еще один пример: исходный код программ для Android пишется на языках Java или Kotlin, далее компилируется в байт-код формата DEX (Dalvik EXecutable), а затем файлы формата .dex помещаются в исполняемые APK-файлы – на Android-устройствах они запускаются в среде выполнения Android Runtime (ART), транслирующей полученный байт-код в инструкции для процессора конкретного устройства. Программы на интерпретируемых языках проще тестировать и отлаживать, поскольку интерпретатор последовательно выполняет инструкции программы и сразу сообщает об ошибках, а этап компиляции либо отсутствует, либо компиляция происходит незаметно. Однако программы на интерпретируемых языках работают медленнее за счет наличия промежуточного слоя (интерпретатора), а скомпилированные программы выполняются напрямую процессором.
3) Гибридные (условно компилируемые): для того, чтобы сделать программу более универсальной и кроссплатформенной (возможность запускать программу на различных ОС и процессорных архитектурах), используют гибридный подход – исходный код преобразуется в промежуточный язык, который затем уже переводится средой выполнения в машинный код конкретной платформы. Например, исходный код программы на языках C# или Visual Basic.NET транслируется в промежуточный язык CIL (Common Intermediate Language) и затем компилируется в машинный код средствами динамической компиляции JIT (Just-In-Time) в среде выполнения .NET (CLR – Common Language Runtime) уже непосредственно в момент исполнения на конкретной ОС и платформе. Аналогичным образом JIT-компиляция используется для обеспечения платформонезависимости и ускорения, например, внутри JVM (Java Virtual Machine) при запуске Java-программ, а также при работе современных JavaScript-движков (V8 от Google, SpiderMonkey от Mozilla), которые переводят ресурсоёмкие участки JavaScript-кода в оптимизированный машинный код, сохраняющийся в памяти и выполняемый напрямую процессором. Отметим также, что сложность работы оптимизаторов в JIT-движках и особенности их работы с памятью (запись в область память скомпилированного кода и затем его исполнение) приводят к опасным уязвимостям (по подсчетам команды Microsoft Edge, более 45% всех уязвимостей, связанных с работой оптимизатора V8 приходилось на JIT-компилятор) и несовместимости с функционалом ОС по защите памяти (технологии Arbitrary Code Guard, Control Flow Guard, Hardware-enforced Stack Protection, Control-Flow Enforcement Technology). Именно поэтому для уменьшения поверхности атаки производители некоторых браузеров рекомендуют отключать JIT-оптимизацию. В Android повышенная безопасность достигается за счет отключения оптимизаторов JavaScript-движка V8 в режиме Advanced Protection, в браузере MS Edge можно отключить JIT-компиляцию через режим Enhanced Security, в Google Chrome отключить оптимизатор JavaScript можно в настройках, в Mozilla Firefox оптимизация JIT отключается через служебное меню about:config – в значение «false» нужно перевести параметры javascript.options.baselinejit, javascript.options.ion, javascript.options.asmjs, javascript.options.wasm_baselinejit, javascript.options.wasm_optimizingjit.
Обфускация используется для усложнения реверс-инжиниринга программ, который может выполняться с помощью дебаггеров (позволяют анализировать программу во время её работы), дизассемблеров (переводят машинный код бинарного файла в низкоуровневый ассемблерный код), декомпиляторов (переводят машинный код бинарного файла в человекочитаемый код на языке высокого уровня). О данных инструментах мы поговорим детально в следующей статье, но на текущем этапе нам важно знать, что декомпилятор попытается лишь «угадать» исходный код программы, но не сможет восстановить его в точности – скорее всего, будут потеряны исходные комментарии, имена функций и переменных, будет нарушена структура кода. Нужно учесть, что сложнее восстановить исходный код из бинарных файлов, а проще – из промежуточного и байт-кода (например, из программ на Java или C#, из DEX-файлов). Например, злоумышленники, создающие ВПО для Android, зачастую выполняют компиляцию своего кода (т.е. создают аналог «нативных библиотек»), поскольку реверс-инжиниринг скомпилированных бинарных файлов сложнее, чем реверс-инжиниринг байт-кода DEX. Задача обфускации состоит в усложнении подобной декомпиляции, для чего используют средства автоматизации – обфускаторы, которые применяют и атакующие, и законопослушные разработчики софта. При этом обфускация не гарантирует безуспешность реверс-инжиниринга и невозможность восстановления исходного кода – скорее, обфускация решает задачу усложнения реверс-инжиниринга до такой степени, когда это становится нецелесообразно и неэффективно с точки зрения противника.
Помимо обфускации, для защиты кода могут использоваться методы исполнения кода на стороне сервера (в веб-приложениях) и криптографические методы (шифрование, цифровая подпись). По сравнению с шифрованием применение обфускации никак не регламентируется (отсутствуют экспортные или законодательные ограничения), нет необходимости использования и надежного хранения ключей шифрования, не требуется применять специальное оборудование для выполнения криптографических преобразований – именно поэтому обфускация в некоторых случаях может быть предпочтительной опцией. При этом обфускация усложняет процесс создания и отладки программ разработчиками, увеличивает размер итогового исполняемого файла и может значительно замедлить скорость работы программы – однако, некоторые обфускаторы позволяют даже оптимизировать код.
В соответствии с принятой таксономией, методы обфускации (трансформации/преобразования кода) делятся на следующие типы:
1. Обфускация представления (Layout obfuscation):
· Перемешивание (изменение) имён переменных и функций;
· Изменение форматирования;
· Удаление комментариев;
· Удаление отладочной информации (считается одним из самых простых и эффективных способов затруднения реверс-инжиниринга).
2. Обфускация данных (Data obfuscation) и методы т.н. «ложного рефакторинга»:
· Хранение и кодирование: разделение переменных, конвертация статических и процедурных данных, трансформация (упаковка) скалярных переменных в объекты, изменение кодировки;
· Агрегация: объединение скалярных переменных, изменение связей наследования, реструктуризация массивов;
· Упорядочивание: переупорядочивание переменных, методов, массивов.
3. Обфускация потока управления (Control/code flow obfuscation):
· Агрегация: вставка функций, выделение операторов в отдельную функцию, объединение и клонирование функций, развёртка (размотка) циклов;
· Упорядочивание: переупорядочивание выражений, операторов, циклов;
· Вычисления: табличная интерпретация, преобразование сводимого управляющего графа к несводимому, избежание библиотечных вызовов, добавление недостижимого (никогда не выполняющегося) и мёртвого (не влияющего на результат работы) кода, усложнение условий выполнения цикла.
4. Превентивные преобразования (Preventive transformations):
· Использование слабостей и особенностей работы инструментов (декомпиляторов, деобфускаторов) для усложнения реверс-инжиниринга;
· Использование слабостей и особенностей техник деобфускации.
Более подробно с техниками обфускации и примерами можно ознакомиться по ссылкам:
· https://citforum.ru/security/articles/obfus/
· https://citforum.ru/security/articles/analysis/
· https://sharcus.blogspot.com/2011/06/blog-post.html
· https://www.sciencedirect.com/science/article/pii/S1877050915032780
· страница международного соревнования по обфусцированию кода на C (The International Obfuscated C Code Contest)
В целом, обфускация бинарных файлов (машинного кода) достигается путём добавления избыточных ветвлений, циклов и функций, выполняется на этапе компиляции и приводит к намеренному усложнению логики программы. Обфускация исходного кода достигается за счёт намеренного запутывания кода, целенаправленного написания т.н. «спагетти-кода» (слабо структурированного, трудного для восприятия кода), добавления избыточных конструкций, удаления комментариев. В качестве примера техники обфускации исходного кода рассмотрим несложный Python-код для сложения двух вводимых пользователем чисел:
def sum(a, b):
return (a + b)
# Получить два числа от пользователя
a = int(input('Enter 1st number: '))
b = int(input('Enter 2nd number: '))
# Вывести на экран сумму чисел
print(f'Sum of {a} and {b} is {sum(a, b)}')
А вот как будет выглядеть та же программа, но в обфусцированном виде с использованием динамического импорта модуля через функцию __import__() и динамического выполнения кода через функцию exec(), с замещением букв ASCII-кодами, с заменой сложения a+b на операцию a-(-b), с удаленными комментариями:
_m = __import__('builtins')
_fn_name = ''.join([chr(115), chr(117), chr(109)])
exec(f"def {_fn_name}(*_):"
f" return (_[0] - (-_[1]))")
_p1 = ''.join(map(chr, [69,110,116,101,114,32,49,115,116,32,110,117,109,98,101,114,58,32]))
_p2 = ''.join(map(chr, [69,110,116,101,114,32,50,110,100,32,110,117,109,98,101,114,58,32]))
_a = getattr(_m, 'int')(getattr(_m, 'input')(_p1))
_b = getattr(_m, 'int')(getattr(_m, 'input')(_p2))
_result = globals()[_fn_name](_a, _b)
getattr(_m, 'print')(f"{''.join(map(chr,[83,117,109]))} of {_a} and {_b} is {_result}")
Подобная обфускация выполняется зачастую с помощью программ-обфускаторов, таких как StarForce C++ Obfuscator, Themida, Digital.ai Application Security, O-MVLL, Obfusk8. Также распространены .NET-обфускаторы, инструменты для обфускации скриптовых языков (Invoke-Stealth для PowerShell , Blind Bash для Linux Bash, BatchObfuscator для Windows Batch, Pyarmor для Python) и другие инструменты. Обфускация может достигаться за счёт шифрования строк, API-ключей, URL-адресов и доменов с использованием логической операции XOR с некоторым секретным ключом, а также за счёт шифрования блока данных в бинарном файле – в случае ВПО расшифрование осуществляется уже во время работы дроппера или лоадера непосредственно в оперативной памяти, что помогает избежать обнаружения ВПО сетевыми и файловыми средствами защиты. Кроме того, обфускация может быть побочным эффектом применения методов минификации при программировании, кодирования и сжатия данных, а также использования оптимизирующих компиляторов и программ-упаковщиков, таких как UPX, MPRESS, PECompact, Alternate EXE Packer (которыми активно пользуются и злоумышленники, что приводит к ложноположительным срабатываниям средств защиты).
В заключение отметим, что в матрице MITRE ATT&CK тактическая цель атакующих для избежания обнаружения (тактика «Defense Evasion») может быть достигнута за счет вредоносной техники «Обфусцированные файлы или информация» (Obfuscated Files or Information), которая включает в себя ряд подтехник:
· Binary Padding («Набивка» бинарных файлов): атакующие могут добавлять к вредоносному файлу мусорные данные для увеличения его размера до неподдерживаемого средствами защиты и «песочницами» уровня;
· Software Packing (Упаковка программ): атакующие могут использовать программы-упаковщики и защиту программ с помощью виртуальных машин, запускающихся при выполнении кода;
· Steganography (Стеганография): техника сокрытия данных в различных цифровых объектах (изображениях, аудио- и видео-записях);
· Compile After Delivery (Компиляция после доставки): атакующие могут загружать вредоносный код в целевую систему в виде текста (возможно, зашифрованного или обфусцированного), который затем будет скомпилирован в исполняемый файл уже на самом атакуемом устройстве;
· Indicator Removal from Tools (Удаление индикаторов из инструментов): злоумышленники могут изменить характерные индикаторы компрометации (например, изменить набор доменов или IP-адресов, к которым обращается ВПО) или пересобрать ВПО для изменения хэш-суммы файла;
· HTML Smuggling (буквально «Контрабанда HTML»): как мы уже писали ранее, при использовании техники «HTML Smuggling» размещенный на веб-странице JavaScript-код запускает скачивание двоичного объекта (JS blob), содержимое которого декодируется браузером на локальном ПК и затем сохраняется на диске, а пользователя под разными предлогами побуждают его запустить;
· Dynamic API Resolution (Динамический резолвинг API): вызываемые API-функции операционной системы могут дать ИБ-аналитикам информацию о работе ВПО, поэтому атакующие применяют методы скрытия используемых API-функций (например, используют хэши или идентификаторы для непрямого вызова API-функций);
· Stripped Payloads (Удаление полезной нагрузки): атакующие удаляют человекочитаемую информацию (строки, символы) из исходного кода и на этапе компиляции для усложнения реверс-инжиниринга ВПО;
· Embedded Payloads (Встраивание полезной нагрузки): атакующие прячут вредоносную нагрузку в других файлах (например, в DLL, LNK, PNG);
· Command Obfuscation (Обфускация команд): атакующие изменяют внешний вид команд на нечитаемый, например, с применением кодировок base64 или URL encoding, за счет разделения команд (например, «ShellEx»+»ecute»), использования различного форматирования;
· Fileless Storage (Бесфайловое хранение): злоумышленники сохраняют ВПО не в виде файлов, а в оперативной памяти, реестре, WMI-репозитории на атакуемом устройстве;
· LNK Icon Smuggling (буквально «Контрабанда иконок LNK-файлов»): злоумышленники пользуются полями метаданных в LNK-файлах для сокрытия в них вредоносного содержимого (аналогичным образом вредоносный контент может быть помещен в метаданные других типов файлов, например, в офисные документы);
· Encrypted/Encoded File (Зашифрованный/закодированный файл): злоумышленники применяют шифрование и кодирование вредоносного содержимого для предотвращения его обнаружения средствами защиты;
· Polymorphic Code (Полиморфный код): полиморфный (мутирующий) код изменяется во время исполнения при каждом новом запуске, что помогает избежать обнаружения;
· Compression (Сжатие): атакующие могут использовать инструменты для сжатия и архивации файлов, шифровать и устанавливать пароль для открытия архива (что помогает обходить средства защиты), создавать самораспаковывающиеся архивы;
· Junk Code Insertion (Вставка мусорного кода): атакующие добавляют в исходный вредоносный код дополнительные мусорные конструкции для усложнения анализа кода;
· SVG Smuggling (буквально «Контрабанда SVG-файлов»): атакующие используют особенности графических SVG-файлов, которые могут отображать HTML и исполнять JavaScript при загрузке изображения с дальнейшим локальным формированием вредоносного объекта.