• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Разработка вредоносного ПО. Часть 6 - расширенная обфускация с помощью LLVM и метапрограммирование шаблонов

yashechka

Генератор контента.Фанат Ильфака и Рикардо Нарвахи
Эксперт
Регистрация
24.11.2012
Сообщения
2 344
Реакции
3 563
Введение

Это шестой пост в серии, посвященной разработке вредоносного ПО. В этой серии статей мы исследуем и попытаемся реализовать несколько методов, используемых вредоносными приложениями для выполнения кода, укрытия от защиты и персистентности.

Сегодня мы рассмотрим анти-дизассемблерную обфускацию с использованием LLVM и метапрограммирование шаблонов.

Обфускация LLVM

LLVM - это инфраструктура компилятора. Чтобы понять, что это такое, нам нужно погрузиться в процесс компиляции (это наиболее точно для неуправляемого кода, такого как C/C++).

Мы можем выделить три этапа создания сборки из исходного кода:

1. Фронт-энд, в который входят:

-сканер, который выполняет лексический анализ кода и производит токены (строки с определенным значением)

-парсер, который создает абстрактное синтаксическое дерево (токены, сгруппированные в дерево, которое представляет фактический алгоритм, реализованный в исходном коде)

-семантический анализ (в основном проверка типов), во время которого AST проверяется на наличие ошибок, таких как неправильное использование типов или использование переменных перед инициализацией

- генерация промежуточного представления, обычно на основе AST

2. Оптимизация, которая направлена на снижение сложности кода, например, путем предварительного расчета.

Оптимизация не должна изменять сам алгоритм/программу.

3. Серверная часть, которая переводит промежуточное представление в ожидаемый результат (ассемблер или байт-код).

Ядром LLVM является оптимизатор, но проект также включает интерфейс компилятора - clang - который предназначен для использования с цепочкой инструментов LLVM.

Обфускатор-LLVM

Мы будем использовать проект Obfuscator-LLVM, который является форком LLVM с открытым исходным кодом.

Обфускация работает на упомянутом промежуточном уровне представления (IR). Другими словами, это своего рода «антиоптимизация». Clang используется для генерации IR из исходного кода, затем IR обрабатывается, чтобы скрыть поток кода, и, наконец, создается ассемблерный код.

Настройка

Пройдя теоретическое введение, давайте подготовим среду для обфускации кода C++. Необходимо загрузить и скомпилировать Obfuscator-LLVM. Последняя ветка - llvm-4.0 (с 2017 года последняя версия LLVM в настоящее время - 11.0), и код необходимо компилировать с помощью Visual Studio 2017, а не 2019 (так как при компиляции возникают некоторые ошибки). Нам нужно использовать CMake для создания проекта VS2017, а затем его скомпилировать (учитывая целевую архитектуру). Мы можем использовать командную строку разработчика для VS 2017, которая является частью Visual Studio 2017:

Код:
git clone -b llvm-4.0 https://github.com/obfuscator-llvm/obfuscator
cd obfuscator
mkdir build
cd build
cmake -G "Visual Studio 15 2017 Win64" ..

Примечание: мне пришлось вручную определить ENDIAN_LITTLE, чтобы избавиться от некоторых ошибок компиляции.

Есть разные способы использования компилятора Obfuscator-LLVM:

- использовать вручную через командную строку

- добавить компилятор в качестве настраиваемого инструмента ассемблера для .cpp и других файлов в Visual Studio (в соответствующем файле Property Pages)

- используйте VS Installer для установки набора инструментов платформы clang-cl и вручную замените версию clang Visual Studio на скомпилированный компилятор (это вроде как проблема курица-яйцо :)) https://en.wikipedia.org/wiki/Bootstrapping_(compilers)


Использование и особенности

Напишем простую программу, которая выполняет довольно простые вычисления на основе псевдослучайного значения:

C++:
int main()
{
    int a = GetTickCount64();
    int b = a % 10;
    int c = 0;
    for (int i = 0; i < b; i++)
    {
        c += a % i;
    }
    return c;
}

Примечание. Я скомпилировал этот код без зависимости от CRT, поэтому двоичный файл имеет небольшой размер и не требует дополнительного кода (например, mainCRTStartup и т.д.)

Вот как выглядит код после декомпиляции с помощью Ghidra:

26.png


И граф программы:

27.png


Obfuscator-LLVM имеет 3 функции обфускации кода: подстановка инструкций, фиктивный поток управления и сглаживание потока управления. Давайте изучим их. Подробности можно найти в репозитории проекта. https://github.com/obfuscator-llvm/obfuscator/wiki/Features

Эти функции используют случайное значение, которое должно быть указано в качестве параметра командной строки (-mllvm -aesSeed = 1234567890ABCDEF1234567890ABCDEF) в системах Windows (в Linux используется / dev / random).

Замена инструкций

Это заменяет простые арифметические операции более сложными, но эквивалентными. Например: a = b + c можно заменить на r = rand(); а = Ь + г; а = а + с; а = а - г ;. Случайное значение рассчитывается во время компиляции.

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

Давайте добавим следующие ключи в командную строку компиляции: -mllvm -sub -mllvm -sub_loop = 5 -mllvm -aesSeed = 1234567890ABCDEF1234567890ABCDEF

Полученный ассемблерный код (декомпилированный):

28.png


И его граф:

29.png


Обратите внимание, что декомпилятор Ghidra довольно хорошо справился с "деоптимизацией" обфускатора.

Поддельный поток управления

Это добавляет непрозрачные предикаты перед блоками инструкций. Непрозрачный предикат - это в основном часть (предпочтительно случайного) кода, которая оценивается во время выполнения до заранее определенного логического значения (истина или ложь). За ним следует условный переход, который указывает на исходный блок инструкций.

Эта обфускация также может применяться несколько раз и может нацеливаться на случайные блоки кода.

Пример использования: -mllvm -bcf -mllvm -bcf_prob = 100 -mllvm -bcf_loop = 1 -mllvm -aesSeed = 1234567890ABCDEF1234567890ABCDEF

Полученный ассемблерный код:

30.png


И его граф:

31.png


Сглаживание потока управления

Это нарушает последовательность блоков инструкций, помещая их на один уровень в зацикленном операторе switch. Определены дополнительные переменные, которые фактически контролируют порядок выполнения. Смотри диаграмму ниже - она должна прояснить ситуацию:

32.png


Эту обфускацию также можно применить несколько раз к одному блоку.

Пример использования: -mllvm -fla -mllvm -split -mllvm -aesSeed = 1234567890ABCDEF1234567890ABCDEF

Полученный ассемблерный код (декомпилированный):

33.png


И его граф:

34.png


Тестирование

Теперь давайте скомпилируем и обфусцируем несколько простых вредоносных программ. Помните простейший инжектор шелл-кода из первой части серии? Обфускация LLVM не поможет, потому что наиболее очевидные индикаторы (шелл-код и импорт) останутся неизменными.

Вот почему мы протестируем другой код - например, эту классическую обратную оболочку https://github.com/sh3llc0d3r1337/windows_reverse_shell_1/blob/master/ReverseShell.cpp. На самом деле здесь используется тот же метод, что и в шеллкоде shell_reverse_tcp (создать IP-сокет и создать процесс cmd со стандартными потоками, подключенными к сокету).

Интересно, что при загрузке скомпилированных двоичных файлов в VirusTotal было обнаружено только одно обнаружение кода, скомпилированного без обфускации, и 6 обнаружений при применении нескольких методов обфускации.

Заключение

Obfuscator-LLVM - отличный ресурс для изучения и понимания того, что на самом деле происходит во время компиляции кода и как можно изменить этот процесс, чтобы сделать статический анализ ассемблерного кода более сложным и трудоемким. Однако важно помнить, что обфускация на уровне IR может быть отменена (не полностью, но все же).Смотри Эту замечательную статью для примера процесса деобфускации https://blog.quarkslab.com/deobfuscation-recovering-an-ollvm-protected-program.html .

Вот некоторые общие мысли и соображения: С точки зрения пентестера важно объединить несколько уровней мер защиты кода, чтобы минимизировать шансы обнаружения и максимально затруднить ручной анализ (ну, при разумном объеме наших усилий). Это помогает обеспечить эффективную эмуляцию противника, ориентированную на реальные цели. Конечно, более продвинутое вредоносное ПО требует большей работы со стороны защитных групп, что тоже хорошо.

