Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
zyrox — Zyrox: плагин-обфускатор на основе LLVM, действующий на этапе компиляции. | Kitploit
Инструменты/GitHubGitHub/peterhackz/zyrox
Статический анализАнализ КодаОбратная инженерияАнализ Бинарных ФайловСтатьи и ИсследованияОбучение и ОбразованиеУчебные Маршруты и Курсы
GitHubpeterhackz/zyrox

zyrox

Zyrox: плагин-обфускатор на основе LLVM, действующий на этапе компиляции.

Репозиторий
941146 месяцев назадПроверено Kitploit
Сайт

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

Zyrox LLVM Obfuscator

плагин LLVM для обфускации нативного кода на этапе компиляции и компоновки

Зачем

почему бы и нет ¯\_(ツ)_/¯

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

Я считаю, что лучший способ учиться — это создавать, поэтому я создал этот проект, чтобы узнать больше об этих темах.

Исследование

Я написал 4 блога, объясняющих концепции, лежащие в основе Zyrox:

  • Часть I: Создание Zyrox: собственный обфускатор LLVM
  • Часть II: Флаттенинг потока управления
  • Часть III: Зашифрованные таблицы переходов
  • Часть IV: Финал

Эти части раскрывают тему глубже, чем этот README, и их определённо стоит прочитать, если вам интересна тема.

Сборка

Из шаблона (быстрый старт, рекомендуется)

Это предназначено для тех, кто хочет быстро протестировать Zyrox или научиться интегрировать его в CMake-проект.

Выполните шаги из репозитория Zyrox Template.

Из исходников

установите llvm:

root@kitploit:~
sudo apt update
sudo apt install llvm-18 llvm-18-dev clang-18

склонируйте и соберите zyrox:

root@kitploit:~
git clone --recurse-submodules https://github.com/PeterHackz/zyrox.git
cd zyrox
cmake -S . -B build -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++
cmake --build build --parallel 4

Настройка Python (пост-компиляционного) плагина

убедитесь, что установлены python3 и pip.

Настройка окружения (рекомендуется)

root@kitploit:~
# Create a virtual environment
python3 -m venv .venv

# Activate the env
source .venv/bin/activate

pip install -r requirements.txt

Глобальная установка

root@kitploit:~
pip install -r requirements.txt

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

Быстрое использование

root@kitploit:~
clang -O0 -flto=full -c main.c -o out/main.o
clang -flto=full -fuse-ld=lld -Wl,--load-pass-plugin=./build/libzyrox.so out/main.o -o out/main

После обфускации запустите PyPlugin.py, чтобы зашифровать таблицы переходов:

root@kitploit:~
# if you installed dependencies in a virtual environment, activate it first:
source .venv/bin/activate
#  then run with:
python PyPlugin.py --in=<input_file> [--out=<output_file>] [--tables=<zyrox_tables_file>] [--android]

С CMake

Смотрите репозиторий Zyrox Template, где приведён пример интеграции с CMake.

Контакты

Я понимаю, что это сложная тема, и этот проект в основном создавался в образовательных целях, а также для BSD Brawl. Если у вас есть вопросы или просто хотите пообщаться, смело обращайтесь ко мне:

  • Discord: @s.b
  • Email: [email protected] или [email protected]
  • Discord Server

Любая помощь в виде pull request'ов или issues приветствуется!

Как это работает

ZyroxPlugin.cpp регистрирует проход, затем подключает siphash (подробнее об этом позже) и вызывает StringEncryption для шифрования строк.

Причина, по которой мы шифруем строки на раннем этапе, в том, чтобы логика дешифрования тоже была обфусцирована позже.

Затем вызываются ModuleUtils::ExpandCustomAnnotations и QuickConfig::RegisterPasses для разбора всех выражений __attribute__((annotate("..."))) и запуска QuickJs-конфигурации (расположенной в ZyroxConfig.js)

Каждая функция обфусцируется вызовом Zyrox::RunOnFunction из ZyroxCore.cpp; в будущем будет добавлено больше документации по этому вопросу.

Дополнительные утилиты

