rymga Lock Master es *el* ofuscador de bytecode Java de nueva generación.
Para desmontar tu código, un atacante tiene dos opciones: estudiarlo en reposo u observarlo mientras se ejecuta. rymga Lock Master cierra ambas vías. Tus clases se distribuyen selladas, la lógica queda irreconocible, cada cadena se cifra con una clave vinculada al código que la utiliza y toda la compilación se defiende en cuanto alguien conecta un depurador. A continuación puedes ver el motor completo, pieza por pieza.
Tu bytecode nunca se distribuye en forma de bytecode.
Clases cifradas y eliminadas.ClassGuard protege cada clase mediante cifrado autenticado robusto y elimina los originales del archivo. Solo se distribuyen bloques cifrados opacos: no queda en disco ningún bytecode cargable que se pueda desensamblar.
La mayor parte de tu código, no solo unas pocas clases.El cifrado suele limitarse a las clases ocultas tras una interfaz. El desacoplamiento de tipos elimina esa limitación: reescribe el código mediante interfaces en claro y factorías generadas automáticamente, por lo que también permite sellar las clases que utilizas como tipos concretos. Todo se hace sin refactorizar: acaba cifrada la mayor parte del proyecto, no solo unas pocas clases.
Descifradas únicamente en memoria.Un cargador de arranque generado descifra cada clase bajo demanda, justo al cargarla. La clave se reconstruye en tiempo de ejecución y nunca se almacena tal cual en el artefacto.
Preparado para los entornos de ejecución actuales.El cargador de arranque funciona en cualquier aplicación Java y está diseñado para los entornos de ejecución actuales, que endurecen la reflexión y restringen los cargadores de clases. Aunque esas limitaciones inutilicen los protectores que reasignan el cargador padre, el nuestro sigue descifrando las clases sin problemas.
Una clase se convierte en un bloque cifrado que solo se abre en memoria al cargarla.
Flujo de control reescrito hasta resultar irreconocible.
Reconstruido desde el propio grafo.El flujo de control de cada método se aplana y se redirige mediante un dispatcher enmascarado; después se rellena con predicados opacos, ramas falsas y código muerto que sigue activo. El comportamiento no cambia, pero ya no existe un código Java limpio capaz de reproducirlo.
Bucles y excepciones convertidos en trampas.Se añaden aristas de retorno falsas a los bucles para volverlos irreducibles y se solapan deliberadamente los rangos de excepciones. El descompilador no puede reconstruir la estructura, así que acaba mostrando saltos sin procesar o se detiene a mitad del método.
Las constantes se convierten en cálculos.Los enteros y otras constantes se sustituyen por expresiones de aritmética booleana mixta que solo se resuelven en tiempo de ejecución. Quien analiza el código no ve el valor, sino la expresión que lo genera.
Ramas que solo el llamador puede resolver.Algunos predicados se alimentan con valores procedentes de cada punto de llamada, por lo que saber si una rama está activa depende de quién invoque el método. Si lo analizas por separado, no puedes demostrar que la rama esté muerta: tendrías que estudiar todo el programa a la vez, algo que los descompiladores habituales no hacen.
Flujo de control irreconocible: la única ruta real queda enterrada entre predicados opacos y código muerto.
No queda nada legible en el archivo de clase.
Cada cadena se cifra por separado.No queda ningún literal en claro. Cada uno se cifra por separado, se reconstruye la primera vez que hace falta y después se guarda en caché. No hay texto sin cifrar que se pueda localizar con una búsqueda.
Claves ligadas a la clase llamadora.La clave de cada cadena se deriva de la clase que la utiliza. Si la copias a otro lugar, el resultado al descifrarla es inservible; extraer literales del bytecode no aporta nada.
También se ocultan llamadas y accesos a campos.Las llamadas a métodos y las lecturas de campos se redirigen para que el archivo de clase deje de registrar sus nombres reales. En su lugar quedan marcadores, por lo que ya no resulta evidente qué elemento accede a cuál.
O se extraen por completo de las clases.Opcionalmente, todas las cadenas cifradas se reúnen en un único archivo sellado dentro del JAR y desaparecen definitivamente de los archivos de clase.
Un literal se descifra al usarlo por primera vez con una clave ligada a su clase.
Sellado en reposo. Hostil durante la ejecución.
El análisis estático se queda sin material.Con las clases selladas y las cadenas fuera de su alcance, el desensamblado y la descompilación se atascan. Al atacante solo le queda observar el código mientras se ejecuta, y esa vía también está cerrada.
Los depuradores quedan bloqueados.Una protección en tiempo de ejecución inspecciona la configuración de arranque de la JVM para detectar depuradores y, si así lo configuras, agentes de instrumentación. Si encuentra alguno, las clases cifradas no llegan a descifrarse.
Los volcados en ejecución se impiden por diseño.Como las claves de las clases solo se resuelven en un proceso limpio, no se pueden volcar las clases descifradas desde una JVM sometida a depuración o con un agente conectado.
Casi todo lo que se ejecuta son señuelos.El flujo ofuscado está dominado por rutas muertas, por lo que incluso una captura correcta queda enterrada entre ruido.
Se conecta un depurador y las clases cifradas nunca llegan a abrirse.
Cada compilación permite identificar al titular de la licencia.
Una marca de agua recuperable y ligada a una clave.Cada compilación incorpora un identificador invisible que la vincula a la licencia exacta con la que se generó.
Fragmentada y redundante.La marca se fragmenta, se distribuye entre las clases y se protege con sumas de comprobación, de modo que sobrevive al reempaquetado del JAR, al borrado parcial o a la reofuscación de una parte.
Atribuible, pero nunca expuesta.Para recuperarla hace falta un secreto del proveedor que nunca se incluye en la compilación. Así, una filtración identifica al titular de la licencia sin revelar el mecanismo.
La marca del titular, fragmentada y protegida con sumas de comprobación por todo el archivo.
Reforzado y verificado antes de entregarse.
Verificado de nuevo según las reglas de la JVM.Un verificador de bytecode desarrollado desde cero comprueba cada clase transformada y reproduce la validación que realiza la JVM al cargarla sobre una jerarquía personalizada. Cualquier problema que pudiera provocar un VerifyError se detecta antes de la entrega, nunca en tu servidor.
Consciente de la concurrencia.El código sensible a la temporización y a los hilos, como wait/notify o el arranque de hilos, se refuerza sin alterar sus tiempos. El comportamiento multihilo se mantiene intacto justo donde otros ofuscadores introducen condiciones de carrera.
Determinista e incremental.Las compilaciones son reproducibles y admiten mapas de nombres incrementales, por lo que las API públicas ofuscadas se mantienen estables entre versiones. Es esencial para publicar actualizaciones de plugins y bibliotecas.
Reglas de conservación precisas.Las reglas basadas en patrones glob excluyen de cada transformación los puntos de entrada públicos y los miembros a los que se accede mediante reflexión. La precisión llega hasta un único elemento: puedes conservar solo el nombre de una clase para que siga siendo cargable y ofuscar sus campos y métodos.
Cada clase transformada vuelve a comprobarse según las propias reglas de la JVM.
Robar tu código debería costar más que crearlo. Ese es el objetivo.
No hay ninguna forma honesta de impedir por completo la ingeniería inversa de software compilado. Lo que sí podemos hacer es acumular obstáculos: sellar lo que distribuyes, volver irreconocible lo que queda y hacer que el conjunto se defienda en cuanto alguien intente analizarlo, ya sea en reposo o en ejecución. Además, comprobamos cada compilación antes de entregártela para que toda esa protección nunca se traduzca en un fallo. Hacemos que romper la protección deje de ser rentable y te enseñamos exactamente cómo.
Compruébalo con código real.
Una misma clase en tres versiones: el código original, una pasada ligera y la protección al máximo.
La misma clase tras una pasada ligera. Las cadenas ya han desaparecido.
01/*02 * Decompiled with CFR.03 */0405publicfinal class 公是 {06privatestatic/* synthetic */ String[] 面面;07privatestatic/* synthetic */ String[] 面又;08privatestatic/* synthetic */int 面只;09// ...1011static {12 面只 = 29613 + 2488 ^ 0x28761DD5 ^ 公是.class.getName().hashCode();13 面面 = newString[12];14 公是.面面[0] = 公是.去12400bc1("法前起以年进的其都法定他用其其");15 公是.面面[1] = 公是.去12400bc1("此心同这能与是");16// ...17 }1819/*20 * Exception decompiling21 */22publicstatic/* synthetic */int 又(int var0) {23/*24 * This method has failed to decompile. When submitting a bug report, please provide this stack trace, and (if you hold appropriate legal rights) the relevant class file.25 * 26 * org.benf.cfr.reader.util.ConfusedCFRException: Tried to end blocks [9[TRYBLOCK]], but top level block is 18[UNCONDITIONALDOLOOP]27 * at org.benf.cfr.reader.bytecode.analysis.opgraph.Op04StructuredStatement.processEndingBlocks(Op04StructuredStatement.java:435)28 * at org.benf.cfr.reader.bytecode.analysis.opgraph.Op04StructuredStatement.buildNestedBlocks(Op04StructuredStatement.java:484)29 * at org.benf.cfr.reader.bytecode.analysis.opgraph.Op03SimpleStatement.createInitialStructuredBlock(Op03SimpleStatement.java:736)30 * at org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysisInner(CodeAnalyser.java:850)31 * at org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysisOrWrapFail(CodeAnalyser.java:278)32 * at org.benf.cfr.reader.bytecode.CodeAnalyser.getAnalysis(CodeAnalyser.java:201)33 * at org.benf.cfr.reader.entities.attributes.AttributeCode.analyse(AttributeCode.java:94)34 * at org.benf.cfr.reader.entities.Method.analyse(Method.java:531)35 * at org.benf.cfr.reader.entities.ClassFile.analyseMid(ClassFile.java:1050)36 * at org.benf.cfr.reader.entities.ClassFile.analyseTop(ClassFile.java:942)37 * at org.benf.cfr.reader.Driver.doJarVersionTypes(Driver.java:257)38 * at org.benf.cfr.reader.Driver.doJar(Driver.java:139)39 * at org.benf.cfr.reader.CfrDriverImpl.analyse(CfrDriverImpl.java:76)40 * at org.benf.cfr.reader.Main.main(Main.java:54)41 */42thrownewIllegalStateException("Decompilation failed");43 }4445static {46 三0 = (int)System.nanoTime();47 }48}
El descompilador no pudo reconstruir el código.
¿Aún tienes dudas? Pruébalo tú mismo.
Original
demo.jar
Un motor de licencias autónomo, sin servidor ni dependencias. Ejecútalo con
java -jar demo.jar. El algoritmo de las claves de licencia, el salt secreto y las claves de API quedan a la vista: basta con abrirlo en cualquier descompilador para leerlos. Código fuente en GitHub ↗
El mismo programa, protegido con Lock Master mediante renombrado, cifrado de cadenas, ofuscación media del flujo de control y ClassGuard (sin antimanipulación ni otras técnicas). El resultado es idéntico, pero las clases secretas han desaparecido: están selladas en un bloque cifrado sin nada que descompilar.
Has pasado meses creándolo. A alguien le bastó una tarde para copiarlo sin coste alguno, y cada nueva copia que circula es una venta que te pertenecía.
Publicas una versión. En algún lugar, alguien ya la ha desmontado y distribuido. El problema no es que ocurra, sino que tú eres el último en enterarte.
Con tiempo suficiente, siempre acaba filtrándose alguna copia. Solo queda decidir si no llevará hasta nadie o hasta la persona exacta que la dejó escapar.
Todo software que merece la pena crear también merece la pena robarlo; eso nunca estuvo en tus manos. Lo que sí decides es lo difícil que será robarlo.