Skip to content

Análisis a fondo · Nebula.NET

Vencer a de4dot: cuando el desnudado de un clic vuelve vacío

El primer movimiento de un atacante con conocimientos no es leer tu código — es ejecutar de4dot y dejar que deshaga la ofuscación automáticamente. Si funciona, nunca estuviste protegido. Esto es exactamente cómo funciona el pipeline de de4dot, etapa por etapa, y por qué la salida de Nebula sobrevive a cada etapa.

By Delta1 Labs Engineering Septiembre de 2026 10 min read Desofuscación

de4dot es el desofuscador automático de facto para .NET, y es muy bueno. Apúntalo a un ensamblado protegido por un ofuscador convencional y, con un solo comando, reconoce el esquema y lo revierte — restaurando nombres, descifrando cadenas en línea, reconstruyendo el flujo de control aplanado y quitando la anti-manipulación. Minutos de trabajo, totalmente automatizado. Lo que significa que la prueba honesta de cualquier ofuscador no es «¿parece aterradora la salida?» — es ¿sobrevive la salida a de4dot? Si una herramienta cede ante de4dot app.dll, te vendió un badén.

Nebula está diseñado para responder a esa prueba. No por pasar desapercibido por accidente, sino por derrotar cada técnica concreta de la que depende la pasada automática de de4dot. Para ver por qué, primero tienes que ver qué hace de4dot en realidad.

Cómo funciona de4dot

de4dot ejecuta un pipeline. Primero toma la huella del ensamblado contra una biblioteca de ofuscadores conocidos; una coincidencia desbloquea una des-transformación específica del esquema. Para cualquier cosa que no reconoce recurre a técnicas genéricas que funcionan contra los patrones comunes que usan los ofuscadores, sea cual sea el fabricante. Esas etapas genéricas son las interesantes, porque son las que un ofuscador novedoso tiene que vencer:

El pipeline genérico de de4dot detectarhuella flujo de controldesaplanar cadenasdescifrar en línea proxiesresolver llamadas manipulaciónquitar guardarlimpiado Nebula tiene que vencer cada etapa genérica — una sola capa recuperada es un punto de apoyo.
Figura 1 — Contra un ofuscador desconocido, de4dot se apoya en pasadas genéricas que explotan patrones comunes. Cada una de las etapas intermedias es lo que derrotan las secciones de abajo.

Etapa: flujo de control que no puede replegar por constantes

de4dot desaplana un método tratando la variable de estado del despachador como un valor que puede propagar por constantes: recorre el switch, calcula estáticamente el estado siguiente en cada bloque y reconstruye las aristas originales. Eso funciona cuando el estado siguiente es una asignación literal — que, en la mayoría de los ofuscadores, lo es:

Un despachador que de4dot SÍ puede replegarrecuperable
int state = 0;
while (true) switch (state)
{
    case 0: DoA(); state = 2; break;   // next state is a constant →
    case 2: DoB(); state = 5; break;   // de4dot propagates it and
    case 5: return;                    // rebuilds:  DoA(); DoB();
}

El aplanado de Nebula enmascara el selector con un valor que solo se conoce en tiempo de ejecución. Los estados almacenados no son las etiquetas de los casos; las etiquetas de los casos son state ^ key, y key lo calcula un bucle que de4dot no puede evaluar estáticamente:

El despachador que de4dot encuentra en una compilación de Nebulaaguanta
long key = ComputeKeyAtRuntime();      // opaque; not a compile-time constant
int state = 0x51ED2704;
while (true) switch (state ^ key)      // selector depends on runtime key
{
    case 0x51ED2701: /* … */ state = 0x51ED2705; break;
    case 0x51ED2705: /* … */ state = 0x51ED27F0; break;
    case 0x51ED27F0: /* decoy: unreachable, but not provably dead */ break;
    // …
}

Como el selector es state ^ key y key no es una constante, no hay nada que propagar — el desofuscador de flujo de control de de4dot se ejecuta y el método sigue aplanado. El nivel agresivo de Enterprise añade estados señuelo que no se pueden demostrar muertos ni podar, así que incluso la reconstrucción parcial queda contaminada.

Estado constante → se repliega state = 0 case 0: … state = 2 case 2: … state = 5 ↓ DoA(); DoB(); ✓ reconstruido state ^ clave de runtime → atascado key = runtime() switch(state ^ key) case 0x51ED2701: … + estados señuelo ↓ ninguna constante que replegar ✗
Figura 2 — La propagación de constantes es todo el truco. Quita la constante — haz que el selector dependa de una clave de runtime — y la pasada de desaplanado no tiene nada que calcular.

