Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
zyrox — Zyrox: plugin ofuscador en tiempo de compilación basado en LLVM. | Kitploit
Herramientas/GitHubGitHub/peterhackz/zyrox
Análisis EstáticoAnálisis de CódigoIngeniería InversaAnálisis de BinariosPapers e InvestigaciónAprendizaje y EducaciónRutas de Aprendizaje y Cursos
GitHubpeterhackz/zyrox

zyrox

Zyrox: plugin ofuscador en tiempo de compilación basado en LLVM.

Ver Repositorio
9411hace 6 mesesRevisado por Kitploit

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir
Sitio web

Zyrox LLVM Ofuscador

plugin de compilación y enlace en tiempo de compilación de llvm para ofuscar código nativo

Por qué

por qué no ¯\_(ツ)_/¯

Uno de mis proyectos más grandes, donde aprendí mucho sobre los internos de LLVM, formatos binarios, ensamblador y técnicas de ofuscación.

Creo que aprender construyendo es la mejor manera de aprender, por eso construí este proyecto para aprender más sobre estos temas.

Investigación

He escrito 4 blogs explicando los conceptos detrás de Zyrox:

  • Parte I: Construyendo Zyrox: Un Ofuscador LLVM Personalizado
  • Parte II: Aplanamiento de Flujo de Control
  • Parte III: Tablas de Salto Encriptadas
  • Parte IV: El Final

Estas partes profundizan más que este readme, y definitivamente vale la pena leerlas si estás interesado en el tema.

Compilación

Desde Plantilla (Inicio Rápido, Recomendado)

Esto está pensado para quienes quieran probar rápidamente Zyrox, o aprender cómo integrarlo en un proyecto cmake.

Sigue los pasos en el repositorio Zyrox Template.

Desde el Código Fuente

instala llvm:

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

clona y 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

Configuración del Plugin Python (Post-Compilación)

asegúrate de tener python3 y pip instalados.

Configurar un Entorno (Recomendado)

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

# Activate the env
source .venv/bin/activate

pip install -r requirements.txt

Instalar Globalmente

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

Uso

Uso Rápido

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

Después de la ofuscación, ejecuta PyPlugin.py para encriptar las tablas de salto:

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

Revisa el repositorio Zyrox Template para un ejemplo de integración con CMake.

Contactos

Entiendo que es un tema complejo, y este proyecto fue principalmente con fines educativos, además de servir a BSD Brawl. Si tienes alguna pregunta, o simplemente quieres charlar, no dudes en contactarme:

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

cualquier ayuda, a través de pull requests o issues, es apreciada!

Cómo funciona

ZyroxPlugin.cpp registra el pase, luego enlaza siphash (más sobre esto después) y llama a StringEncryption para encriptar las cadenas.

La razón por la que encriptamos las cadenas temprano es para que la lógica de descifrado también se ofusque después.

luego llama a ModuleUtils::ExpandCustomAnnotations y QuickConfig::RegisterPasses para parsear todas las expresiones __attribute__((annotate("..."))) y ejecutar la configuración de QuickJs (ubicada en ZyroxConfig.js)

Cada función se ofusca llamando a Zyrox::RunOnFunction ubicada en ZyroxCore.cpp, se proporcionará más documentación sobre esto en el futuro.

Utilidad Extra

los switches crean tablas de salto y los nodos PHI son molestos de manejar, por lo que usamos FunctionUtils y BasicBlockUtils para aplanar (en sentencias if) y degradar estos respectivamente.

Pases

oh vaya, por dónde empezar

  • Divisor de Bloques Básicos
  • Aplanamiento de Flujo de Control
  • Rama Indirecta
  • Rama Indirecta Simple
  • Aritmética Booleana Mixta

todos los argumentos del plugin js están en index.d.ts, por lo que no se hablará de ellos en esta documentación.

para documentación de anotaciones, haz clic aquí

Divisor de Bloques Básicos

Este pase divide y mezcla un bloque básico en otros más pequeños. Supongamos que tenemos esto:

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

que se compila 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
}

al usar el Divisor de Bloques Básicos con esta configuración:

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

se convierte 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 %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
}

ahora no será muy diferente para una función tan pequeña, pero ¿notaste cómo dividió un bloque básico? Esto es útil combinado con otros pases como Aplanamiento de Flujo de Control

Aplanamiento de Flujo de Control

Oh, vaya, este pase tiene la mayor cantidad de características entre todos lol. Empezaré explicando cómo funciona y luego su configuración. supongamos que tenemos este código:

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

cada bloque básico (A, B y C) recibe un estado de despachador único, ejemplo: (simplificado)

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

luego inyectamos un bloque despachador que controla todo y el código se convierte en:

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

ahora esto tiene algunos defectos que el ofuscador corrige. como ves, dado que tenemos una única variable de despachador, es fácil desofuscar esto ya que sabemos a dónde va un bloque después de establecer el estado. ¡fácil de arreglar!

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

revisemos las opciones una por una:

  • UseFunctionResolverChance: inyecta una función para verificar el estado, así que en lugar de hacer if (state == expected_state), hace if (injected_resolver(state)). ejemplo:
    root@kitploit:~
    bool __fastcall cff_resolve_state_check_3585(__int64 a1)
    {
        return a1 == 0x288A6154F8A5E3E2LL;
    }
    
  • UseGlobalStateVariablesChance: guarda el valor del estado para ser comparado en una variable global:
    root@kitploit:~
    bool __fastcall cff_resolve_state_check_506(__int64 a1)
    {
        return a1 == qword_1B20D8;
    }
    
  • UseOpaqueTransformationChance: ofusca la verificación en alguna transformación que solo dará verdadero para un estado específico:
    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 variable global en lugar de un número al hacer , como notaste en el ejemplo anterior. (, , )

Rama Indirecta

supongamos que tenemos este código:

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

se transformaría 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]));
// ...

cuando se usa este pase, el plugin generará un archivo zyrox_tables.txt para ser usado por PyPlugin.py.

PyPlugin.py encriptará las tablas de salto y parcheará las entradas de reubicación, luego hará que el reubicador apunte a jump_table[0] de cada tabla. un reubicador básicamente hace esto: target.writePointer(base.add(value)), por lo que al establecer el valor en 0, hacemos que el reubicador nos dé la dirección base y la coloque en la tabla de salto en tiempo de ejecución, y la usamos junto con el goto para generar la dirección en tiempo de ejecución. en modo thumb de arm32, el pase añade automáticamente | 1 después del descifrado.

para usar PyPlugin.py simplemente haz lo siguiente:

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

pasar --android es importante si estás apuntando a la versión arm64 ya que la versión x86_64 tiene una firma de reubicador diferente.

también puedes pasar --out (por defecto usará el mismo archivo pasado a --in) y puedes pasar --tables (por defecto es zyrox_tables.txt)

Rama Indirecta Simple

aunque la rama indirecta parece genial, también conlleva una penalización de rendimiento ya que descifra punteros en tiempo de ejecución, esta es una versión simple que no afecta el rendimiento, donde esto:

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

se convierte en:

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

aunque esto parece simple y fácilmente rompible (estoy de acuerdo), es suficiente para romper IDA y Ghidra sin afectar el rendimiento.

Aritmética Booleana Mixta

también conocido como MBA Sub (Sustitución de Aritmética Booleana Mixta), convierte operaciones simples en complejas que dan la misma salida. usa un conjunto predefinido. ejemplo:

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

puedes ver la lista completa en Passes/MBASub.cpp si estás interesado.

Anotaciones de Zyrox

solo revisa index.d.ts. el analizador de anotaciones usa el mismo orden. para marcar una función solo haz lo siguiente:

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

códigos de anotación:

  • Divisor de Bloques Básicos: bbs
  • Aplanamiento de Flujo de Control: cff
  • Rama Indirecta: ibr
  • Rama Indirecta Simple: sibr
  • Aritmética Booleana Mixta: mba

ejemplo: en index.d.ts vemos:

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

ahora aquí está el detalle, el primer argumento y el compartido para todos los pases es PassIterations, por lo que será el primer argumento en las anotaciones. para anotar algo con bbs hacemos:

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

esto significa: ejecuta Divisor de Bloques Básicos en hello_world 1 vez con tamaño mínimo = 15, tamaño máximo = 30 y probabilidad = 100.

también puedes combinar pases:

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

esto significa ejecuta Divisor de Bloques Básicos luego Rama Indirecta y luego Rama Indirecta Simple en hello_world. Se ejecutarán en el orden de definición de izquierda a derecha.

Descargar herramienta
UseOpaqueTransformationChance
qword_1C64D8
qword_1A63F0
qword_1ACE98
  • UseSipHashedStateChance: usa una pequeña función siphash personalizada para verificar el estado. así que if (state == 23872) se convierte en algo como if (siphash(state) == 3874872081) haciendo más difícil encontrar a dónde salta un bloque. el bloque haría state = 23872 cuando la condición de a dónde va está hasheada. cada llamada siphash usa valores aleatorios para dificultar su emulación.
  • CloneSipHashChance: clona e incluso intenta cuando sea posible en línea la función siphash creando más de un hermano para ella, lo que hace que no sea suficiente con enganchar una sola función. es muy preferible usar esto ya que solo aumenta el tamaño del binario y no afecta el rendimiento.