В общем в рамках этого цикла думаю осталось две статьи, эта статья закончит теорию.
А две другие будут практика, где мы попробуем использовать полученные знания для обхода Windows defender.
Далее если у меня будет силы и время, попробую оформить эти статьи в pdf книге.
Итак теперь по теме...
Уменьшение бинарной энтропии
Введение
Энтропия относится к степени случайности в предоставленном наборе данных. Существует различные типы мер энтропии, такие как энтропия Гиббса, энтропия Больцмана и энтропия Реньи. Однако в контексте кибербезопасности термин "энтропия" обычно относится к Энтропии Шеннона, которая выдает значение от 0 до 8. С увеличением уровня случайности в наборе данных увеличивается и значение энтропии.
Бинарные файлы вредоносных программ обычно имеют более высокое значение энтропии по сравнению с обычными файлами. Высокая энтропия, как правило, является индикатором сжатых, зашифрованных или упакованных данных, которые часто используются вредоносными программами для скрытия нагрузки. Сжатые, зашифрованные или упакованные данные часто генерируют большое количество случайного вывода, что объясняет, почему энтропия выше в файлах с вредоносными программами.
На рисунке ниже сравнивается энтропия легитимного программного обеспечения и образцов вредоносных программ. Обратите внимание, как у большинства файлов с вредоносными программами значение энтропии находится в диапазоне от 7,2 до 8, в то время как безвредные файлы в основном находятся в диапазоне от 5,6 до 6,8. Изображение взято из статьи
С этим сказанным, целью данного раздела является уменьшение энтропии вредоносного файла и размещение его в приемлемом диапазоне, подобном безвредному файлу.
Измерение энтропии файла
Для понимания того, как уменьшить энтропию файла, важно сначала понять, как ее вычислить. Существует несколько инструментов, которые могут определить энтропию данного файла, таких как
Однако ради простоты, в коде, предоставленном в этом разделе, есть файл на Python, EntropyCalc.py, который вычисляет энтропию файла. Кроме того, с помощью этого скрипта на Python можно вычислить энтропию разделов PE-файла с помощью флага -pe.
Следующее изображение показывает файл EntropyCalc.py в действии.
EntropyCalc.py
EntropyCalc.py использует функцию calc_entropy для вычисления энтропии указанных данных в буфере. Эта функция использует формулу энтропии Шеннона для вычисления значения энтропии.
Уменьшение энтропии
Как уже упоминалось ранее, файл с вредоносными программами обычно содержит данные, которые часто обфусцированы или закодированы способом, который увеличивает их энтропию. Для решения этой проблемы одним из решений является изменение используемого алгоритма шифрования, так как некоторые алгоритмы шифрования генерируют более высокую энтропию для своих данных в виде шифротекста, чем другие.
Например, использование шифрования XOR с одним байтом не изменяет общей энтропии выходных данных. Недостатком этого алгоритма является то, что он считается слабым алгоритмом шифрования.
Другим эффективным методом для поддержания низкой энтропии является использование алгоритмов обфускации, объясненных в начальных статьях, таких как IPv4fuscation, IPv6fuscation, Macfuscation и UUIDfuscation, вместо использования алгоритмов шифрования. Эти методы обфускации выводят данные, которые имеют степень организации и порядка. Поэтому похожие байтовые узоры в наборе данных будут иметь более низкие значения энтропии по сравнению с набором данных с полностью случайными байтами.
Вставка английских строк
Другим методом для уменьшения энтропии является вставка английских строк в код окончательной реализации. Эта техника была замечена в различных образцах вредоносных программ, где в код вставляется случайный набор английских строк. Это работает потому, что английский алфавит состоит всего из 26 символов, что означает, что есть всего 26 * 2 (верхний и нижний регистр букв) разных возможностей для каждого байта. Это меньше, чем количество возможностей, которые выдают алгоритмы шифрования (255 возможностей). Если вы хотите использовать эту технику, рекомендуется использовать либо только строчные, либо только прописные буквы, чтобы уменьшить количество возможностей для каждого байта.
Сказав это, следует отметить, что такой подход не рекомендуется, потому что строки, вставленные в реализацию, могут быть использованы впоследствии в качестве сигнатур для обнаружения вредоносного программного обеспечения.
Дополнение одинаковыми байтами
Более простым способом уменьшения энтропии является дополнение шифротекста полезной нагрузки одинаковым байтом. Это работает потому, что добавленные байты будут иметь энтропию 0,00, так как они все одинаковы.
Например, на следующем изображении показано, как энтропия шелл-кода Msfvenom резко снижается с 5,88325 до 3,77597 после добавления к нему 285 байтов 0xEA.
Недостатком этого подхода является увеличение размера полезной нагрузки. Более крупные нагрузки потребуют больше байтов, что также увеличит размер.
Независимость от библиотеки CRT Библиотека CRT, или C Runtime library, представляет собой стандартный интерфейс для языка программирования C, который содержит набор функций и макросов. Эти функции обычно связаны с управлением памятью (например, memcpy), открытием и закрытием файлов (например, fopen) и манипулированием строками (например, strcpy).
Удаление библиотеки CRT
Может значительно уменьшить энтропию окончательной реализации. На следующем изображении сравниваются два файла, Hello World.exe и Hello World - No CRT.exe, которые имеют одинаковый код, но скомпилированы с и без библиотеки CRT. Hello World - No CRT.exe имеет значительно более низкое значение энтропии по сравнению с Hello World.exe.
Инструмент Maldev Academy - EntropyReducer
Также можно уменьшить энтропию полезной нагрузки с помощью инструмента
EntropyReducer использует собственный алгоритм, который использует связанные списки для вставки нулевых байтов между каждым блоком полезной нагрузки размером BUFF_SIZE.
Объяснение связанных списков выходит за рамки данной статьи, однако хорошо документированный readme и хорошо прокомментированный код в репозитории должны быть достаточными для понимания алгоритма инструмента.
Брут ключа дешифровки полезной нагрузки
Введение
В начальных статьях демонстрировались процессы шифрования и дешифрования данных, а также предупреждалось о том, что сохранение ключа шифрования в виде открытого текста в бинарном файле делает его легкодоступным. Один из способов решить эту проблему - шифровать ключ другим ключом и расшифровывать его во время выполнения программы. Чтобы избежать жесткой привязки ключа к бинарному файлу, ключ получается методом "грубой силы".
В этом модуле будет продемонстрирован алгоритм дешифрования XOR, при котором программа должна угадать ключ методом "грубой силы".
Процесс шифрования ключа
Для выполнения "грубой силы" при дешифровании ключа необходим байт-подсказка в функциях шифрования и дешифрования. Знание значения одного байта до и после процесса шифрования позволяет осуществить процесс дешифрования. В данном случае первый байт выбран в качестве байта-подсказки.
Например, если байт-подсказка - BA, а после шифрования он становится равным 71, то процесс дешифрования будет перебирать значения до тех пор, пока не будет получено значение BA, что указывает на правильный ключ.
Функция шифрования ключа
Функция GenerateProtectedKey принимает байт-подсказку и добавляет его в начало ключа в виде первого байта. Затем она использует алгоритм XOR для шифрования ключа с использованием случайно сгенерированного ключа во время выполнения.
Обратите внимание, что функция PrintHex предназначена для вывода входного буфера в виде шестнадцатеричного массива и используется для вывода сгенерированного ключа в виде открытого текста.
Процесс дешифрования ключа
Поскольку ключ шифрования, используемый для шифрования ключа, не сохраняется нигде, функция дешифрования должна угадать значение b, показанное в функции GenerateProtectedKey. Для этого функция дешифрования будет выполнять операцию XOR с первым байтом ключа, который является байтом-подсказкой, с разными ключами до тех пор, пока полученный байт не станет равным байту-подсказке исходного ключа. Когда это происходит, функция узнает, что было выбрано правильное значение b. Фрагмент кода ниже демонстрирует эту логику.
Продолжая пример, когда 71 становится равным BA, это означает, что правильное значение b было угадано.
Функция дешифрования ключа
Функция BruteForceDecryption требует тот же байт-подсказку, которая передавалась в функцию шифрования.
Заключение
Несмотря на свою простоту, подход с использованием "грубой силы" может быть использован для защиты ключей от анализа вирусными аналитиками и исследователями, которые пытаются извлечь ключ из бинарного файла. Это заставляет их отлаживать бинарный файл, чтобы понять, как генерируется ключ, что может быть полезным при использовании методов антианализа.
Удаление библиотеки CRT
Введение
До этого раздела все проекты кода компилировались с использованием опции "Выпуск" или "Отладка" в Visual Studio.
Для разработчиков вредоносных программ важно понимать разницу между опциями компиляции "Выпуск" и "Отладка" в Visual Studio, а также последствия изменения настроек компилятора по умолчанию. Изменение настроек компилятора Visual Studio может повлиять на созданный двоичный файл, такое как уменьшение его размера или снижение энтропии.
Опция "Выпуск" против "Отладка"
Конфигурации сборки "Выпуск" и "Отладка" определяют, как программа компилируется и выполняется, и каждая из них выполняет свою задачу и предоставляет разные возможности. Наиболее важные различия между этими двумя опциями приведены ниже.
Производительность - Опция сборки "Выпуск" быстрее, чем опция "Отладка". В режиме выпуска включены оптимизации компиляции, которые отключены в режиме "Отладка".
Отладка - Отладка приложений, созданных с использованием конфигурации сборки "Отладка", упрощается, потому что в этом режиме отключены оптимизации компиляции, что делает код более доступным для отладки. Кроме того, конфигурация "Отладка" генерирует файлы символов отладки (.pdb), которые содержат информацию о скомпилированном исходном коде. Это позволяет отладчикам отображать дополнительную информацию, такую как переменные, функции и номера строк.
Развертывание - Версия "Выпуск" приложения используется из-за увеличенной совместимости с их компьютерами, в отличие от версии "Отладка", которая обычно требует дополнительных динамических библиотек (DLL), доступных только в Visual Studio, что делает отладочные приложения совместимыми только с компьютерами, на которых установлена Visual Studio.
Обработка исключений - В конфигурации сборки "Отладка" Visual Studio может приостанавливать выполнение и отображать сообщение об ошибке в виде окна сообщения, когда возникает исключение, указывая имя переменной или номер строки, вызвавших повреждение стека, например. Такие исключения могут привести к зависанию программы, если она скомпилирована в режиме "Выпуск".
Настройки компилятора по умолчанию
Исходя из предыдущих аспектов, опция "Выпуск" предпочтительнее опции "Отладка". Однако опция "Выпуск" все равно имеет несколько проблем.
Совместимость - Некоторые приложения, использующие опцию "Выпуск", всё равно могут вызывать ошибки, аналогичные приведенной ниже, если целевой компьютер не имеет установленной Visual Studio.
Импортируемые функции CRT - В таблице импорта (IAT) присутствуют несколько неразрешенных функций, которые невозможно разрешить с использованием методов, таких как API-хэширование. Эти функции импортируются из библиотеки CRT, что будет объяснено позже. Пока достаточно понимать, что в любом приложении, созданном настройками компилятора по умолчанию Visual Studio, есть несколько неиспользуемых импортированных функций. В качестве примера, IAT программы 'Hello World' должна импортировать информацию только о функции printf, однако она импортирует следующие функции (вывод сокращен из-за размера).
Размер - Сгенерированные файлы часто больше, чем они должны быть из-за оптимизаций компилятора по умолчанию. Например, следующая программа "Hello World" имеет размер около 11 килобайт.
Информация для отладки - Использование опции "Выпуск" все равно может включать информацию, связанную с отладкой, и другие строки, которые могут быть использованы средствами безопасности для создания статических сигнатур. На изображениях ниже показан результат выполнения программы Strings.exe для программы "Hello World" (вывод сокращен из-за размера).
Библиотека CRT
Библиотека CRT, также известная как Microsoft C Run-Time Library, представляет собой набор низкоуровневых функций и макросов, обеспечивающих базовую функциональность для стандартных программ на C и C++. Она включает функции управления памятью (например, malloc, memset и free), манипулирования строками (например, strcpy и strlen) и функции ввода-вывода (например, printf, wprintf и scanf).
DLL-файлы библиотеки CRT названы vcruntimeXXX.dll, где XXX - это номер версии используемой библиотеки CRT. Также существуют DLL-файлы, такие как api-ms-win-crt-stdio-l1-1-0.dll, api-ms-win-crt-runtime-l1-1-0.dll и api-ms-win-crt-locale-l1-1-0.dll, которые также связаны с библиотекой CRT. Каждая из этих DLL выполняет конкретные функции и экспортирует несколько функций. Эти DLL-файлы связываются компилятором на этапе компиляции и, следовательно, находятся в таблице импорта (IAT) созданных программ.
Решение проблем совместимости
По умолчанию, при компиляции приложения в Visual Studio, опция Runtime Library установлена на "Multi-threaded DLL (/MD)". С этой опцией DLL-файлы библиотеки CRT связываются динамически, что означает их загрузку во время выполнения. Это вызывает проблемы совместимости, о которых упоминалось ранее. Для их решения установите опцию Runtime Library на "Multi-threaded (/MT)", как показано ниже.
Multi-threaded (/MT)
Компилятор Visual Studio может быть настроен на статическую связь функций CRT, выбрав опцию "Multi-threaded (/MT)". Это приводит к тому, что функции, такие как printf, будут представлены напрямую в созданной программе, а не импортируются из DLL-файлов библиотеки CRT. Обратите внимание, что это увеличит размер конечного бинарного файла и добавит больше функций WinAPI в таблицу импорта, но удалит DLL-файлы библиотеки CRT.
Использование опции "Multi-threaded (/MT)" для компиляции программы "Hello World" приводит к следующей таблице импорта.
Размер бинарного файла также значительно увеличивается, как показано ниже.
Библиотека CRT и отладка
После удаления библиотеки CRT программа может быть скомпилирована только в режиме "Выпуск". Это делает отладку кода более сложной. Поэтому рекомендуется удалять библиотеку CRT только после завершения отладки и разработки.
Дополнительные изменения компилятора
Предыдущие разделы демонстрировали, как статически связать библиотеку CRT. Однако идеальным решением было бы избегать зависимости от библиотеки CRT как статически, так и динамически, так как это может привести к уменьшению размера бинарного файла и удалению ненужных импортированных функций и отладочной информации. Для этого необходимо внести изменения в несколько опций компиляции Visual Studio.
Отключение исключений C++
Опция Enable C++ Exceptions используется для генерации кода, который правильно обрабатывает исключения, выбрасываемые кодом. Однако, поскольку библиотека CRT больше не связана, эта опция не требуется и должна быть отключена.
Отключение оптимизации всей программы
Опция Whole Program Optimization должна быть отключена, чтобы предотвратить выполнение компилятором оптимизаций, которые могут повлиять на стек. Отключение этой опции предоставляет полный контроль над скомпилированным кодом.
Отключение отладочной информации
Отключите опции Generate Debug Info и Generate Manifest, чтобы удалить добавленную отладочную информацию.
Игнорирование всех библиотек по умолчанию
Установите опцию Ignore All Default Libraries на "Yes (/NODEFAULTLIB)", чтобы исключить связывание системных библиотек по умолчанию компилятором с программой. Это приведет к исключению связывания библиотеки CRT, а также других библиотек. В этом случае пользователь должен предоставить необходимые функции, которые обычно предоставляются этими библиотеками по умолчанию. На изображении ниже показано установление опции "Yes (/NODEFAULTLIB)".
К сожалению, компиляция с этой опцией приводит к ошибкам, как показано ниже.
Установка символа точки входа
Первая ошибка "LNK2001 - unresolved external symbol mainCRTStartup" указывает на то, что компилятор не смог найти определение символа "mainCRTStartup". Это ожидаемо, так как "mainCRTStartup" является точкой входа для программы, связанной с библиотекой CRT, что не имеет места в данном случае. Чтобы решить эту проблему, следует установить новый символ точки входа, как показано ниже.
Символ "main" представляет собой функцию main в исходном коде. Для выбора другой функции в качестве точки входа просто установите символ точки входа на имя этой функции. Повторная компиляция приводит к меньшему количеству ошибок, как показано ниже.
Отключение проверки безопасности
Следующая ошибка, "LNK2001 - unresolved external symbol __security_check_cookie", означает, что компилятор не нашел символ "__security_check_cookie". Этот символ используется для выполнения проверки стековой куки, которая является функцией безопасности, предотвращающей переполнение буфера стека. Чтобы решить эту проблему, установите опцию Security Check на "Disable Security Check (/Gs-)", как показано ниже.
Отключение проверок SDL
После отключения проверки безопасности ошибка исчезает, но появляется новое предупреждение.
Предупреждение "D9025 - overriding '/sdl' with '/GS-'" можно решить, отключив проверки Security Development Lifecycle (SDL).
Остаются две неразрешенные ошибки символов, которые решаются в разделе Замена функций библиотеки CRT ниже.
Замена функций библиотеки CRT
Из-за удаления библиотеки CRT остались две неразрешенные ошибки из-за использования функции printf для вывода на консоль, хотя библиотека CRT была удалена из программы.
При удалении библиотеки CRT необходимо написать собственные версии функций, такие как printf, strlen, strcat, memcpy. Для этой цели можно использовать библиотеки, например,
Замена функции Printf
Для демонстрационной программы, используемой в этом разделе, функция printf заменяется следующей макроинструкцией.
Макроинструкция PRINTA принимает два аргумента:
Создание вредоносного ПО, независимого от библиотеки CRT
При создании вредоносного программного обеспечения, которое не использует библиотеку CRT, следует учесть несколько моментов.
Использование внутренних функций
Некоторые функции и макросы в Visual Studio используют функции CRT для выполнения своих задач. Например, макрос ZeroMemory использует функцию CRT memset для заполнения указанного буфера нулями. Это требует от разработчика поиска альтернативы этому макросу, так как его нельзя использовать. В этом случае может использоваться функция из
Другим решением может быть ручное установление пользовательских версий функций, основанных на CRT, таких как memset. Это заставляет компилятор обрабатывать эту пользовательскую функцию вместо экспортированной версии CRT. Последовательно макросы, такие как ZeroMemory, также будут использовать эту пользовательскую функцию.
Для демонстрации этого можно указать пользовательскую версию функции memset компилятору следующим образом, используя ключевое слово intrinsic.
Скрытие окна консоли
Вредоносное программное обеспечение не должно создавать окно консоли при выполнении, так как это вызывает подозрение и позволяет пользователю завершить программу, закрыв окно. Для предотвращения этого можно использовать функцию ShowWindow(NULL, SW_HIDE) в начале функции точки входа, хотя это требует времени (в миллисекундах) и может вызвать заметное мерцание.
Лучшим решением является установка программы для компиляции как GUI-программы, установив опцию SubSystem Visual Studio в "Windows (/SUBSYSTEM:WINDOWS)".
Демонстрация
После выполнения всех описанных в этом разделешагов, получаются следующие результаты.
Во-первых, размер исполняемого файла уменьшается с 112,5 килобайт до примерно 3 килобайт.
Затем в IAT не обнаружено неиспользуемых функций.
В бинарном файле обнаружено меньше строк без отладочной информации.
Наконец, удаление библиотеки CRT приводит к меньшему детекту.
Файл загружается на VirusTotal дважды: первый раз с использованием опции "Multi-threaded (/MT)", чтобы статически связать библиотеку CRT, а второй раз после полного удаления библиотеки CRT.
IAT Camouflage
Введение
Удаление библиотеки C Runtime из конечного бинарного файла позволяет очистить IAT от неиспользуемых функций WinAPI. Однако это может вызвать подозрение, если бинарный файл импортирует очень мало функций WinAPI, особенно если это совмещается с хэшированием API, что может даже привести к отсутствию импортированных функций.
Для разработчика вредоносных программ важно, чтобы реализация вредоносного ПО выглядела нормально. Иметь реализацию с фиктивным IAT более эффективно, чем отсутствие импортированных функций. Этот модуль подробно рассмотрит эту концепцию.
Давайте начнем с бинарного файла с именем IatCamouflage.exe, который не использует библиотеку CRT и был скомпилирован аналогично тому, как показанно выше.
Когда бинарный файл выполняется, Process Hacker выделит процесс розовым цветом и отобразит заметку, когда мышь наводится на процесс. Process Hacker предполагает, что бинарный файл упакован из-за отсутствия импорта в IAT.
Убедитесь, что IatCamouflage.exe импортирует одну функцию с помощью dumpbin.exe.
Манипуляция IAT
Манипуляция IAT может быть легко выполнена с использованием доброжелательных WinAPI, которые не изменяют поведение программы. Это можно сделать, вызывая WinAPI с параметрами NULL или используя WinAPI на фиктивных данных, которые не повлияют на программу. Кроме того, эти функции могут размещаться в if-условиях, которые никогда не будут выполняться.
Тем не менее, некоторые компиляторы могут изменять ход выполнения кода с использованием оптимизации "удаление мертвого кода". Это свойство оптимизации компилятора для удаления кода, который не влияет на работу программы.
Пример удаления мертвого кода
В следующем фрагменте кода вызываются несколько функций WinAPI внутри if-условия, которое никогда не будет выполнено.
Если проект Visual Studio не имеет зависимости от библиотеки CRT и компилирует вышеуказанный код, то функции WinAPI не будут видны в IAT бинарного файла. Компилятор знает, что if-условие невозможно выполнить, и, следовательно, весь код if-условия не включается в скомпилированный бинарный файл, что приводит к тому, что функции WinAPI не будут в IAT бинарного файла. Существуют два способа решения этой проблемы:
Для этого метода необходимо просто отключить опцию оптимизации Visual Studio, как показано на изображении ниже. Это отключит свойство оптимизации компилятора "удаление мертвого кода", что приведет к тому, что функции WinAPI будут видны в IAT. Однако отключение оптимизации на более крупных программах может негативно сказаться на производительности, поскольку компилятор больше не оптимизирует эффективность и скорость кода. Поэтому программа может потреблять больше памяти или работать медленнее.
Обман компилятора
Для этого метода необходимо использовать логику, чтобы обмануть компилятор и заставить его думать, что if-условие может быть допустимым. В приведенном ниже фрагменте кода используется логика, которая делает сложно определить компилятору, будет ли if-условие выполняться, что заставляет его включить эту логику в скомпилированный бинарный файл, даже если if-условие никогда не будет удовлетворено.
Ниже приведены несколько моментов, чтобы облегчить понимание этого фрагмента кода.
Функция RandomCompileTimeSeed используется для генерации случайного значения во время компиляции с использованием макроса TIME.
Функция Helper выделяет кучу и устанавливает первые 4 байта равными значению RandomCompileTimeSeed() % 0xFF, что ограничивает значение числа, чтобы оно было меньше 0xFF (в шестнадцатеричной системе) или 255 (в десятичной системе).
Функция IatCamouflage содержит переменную A, которая является указателем на целое число и устанавливается равной первым четырем байтам буфера, возвращенного функцией Helper.
Поскольку функция Helper всегда будет возвращать значение меньше 255, if-условие if (*A > 350) всегда будет ложным. Здесь интересно то, что компилятор не знает об этом и, следовательно, включит эту логику в скомпилированный бинарный файл.
Результаты
Скомпилируйте приведенный выше фрагмент кода и проверьте IAT бинарного файла. Как и ожидалось, доброжелательные функции WinAPI внутри if-условия теперь видны.
Эти импортированные функции достаточно, чтобы сделать бинарный файл незаразным при статическом анализе. С другой стороны, вредоносные функции WinAPI должны быть удалены из IAT с использованием хэширования API.
А две другие будут практика, где мы попробуем использовать полученные знания для обхода Windows defender.
Далее если у меня будет силы и время, попробую оформить эти статьи в pdf книге.
Итак теперь по теме...
Уменьшение бинарной энтропии
Введение
Энтропия относится к степени случайности в предоставленном наборе данных. Существует различные типы мер энтропии, такие как энтропия Гиббса, энтропия Больцмана и энтропия Реньи. Однако в контексте кибербезопасности термин "энтропия" обычно относится к Энтропии Шеннона, которая выдает значение от 0 до 8. С увеличением уровня случайности в наборе данных увеличивается и значение энтропии.
Бинарные файлы вредоносных программ обычно имеют более высокое значение энтропии по сравнению с обычными файлами. Высокая энтропия, как правило, является индикатором сжатых, зашифрованных или упакованных данных, которые часто используются вредоносными программами для скрытия нагрузки. Сжатые, зашифрованные или упакованные данные часто генерируют большое количество случайного вывода, что объясняет, почему энтропия выше в файлах с вредоносными программами.
На рисунке ниже сравнивается энтропия легитимного программного обеспечения и образцов вредоносных программ. Обратите внимание, как у большинства файлов с вредоносными программами значение энтропии находится в диапазоне от 7,2 до 8, в то время как безвредные файлы в основном находятся в диапазоне от 5,6 до 6,8. Изображение взято из статьи
Вы должны зарегистрироваться, чтобы увидеть внешние ссылки
, которая показывает, как использовать энтропию файла для поиска угроз.С этим сказанным, целью данного раздела является уменьшение энтропии вредоносного файла и размещение его в приемлемом диапазоне, подобном безвредному файлу.
Измерение энтропии файла
Для понимания того, как уменьшить энтропию файла, важно сначала понять, как ее вычислить. Существует несколько инструментов, которые могут определить энтропию данного файла, таких как
Вы должны зарегистрироваться, чтобы увидеть внешние ссылки
и
Вы должны зарегистрироваться, чтобы увидеть внешние ссылки
.Однако ради простоты, в коде, предоставленном в этом разделе, есть файл на Python, EntropyCalc.py, который вычисляет энтропию файла. Кроме того, с помощью этого скрипта на Python можно вычислить энтропию разделов PE-файла с помощью флага -pe.
Следующее изображение показывает файл EntropyCalc.py в действии.
EntropyCalc.py
EntropyCalc.py использует функцию calc_entropy для вычисления энтропии указанных данных в буфере. Эта функция использует формулу энтропии Шеннона для вычисления значения энтропии.
Код:
def calc_entropy(buffer):
if isinstance(buffer, str):
buffer = buffer.encode()
entropy = 0
for x in range(256):
p = (float(buffer.count(bytes([x])))) / len(buffer)
if p > 0:
entropy += - p * math.log(p, 2)
return entropy
Уменьшение энтропии
Как уже упоминалось ранее, файл с вредоносными программами обычно содержит данные, которые часто обфусцированы или закодированы способом, который увеличивает их энтропию. Для решения этой проблемы одним из решений является изменение используемого алгоритма шифрования, так как некоторые алгоритмы шифрования генерируют более высокую энтропию для своих данных в виде шифротекста, чем другие.
Например, использование шифрования XOR с одним байтом не изменяет общей энтропии выходных данных. Недостатком этого алгоритма является то, что он считается слабым алгоритмом шифрования.
Другим эффективным методом для поддержания низкой энтропии является использование алгоритмов обфускации, объясненных в начальных статьях, таких как IPv4fuscation, IPv6fuscation, Macfuscation и UUIDfuscation, вместо использования алгоритмов шифрования. Эти методы обфускации выводят данные, которые имеют степень организации и порядка. Поэтому похожие байтовые узоры в наборе данных будут иметь более низкие значения энтропии по сравнению с набором данных с полностью случайными байтами.
Вставка английских строк
Другим методом для уменьшения энтропии является вставка английских строк в код окончательной реализации. Эта техника была замечена в различных образцах вредоносных программ, где в код вставляется случайный набор английских строк. Это работает потому, что английский алфавит состоит всего из 26 символов, что означает, что есть всего 26 * 2 (верхний и нижний регистр букв) разных возможностей для каждого байта. Это меньше, чем количество возможностей, которые выдают алгоритмы шифрования (255 возможностей). Если вы хотите использовать эту технику, рекомендуется использовать либо только строчные, либо только прописные буквы, чтобы уменьшить количество возможностей для каждого байта.
Сказав это, следует отметить, что такой подход не рекомендуется, потому что строки, вставленные в реализацию, могут быть использованы впоследствии в качестве сигнатур для обнаружения вредоносного программного обеспечения.
Дополнение одинаковыми байтами
Более простым способом уменьшения энтропии является дополнение шифротекста полезной нагрузки одинаковым байтом. Это работает потому, что добавленные байты будут иметь энтропию 0,00, так как они все одинаковы.
Например, на следующем изображении показано, как энтропия шелл-кода Msfvenom резко снижается с 5,88325 до 3,77597 после добавления к нему 285 байтов 0xEA.
Недостатком этого подхода является увеличение размера полезной нагрузки. Более крупные нагрузки потребуют больше байтов, что также увеличит размер.
Независимость от библиотеки CRT Библиотека CRT, или C Runtime library, представляет собой стандартный интерфейс для языка программирования C, который содержит набор функций и макросов. Эти функции обычно связаны с управлением памятью (например, memcpy), открытием и закрытием файлов (например, fopen) и манипулированием строками (например, strcpy).
Удаление библиотеки CRT
Может значительно уменьшить энтропию окончательной реализации. На следующем изображении сравниваются два файла, Hello World.exe и Hello World - No CRT.exe, которые имеют одинаковый код, но скомпилированы с и без библиотеки CRT. Hello World - No CRT.exe имеет значительно более низкое значение энтропии по сравнению с Hello World.exe.
Инструмент Maldev Academy - EntropyReducer
Также можно уменьшить энтропию полезной нагрузки с помощью инструмента
Вы должны зарегистрироваться, чтобы увидеть внешние ссылки
, разработанного командой MalDev Academy.EntropyReducer использует собственный алгоритм, который использует связанные списки для вставки нулевых байтов между каждым блоком полезной нагрузки размером BUFF_SIZE.
Объяснение связанных списков выходит за рамки данной статьи, однако хорошо документированный readme и хорошо прокомментированный код в репозитории должны быть достаточными для понимания алгоритма инструмента.
Брут ключа дешифровки полезной нагрузки
Введение
В начальных статьях демонстрировались процессы шифрования и дешифрования данных, а также предупреждалось о том, что сохранение ключа шифрования в виде открытого текста в бинарном файле делает его легкодоступным. Один из способов решить эту проблему - шифровать ключ другим ключом и расшифровывать его во время выполнения программы. Чтобы избежать жесткой привязки ключа к бинарному файлу, ключ получается методом "грубой силы".
В этом модуле будет продемонстрирован алгоритм дешифрования XOR, при котором программа должна угадать ключ методом "грубой силы".
Процесс шифрования ключа
Для выполнения "грубой силы" при дешифровании ключа необходим байт-подсказка в функциях шифрования и дешифрования. Знание значения одного байта до и после процесса шифрования позволяет осуществить процесс дешифрования. В данном случае первый байт выбран в качестве байта-подсказки.
Например, если байт-подсказка - BA, а после шифрования он становится равным 71, то процесс дешифрования будет перебирать значения до тех пор, пока не будет получено значение BA, что указывает на правильный ключ.
Функция шифрования ключа
Функция GenerateProtectedKey принимает байт-подсказку и добавляет его в начало ключа в виде первого байта. Затем она использует алгоритм XOR для шифрования ключа с использованием случайно сгенерированного ключа во время выполнения.
Обратите внимание, что функция PrintHex предназначена для вывода входного буфера в виде шестнадцатеричного массива и используется для вывода сгенерированного ключа в виде открытого текста.
C:
/*
HintByte: байт-подсказка, который будет сохранен в качестве первого байта ключа
sKey: размер ключа для генерации
ppProtectedKey: указатель на буфер PBYTE, который получит зашифрованный ключ
*/
VOID GenerateProtectedKey(IN BYTE HintByte, IN SIZE_T sKey, OUT PBYTE* ppProtectedKey) {
// Генерация начального значения
srand(time(NULL));
// 'b' используется как ключ алгоритма шифрования ключа
BYTE b = rand() % 0xFF;
// 'pKey' - это буфер для генерации исходного ключа
PBYTE pKey = (PBYTE)malloc(sKey);
// 'pProtectedKey' - это зашифрованная версия 'pKey', использующая 'b'
PBYTE pProtectedKey = (PBYTE)malloc(sKey);
if (!pKey || !pProtectedKey)
return;
// Генерация еще одного начального значения
srand(time(NULL) * 2);
// Ключ начинается с байта-подсказки
pKey[0] = HintByte;
// Генерация остальной части ключа
for (int i = 1; i < sKey; i++){
pKey[i] = (BYTE)rand() % 0xFF;
}
printf("[+] Сгенерированный байт ключа: 0x%0.2X \n\n", b);
printf("[+] Исходный ключ: ");
PrintHex(pKey, sKey);
// Шифрование ключа с использованием алгоритма XOR
// Используется 'b' в качестве ключа
for (int i = 0; i < sKey; i++){
pProtectedKey[i] = (BYTE)((pKey[i] + i) ^ b);
}
// Сохранение зашифрованного ключа через указатель
*ppProtectedKey = pProtectedKey;
// Освобождение буфера с исходным ключом
free(pKey);
}
Процесс дешифрования ключа
Поскольку ключ шифрования, используемый для шифрования ключа, не сохраняется нигде, функция дешифрования должна угадать значение b, показанное в функции GenerateProtectedKey. Для этого функция дешифрования будет выполнять операцию XOR с первым байтом ключа, который является байтом-подсказкой, с разными ключами до тех пор, пока полученный байт не станет равным байту-подсказке исходного ключа. Когда это происходит, функция узнает, что было выбрано правильное значение b. Фрагмент кода ниже демонстрирует эту логику.
C:
if (((EncryptedKey[0] ^ b) - 0) == HintByte)
// Тогда значение 'b' - это ключ шифрования XOR
else
// Тогда значение 'b' не является ключом шифрования XOR, попробуйте с другим значением 'b'
Продолжая пример, когда 71 становится равным BA, это означает, что правильное значение b было угадано.
Функция дешифрования ключа
Функция BruteForceDecryption требует тот же байт-подсказку, которая передавалась в функцию шифрования.
C:
/* - HintByte : тот же байт-подсказка, что и в функции генерации ключа
- pProtectedKey : зашифрованный ключ - sKey : размер ключа
- ppRealKey : указатель на буфер PBYTE, который получит расшифрованный ключ */
BYTE BruteForceDecryption(IN BYTE HintByte, IN PBYTE pProtectedKey, IN SIZE_T sKey, OUT PBYTE* ppRealKey) {
BYTE b = 0;
PBYTE pRealKey = (PBYTE)malloc(sKey);
if (!pRealKey)
return NULL;
while (1){
// Используя байт-подсказку, если он равен, то мы найдем значение 'b', необходимое для дешифрования ключа
if (((pProtectedKey[0] ^ b) - 0) == HintByte)
break;
// иначе увеличьте 'b' и попробуйте снова
else
b++;
}
// Обратный алгоритм XOR-шифрования, так как 'b' теперь известен
for (int i = 0; i < sKey; i++){
pRealKey[i] = (BYTE)((pProtectedKey[i] ^ b) - i);
}
// Сохранение расшифрованного ключа через указатель
*ppRealKey = pRealKey;
return b;
}
Заключение
Несмотря на свою простоту, подход с использованием "грубой силы" может быть использован для защиты ключей от анализа вирусными аналитиками и исследователями, которые пытаются извлечь ключ из бинарного файла. Это заставляет их отлаживать бинарный файл, чтобы понять, как генерируется ключ, что может быть полезным при использовании методов антианализа.
Удаление библиотеки CRT
Введение
До этого раздела все проекты кода компилировались с использованием опции "Выпуск" или "Отладка" в Visual Studio.
Для разработчиков вредоносных программ важно понимать разницу между опциями компиляции "Выпуск" и "Отладка" в Visual Studio, а также последствия изменения настроек компилятора по умолчанию. Изменение настроек компилятора Visual Studio может повлиять на созданный двоичный файл, такое как уменьшение его размера или снижение энтропии.
Опция "Выпуск" против "Отладка"
Конфигурации сборки "Выпуск" и "Отладка" определяют, как программа компилируется и выполняется, и каждая из них выполняет свою задачу и предоставляет разные возможности. Наиболее важные различия между этими двумя опциями приведены ниже.
Производительность - Опция сборки "Выпуск" быстрее, чем опция "Отладка". В режиме выпуска включены оптимизации компиляции, которые отключены в режиме "Отладка".
Отладка - Отладка приложений, созданных с использованием конфигурации сборки "Отладка", упрощается, потому что в этом режиме отключены оптимизации компиляции, что делает код более доступным для отладки. Кроме того, конфигурация "Отладка" генерирует файлы символов отладки (.pdb), которые содержат информацию о скомпилированном исходном коде. Это позволяет отладчикам отображать дополнительную информацию, такую как переменные, функции и номера строк.
Развертывание - Версия "Выпуск" приложения используется из-за увеличенной совместимости с их компьютерами, в отличие от версии "Отладка", которая обычно требует дополнительных динамических библиотек (DLL), доступных только в Visual Studio, что делает отладочные приложения совместимыми только с компьютерами, на которых установлена Visual Studio.
Обработка исключений - В конфигурации сборки "Отладка" Visual Studio может приостанавливать выполнение и отображать сообщение об ошибке в виде окна сообщения, когда возникает исключение, указывая имя переменной или номер строки, вызвавших повреждение стека, например. Такие исключения могут привести к зависанию программы, если она скомпилирована в режиме "Выпуск".
Настройки компилятора по умолчанию
Исходя из предыдущих аспектов, опция "Выпуск" предпочтительнее опции "Отладка". Однако опция "Выпуск" все равно имеет несколько проблем.
Совместимость - Некоторые приложения, использующие опцию "Выпуск", всё равно могут вызывать ошибки, аналогичные приведенной ниже, если целевой компьютер не имеет установленной Visual Studio.
Импортируемые функции CRT - В таблице импорта (IAT) присутствуют несколько неразрешенных функций, которые невозможно разрешить с использованием методов, таких как API-хэширование. Эти функции импортируются из библиотеки CRT, что будет объяснено позже. Пока достаточно понимать, что в любом приложении, созданном настройками компилятора по умолчанию Visual Studio, есть несколько неиспользуемых импортированных функций. В качестве примера, IAT программы 'Hello World' должна импортировать информацию только о функции printf, однако она импортирует следующие функции (вывод сокращен из-за размера).
Размер - Сгенерированные файлы часто больше, чем они должны быть из-за оптимизаций компилятора по умолчанию. Например, следующая программа "Hello World" имеет размер около 11 килобайт.
Информация для отладки - Использование опции "Выпуск" все равно может включать информацию, связанную с отладкой, и другие строки, которые могут быть использованы средствами безопасности для создания статических сигнатур. На изображениях ниже показан результат выполнения программы Strings.exe для программы "Hello World" (вывод сокращен из-за размера).
Библиотека CRT
Библиотека CRT, также известная как Microsoft C Run-Time Library, представляет собой набор низкоуровневых функций и макросов, обеспечивающих базовую функциональность для стандартных программ на C и C++. Она включает функции управления памятью (например, malloc, memset и free), манипулирования строками (например, strcpy и strlen) и функции ввода-вывода (например, printf, wprintf и scanf).
DLL-файлы библиотеки CRT названы vcruntimeXXX.dll, где XXX - это номер версии используемой библиотеки CRT. Также существуют DLL-файлы, такие как api-ms-win-crt-stdio-l1-1-0.dll, api-ms-win-crt-runtime-l1-1-0.dll и api-ms-win-crt-locale-l1-1-0.dll, которые также связаны с библиотекой CRT. Каждая из этих DLL выполняет конкретные функции и экспортирует несколько функций. Эти DLL-файлы связываются компилятором на этапе компиляции и, следовательно, находятся в таблице импорта (IAT) созданных программ.
Решение проблем совместимости
По умолчанию, при компиляции приложения в Visual Studio, опция Runtime Library установлена на "Multi-threaded DLL (/MD)". С этой опцией DLL-файлы библиотеки CRT связываются динамически, что означает их загрузку во время выполнения. Это вызывает проблемы совместимости, о которых упоминалось ранее. Для их решения установите опцию Runtime Library на "Multi-threaded (/MT)", как показано ниже.
Multi-threaded (/MT)
Компилятор Visual Studio может быть настроен на статическую связь функций CRT, выбрав опцию "Multi-threaded (/MT)". Это приводит к тому, что функции, такие как printf, будут представлены напрямую в созданной программе, а не импортируются из DLL-файлов библиотеки CRT. Обратите внимание, что это увеличит размер конечного бинарного файла и добавит больше функций WinAPI в таблицу импорта, но удалит DLL-файлы библиотеки CRT.
Использование опции "Multi-threaded (/MT)" для компиляции программы "Hello World" приводит к следующей таблице импорта.
Размер бинарного файла также значительно увеличивается, как показано ниже.
Библиотека CRT и отладка
После удаления библиотеки CRT программа может быть скомпилирована только в режиме "Выпуск". Это делает отладку кода более сложной. Поэтому рекомендуется удалять библиотеку CRT только после завершения отладки и разработки.
Дополнительные изменения компилятора
Предыдущие разделы демонстрировали, как статически связать библиотеку CRT. Однако идеальным решением было бы избегать зависимости от библиотеки CRT как статически, так и динамически, так как это может привести к уменьшению размера бинарного файла и удалению ненужных импортированных функций и отладочной информации. Для этого необходимо внести изменения в несколько опций компиляции Visual Studio.
Отключение исключений C++
Опция Enable C++ Exceptions используется для генерации кода, который правильно обрабатывает исключения, выбрасываемые кодом. Однако, поскольку библиотека CRT больше не связана, эта опция не требуется и должна быть отключена.
Отключение оптимизации всей программы
Опция Whole Program Optimization должна быть отключена, чтобы предотвратить выполнение компилятором оптимизаций, которые могут повлиять на стек. Отключение этой опции предоставляет полный контроль над скомпилированным кодом.
Отключение отладочной информации
Отключите опции Generate Debug Info и Generate Manifest, чтобы удалить добавленную отладочную информацию.
Игнорирование всех библиотек по умолчанию
Установите опцию Ignore All Default Libraries на "Yes (/NODEFAULTLIB)", чтобы исключить связывание системных библиотек по умолчанию компилятором с программой. Это приведет к исключению связывания библиотеки CRT, а также других библиотек. В этом случае пользователь должен предоставить необходимые функции, которые обычно предоставляются этими библиотеками по умолчанию. На изображении ниже показано установление опции "Yes (/NODEFAULTLIB)".
К сожалению, компиляция с этой опцией приводит к ошибкам, как показано ниже.
Установка символа точки входа
Первая ошибка "LNK2001 - unresolved external symbol mainCRTStartup" указывает на то, что компилятор не смог найти определение символа "mainCRTStartup". Это ожидаемо, так как "mainCRTStartup" является точкой входа для программы, связанной с библиотекой CRT, что не имеет места в данном случае. Чтобы решить эту проблему, следует установить новый символ точки входа, как показано ниже.
Символ "main" представляет собой функцию main в исходном коде. Для выбора другой функции в качестве точки входа просто установите символ точки входа на имя этой функции. Повторная компиляция приводит к меньшему количеству ошибок, как показано ниже.
Отключение проверки безопасности
Следующая ошибка, "LNK2001 - unresolved external symbol __security_check_cookie", означает, что компилятор не нашел символ "__security_check_cookie". Этот символ используется для выполнения проверки стековой куки, которая является функцией безопасности, предотвращающей переполнение буфера стека. Чтобы решить эту проблему, установите опцию Security Check на "Disable Security Check (/Gs-)", как показано ниже.
Отключение проверок SDL
После отключения проверки безопасности ошибка исчезает, но появляется новое предупреждение.
Предупреждение "D9025 - overriding '/sdl' with '/GS-'" можно решить, отключив проверки Security Development Lifecycle (SDL).
Остаются две неразрешенные ошибки символов, которые решаются в разделе Замена функций библиотеки CRT ниже.
Замена функций библиотеки CRT
Из-за удаления библиотеки CRT остались две неразрешенные ошибки из-за использования функции printf для вывода на консоль, хотя библиотека CRT была удалена из программы.
При удалении библиотеки CRT необходимо написать собственные версии функций, такие как printf, strlen, strcat, memcpy. Для этой цели можно использовать библиотеки, например,
Вы должны зарегистрироваться, чтобы увидеть внешние ссылки
. Например,
Вы должны зарегистрироваться, чтобы увидеть внешние ссылки
заменяет функцию strcmp для сравнения строк.Замена функции Printf
Для демонстрационной программы, используемой в этом разделе, функция printf заменяется следующей макроинструкцией.
C:
#define PRINTA( STR, ... ) \
if (1) { \
LPSTR buf = (LPSTR)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, 1024 ); \
if ( buf != NULL ) { \
int len = wsprintfA( buf, STR, __VA_ARGS__ ); \
WriteConsoleA( GetStdHandle( STD_OUTPUT_HANDLE ), buf, len, NULL, NULL ); \
HeapFree( GetProcessHeap(), 0, buf ); \
} \
}
Макроинструкция PRINTA принимает два аргумента:
- STR - Строка формата, которая определяет, как выводить результат.
- VA_ARGS или ... - Аргументы, которые нужно вывести. Макроинструкция PRINTA выделяет кучевой буфер размером 1024 байта, затем использует функцию wsprintfA для записи отформатированных данных из переменных аргументов (VA_ARGS) в буфер с использованием строки формата (STR). Затем используется функция WriteConsoleA WinAPI для записи полученной строки на консоль, которая получается через функцию GetStdHandle WinAPI.
C:
#include <Windows.h>
#include <stdio.h>
#define PRINTA( STR, ... ) \
if (1) { \
LPSTR buf = (LPSTR)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, 1024 ); \
if ( buf != NULL ) { \
int len = wsprintfA( buf, STR, __VA_ARGS__ ); \
WriteConsoleA( GetStdHandle( STD_OUTPUT_HANDLE ), buf, len, NULL, NULL ); \
HeapFree( GetProcessHeap(), 0, buf ); \
} \
}
int main() {
PRINTA("Hello World ! \n");
return 0;
}
Создание вредоносного ПО, независимого от библиотеки CRT
При создании вредоносного программного обеспечения, которое не использует библиотеку CRT, следует учесть несколько моментов.
Использование внутренних функций
Некоторые функции и макросы в Visual Studio используют функции CRT для выполнения своих задач. Например, макрос ZeroMemory использует функцию CRT memset для заполнения указанного буфера нулями. Это требует от разработчика поиска альтернативы этому макросу, так как его нельзя использовать. В этом случае может использоваться функция из
Вы должны зарегистрироваться, чтобы увидеть внешние ссылки
в качестве замены.Другим решением может быть ручное установление пользовательских версий функций, основанных на CRT, таких как memset. Это заставляет компилятор обрабатывать эту пользовательскую функцию вместо экспортированной версии CRT. Последовательно макросы, такие как ZeroMemory, также будут использовать эту пользовательскую функцию.
Для демонстрации этого можно указать пользовательскую версию функции memset компилятору следующим образом, используя ключевое слово intrinsic.
C:
#include <Windows.h>
// Ключевое слово `extern` задает функцию `memset` как внешнюю функцию.
extern void* __cdecl memset(void*, int, size_t);
// Макросы #pragma intrinsic(memset) и #pragma function(memset) - это инструкции компилятора, специфичные для Microsoft.
// Они заставляют компилятор генерировать код для функции memset с использованием встроенной интригирующей функции.
#pragma intrinsic(memset)
#pragma function(memset)
void* __cdecl memset(void* Destination, int Value, size_t Size) {
// логика, аналогичная memset
unsigned char* p = (unsigned char*)Destination;
while (Size > 0) {
*p = (unsigned char)Value;
p++;
Size--;
}
return Destination;
}
int main() {
PVOID pBuff = HeapAlloc(GetProcessHeap(), 0, 0x100);
if (pBuff == NULL)
return -1;
// это будет использовать нашу версию 'memset' вместо версии CRT Library
ZeroMemory(pBuff, 0x100);
HeapFree(GetProcessHeap(), 0, pBuff);
return 0;
}
Скрытие окна консоли
Вредоносное программное обеспечение не должно создавать окно консоли при выполнении, так как это вызывает подозрение и позволяет пользователю завершить программу, закрыв окно. Для предотвращения этого можно использовать функцию ShowWindow(NULL, SW_HIDE) в начале функции точки входа, хотя это требует времени (в миллисекундах) и может вызвать заметное мерцание.
Лучшим решением является установка программы для компиляции как GUI-программы, установив опцию SubSystem Visual Studio в "Windows (/SUBSYSTEM:WINDOWS)".
Демонстрация
После выполнения всех описанных в этом разделешагов, получаются следующие результаты.
Во-первых, размер исполняемого файла уменьшается с 112,5 килобайт до примерно 3 килобайт.
Затем в IAT не обнаружено неиспользуемых функций.
В бинарном файле обнаружено меньше строк без отладочной информации.
Наконец, удаление библиотеки CRT приводит к меньшему детекту.
Файл загружается на VirusTotal дважды: первый раз с использованием опции "Multi-threaded (/MT)", чтобы статически связать библиотеку CRT, а второй раз после полного удаления библиотеки CRT.
IAT Camouflage
Введение
Удаление библиотеки C Runtime из конечного бинарного файла позволяет очистить IAT от неиспользуемых функций WinAPI. Однако это может вызвать подозрение, если бинарный файл импортирует очень мало функций WinAPI, особенно если это совмещается с хэшированием API, что может даже привести к отсутствию импортированных функций.
Для разработчика вредоносных программ важно, чтобы реализация вредоносного ПО выглядела нормально. Иметь реализацию с фиктивным IAT более эффективно, чем отсутствие импортированных функций. Этот модуль подробно рассмотрит эту концепцию.
Давайте начнем с бинарного файла с именем IatCamouflage.exe, который не использует библиотеку CRT и был скомпилирован аналогично тому, как показанно выше.
C:
#include <Windows.h>
int main() {
// бесконечное ожидание
WaitForSingleObject((HANDLE)-1, INFINITE);
return 0;
}
Когда бинарный файл выполняется, Process Hacker выделит процесс розовым цветом и отобразит заметку, когда мышь наводится на процесс. Process Hacker предполагает, что бинарный файл упакован из-за отсутствия импорта в IAT.
Убедитесь, что IatCamouflage.exe импортирует одну функцию с помощью dumpbin.exe.
Манипуляция IAT
Манипуляция IAT может быть легко выполнена с использованием доброжелательных WinAPI, которые не изменяют поведение программы. Это можно сделать, вызывая WinAPI с параметрами NULL или используя WinAPI на фиктивных данных, которые не повлияют на программу. Кроме того, эти функции могут размещаться в if-условиях, которые никогда не будут выполняться.
Тем не менее, некоторые компиляторы могут изменять ход выполнения кода с использованием оптимизации "удаление мертвого кода". Это свойство оптимизации компилятора для удаления кода, который не влияет на работу программы.
Пример удаления мертвого кода
В следующем фрагменте кода вызываются несколько функций WinAPI внутри if-условия, которое никогда не будет выполнено.
C:
int z = 4;
// Невозможное if-условие, которое никогда не выполнится
if (z > 5) {
// Случайные доброжелательные WinAPI
unsigned __int64 i = MessageBoxA(NULL, NULL, NULL, NULL);
i = GetLastError();
i = SetCriticalSectionSpinCount(NULL, NULL);
i = GetWindowContextHelpId(NULL);
i = GetWindowLongPtrW(NULL, NULL);
i = RegisterClassW(NULL);
i = IsWindowVisible(NULL);
i = ConvertDefaultLocale(NULL);
i = MultiByteToWideChar(NULL, NULL, NULL, NULL, NULL, NULL);
i = IsDialogMessageW(NULL, NULL);
}
Если проект Visual Studio не имеет зависимости от библиотеки CRT и компилирует вышеуказанный код, то функции WinAPI не будут видны в IAT бинарного файла. Компилятор знает, что if-условие невозможно выполнить, и, следовательно, весь код if-условия не включается в скомпилированный бинарный файл, что приводит к тому, что функции WinAPI не будут в IAT бинарного файла. Существуют два способа решения этой проблемы:
- Отключение оптимизации кода.
- Обман компилятора, чтобы он думал, что этот код используется.
Для этого метода необходимо просто отключить опцию оптимизации Visual Studio, как показано на изображении ниже. Это отключит свойство оптимизации компилятора "удаление мертвого кода", что приведет к тому, что функции WinAPI будут видны в IAT. Однако отключение оптимизации на более крупных программах может негативно сказаться на производительности, поскольку компилятор больше не оптимизирует эффективность и скорость кода. Поэтому программа может потреблять больше памяти или работать медленнее.
Обман компилятора
Для этого метода необходимо использовать логику, чтобы обмануть компилятор и заставить его думать, что if-условие может быть допустимым. В приведенном ниже фрагменте кода используется логика, которая делает сложно определить компилятору, будет ли if-условие выполняться, что заставляет его включить эту логику в скомпилированный бинарный файл, даже если if-условие никогда не будет удовлетворено.
C:
// Генерация случайного seed во время компиляции
int RandomCompileTimeSeed(void)
{
return '0' * -40271
__TIME__[7] * 1
__TIME__[6] * 10
__TIME__[4] * 60
__TIME__[3] * 600
__TIME__[1] * 3600
__TIME__[0] * 36000;
}
// Функция-пустышка, делающая if-условие в 'IatCamouflage' интересным
PVOID Helper(PVOID *ppAddress) {
PVOID pAddress = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0xFF);
if (!pAddress)
return NULL;
// Установка первых 4 байтов в pAddress равными случайному числу (меньше 255)
*(int*)pAddress = RandomCompileTimeSeed() % 0xFF;
// Сохранение базового адреса по указателю и возврат его
*ppAddress = pAddress;
return pAddress;
}
// Функция, импортирующая WinAPI, но никогда их не использующая
VOID IatCamouflage() {
PVOID pAddress = NULL;
int* A = (int*)Helper(&pAddress);
// Невозможное if-условие, которое никогда не выполнится
if (*A > 350) {
// Несколько случайных функций WinAPI
unsigned __int64 i = MessageBoxA(NULL, NULL, NULL, NULL);
i = GetLastError();
i = SetCriticalSectionSpinCount(NULL, NULL);
i = GetWindowContextHelpId(NULL);
i = GetWindowLongPtrW(NULL, NULL);
i = RegisterClassW(NULL);
i = IsWindowVisible(NULL);
i = ConvertDefaultLocale(NULL);
i = MultiByteToWideChar(NULL, NULL, NULL, NULL, NULL, NULL);
i = IsDialogMessageW(NULL, NULL);
}
// Освобождение выделенного в 'Helper' буфера
HeapFree(GetProcessHeap(), 0, pAddress);
}
Ниже приведены несколько моментов, чтобы облегчить понимание этого фрагмента кода.
Функция RandomCompileTimeSeed используется для генерации случайного значения во время компиляции с использованием макроса TIME.
Функция Helper выделяет кучу и устанавливает первые 4 байта равными значению RandomCompileTimeSeed() % 0xFF, что ограничивает значение числа, чтобы оно было меньше 0xFF (в шестнадцатеричной системе) или 255 (в десятичной системе).
Функция IatCamouflage содержит переменную A, которая является указателем на целое число и устанавливается равной первым четырем байтам буфера, возвращенного функцией Helper.
Поскольку функция Helper всегда будет возвращать значение меньше 255, if-условие if (*A > 350) всегда будет ложным. Здесь интересно то, что компилятор не знает об этом и, следовательно, включит эту логику в скомпилированный бинарный файл.
Результаты
Скомпилируйте приведенный выше фрагмент кода и проверьте IAT бинарного файла. Как и ожидалось, доброжелательные функции WinAPI внутри if-условия теперь видны.
Эти импортированные функции достаточно, чтобы сделать бинарный файл незаразным при статическом анализе. С другой стороны, вредоносные функции WinAPI должны быть удалены из IAT с использованием хэширования API.
Вложения
Последнее редактирование: