ESTUDIO COLECTIVO DE DESPROTECCIONES
WKT Tutorialz Site
WKT
Programa  Reversing with ToPo v1.0 W95 / W98 / NT 
Descripción  Pequeña utilidad para ampliar ejecutables y DLLs
Tipo  Herramienta win32ASM para Ingeniería Inversa
Url  http://i.am/MrCrimson
Protección  Ninguna. Freeware cortesía de MrCrimson/[WkT]
Dificultad  1) Principiante, 2) Amateur, 3) Aficionado, 4) Profesional, 5) Especialista 
Herramientas  Uedit y (opcionalmente) SoftICE
Objetivo  Ampliar las posibilidades de cracking
Cracker  MrCrimson/[WkT!99]
Fecha  23 de Octubre de 1999 
Introducción

Los esquemas de protección de software pueden dividirse en dos grandes grupos. Los que se apoyan en dispositivos de hardware y los que lo hacen exclusivamente en el software.
Los metodos soft pueden a su vez dividirse en aquellos en los que la limitación de la versión es condicional (Serial, fecha limite, numero de usos) y en los que es incondicional (opciones físicamente eliminadas del codigo)

Salvo la elegante alternativa de los KeyMakers y los RegPatches, el resto de  las desprotecciones exige que se realicen CAMBIOS en ficheros binarios, ya sean datos o instrucciones de codigo.

Una regla de oro del crackista (que  palabro!) consiste en que la modificación binaria propuesta debe PARCHEAR los contenidos existentes SIN AÑADIR un sólo byte.
Bueno... en la inmensa mayoría de los casos esto no resulta un problema puesto que lo que se pretende es cambiar es:

  • el valor de un flag
  • una condición de bifurcacion
  • la ocurrencia de un CALL 
No sólo NO es un problema sino que además establece el alcance del RETO: 

"...sólo modificar en el fichero los bytes existentes y además en mínimo número..."

Sin embargo, hay un "paquete" de situaciones en las que esta restricción se hace un tanto insoportable y que podría responder a la siguiente clasificación:

  • Empaquetados/Encriptados: el código NO está accesible en el fichero.
  • Reconstrucción de código no presente.
  • Emuladores de llave de hardware.
  • Adición de NUEVOS elementos a un programa sin tener el código fuente.
Qué tienen en común estos casos? pues se me ocurren dos cosas:
  • Necesidad de un área "espaciosa" para escribir nuestro código añadido.
  • Necesidad de convivir en el mismo espacio de memoria del programa.
Puede verse que esto trata de INGENIERÍA INVERSA en el aspecto más amplio y no sólo en lo que respecta al cracking. 
Pero cómo se consigue expandir el área de código de un programa en una forma segura y estable...? La respuesta tiene nombre de bicho: ToPo v1.0
ToPo: Arreglando el cuarto de invitados

ToPo v1.0 es una utilidad que permite añadir a un ejecutable (o DLL) una extensión vacía para ser rellenada con código fresco. Podemos añadir típicamente una decena de estas "extensiones ortopédicas" dependiendo de la estructura interna del fichero. Entonces podremos hexeditar y añadir nuevas rutinas, parchear el código en memoria, reconstruir código incompleto, diseñar emuladores de dongle, trainers para juegos...y todo ello sin limitación de espacio! (~ 10 Mb)

El principio en que se basa la herramienta es muy simple. La estructura del formato PE (ejecutables y librerías Win32) consiste en una colección de cabeceras con multitud de datos (muchos no usados para nada). 

Entre las cabeceras más importantes están las llamadas cabeceras de sección. Son paquetes de 40 bytes donde se definen las características de los bloques de datos del programa (código, constantes, iconos, punteros, etc). 

La zona en que habitan estas cabeceras suele disponer de espacio libre para añadir del orden de 10 nuevas y nada impide que un programa tenga mas de una seccion de código (como ejemplo sirvan los empaquetadores en los que la rutina de desempaquetado suele alojarse en una sección propia).

Así las cosas podemos DECLARAR  una nueva sección del tamaño que nos apetezca y AÑADIRLA al final del fichero sin ningún tipo de problemas. El nuevo invitado tendrá un "segmento" de direcciones a las que (y desde las cuales) se podrá acceder al resto de la imagen del fichero en memoria.

Reinventando los virus, MrCrimson? bueno cada cual es libre de inocular lo que le parezca... personalmente prefiero darle aplicaciones constructivas como el CrAcKiNg!
 

ToPo: Bichos en el jardín 





Este es el cuadro de diálogo de la herramienta perforadora. El usuario solo tiene que seleccionar su fichero víctima (32 bits only, plz) e indicar cuantos bytes necesita para escribir su código. El click en "Do It!" informa acerca del éxito del resultado y de donde puede localizarse el nuevo inquilino (tanto en memoria como en fichero).

Las opciones permiten:

  • Backup file: saludable copia de seguridad antes de inyectar el alien.
  • Redirect Entrypoint: Hace que el programa COMIENCE su ejecución en la nueva sección (".topo") al final de la cual se vuelve al inicio natural del programa. Esto nos permite realizar los cambios que sean necesarios en el código mapeado en memoria. 
  • Make code writable: Permite que podamos escribir tranquilamente en el código principal. Necesario si vamos a parchear. 


Y con esto está todo dicho. Vamos a ver que se puede hacer en la práctica.

Ejemplo (1): Adición de código nuevo

Como víctima utilizaremos un programa del que todos disponemos: el bloc de notas (ejecutable: "notepad.exe" alojado en "\windows").
Vamos a añadir un trozo de código de 100 bytes con una llamada API. Para ello haz una copia en otro directorio y arranca ToPo v1.0:
 
  • selecciona: notepad.exe
  • tamaño a añadir: 100 bytes
Seleccionar opciones:
  • Backup
  • Redirect entrypoint


Una vez hecho se nos informa de que la nueva sección estará disponible en el offset de fichero x8A00h y si trazamos con Winice lo encontraremos a partir de la dirección :40C000.
La opción "redirect entrypoint" ha cambiado el EntryPoint original :401000 (inicio de la sección ".code") por  :40C000 (inicio de la sección ".topo"). Además, al final del bloque ha insertado un JMP 401000 de forma que despues de nuestros arreglos regresamos automaticamente a la ejecución natural del programa.

Vamos a hacer que el NOTEPAD genere una especie de splash precaria esperando confirmación antes de arrancar. Todos conoceis ya la sintaxis de la función MessageBox:

push Estilo
push Titulo_ventana
push Mensaje
push Handle_ventana_Padre
Call MessageBox

Como estilo elegimos el más simple: un simple botón "OK" (valor 0)
Como título pondré: "MrCrimson dice:"
Como mensaje: "Tomando el control desde el principio"
Nuestra ventana no tendra padre (sob!) con lo que el handle sera cero.

Ahora, la forma más fácil de proceder consiste en arrancar Winice y con el comando "A"
comenzar a escribir el código en nuestra parcela. Cómo definimos las strings...? muy fácilmente:

-Escribimos todas nuestras constantes y variables a partir del 3er byte.
-Reservamos los dos(*)  primeros bytes para un JMP que salte por encima de estas declaraciones. 

(*) el número exacto dependerá del tamaño de los datos).

Por ejemplo:

:40C000 jmp 40C005
:40C002 686F6C6100 <---- "hola" (00 terminador de strings)
:40C005 continua el codigo

el offset :40C002 contiene la cadena "hola" y podemos usarlo como argumento en nuestras llamadas a función. De esta misma forma se definen variables y constantes en general (el segmento ".topo" tiene atributos de leible, escribible y ejecutable!).

No os preocupeis si Winice o W32Dasm interpretan las variables así definidas como instrucciones de código inexistentes. Es normal. Intentan desensamblar lo inensamblable...

Después escribimos los pushes, la llamada a MessageBox y dejamos el resto como está (una sucesión de NOPs hasta el JMP [original_entrypoint]. Copiamos con buena letra el código ensamblado resultante en un papelito y abandonamos Winice no sin antes haber borrado lo escrito restaurando los NOPs originales. 

Ahora hexeditamos (Uedit) el notepad.exe, nos vamos al offset donde comienza la sección ".topo" y tecleamos el churro de bytes. Cerramos y ejecutamos notepad obteniendo la ventana esperando confirmación para iniciar el "Bloc de Notas".

En este ejemplo, el código añadido tiene este aspecto:

//******************** Program Entry Point ********
:0040C000 EB3B                    jmp 0040C03D ;--> salto hasta 
                                                    después de
                                                    definir las
                                                   "strings" 
;Codigo inexistente... se trata de las strings:
;T=54h, o=6Fh, m=6Dh, a=61h, m=6Dh,...."Tomamos el control..."
:0040C002 54                      push esp 
:0040C003 6F                      outsd 
:0040C004 6D                      insd
:0040C005 61                      popad
:0040C006 6D                      insd
:0040C007 6F                      outsd
:0040C008 7320                    jnb 0040C02A
:0040C00A 65                      BYTE 065h
:0040C00B 6C                      insb
:0040C00C 20636F                  and byte ptr [ebx+6F], ah
........................................
; Una vez definidas las variables, comenza el código:

:0040C03D 6A00                    push 00000000 ; estilo 0
:0040C03F 682CC04000              push 0040C02C ; "MrCrimson says:"
:0040C044 6802C04000              push 0040C002 ; "Tomamos el ctrl..."
:0040C049 6A00                    push 00000000 ; Padre 0

* Reference To: USER32.MessageBoxA
                                  |
:0040C04B FF1530744000            Call dword ptr [00407430]
:0040C050 90                      nop
:0040C051 90                      nop
:0040C052 90                      nop     ; hasta completar 100
......................................
:0040C05F E99C4FFFFF              jmp 00401000 ; notepad normal
 

La llamada a MessageBox parece un tanto misteriosa (offset :407430) pero los bytes que componen la instrucción pueden obtenerse fácilmente ensamblando desde Winice la linea:

call dword ptr[MessageBoxA]

NOTA: En la primera versión de este tute un descuido me llevo a codificar esta llamada mediante:
[Winice (A)ssemble] call MessageBox
El resultado era que funcionaba perfectamente en mi ordenador pero no en otros...
Para que el enlace dinámico con la función en la dll (en este caso user32.dll) proceda correctamente, la llamada debe realizarse como se ha indicado.

Una vez completado ejecutamos notepad y obtendremos una ventana con una mesaje chorra esperando un click en el OK para proseguir con el "bloc de notas".

Este ejemplo ha mostrado como INCRUSTAR un trozo de código de ~100 bytes donde aparentemente no cabría un alfiler. :o)

Ejemplo (2): Parcheador de Empaquetados Universal

Por lo general, los empaquetadores/encriptadores de ficheros operan según una idea común.
El código util está cifrado de manera que desensamblando no se puede analizar su comportamiento. En el momento de la ejecución entra en juego una rutina que escribe en memoria la versión no-encriptada y a partir de ese momento todo transcurre como si de un ejecutable normal se tratara.
La forma de atacar a estos programas tan poco comunicativos se basa en cargadores o loaders. 
Un cargador es un programa  capaz de cargar en memoria otros ejecutables y detenerse antes de comenzar su ejecución de manera que puede aprovecharse la ocasión para realizar los oportunos parcheos. Sin embargo son fáciles de engañar y tienen problemas para "seguir la pista" a nuevos threads (al menos los que he podido examinar).

ToPo no tiene este tipo de problemas porque genera un "enemigo interior" que puede actuar justo después del desencriptado del código cualquiera que sea el algoritmo. Como ejemplo veremos como funciona con un par de empaquetadores de élite.
 

(a) UPX v0.84

Intentamos repetir la gracia anterior con una versión de Notepad.exe empaquetada con UPX.
para ello tecleamos en la línea de comandos del DOS:

C:\> upx -9 notepad.exe

y ya está. Pfff....reducido a la mitad de tamaño!. Me encanta este packer :o)

Ahora debemos añadir nuestra burbuja pero esta vez NO queremos redirección del entrypoint ya que este hace referencia al comienzo de la rutina de descompresión y no
al código del notepad.
Ok, tenemos el Notepad.exe, empaquetado con UPX e inoculado con una sección topo de 100 bytes (no necesitamos tantos en realidad...). Trazamos un poco para ver como respira el asunto poniendo un BPM en 401000 (el entrypoint original de notepad). Dejamos que fluya el código con F5 y... reaparecemos en :40C080. Se está escribiendo el código desempaquetado en su ubicación original... es decir que estamos en la rutina desempaquetadora. Trazamos un poco hacia el final para comprobar que la cosa acaba con un JMP al entrypoint del programa empaquetado (:401000).
Esta rutina está perfectamente accesible por lo que desde UltraEdit modificamos el salto al inicio de nuestra sección ".topo" (JMP 40E000).

A partir de aqui todo se reduce al caso anterior. Disponemos del código desempaquetado en memoria ANTES de comenzar la ejecución. Añadimos, como antes, nuestras "strings", los pushes y la llamada a messagebox (no copiar directamente porque las direcciones relativas no son las mismas). Por ultimo cerramos con un JMP al entrypoint de notepad :401000.
 

(b) ASPack v1.08.03
 

Este packer presenta algunas diferencias con el anterior. La compresión no es tan buena pero a cambio establece "potentes" medios de protección del código empaquetado. Siguiendo el ejmplo anterior intentaremos ejecutar nuestra lamer-splash al comienzo de la ejecución del notepad.

Con las pautas ya explicadas (BPM a la primera instruccion del notepad, :401000) intentamos cazar el final de la rutina de descompresión para saltar desde allí a nuestra fortaleza de código en blanco. Y facilmente la encontramos en:

.........................
:40C554 MOV [ESP+1C],EAX
:40C558 POPAD
:40C559 JNZ 40C563
:40C55B MOV EAX,1
:40C560 RET 000C
:40C561 PUSH EAX
:40C561 RET <------ Esto nos lleva al comienzo de "notepad"
 

Cuando abrimos "notepad.exe" con Uedit para buscar este trozo de código descubrimos que NO ESTA. Impresionante. La rutina que desencripta esta también encriptada.
No problemo. Buscaremos el código que desempaqueta al desempaquetador siguiendo la misma tecnica de los breakpoints en memoria. Así llegamos a:

:40C0AE REPZ MOVSD
:40C0B0 MOV ECX,EAX
:40C0B2 AND ECX,03
:40C0B5 REPZ MOVSB <-----Copia el unpacker unpacked!
:40C0B7 MOV EAX,[EBP+4450B5]

Este código SI esta accesible (bueno estaría que no) y perfectamente editable. Vamos pues con Uedit y modificamos la instrucción en :40C0b7 según:

:40C0B7 JMP 40F000 <---salto a la sección ".topo"

No debemos preocuparnos por las modificaciones que hacemos para acceder al ".topo" porque antes de regresar restauraremos todo.
Al comienzo del ".topo" codificamos lo siguiente (ayudandonos de (A)ssembly en Winice):

;Parcheamos el codigo en :40C554 por "jmp 40f032"
;Es un salto a la mitad de la nueva sección donde
;pondremos nuestra messagebox

:0040F000 mov dword ptr [0040C554], 002AD9E9
:0040F00A mov byte ptr [0040C558], 00

;Restauramos el contenido en :40c0b7 a su
;código original "mov eax,[ebp+4450b5]"

:0040F011 mov dword ptr [0040C0B7], 50B5858B
:0040F01B mov word ptr [0040C0BB], 0044

;regresamos a :40c0b7 como si nada hubiera
;pasado...

:0040F024 jmp 0040C0B7

Perdidos...? Recapitulemos: hemos parcheado con Uedit la rutina que desempaqueta al desempaquetador del codigo. El objeto del parche es establecer un acceso a ".topo" que
contendrá un nuevo parche en memoria. Este parche modifica el desempaquetador final para que salte de nuevo a ".topo" (hacia la mitad de la sección) ANTES de lanzar el Notepad.
Creo que lo realmente lioso es intentar contarlo...hacerlo es muy fácil.

La segunda mitad de ".topo" contiene el siguiente codigo más familiar:

:0040F032 60                      pushad       ;por seguridad
:0040F033 EB3B                    jmp 0040F070 ;saltamos las strings

; strings usadas para la ventana messagebox
:0040F035 54                      push esp
:0040F036 6F                      outsd
:0040F037 6D                      insd
.......................................
:0040F069 7061                    jo 0040F0CC
:0040F06B 636B20                  arpl dword ptr [ebx+20], ebp
:0040F06E 2000                    and byte ptr [eax], al
 

:0040F070 6A00                    push 00000000 ;estilo 0
:0040F072 685FF04000              push 0040F05F ;string titulo
:0040F077 6835F04000              push 0040F035 ;string mensaje
:0040F07C 6A00                    push 00000000 ;Padre/Hwnd

* Reference To: USER32.MessageBoxA
                                  |
:0040F07E FF1530744000            call dword ptr [00407430]

;Restauramos las instrucciones que habia antes del salto
;"40C554 mov [esp+1c],eax"
;"40C558 popad"

:0040F084 C70554C540008944241C    mov dword ptr [0040C554], 1C244489
:0040F08E C60558C5400061          mov byte ptr [0040C558], 61
:0040F095 61                      popad
:0040F096 E9BAD4FFFF              jmp 0040C554

Y así acaba esta historia. 
De manera muy similar se pueden parchear hasta la saciedad todos los packers/crypters. Al menos así ha sido con los que he probado. 

Conclusión
La técnica ilustrada resuelve dos inconvenientes clásicos del Arte del Cracking. Por una parte la tradicional limitación de espacio que nos impide en ocasiones generar productos más creativos. Por otro lado se trata de una estrategia "en caliente" puesto que trabaja desde dentro del código como una parte más del programa original.

Como inconveniente citar el hecho de que cambia el tamaño de fichero y por tanto los clasicos parcheadores de diferencias no trabajarian con éxito. 
Un crack basado en esta técnica no sería un parcheador sino más bien un "inyector".

Eso es todo por el momento. Este tutorial era originalmente más largo pero creo que con los ejemplos propuestos queda claro lo que se puede hacer con esta técnica.
Espero que hayas pasado un buen rato leyendo este ladrillo.

Los ejemplos de este tute están aquí (47kb): ejemplos.zip
ToPo v1.0 esta disponible en http://i.am/MrCrimson

Up The Hammer!
MrCrimson/[WkT!99]




[ Entrada | Documentoz GenΘricoz | WKT TEAM Main Site ]
[ Todo el ECD | x Tipo de Protecci≤n | x Fecha de Publicaci≤n | x orden AlfabΘtico ]