Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
zyrox — Zyrox: LLVM based, compile-time obfuscator plugin. | Kitploit
Tools/GitHubGitHub/peterhackz/zyrox
Static AnalysisCode AnalysisReverse EngineeringBinary AnalysisPapers & ResearchLearning & EducationLearning Paths & Courses
GitHubpeterhackz/zyrox

zyrox

Zyrox: LLVM based, compile-time obfuscator plugin.

View Repository
9411167 months agoReviewed by Kitploit

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Website
Share

Zyrox LLVM Obfuscator

llvm compile and link-time plugin for obfuscating native code

Why

why not ¯\_(ツ)_/¯

One of my biggest projects, where I learned a lot about LLVM internals, binary formats, assembly and obfuscation techniques.

I believe that learning through building is the best way to learn, thus I built this project to learn more about these topics.

Research

I have wrote 4 blogs explaining the concepts behind Zyrox:

  • Part I: Building Zyrox: A Custom LLVM Obfuscator
  • Part II: Control Flow Flattening
  • Part III: Encrypted Jump Tables
  • Part IV: The Finale

These parts go deeper than this readme, and definitely worth a read if you are interested in the topic.

Building

From Template (Quick Start, Recommended)

This is intended for who wants to quick test Zyrox, or learn how to integrate it in a cmake project.

Follow the steps in Zyrox Template repo.

From Source

install llvm:

sudo apt update
sudo apt install llvm-18 llvm-18-dev clang-18

clone and compile zyrox:

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 (Post-Compile) Plugin setup

make sure you have python3 and pip installed.

Setup an Environment (Recommended)

# Create a virtual environment
python3 -m venv .venv

# Activate the env
source .venv/bin/activate

pip install -r requirements.txt

Install Globally

pip install -r requirements.txt

Usage

Quick Usage

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

After obfuscation, run PyPlugin.py to encrypt jump tables:

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

With CMake

Check out the Zyrox Template repo for an example CMake integration.

Contacts

I get this is a complex topic, and this project was mostly for educational purposes, as well as to serve BSD Brawl. If you have any question, or just want to chat, feel free to reach out to me:

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

any help, through pull requests or issues is appreciated!

How it works

ZyroxPlugin.cpp registers the pass, then links siphash (more on this later) and call StringEncryption to encrypt strings.

The reason we encrypt strings early is so that decryption logic gets obfuscated too later.

then it calls ModuleUtils::ExpandCustomAnnotations and QuickConfig::RegisterPasses to parse all __attribute__((annotate("..."))) expressions and run QuickJs config (located in ZyroxConfig.js)

Every function is obfuscated by calling Zyrox::RunOnFunction located in ZyroxCore.cpp, more documentation about this will be provided in the future.

Extra Util

switches create jump tables and PHI nodes are annoying to deal with thus we use FunctionUtils and BasicBlockUtils to flatten (into if statements) and demote these respectively.

Passes

oh man, where do I start

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

all js-plugin args are in index.d.ts so will not be talked about in this documentation.

for annotations documentation, click here

Basic Block Splitter

This pass splits and shuffles a basic block into smaller ones. suppose we have this:

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;
}

which gets compiled into:

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
}

when using Basic Block Splitter with this config:

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

it becomes:

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
}

now it won't be that much different for such small function but notice how it split a basic block? this is helpful combined with other passes like Control Flow Flattening

Control Flow Flattening

Oh, man this pass have the most features among all lol. I will start by explaining how it works then it's config suppose we have this code:

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

each basic block (A, B and C) gets assigned a unique dispatcher state, example: (simplified)

states = {
    1: LABEL_A,
    2: LABEL_B,
    3: LABEL_C,
};

then we inject a dispatcher block that controls everything and the code becomes:

         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
Download Tool