Etapa: cifrado de cadenas que no puede quitar automáticamente

La victoria estrella de de4dot es el descifrado de cadenas. Reconoce el descifrador canónico — un método estático que mapea un token a una cadena en texto plano —, lo invoca directamente para cada sitio de llamada y reemplaza ldc.i4 token; call Decrypt por el literal recuperado. La suposición crítica es que el descifrador es una función pura de su argumento, de modo que de4dot puede llamarlo de forma aislada:

El descifrador que de4dot espera — puro, invocablerecuperable
static string Decrypt(int token) { … }      // depends only on 'token'
// de4dot just calls Decrypt(0x1A2B) itself and inlines the result.

El descifrado de Nebula toma una sal por sitio de llamada. Cada sitio pasa su propio valor, y el resultado de la rutina depende de desde dónde se llamó — así que no hay una única función pura que de4dot pueda invocar para reproducir las cadenas:

Descifrado por sitio de llamada en una compilación de Nebulaaguanta
x.d("\uded4\uded4\ude98…", 52751);   // salt differs per site
x.d("\uf5f7\uf5f3\uf5f9…", 58659);
x.d("\uecf7\uece4\ueced…", 64565);
// no single Decrypt(token) reproduces these; the auto-decrypt pass fails.

Esa firma no es la forma string(int) que de4dot detecta automáticamente, y llamarla a ciegas con el contexto de sitio equivocado produce basura — así que los literales siguen cifrados en la salida.

Etapa: un grafo de llamadas que no puede resolver

El arreglador de llamadas proxy de de4dot revierte esquemas de delegado/proxy conocidos — reconoce la forma concreta en que un ofuscador dado reenvía las llamadas y reescribe el sitio de vuelta al destino real. La ofuscación de referencias de Nebula enruta las llamadas a través de métodos proxy inyectados y sin marca que no coinciden con ningún esquema catalogado, así que el arreglador genérico no tiene ningún patrón que aplicar. Ni de4dot ni un decompilador pueden saber qué método externo o del BCL invoca realmente un sitio dado.

Etapa: métodos sin ningún IL en absoluto

La desofuscación, por definición, opera sobre IL ofuscado. La virtualización de código no le deja a de4dot nada sobre lo que operar: el cuerpo del método es una llamada a una VM embebida, y la lógica vive como bytecode personalizado en un recurso. No hay grafo aplanado que reconstruir, ni descifrador que invocar, ni proxy que resolver — solo un lenguaje que de4dot nunca ha visto.

«Un desofuscador revierte transformaciones que reconoce. El trabajo de Nebula es asegurarse de que la pasada automática no reconozca nada — y, donde importa, no encuentre ningún IL en absoluto.»

La prueba — ejecútala tú mismo

No te pedimos que te lo creas por fe; lo probamos en cada versión. Compilamos una muestra, la protegemos con Nebula y la pasamos por de4dot — incluso forzando el desofuscador de flujo de control dedicado con --f cflow. El resultado es consistente:

de4dot contra una compilación protegida con Nebula
> de4dot sample.dll
Detected Unknown Obfuscator
Cleaning sample.dll
Saving "sample-cleaned.dll"

> ilspycmd sample-cleaned.dll   # inspect the "cleaned" output
  • control flow: still flattened (state ^ key intact)
  • strings:      still encrypted (per-site salts intact)
  • virtualized:  still a VirtualMachine.Run(...) stub

de4dot se ejecuta hasta el final e informa éxito — pero el ensamblado «limpiado» está funcionalmente tan protegido como la entrada. Puedes reproducirlo en tres pasos: protege tu compilación con nebula --config nebula.config.json, ejecuta de4dot yourapp.dll, luego abre el resultado en Glass.NET o ILSpy y confirma que las capas siguen intactas.

Qué llega a cada edición

La resistencia a la desofuscación automática empieza en el nivel licensed (Pro) y es más fuerte en Enterprise:

TécnicaFreeProEnterprise
Despacho de flujo de control no replegable—✓✓
Cifrado de cadenas por sitio de llamada—✓✓
Ofuscación de llamadas proxy (grafo de llamadas)—✓✓
Nivel agresivo (estados señuelo)——✓
Virtualización de código——✓

Publica una salida que sobreviva al desnudado de un clic.

La protección por capas de Nebula.NET está diseñada para derrotar la desofuscación automática de de4dot — y lo verificamos en cada versión.