В любом случае, не забудьте рассмотреть возможность внедрения некоторой обфускации на промежуточном уровне представления в процесс ассемблирования наступательного инструментария.

Другие обфускаторы на основе LLVM

Также не забудьте проверить другие обфускаторы на основе LLVM и статьи о создании пользовательских обфускаторов с помощью LLVM:

https://github.com/HikariObfuscator/Hikari/

https://medium.com/@polarply/build-your-first-llvm-obfuscator-80d16583392b

http://www.babush.me/dumbo-llvm-based-dumb-obfuscator.html

https://github.com/emc2314/YANSOllvm

https://blog.scrt.ch/2020/06/19/engineering-antivirus-evasion/

https://blog.scrt.ch/2020/07/15/engineering-antivirus-evasion-part-ii/


Метапрограммирование шаблона

Прежде чем углубляться в детали конструкций C++, таких как шаблоны, константные выражения и метапрограммирование, давайте рассмотрим простой случай: у нас есть исходный код с некоторыми строковыми литералами (например, IP-адресами, доменными именами и т.д.), которые необходимо скрыть, чтобы они были невидимы в ассемблерном коде и раскрывались только во время выполнения. Здесь проще всего зашифровать эти литералы и заменить их вызовом процедуры дешифрования, например:

C++:
const char* address = "www.example.com";

заменить на:

C++:
char* Decrypt(const char* data);
(...)
char* addr = Decrypt("xxx.yyyyyyy.zzz");

Конечно, нам придется учитывать длину строки, терминаторы нулевого байта и т.д.

Мы бы предпочли использовать значения открытого текста в исходном коде и автоматически скрывать/шифровать их в процессе ассемблирования. Замену простых строк на зашифрованные можно автоматизировать с помощью задачи предварительного ассемблирования, например какой-нибудь скрипт на Python. Но есть другой, более крутой способ сделать это.

Введение

Давайте познакомимся с некоторыми функциями, представленными в стандарте C++ 11: шаблонами и выражениями. Следующее не охватывает всех деталей концепций метапрограммирования - это всего лишь простое введение, которое поможет понять, как на самом деле работает обфускация на основе метапрограммирования шаблонов.

Шаблоны

Шаблоны - это функции, которые работают с универсальными типами. Шаблоны позволяют просто создавать функции, которые работают с несколькими типами (базовыми типами, структурами, классами). Например, мы можем использовать следующий шаблон:

C++:
template <typename T>
bool Equal(T arg1, T arg2)
{
    return (arg1 == arg2)
}

вместо определения перегруженных функций:

C++:
bool Equal(int arg1, int arg2);
bool Equal(double arg1, double arg2);

И пример использования шаблона:

C++:
Equal <int>(1, 2));

Конечно, типы должны реализовывать оператор ==, чтобы использовать шаблон функции Equal.

Шаблоны также можно использовать для создания общей структуры или класса, которые затем могут быть созданы для использования с определенным типом:

C++:
template <typename T>
struct Stack
{
    void push(T* object);
    T* pop();
};

Stack<Fruit> fruitStack;
Stack<Vegetable> vegetableStack;

Это также обеспечивает безопасность типов, в этом случае вы не сможете смешивать фрукты с овощами - fruitStack.push (new Vegetable ()); выдаст ошибку компиляции.

Рассмотрим другой пример - использование шаблона для рекурсивного факториального вычисления:

