Skip to Content
Nueva versión 12 disponible 🎉

Código dinámico

Cambio del código de demostración al código de la versión comercial con el cifrado de código.

El cifrado de código permite cifrar métodos concretos de una aplicación, que después pueden intercambiarse según las funciones a las que tenga acceso el usuario. Por ejemplo, si una aplicación tiene funciones básicas y avanzadas, los métodos cifrados de las funciones avanzadas podrían almacenarse separados del ejecutable principal y cargarse en tiempo de ejecución solo si el usuario tiene una licencia que le da acceso a esas funciones. Esta selección de código dinámico permite a los desarrolladores crear una versión de demostración o de prueba de su aplicación con funciones limitadas y desbloquear funcionalidad adicional proporcionando a los usuarios una clave de licencia. Con el cifrado de código, los desarrolladores protegen su propiedad intelectual y a la vez ofrecen a sus usuarios opciones de licencia flexibles.

El código dinámico que ofrece el cifrado de código permite cambiar entre distintas versiones de un método en tiempo de ejecución en función de condiciones externas. Por ejemplo, una aplicación puede tener un conjunto limitado de funciones en modo de demostración y, cuando hay un archivo de licencia válido, habilitar funciones más avanzadas. Esto se consigue cifrando el código de las versiones limitada y avanzada de las funciones y usando un mecanismo de selección de código dinámico que cambia de una a otra según exista o no el archivo de licencia.

Aplicación de demostración

En la forma clásica de implementar una aplicación de demostración, el código de la versión comercial ya está presente en la versión de demostración, y el archivo de licencia permite ejecutar las funciones avanzadas. Sin embargo, este planteamiento tiene un inconveniente importante: el código que se ejecuta cuando está presente el archivo de licencia no está protegido, y un atacante podría eludir la validación de la licencia para desbloquear las funciones con licencia.

En cambio, con la selección de código dinámico, el código que desbloquea las funciones avanzadas está cifrado y no puede ejecutarse de ningún modo sin el archivo de licencia, que contiene la clave de cifrado necesaria para descifrar y ejecutar el código. Esto protege mejor contra la ingeniería inversa y el acceso no autorizado a las funciones avanzadas. El archivo de licencia actúa como una clave única que desbloquea secciones concretas del código, lo que dificulta que los atacantes eludan el mecanismo de licencias y accedan a las funciones avanzadas.

Considere el siguiente proyecto de ejemplo, disponible en GitHub. El código fuente del ejemplo puede obtenerse con Git desde la línea de comandos ejecutando el comando siguiente:

git clone https://github.com/babelfornet/dynamic-code-example.git

El ejemplo muestra cómo usar el código dinámico para cambiar el comportamiento de la aplicación según la presencia de determinados archivos.

[Obfuscation(Feature = "msil encryption:id=feature1;source=demo;internal=true;password=1234", Exclude = false)] public void Feature1Demo() { Console.WriteLine("Feature1 Demo"); Feature2Demo(); } [Obfuscation(Feature = "msil encryption:id=feature2;source=demo;internal=true;password=1234", Exclude = false)] public static void Feature2Demo() { Console.WriteLine("Feature2 Demo"); }

Estos son dos métodos de demostración marcados para que, en tiempo de ejecución, la selección de código dinámico los sustituya por otras versiones de los métodos. Los métodos Feature1Demo() y Feature2Demo() se cifran con la función de cifrado de código con los id «feature1» y «feature2», respectivamente. El origen del código cifrado se especifica como «demo», y la propiedad internal tiene el valor true para mantener el código cifrado dentro del ensamblado. La contraseña del cifrado es «1234».

Cuando una aplicación se ejecuta sin un archivo de licencia válido, se considera que está en modo de demostración. En ese caso, el ensamblado llama a los dos métodos anteriores, marcados para que el código dinámico los sustituya en tiempo de ejecución. Estos métodos implementan la funcionalidad básica de la aplicación, que puede ser limitada en comparación con la versión con licencia completa.

static void Main(string[] args) { var app = new App(); app.Feature1Demo(); }

Considere ahora los dos métodos siguientes, que sustituirán a los métodos de demostración cuando la aplicación tenga licencia. Observe que el origen es «retail» y que el método con el id «feature1», Feature1Retail(), sustituirá a Feature1Demo(), mientras que el método con el id «feature2», Feature2Retail(), sustituirá a Feature2Demo() si se encuentra un archivo de licencia.

[Obfuscation(Feature = "msil encryption:id=feature1;source=retail;internal=true;password=4567", Exclude = false)] public static void Feature1Retail() { Console.WriteLine("Feature1 Retail"); Feature2Retail(); } [Obfuscation(Feature = "msil encryption:id=feature2;source=retail;internal=true;password=4567", Exclude = false)] public static void Feature2Retail() { Console.WriteLine("Feature2 Retail"); }

El ensamblado, que se ejecuta en modo de demostración, no llama a los dos métodos anteriores en tiempo de ejecución, y estos se cifran con una contraseña distinta de la que se usa para los métodos de demostración. Esa contraseña no está al alcance del código de la aplicación, porque Babel quita la información durante el proceso de ofuscación, después de cifrar los métodos.

Hace falta una forma de cambiar dinámicamente entre «demo» y «retail» con el cifrado de código. Como los métodos anteriores se cifraron con una contraseña, hay que definir el punto de entrada del cifrado de código de Babel que obtiene la contraseña en tiempo de ejecución.

