Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
zyrox — Zyrox : plugin d'obfuscation au moment de la compilation basé sur LLVM. | Kitploit
Outils/GitHubGitHub/peterhackz/zyrox
Analyse StatiqueAnalyse de CodeRétro-ingénierieAnalyse de BinairesArticles et RechercheApprentissage et ÉducationParcours et Cours
GitHubpeterhackz/zyrox

zyrox

Zyrox : plugin d'obfuscation au moment de la compilation basé sur LLVM.

Voir le dépôt
94114il y a 6 moisVérifié par Kitploit
Site web

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

Zyrox LLVM Obfuscator

Plugin LLVM pour l'offuscation de code natif lors de la compilation et de l'édition de liens

Pourquoi

pourquoi pas ¯\_(ツ)_/¯

Un de mes plus gros projets, où j'ai beaucoup appris sur les internes de LLVM, les formats binaires, l'assembleur et les techniques d'offuscation.

Je crois qu'apprendre en construisant est la meilleure façon d'apprendre, donc j'ai construit ce projet pour en apprendre davantage sur ces sujets.

Recherche

J'ai écrit 4 articles expliquant les concepts derrière Zyrox :

  • Partie I : Construction de Zyrox : un offuscateur LLVM personnalisé
  • Partie II : Aplatissement du flux de contrôle
  • Partie III : Tables de saut chiffrées
  • Partie IV : Le final

Ces parties vont plus en détail que ce readme, et valent vraiment la peine d'être lues si le sujet vous intéresse.

Construction

À partir du modèle (Démarrage rapide, recommandé)

Télécharger l’outil

Ceci est destiné à ceux qui veulent tester rapidement Zyrox, ou apprendre à l'intégrer dans un projet cmake.

Suivez les étapes dans le dépôt Zyrox Template.

À partir des sources

installez llvm :

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

clonez et compilez 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

Configuration du plugin Python (Post-Compilation)

assurez-vous d'avoir python3 et pip installés.

Configurer un environnement (recommandé)

root@kitploit:~
# Crée un environnement virtuel
python3 -m venv .venv

# Active l'environnement
source .venv/bin/activate

pip install -r requirements.txt

Installation globale

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

Utilisation

Utilisation rapide

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

Après l'offuscation, exécutez PyPlugin.py pour chiffrer les tables de saut :

root@kitploit:~
# si vous avez installé les dépendances dans un environnement virtuel, activez-le d'abord :
source .venv/bin/activate
#  puis exécutez avec :
python PyPlugin.py --in=<input_file> [--out=<output_file>] [--tables=<zyrox_tables_file>] [--android]

Avec CMake

Consultez le dépôt Zyrox Template pour un exemple d'intégration CMake.

Contacts

Je comprends que c'est un sujet complexe, et ce projet était surtout à des fins éducatives, ainsi que pour servir BSD Brawl. Si vous avez des questions, ou simplement envie de discuter, n'hésitez pas à me contacter :

  • Discord : @s.b
  • Email : [email protected] ou [email protected]
  • Serveur Discord

toute aide, via des pull requests ou des issues, est appréciée !

Comment ça fonctionne

ZyroxPlugin.cpp enregistre le pass, puis lie siphash (plus de détails plus tard) et appelle StringEncryption pour chiffrer les chaînes.

La raison pour laquelle nous chiffrons les chaînes tôt est que la logique de déchiffrement soit elle aussi offusquée plus tard.

Ensuite, il appelle ModuleUtils::ExpandCustomAnnotations et QuickConfig::RegisterPasses pour analyser toutes les expressions __attribute__((annotate("..."))) et exécuter la configuration QuickJs (située dans ZyroxConfig.js)

Chaque fonction est offusquée en appelant Zyrox::RunOnFunction situé dans ZyroxCore.cpp, plus de documentation à ce sujet sera fournie à l'avenir.

Utilitaire supplémentaire

Les commutateurs créent des tables de saut et les nœuds PHI sont gênants à traiter, donc nous utilisons FunctionUtils et BasicBlockUtils pour les aplatir (en instructions if) et les rétrograder respectivement.

Passes

oh là là, par où commencer

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

tous les arguments des plugins js sont dans index.d.ts, donc ils ne seront pas abordés dans cette documentation.

pour la documentation des annotations, cliquez ici

Basic Block Splitter

Ce pass divise et mélange un bloc de base en blocs plus petits. Supposons que nous ayons ceci :

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

qui est compilé en :

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
}

en utilisant Basic Block Splitter avec cette configuration :

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

il devient :

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
}

maintenant, cela ne sera pas très différent pour une si petite fonction, mais remarquez comment il a divisé un bloc de base ? c'est utile combiné avec d'autres passes comme Control Flow Flattening

Control Flow Flattening

Oh, ce pass a le plus de fonctionnalités parmi tous, lol. Je vais commencer par expliquer comment il fonctionne, puis sa configuration. Supposons que nous ayons ce code :

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

chaque bloc de base (A, B et C) se voit attribuer un état unique du dispatcher, exemple : (simplifié)

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

ensuite nous injectons un bloc dispatcher qui contrôle tout et le code devient :

root@kitploit:~
         int state = 0;
LABEL_D  goto LABEL_CA // saut du dispatcher vers le premier bloc conditionnel, label condition A
LABEL_CA if state == 1: goto LABEL_A
         // si pas 1, aller vérifier si c'est le label B (fallback)
LABEL_CB if state == 2: goto LABEL_B
LABEL_CC if state == 3: goto LABEL_CC
         // inaccessible
         goto LABEL_D
LABEL_A: bool b = x == 2;
         // IF EQ: goto LABEL_B
         // goto LABEL_C
         state = 2 if b else 3 // mettre à jour l'état pour le bloc que nous voulons et retour au dispatcher
         goto LABEL_D
LABEL_B  do_stuff()
LABEL_C  do_other_stuff()
         state = 1
         goto LABEL_D

maintenant cela a quelques défauts que l'offuscateur corrige. Comme vous le voyez, puisque nous avons une seule variable dispatcher, il est facile de désoffusquer cela car nous savons où un bloc va après avoir défini l'état. Facile à corriger !

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

passons en revue les options une par une :

  • UseFunctionResolverChance : injecte une fonction pour vérifier l'état, donc au lieu de faire if (state == expected_state), il fait if (injected_resolver(state)). exemple :
    root@kitploit:~
    bool __fastcall cff_resolve_state_check_3585(__int64 a1)
    {
        return a1 == 0x288A6154F8A5E3E2LL;
    }
    
  • UseGlobalStateVariablesChance : sauvegarde la valeur d'état à comparer dans une variable globale :
    root@kitploit:~
    bool __fastcall cff_resolve_state_check_506(__int64 a1)
    {
        return a1 == qword_1B20D8;
    }
    
  • UseOpaqueTransformationChance : offusque la vérification en une transformation qui ne donnera vrai que pour un état spécifique :
    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 : utilise une variable globale au lieu d'un nombre lors de l'utilisation de UseOpaqueTransformationChance, comme vous l'avez remarqué dans l'exemple ci-dessus. (qword_1C64D8, qword_1A63F0, qword_1ACE98)
  • UseSipHashedStateChance : utilise une minuscule fonction siphash personnalisée pour vérifier l'état. Donc if (state == 23872) devient quelque chose comme if (siphash(state) == 3874872081) rendant plus difficile de trouver vers quel bloc on saute. le bloc ferait state = 23872 lorsque la condition de destination est hachée. chaque appel siphash utilise des valeurs aléatoires pour rendre son émulation plus difficile.
  • CloneSipHashChance : clone et tente même, si possible, d'inliner la fonction siphash créant ainsi plus d'une copie, ce qui rend le hook d'une seule fonction insuffisant. il est très fortement recommandé d'utiliser cela car cela n'augmente que la taille du binaire et n'affecte pas les performances.

Indirect Branching

supposons que nous ayons ce code :

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

il se transformerait en :

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]));
// ...

lorsque ce pass est utilisé, le plugin produira un fichier zyrox_tables.txt à utiliser par PyPlugin.py.

PyPlugin.py chiffrera les tables de saut et corrigera les entrées de relocalisation, puis fera pointer le relocateur vers jump_table[0] de chaque table. un relocateur fait essentiellement ceci : target.writePointer(base.add(value)), donc en définissant value à 0, nous faisons en sorte que le relocateur nous donne l'adresse de base et la mette dans la table de saut à l'exécution, et nous l'utilisons avec le goto pour générer l'adresse d'exécution. sur arm32 en mode thumb le pass ajoute automatiquement | 1 après le déchiffrement.

pour utiliser PyPlugin.py faites simplement ce qui suit :

(si vous utilisez venv, activez-le d'abord)

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

passer --android est important si vous ciblez la version arm64 car la version x86_64 a une signature de relocateur différente.

vous pouvez aussi passer --out (par défaut le même fichier que --in) et --tables (par défaut zyrox_tables.txt)

Simple Indirect Branching

bien que le branching indirect semble génial, il a aussi un impact sur les performances car il déchiffre les pointeurs à l'exécution, voici une version simple qui n'affecte pas les performances, où cela :

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

devient :

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

bien que cela semble simple et facilement cassable (je suis d'accord), cela suffit pour casser IDA et Ghidra sans affecter les performances.

Mixed Boolean Arithmetic

également connu sous le nom de MBA Sub (Mixed Boolean Arithmetic Substitution), convertit des opérations simples en opérations complexes qui donnent la même sortie. il utilise un ensemble prédéfini. exemple :

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

vous pouvez voir la liste complète dans Passes/MBASub.cpp si vous êtes intéressé.

Zyrox Annotations

il suffit de consulter index.d.ts. le parseur d'annotations utilise le même ordre. pour marquer une fonction, faites simplement ce qui suit :

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

codes d'annotation :

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

exemple : dans index.d.ts nous voyons :

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

maintenant voilà le truc, le premier argument et celui partagé par tous les passes est PassIterations, donc ce sera le premier argument dans les annotations. pour annoter quelque chose avec bbs nous faisons :

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

cela signifie : exécute Basic Block Splitter sur hello_world 1 fois avec min size = 15, max size = 30 et chance = 100.

vous pouvez aussi combiner des passes :

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

cela signifie exécuter Basic Block Splitter puis Indirect Branching et ensuite Simple Indirect Branching sur hello_world. Ils s'exécuteront dans l'ordre de définition de gauche à droite.