C++:
template <int N>
struct Factorial
{
    enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0>
{
    enum { value = 1 };
};

Factorial<5>::value // 5! = 120

Здесь мы видим, что целое число может быть аргументом шаблона и что специализация шаблона (template <>) необходима для определения значения для конкретного аргумента.

Постоянные выражения

Спецификатор constexpr указывает, что значение некоторого выражения может быть оценено во время компиляции. Например, когда такое постоянное выражение определено:

C++:
constexpr int sum(int a, int b)
{
    return (a + b);
}

Sun(1 + 2) будет предварительно вычислена во время компиляции - это вычисление не потребляет ресурсы во время выполнения приложения.

Метапрограммирование

Метапрограммирование - это просто изменение программ другими программами или самими собой. Оказывается, шаблоны являются своего рода функциональным языком программирования и могут использоваться компилятором для генерации исходного кода.

Помните? Это именно то, что мы делали со сценариями предварительной сборки - создавали временный исходный код с обфусцированными конфиденциальными данными.

Обфускация строк

Поняв способность писать код, который может выполняться компиляторами, давайте создадим простой обфускатор строк, который заменит данные в виде открытого текста значениями, обработанными XOR, непосредственно перед компиляцией. Мы хотели бы использовать обфускацию следующим образом: Obfuscated ("secret") ;. Обфусцированный макрос должен заменить "secret" на функцию дешифрования с зашифрованным аргументом: Decrypt_runtime(Encrypt_compiletime (secret)).

Чтобы использовать постоянную строку во время компиляции, нам нужно знать ее точную длину. Поэтому нам понадобится функция времени компиляции, которая работает с этим значением длины. Итак, сначала нам нужно создать шаблон, который получит целое число в качестве аргумента: template <unsigned int N>.

Теперь мы создадим структуру, которая содержит обфусцированную строку (которая заменит открытый текст в исходном коде) и имеет функцию времени компиляции (constexpr) в качестве конструктора для обфускации открытого текста:

C++:
struct Obfuscator
{
    char data[N] = { 0 };
    constexpr Obfuscator(const char* plaintext)
    {
        for (int i = 0; i < N; i++)
        {
            data[i] = plaintext[i] ^ 0x00;
        }
    }
}

Теперь мы обфусцируем данные в исходном коде, создавая структуру Obfuscator<7> из шаблона Obfuscator<N> (7 = длина строки + нулевой байт):

C++:
constexpr Obfuscator<7> obfuscated = Obfuscator<7>("secret");

Чтобы фактически использовать данные в приложении, нам нужно их расшифровать, поэтому мы добавляем функцию деобфускации (которая работает с постоянным значением, следовательно, с идентификатором const после его объявления) в шаблон Obfuscator:

C++:
const char* Deobfuscate() const
{
    char plaintext[N] = { 0 };
    for (int i = 0; i < N; i++)
    {
        plaintext[i] = data[i] ^ 0x11;
    }
    return plaintext;
}

Теперь мы можем деобфускировать постоянную переменную obfuscated : obfuscated.Deobfuscate ().

Последнее, что нужно сделать, - это создать вспомогательный макрос, который упрощает обфускацию в исходном коде. Воспользуемся еще одним плюсом C++ 11 - лямбда-функциями:

C++:
#define Obfuscated(string) []() -> const char* \
{ \
    constexpr auto secret = Obfuscator<sizeof(string) / sizeof(string[0])>(string); \
    return secret.Deobfuscate(); \
}()

Благодаря этому строковые литералы, появляющиеся в двоичном файле, зашифрованы XOR. Можно улучшить этот метод, чтобы приложение создавало строки на основе стека, которые не отображаются в разделе .text PE-файла.

Другие возможности

С помощью метапрограммирования шаблонов можно реализовать довольно продвинутую обфускацию строк и кода. Для более подробного объяснения смотри этот замечательный воркпепер Себастьяна Андриве. https://www.blackhat.com/docs/eu-14...amming-Applied-To-software-Obfuscation-wp.pdf и его инструмент ADVobfuscator https://github.com/andrivet/ADVobfuscator , который реализует описанные концепции. Доступно несколько таких обфускаторов, и самое лучшее в них то, что мы можем использовать их, просто добавляя файлы заголовков в проект:

https://github.com/fritzone/obfy

https://github.com/revsic/cpp-obfuscator


Резюме

Этот пост был просто введением в продвинутые и мощные методы обфускации, которые используют инфраструктуру компилятора LLVM и метапрограммирование шаблонов.

В следующий раз мы поговорим о кейлоггерах и реализуем один из них.

Переведено специально для xss.pro
Автор перевода: yashechka
Источник: https://0xpat.github.io/Malware_development_part_6
 
Последнее редактирование:


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх