| ESTUDIO COLECTIVO DE DESPROTECCIONES Última Actualizacion: 25/10/2001 |
Programa | Cuentapasos 3.75 | W95 / W98 / NT
Descripción | Control del gasto telefónico
Tipo | Shareware
Tipo de Tutorial | [X]Original, []Adaptación, []Aplicación, []Traducción
Url | http://www.cuentapasos.com
Protección | Nag Screen. Periodo evaluación de 30 dias
Dificultad | 1) Principiante, 2) Amateur, 3) Aficionado, 4) Profesional, 5) Especialista
Herramientas | SoftIce v4.0, SmartCheck 6.0, Spy++
Objetivo | Simular estar registrados.
Cracker | Mr. Blue
Grupo | Whiskey Kon Tekila
Fecha | 18 de Octubre de 1999
INTRODUCCION
Quién no haya intentado crackear este clásico, o alguna de sus versiones predecesoras, que me tire la primera piedra... Todos hemos oído hablar del Cuentapasos que, a pesar de existir otros programas equivalentes de distribución gratuita, sigue siendo una codiciada presa. Nos encontramos ante una aplicación escrita en Visual Basic, en la que el autor ha intentado protegerla por todos lados, tapando entradas y huecos, cerrándonos puertas de formas más o menos inteligentes. Y sabemos que cuanto más interés pone el autor en proteger una aplicación, más interesante hace a la presa, desde el punto de vista didáctico que es el que nos mueve a los miembros de WKT!. Sirva el presente tutorial para demostrar la gran dificultad que supone para cualquier programador realizar una protección eficiente de sus aplicaciones cuando estas están escritas en Visual Basic. La nueva forma de compilación en p-code no hace más que facilitar las cosas a los amantes de la ingeniería inversa. De hecho, durante el tutorial se mostrará que desproteger un programa de Visual Basic p-compilado es mucho más sencillo e inmediato que el mismo compilado en código nativo. Aprenderemos, además de cómo pelearnos con éxito contra el p-code, otras técnicas exóticas. Reconozco que podría haberse hecho de otra forma menos elaborada, pero nuestra meta no es crackear; es aprender, enseñar y mostrar nuevas técnicas crackeando.
RECONOCIMIENTO Y EXPLORACION
Veamos a que nos enfrentamos. Al ejecutar el programa, nos aparece una pantalla de bienvenida, en la que posteriormente aparece que es una versión de evaluación. Sobre esta pantalla aparece otra que nos muestra los datos de la aplicación, dirección de correo electrónico del autor, etc... También aparece un código de compra, tanto en el título como en el botón de la derecha. Lo que nos interesa es que aparecen los días transcurridos del periodo de evaluación y los días que nos restan que no es más que treinta menos los días transcurridos. Además aparecen dos botones, Aceptar y Salir. Estos serán nuestros enemigos, la nag-screen y las rutinas que calculan los días restantes de evaluación. Nuestros objetivos serán que el usuario no se vea obligado a pulsar Aceptar para arrancar la aplicación (es irritante) y por supuesto, "ampliar" el periodo de evaluación (que alguien puede pensar que es insuficiente). Es una buena costumbre, para empezar a tomar contacto con el programa, y sobre todo en esta fase inicial en la que tenemos que planear nuestra estrategia, pasar a la víctima por un detector de formatos como el GetTyp. La salida del GetTyp nos muestra que el ejecutable se encuentra comprimido. Más explicaciones en el tutorial de Mr.Orange sobre descompresión manual con ProcDump. Al encontrarse comprimido, no podremos realizar un parcheador que actúe sobre el fichero en disco, tendremos que descomprimirlo previamente.
EL RATON VIRTUAL
Para saltar la nagscreen vamos a utilizar un método clásico, que muchos pueden desconocer. Vamos a simular un click del ratón sobre el botón Aceptar, sin intervención del usuario. Es un método en desuso pero elegante y limpio, que nos permite comprender un poco mejor el funcionamiento del Windows y aprender muchas cosas, que es para lo que estamos aquí. ¿Cómo se hace? Lo primero que debemos hacer es obtener el identificador del proceso del cual queremos depende el botón que queremos pulsar. Esto puede hacerse de dos maneras, o ejecutamos nosotros el proceso vícitma mediante CreateProcess o nos enganchamos al proceso que ya está en ejecución. Lógicamente, lo que a nosotros nos interesa es lo primero, para lo que crearemos un cargador del programa que será el que cargue a la aplicación víctima, esperará a que se cargue y buscará una referencia o handle de la ventana en la que se encuentra el botón que queremos pulsar. Después, buscaremos en esa ventana el botón que nos interesa y finalmente simularemos un click sobre él. Para buscar la ventana se utiliza la función EnumThreadWindows. Esta función sirve para enumerar todas las ventanas padre asociadas al thread que le pasamos como parámetro. Para ello, utiliza una función de tipo Callback que no es más que una función escrita por nosotros, que es ejecutada por EnumThreadWindows cada vez que encuentra una ventana. EnumThreadWindows le pasa a nuestra función de Callback el puntero a la ventana que ha encontrado.Nuestra función , a su salida, devolverá un booleano: False si ya se ha encontrado la ventana o True en caso contrario. EnumThreadWindows no devolverá el control a nuestro cargador hasta que la función de Callback haya devuelto un False o bien se hayan enumerado ya todas las ventanas. ¿Y cómo sabemos si una ventana es la que buscamos o no? Para eso, nuestra función de Callback puede utilizar varias funciones: GetClassName. Devuleve el nombre de la clase de la ventana que le pasamos como parámetro. GetWindowText. Devuelve el texto o título de la ventana. GetWindowRect. Devuelve las coordenadas de la esquina superior izquierda y esquina inferior derecha de la ventana. Una vez que hayamos encontrado la ventana que contiene el botón que queremos pulsar, debemos encontrar un puntero a este botón. En este caso, la función es EnumChildWindows, a la que le pasamos el puntero a una ventana padre, y nos enumera todas la ventanas hijas de ella (botones, cuadros de texto,...). Su funcionamiento es idéntico al de EnumThreadWindows. Ya tenemos el botón, ahora, a pulsarlo. Esto se realiza mediante PostMessage, que manda a la ventana que queramos, el mensaje que queramos. En este caso, enviaremos a un botón el mensaje BM_CLICK, que simula pulsar y soltar el botón del ratón. Más información y ejemplos sobre este tema en las páginas de Fravia, en la sección '+HCU's Papers': Simulating User Input to Eliminate Nag Screens por bb.
ATRAPEN A ESE BOTÓN, ATRÁPENLO!
Botones al borde de un ataque de nervios Antes de nada tenemos que recabar información sobre el botón que queremos "pulsar". Volvemos a arrancar el Cuentapasos pero dejamos sin pulsar el botón Aceptar. Arrancamos el Spy++ (o similar) para que nos muestre los parámetros de los componentes de la nagscreen, en especial los nombres de clase de los objetos de interes, el texto que aparece y las coordenadas. Pulsamos en la opción 'Find' (Alt+F3) y pasamos el punto de mira por todos los componentes de interés. Por si no os habeis dado cuenta todavía, de ejecución en ejecución, los botones Aceptar y Salir se intercambian de manera aleatoria. Así, el autor evita una de las posibles formas de distinguirlos, mediante su situación en pantalla que nos da la función GetWindowRect. Tanto la nagscreen como la ventana de presentación son de la clase ThunderRT5Form, para poderlas distinguir nos tendremos que fijar en el título, ya que la pantalla de presentación no tiene título y la nagscreen tiene "Versión de Evaluación P...". Por lo que parece, no vamos a tener problemas para encontrar la ventana padre de nuestro botón. Vamos a los botones. Podemos olvidarnos del botón "Comprar &Programa P...", ya que es facilmente identificable por tamaño, situación y texto. Tanto el botón Aceptar como Salir, tienen exactamente el mismo tamaño, evitando así el autor del programa que podamos distinguirlos mediante la función GetWindowRect que también permite conocer el tamaño de una ventana. Los dos son de la clase ThunderRT5CommandButton (tampoco podremos distinguirlos por el nombre de la clase), y el texto... oh, oh. Ninguno de los dos tienen texto, así que tampoco nos sirve GetWindowText. Y algunos se preguntarán, "Entonces ¿Aceptar y Salir qué son?" Pues está claro, si no son cadenas de texto serán ... imágenes. Para probarlo, cambiad el color de las ventanas de Windows y veréis que, aunque el botón cambia de color, las palabras Aceptar y Salir están enmarcadas por el fondo gris que trae por defecto el Windows. Como podeis ver, el autor ha obrado inteligentemente y se ha informado convenientemente sobre técnicas de ingeniería inversa (a lo mejor hasta nos lee a nosotros). Bien, no nos pongamos nerviosos. Tenemos dos botones que al parecer, cambian de sitio de manera aleatoria pero ... el orden de creación de los botones siempre será el mismo, independientemente de donde se situen. Si excarvamos más profundamente en la ayuda de la API nos encontramos la función GetNextWindow, que dado el puntero a una ventana hija, nos da el puntero a la ventana hija siguiente o a la posterior. Ya tan solo queda por descubrir que posición ocupa el botón Aceptar en el orden de creación. De hecho, la función EnumChildWindows enumera las ventanas hijas en el orden de creación, bastaría con coger, por ejemplo, la cuarta ventana que nos enumere suponiendo que el botón Aceptar sea la cuarta. Bueno, vamos a ver. Volvemos al Spy++ con esperanzas renovadas y pulsamos las propiedades del botón Aceptar de la nagscreen. En mi caso es el botón de la izquierda y es el último que aparece en la lista, si pinchamos en la etiqueta 'Windows' de la ventana de propiedades del botón podemos recorrenos sus predecesores, y al ser el último, no tiene sucesores. Veamos si esto es cierto cuando Aceptar cambia de sitio. Cerramos el Cuentapasos y lo ejecutamos hasta que el botón Aceptar sea el del centro. Volvemos a ir al Spy++ y el botón Aceptar..... joderl, ahora es el penúltimo. Será "joío". Parece que el autor se ha informado "demasiado bien". Esto significa, que los botones no cambian de sitio, lo que cambia es la funcionalidad de cada uno de ellos, y la imagen que tienen incrustrada. No podemos distinguirlos por la posición, ni por el tamaño, ni por el texto, ni por la clase, .... Soluciones Registrarnos. :-( Aplicar la solución propuesta por bb en Simulating User Input to Eliminate Nag Screens. "Amarrar" a ese travieso botoncito en un sitio fijo. La solución propuesta por bb, para el WinZip (un problema idéntico al nuestro) es utilizar la función GetPixel para poder distinguir qué imagen tiene cada botón. Para unas coordenadas dadas, esta función devuelve el color del pixel correspondiente. Conocemos las coordenadas de los dos botones, tan solo nos queda buscar un pixel en de los botones Aceptar y Salir que sean disitntos. Esto, con cualquier programa gráfico esta "chupao". Sería la solución ideal, ya que siempre debemos intentar modificar los menos posible el ejecutable de nuestra víctima. Como somos más chulos que un ocho, y lo que queremos es mostrar la facilidades que nos ofrece Visual Basic, vamos a "pegar" el botón Aceptar a un sitio fijo, para posteriormente, ametrallarlo a placer con PostMessage. Para hacer esto, tendremos que bucear en el código con ayuda del SoftIce y del SmartCheck, y, lógicamente, cambiar algunas "cosillas" en las rutinas que deciden dónde se pone cada botón. Descubriremos la impactante sensación de "no me entero de ná" que se experimenta inicialmente al sumergirnos en el p-code. Agarraros al ratón que el viaje va a ser movidito.
P-CODE: DESCENSO A LOS INFIERNOS DE MICRO$OFT
Número aleatorios Antes de entrar en faena, es casi obligado leerse el fantástico tutorial de Esiel2, CRACKEANDO EN VISUAL BASIC. Aprenderéis todo lo básico para para crackear programas de Visual Basic, y cómo configurar nuestras herramientas adecuadamente para sacarles el máximo partido al atacar programas escritos en este lenguaje. Tal y como hacemos siempre que tenemos una pieza realizada en Visual Basic, recurrimos al alucinante SmartCheck. Arrancamos el SmartCheck, cargamos el programa y, lo primero que nos aparece, a modo de advertencia es lo siguiente: cpasos32.exe is compiled to p-code...etc... Se podría decir que los amigos de NuMega nos quieren advertir que bajo esas condiciones, nuestra ya esacasa salud mental puede correr peligro. Tenemos la oportunidad de arrepentirnos, como en toda aplicación de Windows que se precie, pero "semos" valientes y elegimos continuar. Inicialmente no vamos a necesitar las llamadas a la API, por lo que podemos habilitar la opción de suprimir las llamadas a la API. Ejecutamos el Cuentapasos desde el SmartCheck, habilitando desde el principio la captura de eventos, y nos acomodamos en la silla, para ver pasar la película. Cuando aparezca la nagscreen, nos fijamos que posición ocupa el botón Aceptar y pulsamos Salir . Como vamos a intentar localizar donde pueden estar las rutinas que deciden donde se situa cada botón con llegar a la nagscreen tenemos de sobra. Nos vamos a la zona en la que se carga la nagscreen, al final. Buscamos el evento frmRegistro_Load: Como puede verse, lo último que hace la aplicación al cargar la nagscreen es inicializar las propiedades de los botones. En esta ocasión, el botón Aceptar me ha salido a la izquierda, por lo que tus resultados serán distintos si te ha salido en el centro. Así, en el evento 15242, se asigna la propiedad Caption del botón que muestra la ayuda de como registrarse. A partir del evento 15244, se asignan las propiedades a otros dos botones que no pueden ser otros que lo que a nosotros nos traen de cabeza. Se cargan las imágenes 'imgAcepto' y 'imgSalir', y se asignan a la propiedad Picture de cada uno de los botones. Al botón 'cmdAceptar(1)' se le activa la propiedad Cancel. Si desempolvamos la ayuda de Visual Basic, encontramos que si esta propiedad es TRUE, la tecla ESC activará al botón. Si volvemos a cargar el Cuentapasos (sin el SmartCheck) y pulsamos ESC en la nagscreen, la aplicación termina. Esto significa que en este caso el botón 'cmdAceptar(1)' está funcionando como Salir. Conclusión, 'cmdAceptar(0)' es el botón de la izquierda y el otro el del centro. Esto podemos probarlo ejecutando varias veces el Cuentapasos, y viendo como al cambiar Aceptar al centro, la propiedad Cancel se la activa a 'cmdAceptar(0)'. Veis lo que hay en el evento 15243. Justo después de inicializar el botón de Comprar y justo antes de los de Aceptar y Salir, se genera un número aleatorio mediante la función Rnd(). Por su delatadora situación, creo que todos apostaríamos el brazo a que la decisión de dónde se colocan los botones, tiene mucho que ver con el numerito obtenido. Si nos las amañamos para que dicho numerito tenga siempre un valor fijo, el botón Aceptar se quedará fijo. Vemos desde donde se invoca la función Rnd(), ¿desde "MSVBVM50.DLL!000FE7BD"? Arrea, ¿qué es esto?. Comenzamos a mirar el resto de funciones y TODAS se ejecutan desde las direcciones 0FE7BD y 0FE7ED de MSVBVM50.DLL, exceptuando aquellas que tengan que ver con objetos visuales (forms, botones,...) ¿Se nos vuelve loco el SmartCheck con programas p-compilados? La puerta del sótano Vamos a ver si el SoftIce nos aclara algo. Lo primero que debemos hacer, como con todos los programas de Visual Basic, es cargar los símbolos de las librerías de VB. En este caso, como nos lo ha "chivado" el SmartCheck, es la librería MSVBVM50.DLL. Lo de "MSVBVM" debe ser algo así como MicroSoft Visual Basic Virtual Machine, o máquina virtual de Visual Basic (toda una máquina de torturas, ya vereis...) Consultamos cuál de las funciones que exporta, puede ser Rnd(). Tenéis un listado de todas las funciones que se exportan en el tutorial de Mr.Brown para VB5. Nos saltan a los ojos dos de ellas:
`Addr:0F004A95` | `Ord: 593 (0251h)` | `Name: rtcRandomNext`
`Addr:0F03AE08` | `Ord: 594 (0252h)` | `Name: rtcRandomize`
Visual Basic dispone de dos funciones para generar números aleatorios: Randomize. Se utiliza para inicializar el generador de números aletorios. El Cuentapasos la utiliza, por ejemplo, al inicio del frmRegistro_Load. Rnd(). Devuelve un número aleatorio entre 0 y 1. La función Rnd() se corresponderá con la rtcRandomNext exportada por la Dll. Lo primero que debemos ver es el número de veces que el Cuentapasos llama a la función Rnd() antes de la llamada que nos interesa. Nos colocamos en el SmartCheck y buscamos las llamadas a Rnd(). Se producen dos llamadas, la nuestra es la segunda. Bajada a los infiernos Nos vamos al SoftIce, y colocamos un bpx rtcRandomNext. Volvemos al Windows y ejecutamos el Cuentapasos. Vemos como se carga el splash y ... BOOM... salta el SoftIce. Como la que nos interesa es la segunda llamada, pulsamos Ctrl+D y enseguida vuelve a saltar el punto de ruptura. No nos importa que es lo que hace la función rtcRandomNext para generar el número aleatorio, solo nos interesa saber que es lo que el Cuentapasos se propone hacer con él. Pulsamos F11 y salimos justo destrás del call que llamó a la función, en la dirección 0F0FE7C0h de la Dll del VB. La función Rnd() devuelve un número entre cero y uno, por lo que debe devolverlo en los registros de punto flotante. Para ver el contenido de esos registros desde SoftIce escribimos wf. Efectivamente, el número aleatorio generado se encuentra en el registro ST0. Empezamos a ejecutar paso a paso pulsando F8, y en seguida encontramos algo interesante:
`0F0FE7CC` | `mov al, [esi]`
`0F0FE7CE` | `inc esi`
`0F0FE7CF` | `jmp ds:[eax*4+0F0FED94]`
Cargamos en AL el contenido de la dirección apuntada por ESI (0F4h), incrementamos ESI y saltamos a una dirección que depende de lo que hemos leído con ESI. Si vemos a dónde esta apuntado ESI, descubriremos que está apuntando al código de nuestro querido Cuentapasos. Tras el salto, aterrizamos en 0F0FDE15. Seguimos pulsando F8 y vemos como se carga en la pila un byte de valor 0Ah que está en el código del Cuentapasos (siempre apuntado por ESI). Nuevamente, uno de los bytes del código del ejecutable (0EBh) es utilizado para efectuar un salto exactamente igual que antes, al contenido de la dirección 0EBh*4+0F0FED94=0F0FF140 Esta vez vamos a parar a 0F0FD5E5, en donde el 0Ah cargado anteriormente en la pila es volcado en el registro ST0, desplazando el número aleatorio al ST1. Se elimina el 0Ah de la pila y se vuelve a realizar otro salto igual que el anterior. Caemos en 0F0FDFCB. Vamos a parar un momento a ver si nos aclaramos la cabeza antes de que cometamos una locura con el monitor y la silla ... Una luz al final del pasillo... Nos desplazamos por la ventana de código y le echamos una mirada a los alrededores de donde estamos situados. Nos encontramos porciones de código de 5-10 instrucciones, todas terminadas por el mismo código que cité antes; carga de un byte apuntado por ESI en AL, incremento ESI y salto al contenido de la dirección calculada como EAX*4+0F0FED94h. Vamos a echarle un vistazo a esa especie de tabla de direcciones que parecen usar todas las porciones de código al terminar para proseguir con la ejecución. Miremos, por ejemplo, en la dirección 0F0FF140h, que se cálculo al final de la parte de código que cargo el 0Ah del código del Cuentapasos en la pila. Encontramos en dicha dirección, 0F0FD5E5h, que es la dirección de la porción de código en la que se carga el contenido de la pila en ST0. Las direcciones que se encuentran alrededor, apuntan igualmente a porciones de código que al finalizar, vuelven a recurrir a esta tabla para ver a donde saltan. Del infierno al cielo Dicho en otras palabras, el ejecutable (cpasos32.exe) nunca es ejecutado. Su código es leído e interpretado completamente por la Dll. Por lo tanto, todas y cada una de las instrucciones en ensamblador que conocemos, tendrán que tener una porción de código en la Dll que las sustituya. Volvamos al inicio, a la dirección 0F0FE7CCh, justo después de volver de la función Rnd(). Allí, cargamos del ejecutable un byte, 0F4h, que nos hizo saltar a una dirección en la que se cargo el siguiente byte del ejecutable, 0Ah, en la pila. Por tanto, nuestro "push 0Ah" del ensamblador que en código máquina se codifica como "6A 0A", en p-code es "F4 0A". El F4h será interpretado como "introducir el siguiente byte en la pila" por la tabla situada en 0F0FED94h, al ejecutarse con la Dll. No es más que la filosofía de Visual Basic, en la que las funciones más generales no se implementan en los ejecutables, si no en unas librerías comunes a todas las aplicaciones de VB, pero llevada a sus últimas consecuencias. No solo se implementan en las librerías las funciones más usuales si no que además se meten TODAS las instrucciones que puede ejecutar la aplicación. Algunos pensarán, "Pues bien, es tan solo una especie de traducción rara que lo que hace es complicar las cosas. Es un lenguaje completamente interpretado. ¿Dónde está esa ventaja del p-code?". Si alguien no lo ve claro ahora, cuando ataquemos a las rutinas de cálculo del periodo restante de evaluación, se le abrirán los ojos. Clavando un botón Recordamos por donde íbamos. Se había generado un número aleatorio entre 0 y 1 que almacenamos en el registro ST0. Posteriormente, cargamos un 0Ah del código del Cuentapasos en el registro ST0, desplazando el número aleatorio a ST1. Nos situamos en 0F0FDFCB, en donde multiplicamos el contenido de los registros ST0 y ST1 almacenando el resultado en ST0. Nuevo acceso a la tabla de interpretación y aparecemos en 0F0FD5B5, en donde el contenido del registro ST0 es redondeado al entero más cercano y volcado a la pila. Se almacenan los flags FPU (que son como los flags de toda la vida pero exclusivos de los registros de coma flotante) en el registro EAX. Una nueva visita a la tabla y saltamos a 0F0FDE28h para cargar un valor de 4 bytes del ejecutable (2) en la pila. En este caso es un "push" de una doble palabra, no de un byte. Otro paseo por la tabla y caemos en 0F0FE013. Recuperamos de la pila el 2 anterior en ECX, y lo que volcamos anteriormente del registro ST0 en EAX. Dividimos este último por el 2, almacenándose el cociente en EAX y el resto en EDX. Se vuelca el resto a la pila y nos disponemos a saltar a otro sitio. Del número aleatorio generado, tan solo nos queda el valor volcado a la pila. Al haberse dividido por dos, este valor será 0 o 1. La aplicación decidirá las posiciones de los botones según este valor. Nos hemos quedado con el resto de la división: round(x*10) / 2 donde 'x' es el número aleatorio generado, entre cero y uno. Pulsamos Ctrl+D para ejecutar completamente el Cuentapasos. Los que tuvierais como resto de la división un cero, ¿a qué tenéis el botón Aceptar a la izquierda? ¿A que los que lo tenéis en centro, el resto os daba uno? Podemos probar nuestra teoría tanto con el SmartCheck como con el SoftIce (más rápido). Ejecutamos varias veces el Cuentapasos, capturando el valor aleatorio devuelto. Calculamos metalmente cual sería el resto y vemos si se corresponde con la ubicación del botón Aceptar. Todavía podemos ir más lejos. ¿Cómo influye el resto de la división en la elección de qué hace un botón y qué hace el otro? Si recordamos lo que vimos con el SmartCheck, 'cmdAceptar' es un array de dos botones. 'cmdAceptar(0)' es el botón de la izquierda y 'cmdAceptar(1)' el del centro. Lo que la aplicación parece hacer, es asignar la función de Aceptar a 'cmdAceptar(resto)' y Salir a 'cmdAceptar(resto xor 1)'. Vamos a inmovilizar el puto botoncito de los.... . Tenemos que obligar a que el resto de la división tome siempre el mismo valor, cero o uno, independientemente del número aleatorio generado. Se nos pueden ocurrir dos formas: Cambiar las instrucciones. Después del ejercicio que acabamos de hacer, conocemos los códigos que corresponden a varias instrucciones (push, división, multiplicación,..).Así, por ejemplo, podríamos sustituir las instrucciones que multiplican, redondean y almacenan en pila el aleatorio generado por un "push 00000002" o un "push 00000001", que ya sabemos como se codifican en p-code. Así, en lugar de meter en la pila el aleatorio multiplicado por 10 y redondeado, meteríamos un entero fijo. Debemos tener la precaución de que el número de bytes de la instrucción falsa, sobreescriba completamente las instrucciones a las que sustituye, por que todavía no conocemos como es el "nop" ;-) Cambiar los operandos. Está es la solución más fácil y segura. ¿Qué operandos podemos cambiar? Pues tenemos el 0Ah (de la multiplicación) y el 0000002 (de la división). ¿Formas de obligar a que el resto de la división tome un valor fijo? Por lo menos dos: Multiplicar el aleatorio por cero en lugar de diez. Resto de la división cero, Aceptar siempre a la izquierda. Dividir por uno en lugar de dos. Resto de la división cero, Aceptar siempre a la izquierda. Si hemos descomprimido el ejecutable, podemos ir realizando los parches sobre disco para ir comprobando que efectivamente funcionan. Repetimos los pasos anteriores para ir anotando las direcciones en memoria en las que se encuentra el operando que deseamos cambiar, las cadenas que deberemos buscar para localizar estos operandos en el ejecutable descomprimido con un editor hexadecimal, etc.. Ya sabeis como se hace esto, ¿verdad?
REVENTAMOS EL P-CODE
Ahora, nos olvidamos del Cuentapasos, del SoftIce, de la vecinita esa que está tan buena.... y nos concentramos en lo que hemos descubierto sobre el p-code. Para enfrentarse contra programas en Visual Basic normales, muchos habréis leído tutoriales información sobre funciones clave de Visual Basic que en un momento dado nos pueden ser muy útiles. En casi todos, se recomienda la función __vbastrcomp, utilizada por Visual Basic para comparar cadenas de texto. Así, en programas en donde debes introducir una clave de registro alfanumérica, la aplicación, en algún sitio, deberá comparar la cadena introducida con la que la aplicación considera correcta. En programas escritos en otros lenguajes no interpretados, la rutina de comparación de las cadenas se incluye en el ejecutable. Sabemos que está ahí, pero no sabemos dónde. En Visual Basic, esa función es una de las que no se incluyen en el ejecutable, si no que se encuentra en las librerías compartidas con el resto de aplicaciones de VB. En el caso de __vbastrcomp, nos basta con colocar un punto de ruptura en esta función. Como puede ser llamada muchas veces, podemos limitar el punto de ruptura a que la cadena a comparar sea la que introducimos. La facilidad de desproteger los programas en Visual Basic, radica en esta compartición de funciones, que nos permite conocer, para algunas tareas específicas, donde colocar nuestro punto de ruptura. En el caso del p-code, siguen utilizándose estas rutinas compartidas, pero la novedad es que ahora, no solo se comparten una serie limitada de funciones, sino que todas las instrucciones que puede ejecutar un programa se han portado a una serie de pequeñas "funciones" en las librerías del VB. Examinando la librería del VB, podremos identificar gran cantidad de instrucciones, algunas muy importantes. El Cuentapasos me permitirá ilustrar la importancia de este hecho con más claridad. 30-6 = 36 Nos centramos en el cálculo de los días que nos restan para acabar el periodo de evaluación. La nagscreen y el 'acerca de...' del Cuentapasos nos muestran los días transcurridos y los días que nos restan. Ahora bien, ¿me puede decir alguien cómo puede cualquier aplicación (no solo el Cuentapasos), sabiendo los días transcurridos y el número de días máximo (30), calcular los días que nos quedan de evaluación?..... Llegados a este punto algunos pensareis que Mr.Blue está completamente "grillao". La respuesta es clara, restando. La resta, en ensamblador, se realiza mediante la instrucción "SUB". ¿Y cómo se hace en p-code? Pues como hemos visto, tiene que exisitir una porción de código en MSVBVM50.DLL que se utilice para hacer la resta. ¿Vais cogiendo la onda...? ¿Sentís el cosquilleo por la espalda ....? Si localizamos dónde se encuentra esa porción de código, podemos colocar un punto de ruptura en ella. Puesto que cualquier programa realiza un montón de restas, tendremos que limitar este punto de ruptura a que los operandos tomen el valor que nos interesa. En el caso de los periodos de evaluación, limitaremos a que el operando del que se sustrae, tenga como valor el número de días de evaluación, o el número máximo de ejecuciones, o... Las posibilidades son enormes. ¿Y dóde está esa función? Para encontrarla, podemos seguir traceando el programa, tarde o temprano hará una resta. O también podemos, si tenemos el VB, generarnos un p-code que haga restas, sumas y todas las funciones que queramos descubrir. No tenemos más que pasarlo por el SoftIce. La resta nos la encontramos en 0F0FDF78h, y el resto de funciones aritméticas se encuentran a su alrededor. Encontramos multiplicaciones de enteros, de flotantes, operaciones lógicas,... todo un surtido. La función de resta de enteros, al igual que la de suma de enteros que está más arriba, se encuentran para operadores de 16 bits y para operadores de 32 bits. Si queremos colocar puntos de ruptura, los tendremos que colocar por duplicado:
`0F0FDF78` | `pop eax`
`0F0FDF79` | `sub [esp], eax`
`0F0FDF7C` | `jo 0F0FDAC4`
`0F0FDF82` | `xor eax, eax`
`0F0FDF84` | `mov al, [esi]`
`0F0FDF86` | `inc esi`
`0F0FDF87` | `jmp ds:[eax*4+0F0FED94]`
Esta función, saca un operador de la pila y se lo resta al que queda en la misma. El resultado queda almacenado en la pila, saltando a 0F0FDAC4h en caso de overflow (EAX>(ESP)). En mi Cuentapasos, los días transcurridos son seis así que coloco un punto de ruptura en 0F0FDF79 de la forma`bpx 0F0FDF79 if (*(esp)==1E)&(eax==6)`. Lo colocamos también en la versión de 16 bits, por si las moscas: `bpx 0F0FDF62 if (*(esp)==1E)&(ax==6)`. Pulsamos Ctrl+D, ejecutamos el Cuentapasos y aparecemos en 0F0FDF79. Efectivamente, la aplicación está intentando hacer 30-6. Examinamos a donde apunta ESI. A la dirección 4ADF48h, del ejecutable del Cuentapasos. En realidad, ESI está ya apuntando a la siguiente instrucción a ejecutar (hace las veces de EIP). La instrucción que nos trajo aquí es el byte '0AEh' de 4ADF47h. Aquí, al igual que en el caso anterior podemos cambiar dos cosas: Los operandos. Para cambiarlos tendríamos que retroceder, poner un punto de ruptura antes y ver de donde saca el Cuentapasos los valores de los operandos. Para ello, lo mejor es colocar un punto de ruptura de acceso a memoria en direcciones anteriores a 4ADF47h (nuestros antiguos bpx, se convierten en bpm's). Después traceamos y vamos viendo con atención de donde van saliendo los operandos (ingeniería inversa hacia atrás). La instrucción. Podemos cambiar la resta por otra instrucción, o mejor, por una suma ;-) ¿Como hacemos esto? ¿Cuál es el código de la suma? Veamos, el código de la resta (32 bits) como hemos visto es 0AEh. La función de suma, nos la encontramos algo más arriba, en 0F0FDF3Dh. Para saber su código, nos vamos a la tabla de 0F0FED94h. El puntero a la función resta, debe encontrarse en: 4*0AEh + 0F0FED94h = 0F0FF04Ch Nos vamos allí, y efectivamente encontramos la dirección de la función resta de 32 bits. Vamos a ver donde está el puntero a la suma, no debe estar muy lejos. La encontramos cuatro palabras más arriba, en 0F0FF03Ch. El código de la suma será por tanto: (0F0FF03Ch - 0F0FED94h) / 4 = 0AAh El crack consitirá en sustituir en aquellos lugares en donde se realice la resta, 30-6 (6 en mi caso), el código de la operación resta (0AEh) por el de la suma (0AAh). Pulsamos Ctrl+D y vuelve a saltar enseguida el SoftIce. En esta ocasión, el culpable es el 0AEh situado en 4ADFA9h. Lo anotamos y volvemos a pulsar Ctrl+D. Nos aparece la nagscreen. Vamos a por el 'acerca de...'. Pulsamos Aceptar y abrimos el 'acerca de...' .. otra vez nos salta el SoftIce, otro código de resta en 04A4716h. Anotamos y Ctrl+D... otro más en 04A4769h. Este es el último. Repetimos la prueba, pero ahora, en el primer punto de ruptura, sustituímos los bytes 0AEh de 4ADF47h, 4ADFA9h, 4A4716h y 4A4769h por 0AAh. Realizar un cargador que, además de pulsar el botón de la nagscreen tal y como se ha explicado, realice los parches en memoria necesarios es sencillo. Tenéis un ejemplo en otro tutorial de Esiel2, que casualmente versa sobre el antecesor del actual Cuentapasos. Quitamos los puntos de ruptura, para probar, Ctrl+D y ...... ..... ;-) p-code rulezzz!!
CONCLUSIONES
Hemos mostrado como una protección como la de la nagscreen, correctamente implementada por el autor de la aplicación, puede ser fácilmente burlada porque el autor cometió el "error" de programar en Visual Basic. De paso, hemos sacado del baúl una técnica casi olvidada pero de una gran potencia, mediante la cual podríamos entre otras cosas, automatizar tareas, rellenar formularios automáticamente, gastar bromas, etc... Hasta aquí, nada nuevo. La novedad es el p-code. Inicialmente, en una primera toma de contacto, los programas p-code confunden y pueden terminar por aburrir al más intrépido ingeniero inverso. Está invención de Microsoft, desde el punto de vista teórico, puede parecer un gran avance ya que está a un paso de la creación de programas que puedan ejecutarse en cualquier máquina, independientemente del procesador o sistema operativo, siempre y cuando existan unas librerías diseñadas para dicha máquina o sistema operativo que interpreten el código del ejecutable. Nada más lejos de la realidad. Lo que en código nativo es una simple instrucción como la resta, se convierte en los programas p-compilados en varias instrucciones. Igual ocurre con el resto: push, add,... Esto, inevitablemente conduce a que las aplicaciones se enlentezcan o, lo que es lo mismo, que necesitemos más recursos (procesadores más rápidos) para poder mantener las mismas prestaciones. Esta política de Microsoft, por supuesto favorece a los fabricantes de hardware como Intel. Desde el punto de vista de la ingeniería inversa, supone una metedura de pata más de Microsoft (y van ...). Identificando convenientemente las funciones de interés, podremos facilmente desentrañar las protecciones de las aplicaciones. En este tutorial se ha expuesto un método que puede, sistemáticamente, echar abajo casi cualquier sistema de protección de aplicaciones p-compiladas, basado en limitaciones en el número de ejecuciones, periodos de pruebas, etc... Es cuestión de acostumbrarse, donde poníamos antes breakpoints de ejecución ahora ponemos breakpoints de acceso a memoria, donde el EIP decía ahora dice el ESI,... En cierto modo es triste ver como los desvelos de un programador por proteger su aplicación, colocando protecciones a diestro y siniestro, y creando un sistema de protección en conjunto bastante robusto, salta en mil pedazos por el hecho de haber utilizado Visual Basic y su "p-compilación". Si el futuro del Visual Basic pasa por el p-code ... o le abrimos los ojos a los programadores y reaccionan o creo que la ingeniería inversa de VB se va a volver muy aburrida. Para ilustrar dicho método, lo hemos utilizado contra una de las pocas aplicaciones p-compiladas que he podido encontrar (por ahora): nuestro entrañable Cuentapasos. Pero no creais que sus protecciones se limitan tan solo a la nagscreen y al periodo de evaluación. ¡Que va! El Cuentapasos no se acaba ahí, esconde muchos más secretos. Desviaros de la senda seguida en este tutorial para deshacer su protección, buscad caminos alternativos. Quedan muchas preguntas por responder: Dónde se guardan los días transcurridos? O se guarda la fecha de instalación? Dónde se guarda el otro protagonista de la resta, el límite de 30 días? ¿Y si lo cambiamos? Cómo se registra el programa? De dónde sale el código de compra? Por qué no cambia el número de días transcurridos si cambio la fecha? Cuándo se actualiza? El cálculo de los días transcurridos, es igual al ejecutar las primeras veces el Cuentapasos que después? Por qué en algunos ordenadores se cierra el programa a los pocos minutos de conexión? Investigad, y os encontrareis destellos de calidad del programador, de los que da gusto encontrar en un producto nacional, y alguna que otra metedura de pata, de las que hacen que te caigas al suelo de risa ;-D Como veis, se podría escribir un libro sobre el Cuentapasos (este tutorial ya es casi un libro). En cierta manera somos nosotros los que obligamos a los programadores a profundizar en el arte de la programación. Y gran parte de lo que aprenden en el sano intento de "jodernos", lo aplican en otras facetas de sus aplicaciones, mejorando sus prestaciones. Aunque la mayoría no lo quieran reconocer, parte de lo que saben nos lo deben a nosotros, los "crackers". Y por supuestísimo, parte de lo que sabemos nosotros se lo debemos a las "comeduras de coco" de ellos a la hora de implementar una protección. Agradecimientos:
Esiel2/TNT | .... ves lo que te has perdido? ... ;-P
Mr.BlacK/WkT! | .... por sus orientaciones en esta jungla ....
bb | .... no te conozco, no te he visto, pero tu tutorial es una gozada .....
Fravia | .... La Biblia On-Line ....
WkT! | .... por creer en un novato ....
Toby | .... ha sido todo un placer "pelearme" contigo (y olvídate del VB) ....
Mr. Blue
.... era una raza muy atrasada ....
.... al borde del siglo XXI aún utilizaban aplicaciones Micro$oft ...
[ 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