Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
Strumenti/GitHubGitHub/peterhackz/zyrox
Analisi StaticaAnalisi del CodiceReverse EngineeringAnalisi di BinariPaper e RicercaApprendimento e FormazionePercorsi e Corsi
GitHubpeterhackz/zyrox

zyrox

Zyrox: plugin offuscatore basato su LLVM, a tempo di compilazione.

Vedi Repository
94116 mesi faRevisionato da Kitploit

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi
Sito web

Zyrox LLVM Obfuscator

plugin di compilazione e link-time di llvm per offuscare codice nativo

Perché

perché no ¯\_(ツ)_/¯

Uno dei miei progetti più grandi, in cui ho imparato molto sugli internals di LLVM, formati binari, assembly e tecniche di offuscamento.

Credo che imparare costruendo sia il modo migliore per apprendere, quindi ho creato questo progetto per imparare di più su questi argomenti.

Ricerca

Ho scritto 4 blog che spiegano i concetti alla base di Zyrox:

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

Queste parti approfondiscono più di questo readme e valgono sicuramente una lettura se sei interessato all'argomento.

Compilazione

Dal Template (Avvio Rapido, Consigliato)

Questo è pensato per chi vuole testare rapidamente Zyrox, o imparare come integrarlo in un progetto cmake.

Segui i passaggi nel repo Zyrox Template.

Dal Sorgente

installa llvm:

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

clona e compila 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

Configurazione del Plugin Python (Post-Compilazione)

assicurati di avere python3 e pip installati.

Configura un Ambiente (Consigliato)

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

# Activate the env
source .venv/bin/activate

pip install -r requirements.txt

Installazione Globale

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

Utilizzo

Utilizzo Rapido

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

Dopo l'offuscamento, esegui PyPlugin.py per cifrare le jump table:

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]

Con CMake

Dai un'occhiata al repo Zyrox Template per un esempio di integrazione CMake.

Contatti

Capisco che sia un argomento complesso, e questo progetto è stato principalmente per scopi educativi, oltre che per servire BSD Brawl. Se hai domande, o vuoi semplicemente fare due chiacchiere, sentiti libero di contattarmi:

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

qualsiasi aiuto, tramite pull request o issue, è apprezzato!

Come funziona

ZyroxPlugin.cpp registra il pass, poi collega siphash (ne parleremo più avanti) e chiama StringEncryption per cifrare le stringhe.

Il motivo per cui cifriamo le stringhe subito è così che anche la logica di decifratura venga offuscata in seguito.

poi chiama ModuleUtils::ExpandCustomAnnotations e QuickConfig::RegisterPasses per fare il parsing di tutte le espressioni __attribute__((annotate("..."))) ed eseguire la config QuickJs (situata in ZyroxConfig.js)

Ogni funzione viene offuscata chiamando Zyrox::RunOnFunction situata in ZyroxCore.cpp; ulteriore documentazione a riguardo verrà fornita in futuro.

Utility Extra

gli switch creano jump table e i nodi PHI sono fastidiosi da gestire, quindi usiamo FunctionUtils e BasicBlockUtils per appiattirli (in istruzioni if) e declassarli rispettivamente.

Pass

oh cavolo, da dove comincio

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

tutti gli argomenti del plugin js sono in index.d.ts, quindi non verranno trattati in questa documentazione.

per la documentazione sulle annotazioni, clicca qui

Basic Block Splitter

Questo pass divide e mescola un basic block in blocchi più piccoli. Supponiamo di avere questo:

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

che viene compilato in:

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
}

quando si usa Basic Block Splitter con questa config:

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

diventa:

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
}

ora non sarà molto diverso per una funzione così piccola, ma nota come ha diviso un basic block?

questo è utile se combinato con altri pass come Control Flow Flattening

Control Flow Flattening

Oh, cavolo, questo pass ha il maggior numero di funzionalità tra tutti lol. Inizierò spiegando come funziona, poi la sua config. Supponiamo di avere questo codice:

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 ogni basic block (A, B e C) viene assegnato uno stato univoco del dispatcher, esempio: (semplificato)

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

poi iniettiamo un blocco dispatcher che controlla tutto e il codice diventa:

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

ora questo ha alcuni difetti che l'offuscatore corregge. come vedi, dato che abbiamo una singola variabile dispatcher, è facile deoffuscare tutto ciò perché sappiamo dove andrà un blocco dopo aver impostato lo stato. facile da correggere!

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

esaminiamo le opzioni una per una:

  • UseFunctionResolverChance: inietta una funzione per controllare lo stato, quindi invece di fare if (state == expected_state), fa if (injected_resolver(state)). esempio:
    root@kitploit:~
    bool __fastcall cff_resolve_state_check_3585(__int64 a1)
    {
        return a1 == 0x288A6154F8A5E3E2LL;
    }
    
  • UseGlobalStateVariablesChance: salva il valore dello stato da confrontare in una variabile globale:
    root@kitploit:~
    bool __fastcall cff_resolve_state_check_506(__int64 a1)
    {
        return a1 == qword_1B20D8;
    }
    
  • UseOpaqueTransformationChance: offusca il controllo in una trasformazione che restituirà true solo per uno stato specifico:
    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: usa una variabile globale invece di un numero quando si applica , come hai notato nell'esempio sopra. (, , )

Indirect Branching

supponiamo di avere questo codice:

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

verrebbe trasformato in:

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

quando questo pass viene usato, il plugin produrrà un file zyrox_tables.txt da usare con PyPlugin.py.

PyPlugin.py cifrerà le jump table e patchará le voci di rilocazione, quindi farà puntare il relocator a jump_table[0] di ogni tabella. un relocator fondamentalmente fa questo: target.writePointer(base.add(value)), quindi impostando value a 0, facciamo in modo che il relocator ci dia l'indirizzo di base e lo metta nella jump table a runtime, e lo usiamo insieme al goto per generare l'indirizzo runtime. nella modalità thumb di arm32, il pass aggiunge automaticamente | 1 dopo la decifratura.

per usare PyPlugin.py basta fare quanto segue:

(se usi venv, attivalo prima)

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

passare --android è importante se stai puntando alla versione arm64, poiché la versione x86_64 ha una firma del relocator diversa.

puoi anche passare --out (di default userà lo stesso file passato a --in) e puoi passare --tables (di default è zyrox_tables.txt)

Simple Indirect Branching

anche se l'indirect branching sembra ottimo, comporta un calo prestazionale perché decifra i puntatori a runtime; questa è una versione semplice che non influisce sulle prestazioni, dove questo:

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

diventa:

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

anche se questo sembra semplice e facilmente aggirabile (concordo), è sufficiente per rompere IDA e Ghidra senza influire sulle prestazioni.

Mixed Boolean Arithmetic

noto anche come MBA Sub (Mixed Boolean Arithmetic Substitution), converte operazioni semplici in operazioni complesse che producono lo stesso output. usa un insieme predefinito. esempio:

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

puoi vedere l'elenco completo in Passes/MBASub.cpp se sei interessato.

Zyrox Annotations

basta controllare index.d.ts. il parser delle annotazioni usa lo stesso ordine. per marcare una funzione basta fare quanto segue:

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

codici delle annotazioni:

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

esempio: in index.d.ts vediamo:

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

ora, il primo argomento, condiviso da tutti i pass, è PassIterations, quindi sarà il primo argomento nelle annotazioni. per annotare qualcosa con bbs facciamo:

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

questo significa: esegui Basic Block Splitter su hello_world 1 volta con dimensione minima = 15, dimensione massima = 30 e probabilità = 100.

puoi anche combinare i pass:

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

questo significa eseguire Basic Block Splitter poi Indirect Branching e poi Simple Indirect Branching su hello_world. Verranno eseguiti nell'ordine di definizione, da sinistra a destra.

Scarica lo strumento
UseOpaqueTransformationChance
qword_1C64D8
qword_1A63F0
qword_1ACE98
  • UseSipHashedStateChance: usa una piccola funzione siphash personalizzata per controllare lo stato. Quindi if (state == 23872) diventa qualcosa come if (siphash(state) == 3874872081), rendendo più difficile capire verso quale blocco si sta saltando. il blocco farebbe state = 23872 quando la condizione di dove va è sottoposta a hash. ogni chiamata a siphash usa valori casuali per renderne più difficile l'emulazione.
  • CloneSipHashChance: clona e cerca anche, quando possibile, di inlineare la funzione siphash, creando più di una copia, il che rende insufficiente agganciare una singola funzione. è molto preferibile usare questa opzione, perché aumenta solo la dimensione del binario e non influisce sulle prestazioni.