switch создают таблицы переходов, а PHI-узлы раздражающе сложны в обработке, поэтому мы используем FunctionUtils и BasicBlockUtils, чтобы соответственно флаттенить (преобразовывать в if-выражения) и понижать их.

Проходы

ох, с чего бы начать

  • Basic Block Splitter
  • Control Flow Flattening
  • Indirect Branching
  • Simple Indirect Branching
  • Mixed Boolean Arithmetic

все аргументы js-плагина описаны в index.d.ts, поэтому в этой документации они не рассматриваются.

документацию по аннотациям см. здесь

Basic Block Splitter

Этот проход разбивает базовый блок на несколько меньших и перемешивает их. Предположим, у нас есть такой код:

root@kitploit:~
int __test_fn(int x)
{
    if (x == 2) {
        printf("x is 2\n");
    } else {
        printf("x is not 2!, x is: %d\n", x);
    }
    return x + 4 * x - 2 / 4;
}

который компилируется в:

root@kitploit:~
define internal i32 @__test_fn(i32 noundef %0) #0 !zyrox !8 !obfuscated !11 {
  %2 = alloca i32, align 4
  store i32 %0, ptr %2, align 4
  %3 = load i32, ptr %2, align 4
  %4 = icmp eq i32 %3, 2
  br i1 %4, label %5, label %7

5:                                                ; preds = %1
  %6 = call i32 (ptr, ...) @printf(ptr noundef @.str.1)
  br label %10

7:                                                ; preds = %1
  %8 = load i32, ptr %2, align 4
  %9 = call i32 (ptr, ...) @printf(ptr noundef @.str.2, i32 noundef %8)
  br label %10

10:                                               ; preds = %7, %5
  %11 = load i32, ptr %2, align 4
  %12 = load i32, ptr %2, align 4
  %13 = mul nsw i32 4, %12
  %14 = add nsw i32 %11, %13
  %15 = sub nsw i32 %14, 0
  ret i32 %15
}

при использовании Basic Block Splitter с такой конфигурацией:

root@kitploit:~
z.RegisterPass(ObfuscationType.BasicBlockSplitter, {
    PassIterations: 1,
    "BasicBlockSplitter.SplitBlockChance": 100,
    "BasicBlockSplitter.SplitBlockMinSize": 2,
    "BasicBlockSplitter.SplitBlockMaxSize": 5,
});

он становится таким:

root@kitploit:~
define internal i32 @__test_fn(i32 noundef %0) #0 !zyrox !8 !obfuscated !11 {
  %2 = alloca i32, align 4
  store i32 %0, ptr %2, align 4
  %3 = load i32, ptr %2, align 4
  %4 = icmp eq i32 %3, 2
  br i1 %4, label %5, label %14

5:                                                ; preds = %1
  %6 = call i32 (ptr, ...) @printf(ptr noundef @.str.1)
  br label %7

7:                                                ; preds = %14, %5
  %8 = load i32, ptr %2, align 4
  %9 = load i32, ptr %2, align 4
  %10 = mul nsw i32 4, %9
  %11 = add nsw i32 %8, %10
  br label %12

12:                                               ; preds = %7
  %13 = sub nsw i32 %11, 0
  ret i32 %13

14:                                               ; preds = %1
  %15 = load i32, ptr %2, align 4
  %16 = call i32 (ptr, ...) @printf(ptr noundef @.str.2, i32 noundef %15)
  br label %7
}

Для такой маленькой функции разница не будет слишком заметной, но обратите внимание, как разбивается базовый блок. Это полезно в сочетании с другими проходами, такими как Control Flow Flattening

Control Flow Flattening

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

root@kitploit:~
LABEL_A: bool b = x == 2;
         IF EQ: goto LABEL_B
         goto LABEL_C
LABEL_B  do_stuff()
LABEL_C  do_other_stuff()
         goto LABEL_A

каждому базовому блоку (A, B и C) назначается уникальное состояние диспетчера, например: (упрощённо)

root@kitploit:~
states = {
    1: LABEL_A,
    2: LABEL_B,
    3: LABEL_C,
};

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

root@kitploit:~
         int state = 0;
LABEL_D  goto LABEL_CA // dispatcher label jumps to first condition block, label condition A
LABEL_CA if state == 1: goto LABEL_A
         // if not 1, go to check if it is label B (fallback)
LABEL_CB if state == 2: goto LABEL_B
LABEL_CC if state == 3: goto LABEL_CC
         // unreachable
         goto LABEL_D
LABEL_A: bool b = x == 2;
         // IF EQ: goto LABEL_B
         // goto LABEL_C
         state = 2 if b else 3 // update state for the block we want and back to dispatcher
         goto LABEL_D
LABEL_B  do_stuff()
LABEL_C  do_other_stuff()
         state = 1
         goto LABEL_D

У этого подхода есть недостатки, которые исправляет обфускатор. Как видите, поскольку у нас одна переменная-диспетчер, это легко деобфусцировать: мы знаем, куда перейдёт блок после установки состояния. Исправить легко!

root@kitploit:~
z.RegisterPass(ObfuscationType.ControlFlowFlattening, {
    PassIterations: 1,
    "ControlFlowFlattening.UseFunctionResolverChance": 60,
    "ControlFlowFlattening.UseGlobalStateVariablesChance": 60,
    "ControlFlowFlattening.UseOpaqueTransformationChance": 40,
    "ControlFlowFlattening.UseGlobalVariableOpaquesChance": 80,
    "ControlFlowFlattening.UseSipHashedStateChance": 40,
    "ControlFlowFlattening.CloneSipHashChance": 80,
});

давайте разберём параметры по одному:

  • UseFunctionResolverChance: внедряет функцию для проверки состояния, так что вместо if (state == expected_state) выполняется if (injected_resolver(state)). пример:
    root@kitploit:~
    bool __fastcall cff_resolve_state_check_3585(__int64 a1)
    {
        return a1 == 0x288A6154F8A5E3E2LL;
    }
    
  • UseGlobalStateVariablesChance: сохраняет значение состояния для сравнения в глобальной переменной:
    root@kitploit:~
    bool __fastcall cff_resolve_state_check_506(__int64 a1)
    {
        return a1 == qword_1B20D8;
    }
    
  • UseOpaqueTransformationChance: обфусцирует проверку до некоторого преобразования, которое вернёт true только для конкретного состояния:
    root@kitploit:~
    bool __fastcall cff_resolve_state_check_7901(__int64 a1)
    {
        return ((((a1 ^ 0xEA9E45BB6099BC6ELL) + qword_1C64D8) << qword_1A63F0)
                   | (((a1 ^ 0xEA9E45BB6099BC6ELL)
                       + qword_1C64D8) >> qword_1ACE98)) == qword_1B0B80;
    }
    
  • UseGlobalVariableOpaquesChance: использует глобальную переменную вместо числа при выполнении UseOpaqueTransformationChance, как вы заметили в примере выше. (qword_1C64D8, qword_1A63F0, qword_1ACE98)
  • UseSipHashedStateChance: использует небольшую модифицированную функцию siphash для проверки состояния. Таким образом, if (state == 23872) превращается во что-то вроде if (siphash(state) == 3874872081), что затрудняет определение того, куда переходит блок. Блок будет выполнять state = 23872, когда условие перехода захэшировано. Каждый вызов siphash использует случайные значения, чтобы его было сложнее эмулировать.
  • CloneSipHashChance: клонирует и, когда возможно, даже инлайнит функцию siphash, создавая для неё несколько родственных копий, из-за чего перехвата одной функции становится недостаточно. Этот параметр очень желательно использовать, так как он лишь увеличивает размер бинарника и не влияет на производительность.

Indirect Branching

Предположим, у нас есть такой код:

root@kitploit:~
if (x == 2) goto LABEL_A
            goto LABEL_B
LABEL_A:    do_stuff()
LABEL_B:    // ...

он преобразуется в:

root@kitploit:~
@global jump_table = {0, &LABEL_A, &LABEL_B};
if (x == 2) goto jump_table[0] + @inline(decrypt(jump_table[1]));
            goto jump_table[0] + @inline(decrypt(jump_table[2]));
