Colaboraciones en el Guille

Modificando el Código Intermedio de Microsoft - MSIL

[aprende a "destripar" un ensamblado y hacerle los cambios necesarios]

 

Fecha: 31/Oct/2005 (31-10-05)
Autor: Percy Reyes Paredes - bigpercynet@hotmail.com

          percy's blog               percy's artículos Microsoft .NET Y SQL Server

En este artículo usted aprenderá a "destripar" un ensamblado .exe y hacerle los cambios necesarios si es que así se requiere. Pero antes de ir al "grano", vale resaltar y aclarar algunos conceptos sumamente importantes.

Cuando se suele hablar con respecto al código intermedio nos referimos a ese código que es generado por un determinado lenguaje integrado con el .NET Framework, este código se encuentra encapsulado en un .exe o .dll. Por ejemplo, el MSIL generado por Visual Basic .Net  o por Visual C# .Net es tomado por el JIT, convertido a lenguaje máquina y ejecutado. .NET Framework nos brinda un kit de herramientas que nos facilitan realizar muchas tareas específicas como la creación, implementación y administración de aplicaciones y componentes .NET Framework. Todas estas herramientas pueden ejecutarse desde la línea de comando, a excepción por ejemplo de DbgCLR.exe. Bueno, hasta ahora hemos hablado del código MSIL, pero usted se preguntará, es difícil de comprender todo esto ?, la respuesta es no. A continuación le expongo un trozo de código MSIL para que vaya familiarizándose.

.method private instance void  TextBox1_Validated(object sender,
class [mscorlib]System.EventArgs e) cil managed
    {
    // Code size       20 (0x14)
    .maxstack  8
    IL_0000:  nop
    IL_0001:  ldarg.0
    IL_0002:  callvirt instance class [System.Windows.Forms]System.Windows.Forms.Label 
Percynet_ErrorProvider.Form1::get_Label2() IL_0007: ldstr "validado" IL_000c: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::
set_Text(string) IL_0011: nop IL_0012: nop IL_0013: ret } // end of method Form1::TextBox1_Validated

Cada cosilla de este código será explicado más adelante.

Sigamos. Tenemos herramientas de configuración e implementación, de depuración, de seguridad, y otras generales. En este artículo haremos uso de dos herramientas generales, tanto del ilasm.exe, para generar un ensamblado(.dll o .exe), como también, del ildasm.exe para "destripar" el ensamblado. Cada una de esta herramientas usan muchas opciones para arrojar resultados diversos, es su deber investigar al respecto, en esta vez tan sólo se ilustrarán algunas de ellas. Ahora la pregunta es: ¿Cuándo es necesario desensamblar el MSIL?, pues bien, cuando se compila código en un lenguaje de programación que no admite todos los atributos de metadatos en tiempo de ejecución. Particularmente la primera vez que usé esta herramienta fue cuando tuve que editar mi aplicación en una máquina que tan sólo tenía instalada el .NET Framework, pues como no había Visual Studio .NET, me vi en la necesidad de desensamblarla, hacer los cambios necesarios y listo... a ensamblar se ha dicho, pues ya tenía mi aplicación corregida. Esto no es un  mala práctica, pero si usted se encuentra en la red con algún .exe compatible con .NET Framework, y si deseara hacerle cambios, pues no dude en usar esta herramienta para destriparla, fisgonear y "corregir" el código, luego puede usar la herramienta ilasm para ensamblarla...y tener su propia aplicación... je, je, je,... por favor no piratee aplicaciones que no son de su propiedad, yo sólo mencioné esto por cuestiones académicas. No lo tome en serio...! tanto de bla bla bla... mejor sigamos...

Vayamos al ejemplo. El código que se muestra a continuación tiene la funcionalidad de validar el ingreso de números, aquí se emplea un control ErrorProvider para mostrar mensajes de error al usuario. La funcionalidad de la aplicación no es objeto principal de estudio de este artículo, sino más bien el código MSIL de la misma, ya que analizaremos las distintas partes que la componen. Bueno, la aplicación tendrá la siguiente interfaz en tiempo de diseño.

                                        Interfaz en tiempo de diseño

el código fuente es la que sigue:

'El evento Validating ocurre cuando el enfoque está a punto de perder el enfoque..
'por lo menos asi lo entiendo mejor ok..?
Private Sub TextBox1_Validating(ByVal sender As Object, ByVal e As _
System.ComponentModel.CancelEventArgs) Handles TextBox1.Validating
    Me.Label1.AutoSize = True
    If Not IsNumeric(Me.TextBox1.Text) Then
        'esto viene la frecuencia con la que parpadeará el ErrorProvider1
        Me.ErrorProvider1.BlinkRate = 200
        'establecemos la propiedad BlinkStyle= ErrorBlinkStyle.AlwaysBlink,
        'para que siempre esté parpadeando cuando mientras no se corrija el error.
        Me.ErrorProvider1.BlinkStyle = ErrorBlinkStyle.AlwaysBlink
        Me.ErrorProvider1.SetError(TextBox1, "no es correcto el dato ingresado")
        'con esto hacemos que el control TextBox no pierda el enfoque por el momento...
        e.Cancel = True
    Else
        Me.Label1.Text = "Todo es correcto!"
    End If
End Sub

'Este ocurrirá cuando el control TextBox1 haya sido validado..
'en otras palabras.... este evento ocurrirá cuando el control haya 
'perdido el enfoque...no olvides que el control Textbox '
habrá perdido enfoque si es que es válido el dato ingresado
Private Sub TextBox1_Validated(ByVal sender As Object, ByVal e As _
System.EventArgs) Handles TextBox1.Validated
    Me.Label2.Text = "validado"
End Sub

'Este evento ocurre cuando estamos modifiacando el contenido del control TextBox
Private Sub TextBox1_TextChanged(ByVal sender As Object, ByVal e As _
System.EventArgs) Handles TextBox1.TextChanged
    Me.ErrorProvider1.SetError(Me.TextBox1, "")
    Me.Label1.Text = ""
End Sub

Ejecutando la aplicación...

si ingresamos datos no numéricos, entonces...

                                         si ingresamos datos no numéricos

en contraste... si los datos fueran numéricos, entonces la validación sería correcta.

                                        si ingresamos datos numéricos

 

pues bien, una vez que compilemos el código anterior, habremos obtenido un .exe. El archivo .il (código intermedio) se generará cuando desensamblemos el .exe para hacerle los cambios necesarios. Luego de esto, podemos usar la herramienta ilasm para ensamblar el .exe "modificado" de acuerdo a nuestros requerimientos. Esto haremos más abajo.

Usaremos el .exe obtenido para ilustrar la utilidad ildasm.exe. Desde la línea de comandos podemos invocarla escribiendo: ildasm. La siguiente captura de imagen ilustra lo mencionado.

     ejecutando ildasm

Cuando pulse enter, a continuación le "sorprenderá" la siguiente ventana:

     ildasm

Es aquí donde usted debe especificar el archivo .exe o .dll a desensamblar. Vaya a File->open y seleccione el archivo. Para nuestro ejemplo sería así:

     abrir archivo .exe a desensamblar

Luego observaremos la estructura interna de nuestro ensamblado, tanto el manifiesto como todas las declaraciones de la aplicación (métodos, clases, constructor...)

     muestra el manifiesto y todas la declaraciones

El significado de cada uno de los símbolos es como sigue a continuación.

     símbolos y su significado

Pues bien, para visualizar el código MSIL correspondiente a cualquier declaración, puede darle doble click en el respectivo nodo del árbol. Que le parece si vemos el MSIL del constructor del formulario.

msil del constructor del formulario

Si usted quiere ver todo el código de la aplicación puede ir a File->Dump. Debe darle un nombre al archivo .il que podrá abrirlo con la ayuda del bloc de notas. También puede volcar la estructura de nodos del TreeView usando la opción Dump TreeView.

Ahora veremos cómo podemos modificar este código para que actúe como nosotros queramos y no como fue originalmente diseñado. Lo primero que debemos hacer es ejecutar ILDASM.EXE y utilizar la opción de Dump pasa generar un archivo .IL. Al momento de realizar de hacer un Dump (volcado de código) veremos una ventana parecida a la siguiente donde estableceremos lo que que realmente deseemos incluir en el volcado, tanto las líneas de código, el cabezal del volcado, como el código IL....

     opciones de volcado

Pues bien, una vez guardada el archivo .il, procederemos a abrirlo usando el bloc de notas. Debido a que el código obtenido es bastante, para motivos de explicación de este artículo tan sólo extraeré un trozo de ella, específicamente la parte del método Validating, quedando así:

.method private instance void  TextBox1_Validating(object sender,
class [System]System.ComponentModel.CancelEventArgs e) cil managed
    {
    // Code size       116 (0x74)
    .maxstack  3
    .language '{3A12D0B8-C26C-11D0-B442-00A0244A1DD2}', '{994B45C4-E6E9-11D2-903F-00C04FA302A1}', 
'{00000000-0000-0000-0000-000000000000}'
// Source File 'D:\PERCYNET\Todo_Perynet_ErrorProvider\Percynet_ErrorProvider\Form1.vb' IL_0000: nop IL_0001: ldarg.0 IL_0002: callvirt instance class [System.Windows.Forms]System.Windows.Forms.Label
Percynet_ErrorProvider.Form1::get_Label1() IL_0007: ldc.i4.1 IL_0008: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Label::
set_AutoSize(bool) IL_000d: nop IL_000e: ldarg.0 IL_000f: callvirt instance class [System.Windows.Forms]System.Windows.Forms.TextBox
Percynet_ErrorProvider.Form1::get_TextBox1() IL_0014: callvirt instance string [System.Windows.Forms]System.
Windows.Forms.TextBox::get_Text() IL_0019: call bool [Microsoft.VisualBasic]Microsoft.VisualBasic.Information::
IsNumeric(object) IL_001e: brtrue.s IL_005f Si no es número salta a IL_005f... IL_0020: ldarg.0 IL_0021: callvirt instance class [System.Windows.Forms]System.Windows.Forms.ErrorProvider
Percynet_ErrorProvider.Form1::get_ErrorProvider1() IL_0026: ldc.i4 0xc8 IL_002b: callvirt instance void [System.Windows.Forms]System.Windows.Forms.ErrorProvider::
set_BlinkRate(int32) IL_0030: nop IL_0031: ldarg.0 IL_0032: callvirt instance class [System.Windows.Forms]System.Windows.Forms.ErrorProvider
Percynet_ErrorProvider.Form1::get_ErrorProvider1() IL_0037: ldc.i4.1 IL_0038: callvirt instance void [System.Windows.Forms]System.Windows.Forms.ErrorProvider::
set_BlinkStyle(valuetype [System.Windows.Forms]System.Windows.Forms.ErrorBlinkStyle) IL_003d: nop IL_003e: ldarg.0 IL_003f: callvirt instance class [System.Windows.Forms]System.Windows.Forms.ErrorProvider
Percynet_ErrorProvider.Form1::get_ErrorProvider1() IL_0044: ldarg.0 IL_0045: callvirt instance class [System.Windows.Forms]System.Windows.Forms.TextBox
Percynet_ErrorProvider.Form1::get_TextBox1() IL_004a: ldstr "no es correcto el dato ingresado" ---->carga la cadena en memoria IL_004f: callvirt instance void [System.Windows.Forms]System.Windows.Forms.ErrorProvider::
SetError(class [System.Windows.Forms]System.Windows.Forms.Control, string) IL_0054: nop IL_0055: ldarg.2 IL_0056: ldc.i4.1 -->se carga True(1), pero podemos cambiarlo por false (IL_0056:ldc.i4.0) IL_0057: callvirt instance void [System]System.ComponentModel.
CancelEventArgs::set_Cancel(bool) IL_005c: nop IL_005d: br.s IL_0071 IL_005f: nop IL_0060: ldarg.0 IL_0061: callvirt instance class [System.Windows.Forms]System.Windows.Forms.Label
Percynet_ErrorProvider.Form1::get_Label1() IL_0066: ldstr "Todo es correcto!" IL_006b: callvirt instance void [System.Windows.Forms]System.Windows.Forms.
Control::set_Text(string) IL_0070: nop IL_0071: nop IL_0072: nop IL_0073: ret } // end of method Form1::TextBox1_Validating

El significado de cada una de las instrucciones del set CIL, como por ejmplo: ldarg.0, callvirt, ldstr... puede obtenerlo en la siguiente ruta C:\Archivos de programa\Microsoft Visual Studio .NET 2003\SDK\v1.1\Tool Developers Guide\docs. Los archivos que se encuentran detallan claramente la funcionalidad de cada instrucción....

Bueno, vayamos analizando las posibilidades de cambio que podemos aplicarle al código anterior de tal manera ocasionar resultados que no fueron establecidos inicialmente. Por ejemplo, nuestra aplicación está construida de tal manera que cuando el dato ingresado no es número, entonces nos muestre un mensaje "no es correcto el dato ingresado", esta cadena se carga en IL_004a: ldstr. La condición que programamos podemos observarlo a continuación:

If Not IsNumeric(Me.TextBox1.Text) Then ========>  IL_001e:  brtrue.s   IL_005f    Si no es número salta a IL_005f...
 