[Obfuscation(Feature = "msil encryption get password", Exclude = false)] internal static string GetPassword(string source) { Console.WriteLine($"> Get {source} password"); if (source == "demo") return "1234"; string pwd = File.ReadAllText(source + ".txt"); Console.WriteLine($">> {source} password: {pwd}"); return pwd; }

La BVM (máquina virtual de Babel) usa el método GetPassword() para obtener la contraseña necesaria para descifrar un método cifrado cuando este se invoca durante la ejecución del programa.

Si el nombre del origen es «demo», el método devuelve la contraseña «1234», escrita en el propio código, que es la que se usó para cifrar los métodos «demo» Feature1Demo() y Feature2Demo(). En caso contrario, lee la contraseña de un archivo de texto con el mismo nombre que el origen y la extensión «.txt». Después devuelve la contraseña al llamador. Observe que la contraseña de demostración se guarda dentro del código porque la aplicación siempre puede ejecutarse en modo de demostración y no hace falta ocultarla.

El cambio entre los orígenes «demo» y «retail» lo hace el método GetSourceStream(), al que la BVM llama cuando se llama a uno de los métodos cifrados.

[Obfuscation(Feature = "msil encryption get stream", Exclude = false)] internal static object GetSourceStream(string source) { Console.WriteLine($"> Get {source} stream"); if (source == "demo") { string[] sources = { "retail", "special" }; foreach (var src in sources) { if (File.Exists($"{src}.txt")) { Console.WriteLine($">> Switch to {src}"); return src; } } return null; } // External sources if (File.Exists(source + ".eil")) { Console.WriteLine($">>> Read {source} stream"); return File.OpenRead(source + ".eil"); } return null; }

Este método desempeña un papel decisivo en la selección dinámica del origen adecuado de métodos cifrados en función de condiciones externas, como la presencia de un archivo de licencia. De hecho, cuando la aplicación se inicia, llama a los métodos de demostración, que pueden descifrarse con la contraseña que devuelve el método GetPassword().

En la selección de código dinámico, la BVM (máquina virtual de Babel) se encarga de ejecutar el código cifrado y de cambiar entre las distintas versiones de un método en función de condiciones externas. Antes de ejecutar cualquier método «demo», la BVM llama al método GetSourceStream() y le pasa «demo» como parámetro. Como los métodos «demo» son internos del ensamblado, no hace falta devolver ninguna secuencia del origen de demostración. En cambio, si se encuentra el archivo «retail.txt», se quiere cambiar al código del origen «retail», así que hay que indicar a la BVM que se desea ejecutar el origen «retail» devolviendo la cadena «retail» desde el método GetSourceStream().

Cuando la BVM recibe la cadena «retail» de GetSourceStream(), sabe que debe ejecutar el código «retail». A continuación llama a GetPassword() con el argumento «retail». GetPassword() lee la contraseña de la versión «retail» en el archivo retail.txt y la devuelve a la BVM, que la usa para descifrar el código «retail». Una vez descifrado, el código «retail» se ejecuta en lugar del código «demo». De este modo, la aplicación puede cambiar entre distintas versiones del código cifrado en tiempo de ejecución, según exista o no un archivo de licencia, sin necesidad de volver a compilar ni a desplegar el código.

El código dinámico permite definir varios orígenes internos y externos. Por ejemplo, puede declarar otro origen externo, «special», que se carga en lugar del código «retail» cuando está presente el archivo special.txt con la contraseña para descifrar el código.

[Obfuscation(Feature = "msil encryption:id=feature1;source=special;internal=false;password=0000", Exclude = false)] public static void Feature1Special() { Console.WriteLine("Feature1 Special"); Feature2Special(); } [Obfuscation(Feature = "msil encryption:id=feature2;source=special;internal=false;password=0000", Exclude = false)] public static void Feature2Special() { Console.WriteLine("Feature2 Special"); }

El código muestra dos métodos, Feature1Special() y Feature2Special(), marcados con atributos Obfuscation en los que la propiedad «internal» tiene el valor «false». Esto significa que el código ofuscado de estos métodos se genera en un archivo externo con la extensión «.eil» y no se incluye en el propio ensamblado. El archivo externo «.eil» con el código cifrado puede cargarse y descifrarse en tiempo de ejecución con la contraseña correspondiente, especificada en los atributos Obfuscation.

Para ejecutar el ejemplo, compile primero la solución en la configuración de depuración. Después abra el proyecto obfuscate.babel en Babel Desktop y ejecútelo. El proyecto no define una ruta de salida, por lo que Babel Desktop escribe el ensamblado ofuscado en una carpeta BabelOut junto al ensamblado de entrada. A continuación, copie los archivos DynamicCode.exe, DynamicCode.runtimeconfig.json y DynamicCode.deps.json en la carpeta BabelOut. Por último, desde una ventana de PowerShell, ejecute DynamicCode.exe en la carpeta BabelOut; debería ver esta salida:

> Get demo stream > Get demo password Feature1 Demo Feature2 Demo

Para probar el origen «retail», copie el archivo retail.txt en la carpeta BabelOut y ejecute DynamicCode.exe. La salida debería ser:

> Get demo stream >> Switch to retail > Get retail password >> retail password: 4567 Feature1 Retail Feature2 Retail

Para probar el origen «special», copie el archivo special.txt en la carpeta BabelOut y ejecute DynamicCode.exe. La salida debería ser:

> Get demo stream >> Switch to special > Get special stream >>> Read special stream > Get special password >> special password: 0000 Feature1 Special Feature2 Special
Last updated on