Anti-dump
El cifrado de clases elimina el bytecode del disco. Sin embargo, la JVM debe mantenerlo en claro en memoria para ejecutarlo. Por eso, el último ataque posible consiste en ejecutar el programa con un depurador o agente y capturar las clases a medida que se desellan. Anti-dump convierte ese entorno de ejecución en un medio hostil: detecta la observación y, en lugar de usar una comprobación que pueda eliminarse con un parche, incorpora el resultado a la clave de descifrado . Así, con un depurador la clave es incorrecta y nunca llega a descifrarse nada.
Qué hace
Anti-dump es un conjunto de capas independientes que pueden activarse por separado y que están asociadas al guardiánde ClassGuard. En conjunto:
- detectan un depurador o agente de instrumentación, tanto mediante los argumentos de inicio de la JVM —Java puro— como desde el sistema operativo —detección nativa:
TracerPid, bibliotecas inyectadas e indicadores de depuración del sistema operativo—; - incorporan esas señales a la derivación de la clave, de modo que una señal positiva genera una clave incorrecta y el conjunto sellado falla la comprobación de integridad en vez de desellarse mientras está bajo observación;
- refuerzan el proceso en el sistema operativo para que resista los volcados de memoria y la conexión de depuradores —no volcable en Linux, bloqueo de conexiones en macOS y ocultación de hilos en Windows—;
- distribuyen las comprobaciones entre muchos puntos independientes —en el guardián y, opcionalmente, repartidas por tus propios métodos—, de forma que no exista un único lugar que parchear.
Está diseñado para cuando el cifrado de clases ya está activado. Por sí solo no protege nada, pues no habría ningún conjunto sellado que defender. Se configura en classGuard.antiDump precisamente por ese motivo.
antiDebug. El antiguo bloque de nivel superior
antiDebug
realiza una única comprobación ligera en el punto de entrada y se niega a arrancar si hay un depurador. Anti-dump es la versión nativa de ClassGuard y por capas: cuenta con muchos puntos, señales nativas y refuerzo del sistema operativo. La diferencia fundamental es que las comprobaciones son esenciales —forman parte de la clave—, no un simple ifque pueda eliminarse. Usa anti-dump cuando selles clases: ofrece una protección claramente superior.Denegación ante fallos, no un indicador
Esta es la decisión de diseño fundamental y debes tenerla en cuenta al planificar. Una comprobación antidepuración sencilla consiste en una única bifurcación:
if (debuggerPresent) refuse();. Basta con que un atacante invierta esa bifurcación para anularla. En cambio, las señales de detección de anti-dump alimentan las operaciones que reconstruyen la clave de cifrado. Cuando se activa una señal, la clave resultante es incorrecta y el conjunto autenticado se niega a descifrarse con una
AEADBadTagException. No hay ningún booleano que invertir: parchear la comprobación para que devuelva "false" no recupera la clave. Además, los propios bytes de la biblioteca nativa se incorporan a la clave mediante un hash, por lo que sustituirla por una versión modificada también impide el descifrado.
-javaagent), un inicio con depuración remota o una ejecución de jstack/jcmd que se conecte al proceso. Si el entorno de producción usa alguna de estas herramientas, anti-dump impedirá que el plugin cargue. Por eso está desactivado de forma predeterminada. Actívalo solo en compilaciones destinadas a entornos donde nunca se conecte un agente legítimo y
prueba siempre primero la compilación sellada en el entorno real.Ejecución limpia frente a ejecución observada
Salida real del mismo JAR sellado. A la izquierda, un inicio normal: el conjunto se desella y el plugin se activa. A la derecha, el mismo JAR iniciado con un depurador: la clave se altera, el conjunto falla la comprobación de integridad y las clases nunca vuelven a aparecer como bytecode que pueda capturarse.
[INFO]: [ClassGuard] loading sealed pool (META-INF/guardian/pool.bin)
[INFO]: Loading server plugin YourPlugin v1.0.0
[INFO]: [YourPlugin] Enabling YourPlugin v1.0.0
[INFO]: Done (2.314s)! For help, type "help"
# normal launch — the pool unseals, your classes run# same jar, launched under a debugger: java -agentlib:jdwp=... -jar server.jar
[ERROR]: Error occurred while enabling YourPlugin v1.0.0 (Is it up to date?)
java.lang.ExceptionInInitializerError
...
Caused by: java.lang.IllegalStateException: ClassGuard: encrypted class pool
failed its integrity check (tampered, truncated, or wrong key)
at META-INF/guardian/pool.bin
Caused by: javax.crypto.AEADBadTagException: Tag mismatch
# the debugger perturbed the key — the pool never decrypts. Fail-closed.El atacante obtiene una AEADBadTagException, no tus clases. Lo mismo sucede si modifica un byte de la biblioteca nativa incluida —integridad— o si inicia el programa con un
-javaagent de monitorización —detección de argumentos—: cada capa independiente produce la misma clave incorrecta.
Las capas
Cada capa es independiente y puede activarse por separado. Eliminar o neutralizar una no impide que las demás sigan actuando, y cualquiera que se active produce el mismo resultado de denegación ante fallos.
Detección: ¿hay algo observando?
| Capa | Qué detecta |
|---|---|
| argsDetection | Un argumento de inicio -javaagent / -agentpath o un indicador jdwp presente al iniciar, leído de los propios argumentos de entrada de la JVM. Es Java puro y no necesita ninguna biblioteca nativa. |
| jdwpProperty | Un depurador presente al iniciar, detectado mediante los argumentos de inicio de la JVM —Java puro—. Su nombre procede de una propiedad antigua de la JVM que no es fiable en los JDK modernos; ahora lee los argumentos de entrada, que son la señal que realmente funciona. Las conexiones dinámicas que no aparecen en los argumentos de inicio las detecta nativeDetection. |
| nativeDetection | La capa más potente: detecta un depurador nativo —gdb o lldb— mediante TracerPid/ptrace, una biblioteca libjdwp/libinstrument inyectada en el mapa del proceso y los indicadores de depuración del sistema operativo. Detecta tanto observadores presentes al iniciar como los que se conectan de forma dinámica. Requiere incluir la biblioteca nativa. |
Integridad y prevención
| Capa | Qué hace |
|---|---|
| nativeIntegrity | Incorpora a la clave el hash de las bibliotecas nativas incluidas. Si se modifica un byte de la biblioteca —por ejemplo, para anular su detección—, la clave cambia y el conjunto no se descifra. Permite detectar manipulaciones de la capa nativa. |
| nativePrevention | Refuerza el proceso en el sistema operativo para que resista los volcados de memoria y las conexiones de depuradores: PR_SET_DUMPABLE(0) en Linux, PT_DENY_ATTACH en macOS y ocultación de hilos en Windows. Es prevención, no detección: eleva desde el principio el coste de realizar un volcado. |
Ubicación e higiene
| Capa | Qué hace |
|---|---|
| sites | Número de puntos de protección independientes que incorporan la detección a la clave —entre 1 y 16; valor predeterminado: 4—. Cuantos más haya, más puntos deberá encontrar y neutralizar un atacante para obtener la clave correcta. No se solapan: eliminar uno no impide que los demás sigan actuando. |
| periodicChecks | Vuelve a ejecutar la detección cada vez que se resuelve una clase protegida, no solo una vez. Así también se detectan los depuradores que se conecten después del inicio a medida que se desellan más clases. |
| scatterChecks | Distribuye mecanismos de detección autónomos por tus propios métodos, de modo que la detección no se concentre en el guardián ni exista una única clase que parchear. Cada punto está integrado y aplica denegación ante fallos. Está desactivado de forma predeterminada; consulta rendimiento. |
| scatterDensity | Aproximadamente uno de cada N métodos aptos recibe un mecanismo de detección distribuido —entre 2 y 1000; valor predeterminado: 20—. Un valor menor crea más puntos. |
| noRetainPlaintext | Pone a cero los bytes descifrados de cada clase justo después de que la JVM la defina, para impedir que se extraigan en claro de la caché del cargador mediante reflexión. Está activado de forma predeterminada. |
Todas las opciones
Todas las opciones de classGuard.antiDump. Cada capa puede activarse por separado, por lo que puedes deshabilitar cualquiera que cause problemas en tu entorno sin perder las demás.
| Campo | Valor predeterminado | Qué hace |
|---|---|---|
| enabled | false | Interruptor principal de toda la función. Está desactivado de forma predeterminada —denegación ante fallos; consulta la explicación anterior—. |
| sites | 4 | Puntos de protección independientes por capa que incorporan la detección a la clave —limitado al intervalo de 1 a 16—. |
| argsDetection | true | Detecta al iniciar los indicadores -javaagent/-agentpath/jdwp. Es Java puro. |
| jdwpProperty | true | Detección de depuradores en Java puro mediante los argumentos de inicio de la JVM. |
| nativeDetection | true | Detección nativa de depuradores y agentes. Incluye la biblioteca nativa. |
| nativeIntegrity | true | Incorpora a la clave el hash de la biblioteca nativa —permite detectar manipulaciones—. Incluye la biblioteca nativa. |
| nativePrevention | true | Refuerzo contra volcados y conexiones de depuradores en el sistema operativo. Incluye la biblioteca nativa. |
| periodicChecks | true | Repite la comprobación cada vez que se resuelve una clase protegida, no solo una vez. |
| noRetainPlaintext | true | Pone a cero los bytes descifrados de las clases después de definirlas. |
| scatterChecks | false | Distribuye mecanismos de detección por tus propios métodos —sin una única clase que parchear—. |
| scatterDensity | 20 | Aproximadamente uno de cada N métodos aptos recibe un mecanismo de detección distribuido —entre 2 y 1000—. |
| attachListenerCheck | false | Opción peligrosa. Incorpora a la clave la detección del listener de conexiones; aplica denegación ante fallos ante cualquier conexión de un agente, incluso una legítima mediante jstack/jcmd/perfilador. Una sola conexión de diagnóstico inutiliza la compilación de forma permanente y tampoco impide una conexión automatizada. Déjalo desactivado salvo que tengas la certeza de que nunca se producirá ninguna conexión. |
nativeDetection, nativeIntegrity o
nativePrevention , la compilación incorpora las bibliotecas nativas —unos cientos de KB— para Linux, macOS y Windows. Desactiva las tres para mantener la compilación en Java puro, sin bibliotecas integradas; en ese caso, solo
argsDetection/jdwpProperty proporcionan detección.Cómo activarlo
Anti-dump está asociado al cifrado de clases, por lo que el requisito previo es classGuard.enabled con una lista
encrypt que no esté vacía (normalmente junto con el
desacoplamiento de tipos). La configuración mínima activa todas las capas con sus valores predeterminados:
{
"classGuard": {
"enabled": true,
"encrypt": ["*"],
"antiDump": { "enabled": true }
}
}
A continuación se muestran todas las opciones para que puedas reducir o desactivar cualquier capa:
{
"classGuard": {
"enabled": true,
"encrypt": ["*"],
"antiDump": {
"enabled": true,
"sites": 6,
"argsDetection": true,
"jdwpProperty": true,
"nativeDetection": true,
"nativeIntegrity": true,
"nativePrevention": true,
"periodicChecks": true,
"noRetainPlaintext": true,
"scatterChecks": true,
"scatterDensity": 20,
"attachListenerCheck": false
}
}
}
sites a 6–8 para disponer de más puntos de protección independientes y activa scatterChecks si el coste de ejecución adicional resulta aceptable para tu JAR. Mantén attachListenerCheck desactivado. Después, prueba la compilación sellada en el entorno exacto donde vaya a ejecutarse: anti-dump está pensado precisamente para comportarse de forma hostil ante un entorno de ejecución que parezca estar bajo observación.Plataformas
Las capas nativas incluyen bibliotecas precompiladas para Linux, macOS —Intel y Apple Silicon— y Windows, todas para x86-64 y arm64. Se seleccionan automáticamente durante la ejecución según el sistema operativo y la arquitectura. Las capas en Java puro (argsDetection, jdwpProperty) funcionan en todas las plataformas.
La capa nativa se degrada de forma controlada: si no hay una biblioteca compatible con el sistema anfitrión, las señales nativas simplemente indican que no se ha detectado nada y la compilación continúa funcionando. La cobertura nunca depende de que se cargue la capa nativa. En las plataformas compatibles aporta las señales más potentes —depuradores nativos e indicadores del sistema operativo—, invisibles para Java puro.
Rendimiento
Casi todo el coste de anti-dump se produce una sola vez, al cargar las clases , no durante la ejecución estable. Los puntos que incorporan la detección a la clave, el hash de la biblioteca nativa y el refuerzo del sistema operativo se ejecutan una sola vez mientras el guardián construye el conjunto sellado. No generan un coste continuo ni por llamada.
- periodicChecks es la única opción cuyo coste aumenta con el número de clases: vuelve a ejecutar la detección al cargar cada clase sellada —una vez por clase, durante la carga y no en rutas críticas—. El resultado de la comprobación nativa se almacena en caché tras la primera llamada, por lo que repetirla es barato. Desactívala si sellas un número muy elevado de clases y el tiempo de inicio es importante.
- scatterChecks es la única capa que puede afectar a la ejecución estable: un mecanismo de detección insertado en un método crítico se ejecuta en cada llamada. Aumenta
scatterDensitypara reducir el número de puntos o déjalo desactivado si empeora el rendimiento. Su utilidad es la defensa en profundidad —no existe una única clase que parchear—, no ampliar el límite de la protección.
El límite real
No vamos a exagerar sus capacidades. Anti-dump eleva enormemente el coste de capturar clases durante la ejecución y aplica denegación ante fallos frente a depuradores presentes al iniciar, agentes de monitorización y depuradores nativos: las clases nunca se descifran bajo observación. Lo que no hace es vencer a un atacante con control total y programable del entorno de ejecución: en principio, un programa que conecte dinámicamente su propia instrumentación y vuelva a transformar las clases después de cargarlas aún puede acceder al contenido en claro que la JVM debe mantener en memoria. Ese es el límite que comparten todos los ofuscadores de bytecode: el código debe ejecutarse en una máquina controlada por el atacante.
La protección obtenida es concreta y valiosa: el cifrado de clasesimpide descompilar el artefacto en reposo; los volcados casuales y automatizados durante la ejecución terminan en denegación ante fallos; y una ingeniería inversa manual y decidida pasa de «conecta un depurador y léelo» a «crea y automatiza un entorno de instrumentación personalizado que no active ninguno de una docena de puntos independientes con denegación ante fallos». Diseña tu estrategia teniendo esto en cuenta. Si de verdad no puedes distribuir ese recurso, no lo hagas: conserva la lógica más valiosa en el servidor, donde no se aplica el límite de los ofuscadores del lado del cliente.