ESTUDIO COLECTIVO DE DESPROTECCIONES
Última Actualizacion: 25/10/2001

Programa Visual Basic / P-Code W95 / W98 / NT
Descripción Analisis de la máquina virtual de P-Code
Tipo  
Tipo de Tutorial [X]Original, []Adaptación, []Aplicación, []Traducción
Url  
Protección  
Dificultad 1) Principiante, 2) Amateur, 3) Aficionado, 4) Profesional, 5) Especialista
Herramientas  
Objetivo Conocer el funcionamiento de la VM a nivel interno
Cracker Mr.Silver [WkT!]
Grupo http://www.whiskeykontekila.org
Fecha 9 de Septiembre de 2001

INTRODUCCION
Cada día son más las aplicaciones realizadas en ese patético lenguaje que la mayoría de crackers suelen odiar ;). Es por ello que se hace necesario este tutorial. En él vamos a ver que tipos de aplicaciones de VB podemos encontrarnos y como podemos crackearlas sin demasiado esfuerzo.
Las aplicaciones de VB pueden estar compiladas de dos maneras: en modo nativo o en p-code.

El modo nativo indica que la aplicación se compila usando el juego de instrucciones sobre la plataforma de desarrollo con la que se compiló , es decir en un PC se usaría en lenguaje ensamblador de la familia de los x86. Esto permite el trazado con un debugger tal y como se suele hacer normalmente.
El modo P-code indica que la aplicación se compila usando un juego de instrucciones fijo, es decir que el juego de instrucciones usado es el mismo para todas las plataformas de desarrollo. Básicamente se parece mucho al lenguaje Java, al igual que este, se utiliza una maquina virtual para traducir las instrucciones al lenguaje del procesador sobre el cual se ejecuta la aplicación. En un PC esta máquina virtual reside en las famosas DLL msvbvm50.dll (para aplicaciones compiladas con VB5) y msvbvm60.dll (para aplicaciones compiladas con VB6).
Las aplicaciones generadas con P-code suelen ocupar entre un 40 y un 50% menos que las aplicaciones generadas con código nativo, en contraposición se ejecutan más lentamente que estas ultimas.

En este tutorial vamos a tratar las aplicaciones compiladas con P-Code, ya que las otras no tienen ningún misterio a excepción de un API específico el cual se encuentra en msvbvmXX.dll.

Que problema/s se le plantea a un cracker cuando se enfrenta a una aplicación compilada en P-Code?

- Al trazar la aplicación con un debugger, nunca se llega a ver el código real de la aplicación, tan solo se ven los códigos de operación. En su lugar lo que veremos será la maquina virtual (msvbvmXX.dll)

- El desensamblado normal de un archivo compilado con P-code no es válido para su examen ya que las instrucciones no corresponden con el juego de instrucciones del P-code.

Pues ante tales desventajas más de uno se dará por vencido, pero como esto es ingeniería inversa vamos a ver como podemos solventar estos pequeños baches en el camino.
HIPOTESIS
- Si hay una máquina virtual que procesa códigos de operación, se da por seguro que esos códigos de operación tienen un significado al igual que los códigos de operación de nuestra plataforma. El problema es saber que significa cada código de operación.

- Si cuando trazamos la aplicación, lo que vemos es la máquina virtual, seguro que podemos ver que códigos está procesando y seguro que tambien podemos acceder a ellos para modificarlos.
COMPROBACION DE LAS HIPOTESIS
- La primera hipotesis se comprueba facilmente con una visita al site de microsoft:

http://msdn.microsoft.com/library/backgrnd/html/msdn_c7pcode2.htm

encontraremos un artículo introductorio al P-code, que sin ser demasiado específico si que nos proporciona bastante información importante. De la lectura de este artículo (gracias Black!) podemos comprobar que el juego de instrucciones para aplicaciones de VB en P-code, consta de 255 opcodes estandar y 256 más que son menos utilizados (o sea 511 instrucciones diferentes) que pueden ser combinadas con 4 o menos bytes.

- La segunda hipotesis se comprueba con una corta sesión de SoftIce (a big Hi! to all the guys at Numega!). Seguramente la máquina virtual (msvbvmXX.dll) guardará un buffer en memoria con los códigos de operación. De este buffer se irán leyendo los códigos de operación de uno en uno y según el código encontrado la maquina virtual saltará a una rutina específica para el código leído.
Para comprobarlo creé un formulario de VB y en el método Load agregué el código siguiente:

Private Sub Form_Load()
MsgBox "Hola esto es P-code!!!", vbInformation, "Ejemplo"
End Sub

Muy simple como podemos comprobar. Para comprobar la teoría puse un BPX sobre la función rtcMsgBox (que es exportada por la la dll msvbvmxx.dll). Ejecuté el programa y pulse F12 para regresar a la dirección desde donde se llamó a rtcMsgBox. Esto es lo que encontré:

call eax // llama a rtcMsgBox
cmp edi,esp // estamos aquí
jnz 66105595 // comprueba puntero de pila
xor eax,eax // prepara eax para leer siguiente opcode del buffer :-)
mov al,[esi] // lee opcode a ejecutar, en mi caso 36h
inc esi // incrementa puntero
jmp [eax*4+660FDA58] // y salta a la rutina que interpreta el opcode 36h

Vemos que tal y como habiamos deducido se lee el opcode de un buffer (ESI) en AL, y posteriormente se salta a la rutina de interpretación del opcode usando como índice el código de operación (esta es una manera inteligente y rápida de bifurcar el código sin tener que hacer miles de comprobaciones, parece mentira que sea de M$ :) ).
Si continuamos trazando veremos que el acceso al buffer apuntado por ESI se repite continuamente. Pues bien, resulta que mientras estemos en la máquina virtual, el registro ESI, siempre apuntará al buffer con los códigos de operación, de esta forma siempre podemos controlar que opcode va a ejecutarse con el comando de SoftIce:

> d *esi

Tambien podremos comprobar que la dirección 660FDA58 (depende de la versión de la máquina virtual), apunta al inicio de una tabla de punteros a funciones, es decir por cada opcode la máquina virtual guarda una dirección de salto en una tabla. Una investigación más a fondo sobre este hecho, nos revelará que existen más direcciones de salto direccionadas de esta manera, pero por el momento desconozco su utilidad, aunque es posible que sean las funciones que se encargan de interpretar los posibles subcódigos de operación.

Hasta aquí todo parece obvio, el camino a seguir resulta ahora más claro, solo nos falta por saber que significa cada código de operación. Para ello podemos recurrir al unico desensamblador de p-code que yo conozco, el exdec, creado por JosephCo. Esta pequeña utilidad nos desensamblará (si se puede decir así) cualquier aplicación en p-code creada con VB 5 o 6. La puedes bajar de http://codersdomain.cjb.net.

Si utilizamos el exdec sobre nuestro pequeño ejemplo obtendremos:

Proc: 40183c
401808: 27LitVar_Missing
40180B: 27 LitVar_Missing
40180E: 3a LitVarStr: ( local_00B4 ) Ejemplo
401813: 4e FStVarCopyObj local_00C4
401816: 04 FLdRfVar local_00C4
401819: f5 LitI4: 0x40 64 (...@)
40181E: 3a LitVarStr: ( local_0094 ) Hola esto es P-code!!!
401823: 4e FStVarCopyObj local_00A4
401826: 04 FLdRfVar local_00A4
401829: 0a ImpAdCallFPR4: rtcMsgBox
40182E: 36 FFreeVar
401839: 13 ExitProcHresult

La verdad es que todo esto resulta bastante nuevo pero con un poco de arte y paciencia podemos deducir lo siguiente:

Opcode 27 -> LitVar_Missing : Parece que se usa cuando un parámetro de la función a la cual se va a llamar (en este caso rtcMsgBox), no fue especificado es el código, esto es lógico ya que la función MsgBox de VB tiene la siguiente sintaxis:

MsgBox(prompt[, buttons][, title][, helpfile, context])

Un total de 5 parámetros, nosotros solo le dimos 3, por eso hay dos instrucciones LitVar_Missing.

Opcode 3a -> LitVarStr : Parece claro tambien, si nos fijamos vemos que corresponde a la cadena literal "Ejemplo", este cadena es el tercer parámetro de MsgBox y corresponde al titulo de la ventana. Puede deducirse que esta instrucción pasa una cadena literal a la función rtcMsgbox.

Opcode f5 -> LitI4 : Pasa un entero de 4 bytes a rtcMsgBox, esto corresponde al estilo del MsgBox, en este caso corresponde a vbInformation, que muestra el icono de información y un botón de aceptar. Vendria a ser como un PUSH NUMERO_DE_32_BITS.

Opcode 0a -> ImpAdCallFPR4 : Llama a una rutina que se encuentra dentro de la DLL de la máquina virtual, en este caso rtcMsgBox.

Opcode 13 -> ExitProcHresult : Sale del proceso y retorna el control a la rutina que la llamó, vamos un ret en toda regla jeje.

El significado del resto de opcodes no es relevante y en algunos se puede deducir su función con solo leer el nombre ;-)

Lo siguiente es una lista de unas cuantas instrucciones que nos pueden servir de ayuda:

6c -> ILdRf Empuja una dirección a la pila
1b -> LitStr5 Empuja una cadena literal a la pila
fb -> Lead0 Compara dos cadenas (jeje, para que podría servir esto :-)
30 -> EqStr Compara dos cadenas (jeje, para que podría servir esto :-)
2f -> FFree1Str Libera la memoria usada
1a -> FFree1Ad Libera la memoria usada
0f -> VCallAd Ejecuta código dentro de la máquina virtual
1c -> BranchF Salta si la comparación previa era falsa ( vamos lo mismo que un jne/jnz de asm xDDD )
1d -> BranchT Salta si la comparación previa era cierta ( vamos lo mismo que un je/jz de asm xDDD )
1e -> Branch Salta incondicionalmente ( jeje adivina ke utilidad tiene)
fc -> Lead1 Termina la ejecucíón del programa (jeje este es bueno...)
c8 -> End Termina la ejecucíón del programa (jeje este es bueno...)
f3 -> LitI2 Guarda el Integer especificado en la pila
f4 -> LitI2_Byte Convierte un valor de Byte a Integer y lo mete en la pila
70 -> FStI2 Guarda el último Integer que se haya en la pila en la variable global especificada
6b -> FLdI2 Carga en la pila un Integer desde la variable local especificada
a9 -> AddI2 Suma los dos últimos Integers que se empujaron a la pila y mete en pila el resultado
ad -> SubI2 Resta los dos últimos Integers que se empujaron a la pila y mete en pila el resultado
b1 -> MulI2 Multiplica los dos últimos Integers que se empujaron a la pila y mete en pila el resultado como un Integer, creo que si hay desbordamiento se ignora.

Con esto ya podemos empezar a trazar y a cambiar bytes a nuestro antojo, el truco reside en usar el comando BPM sobre la dirección de código que nos de el exdec, esta dirección no es la dirección de código de la maquina virtual que nosotros vemos, si no que se trata de la dirección dentro del buffer apuntado por ESI. Por ejemplo si quisieramos parar la ejecución del programa en esta línea:

401819: f5 LitI4: 0x40 64 (...@)

Tan solo tendriamos que poner un BPM tal como este:

> BPM 401819

Esto nos límita a un máximo de 4 breakpoints de memoria, ya que este tipo de breakpoints basan su funcionamiento en los registros de depuración,y solo existen 4 donde se pueden almacenar direcciones de ruptura, el resto son de uso conjunto.

Una vez que el breakpoint surta efecto, podremos alterar el contenido del buffer de opcodes con:

> E *EDI

Ahora podremos sustituir el opcode por otro que nos interesE más, p. ej. un salto condicional podriamos cambiarlo por uno incondicional etc.

Pero esto no es todo, como he dicho antes la máquina virtual guarda una tabla con direcciones de salto para cada opcode, la dirección de esta tabla variará segun la versión de la máquina virtual con lo cual funcione el programa. Esto nos permite pensar en otra manera alternativa de interrumpir la ejecución del programa, me explico: sabiendo la dirección de salto de cada opcode, podemos establecer breakpoints (de tipo BPX) sobre estas direcciones y asi podriamos controlar por ejemplo cuando se va a ejecutar un BranchX o una VCallAd. Como se haría esto? Pues fácil, primero buscamos la dirección de la tabla en mi caso (660FDA58 ver arriba) para la MSVBVM60.DLL y luego vamos a esta posición en el desensamblado del código. A partir de esta posición, cada DWORD que encontremos es un salto a cada opcode segun su posición. Aquí teneis la lista de direcciones para las dos máquinas virtuales (MSVBVM60.DLL v6.00.8495 y MSVBVM50.DLL v05.00.4319 (SP2)), para que no tengais que andar buscando ;).

VB5 version 4319 -> 7B3EED94h
VB5 version 8244 -> 0F0FED94h
VB6 version 8176 -> 66106954h
VB6 version 8268 -> 66106D14h
VB6 version 8495 -> 660FDA58h
VB6 version 8877 -> 660FEA58h
VB6 version 8964 -> 660FEA58h

Bueno hasta aquí este tutorial, si lo habeis entendido, vereis que con un poco de imaginación y algo de suerte se pueden hacer cosas muy prácticas con la máquina virtual, se puede modificar para que nos loggee los datos que recibirá una instrucción o incluso para saber cuando se comparan dos cadenas, de hecho existe una máquina virtual de la versión 5.0 de VB modificada por Lazarus que nos loggea los parámetros de varias funciones de la maquina virtual, pero no lo hace sobre los códigos de operación, sólo sobre las funciones API exportadas por la maquina virtual, y lo interesante y verdaderamente práctico es poder ver el código que se esta ejecutando gracias a nuestros propios estudios y información aportada por Josephco (tio eres cojonudo) se pudo realizar el WKTVBDebugger

Mr. Silver [WKT!] 2001
[ Entrada | Documentos Genéricos | WkT! Web Site ]
[ Todo el ECD | x Tipo de Protección | x Fecha de Publicación | x Orden Alfabético ]
(c) Whiskey Kon Tekila [WkT!] - The Original Spanish Reversers.
Si necesitas contactar con
nosotros , lee esto antes e infórmate de cómo puedes ayudarnos