es así que en caso no se cumpla, pues se redireccionará hacia IL_005f donde se encuentra prácticamente el inicio del código que mostrará el mensaje "Todo es correcto". Nosotros podemos hacer que en vez de redireccionarse hacia IL_005f se redireccione hacia IL_0020 , ocasionando que por más se ingrese los datos correctos, nos mostrará los siguientes mensajes consecutivos: "no es correcto el dato ingresado"  y "Todo es correcto". Esto produce desconcierto en el usuario, pues el programa no es coherente. Usted puede manipular su propio código y hacer que ella le brinde la funcionalidad que requiera. Esta vez lo que hemos hecho es dañar la funcionalidad de la aplicación, pues esto no es uno de los objetivos principales, pero usted puede usarlo para bien y mejorar la funcionalidad, y no para mal, como yo acabo de ilustrarlo. Sin embargo, queda ilustrada la funcionalidad que nos brinda la herramienta ildasm.

Luego de haber hecho los cambios necesarios, usted puede ensamblar el código usando la herramienta ilasm de la siguiente manera:

C:\ ilasm  Percynet_ErrorProvider.il  /exe

Puede haber mas técnicas al respecto pero este no es el objetivo del artículo. El objetivo fue demostrar a través de un ejemplo práctico, los riesgos de seguridad a los que nos enfrentamos si no tomamos las medidas necesarias. Usted puede "encriptar" su código para que no sea modificado. Debe hacer uso de ofuscadores para que el código de nuestra aplicación sea ilegible en MSIL.

Si deseas saber algo más sobre los ofuscadores, pues aquí te dejo un pequeño extracto que baje del MSDN.

OFUSCADORES

La ofuscación es una técnica que permite cambiar sin problema el nombre de los símbolos de los ensamblados así como otros trucos para frustrar la acción de los descompiladores. Cuando se aplica correctamente, la ofuscación aumenta la protección contra la descompilación, dejando la aplicación intacta.los ofuscadores primitivos cambian el nombre de los identificadores que se encuentran en el código por algo ilegible. Pueden utilizar técnicas de hash o desplazamiento aritmético del juego de caracteres para obtener caracteres no legibles o no imprimibles. Aunque es efectivo, es obvio que son técnicas reversibles y, por lo tanto, poco protectoras. Las herramientas de ofuscación de PreEmptive (denominada Dotfuscator) hacen algo más que cambiar los nombres e incorpora nuevas maneras de crear confusión que hacen casi imposible y, al menos, extremadamente laborioso, realizar ingeniería inversa en la propiedad intelectual de una persona.

Es importante comprender que la ofuscación es un proceso que se aplica a código MSIL compilado, no a código fuente. El entorno de desarrollo y las herramientas no se modifican para ajustarse al cambio de nombre. El código fuente nunca se modifica de ninguna manera, ni siquiera se lee. El código MSIL ofuscado es funcionalmente equivalente al código MSIL tradicional y se ejecutará en Common Language Runtime (CLR) con idéntico resultado. (Sin embargo, lo contrario no sucede. Aunque fuera posible descompilar código MSIL ofuscado en profundidad, habría diferencias semánticas importantes respecto al código fuente original.) La siguiente ilustración muestra el flujo del proceso de ofuscación. La siguiente Figura te ayudará a comprender mejor lo antes mencionado.


procesos de ofuscación de msil de .net

Dotfuscator dispone de todos los métodos normales de ofuscación, además de muchos únicos. Ninguna tecnología de ofuscación es segura al cien por cien. Igual que otros ofuscadores, Dotfuscator simplemente dificulta la tarea de los descompiladores y desensambladores, no ofrece ni proporciona una protección total.Dotfuscator cambia el nombre de todos los métodos posibles. Dotfuscator puede cambiar el nombre de todos los métodos públicos, privados, etc. que no reemplazan un método de una clase no incluida. El cambio de nombre se realiza de manera que se reduce al mínimo el montón de cadenas, lo que ayuda a reducir el tamaño del ejecutable.Con el objetivo de reducir el tamaño, Dotfuscator incluye un sistema de cambio de nombre patentado, Overload-Induction™.

Dotfuscator cambia el nombre de todas las clases, métodos y campos para reducir (normalmente a un carácter) los nombres. El sistema de cambio de nombre de Dotfuscator dispone de propiedades de ofuscación mucho mayores que los sistemas de cambio de nombre típicos. Muchos ofuscadores se basan en cambiar el nombre por caracteres no imprimibles o secuencias confusas para intentar frustrar el resultado del compilador. A diferencia de estos sistemas, el sistema de cambio de nombre Dotfuscator es más compacto e irreversible.

Más ayuda respecto al tema puedes encontrarla en el MSDN online.

Espero les haya sido útil este pequeño aporte... nos vemos...

Percy Reyes
Microsoft Certified Professional

Saludos desde Trujillo - Perú

Por favor, califica este artículo en PanoramaBox, así me animarás a continuar colaborando contigo.


ir al índice principal del Guille