// ...

при использовании этого прохода плагин выведет файл zyrox_tables.txt, который затем используется PyPlugin.py.

PyPlugin.py зашифрует таблицы переходов и пропатчит записи релокаций, а затем заставит релокатор указывать на jump_table[0] каждой таблицы. Релокатор по сути делает следующее: target.writePointer(base.add(value)), поэтому, установив value в 0, мы заставляем релокатор выдать нам базовый адрес и поместить его в таблицу переходов во время выполнения, а мы используем его вместе с goto для генерации адреса во время выполнения. В thumb-режиме arm32 проход автоматически добавляет | 1 после дешифрования.

Чтобы использовать PyPlugin.py, просто сделайте следующее:

(если используете venv, сначала активируйте его)

root@kitploit:~
python3 PyPlugin.py --in <out_obfuscated_file> --android

Передача --android важна, если вы целитесь в версию arm64, поскольку у версии x86_64 другая сигнатура релокатора.

Также можно передать --out (по умолчанию будет использоваться тот же файл, что передан в --in) и --tables (по умолчанию это zyrox_tables.txt)

Simple Indirect Branching

Хотя indirect branching выглядит отлично, он также создаёт нагрузку на производительность, поскольку указатели расшифровываются во время выполнения. Это простая версия, которая не влияет на производительность; например, этот код:

root@kitploit:~
if (x == 2) goto LABEL_A
            goto LABEL_B
LABEL_A:    do_stuff()
LABEL_B:    // ...

становится:

root@kitploit:~
            @stack jump_table = {&LABEL_B, &LABEL_A}
            goto jump_table[!(x == 2)]
LABEL_A:    do_stuff()
LABEL_B:    // ...

Хотя это выглядит просто и легко ломается (согласен), этого достаточно, чтобы сломать IDA и Ghidra, не влияя на производительность.

Mixed Boolean Arithmetic

также известен как MBA Sub (Mixed Boolean Arithmetic Substitution); преобразует простые операции в сложные, дающие тот же результат. Использует предопределённый набор. пример:

root@kitploit:~
a ^ b = (~a & b) | (a & ~b)
b * c = (((b | c) * (b & c)) + ((b & ~c) * (c &  ~b)))
r = rand(); c = b + r; a = a + c; a = a - r

Полный список можно посмотреть в Passes/MBASub.cpp, если интересно.

Zyrox Annotations

просто посмотрите index.d.ts. Парсер аннотаций использует тот же порядок. Чтобы пометить функцию, просто сделайте следующее:

root@kitploit:~
__attribute__((annotate("ibr:1,100"))) void hello_world () {
    some_hello ();
}

коды аннотаций:

  • Basic Block Splitter: bbs
  • Control Flow Flattening: cff
  • Indirect Branching: ibr
  • Simple Indirect Branching: sibr
  • Mixed Boolean Arithmetic: mba

например, в index.d.ts мы видим:

root@kitploit:~
{
    "BasicBlockSplitter.SplitBlockMinSize"?: number;
    "BasicBlockSplitter.SplitBlockMaxSize"?: number;
    "BasicBlockSplitter.SplitBlockChance"?: number;
};

Теперь важный момент: первый аргумент, общий для всех проходов, — это PassIterations, поэтому он будет первым аргументом в аннотациях. Чтобы пометить что-то с помощью bbs, мы пишем:

root@kitploit:~
__attribute__((annotate("bbs:1,15,30,100"))) void hello_world () {
    some_hello ();
}

это означает: запустить Basic Block Splitter для hello_world 1 раз с минимальным размером = 15, максимальным размером = 30 и вероятностью = 100.

можно также комбинировать проходы:

root@kitploit:~
__attribute__((annotate("bbs:1,15,30,100 ibr:1,100 sibr:1,100"))) void hello_world () {
    some_hello ();
}

это означает: запустить Basic Block Splitter затем Indirect Branching и затем Simple Indirect Branching для hello_world. Они будут выполняться в порядке определения слева направо.

Скачать инструмент