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

Programa Descompresión manual con ProcDump 2 W95 / W98 / NT
Descripción Herramienta descompresora universal, o casi ;-)
Tipo Tutorial técnicas de cracking
Tipo de Tutorial [X]Original, []Adaptación, []Aplicación, []Traducción
Url http://protools.cjb.net
Protección Empaquetador desconocido
Dificultad 1) Principiante, 2) Amateur, 3) Aficionado, 4) Profesional, 5) Especialista
Herramientas SoftIce, ProcDump
Objetivo Desempaquetar un ejecutable
Cracker Mr.Blue
Grupo Whiskey Kon Tekila
Fecha 6 de Abril de 2000

INTRODUCCION

Seguro que alguien se ha encontrado alguna vez con algún ejecutable que no consigue desensamblar. Quizá esté empaquetado... Le pasamos el Gettyp y, o no nos consigue descubri el empaquetador utilizado, o bien descubre el empaquetador pero no disponemos de ningún desempaquetador para el mismo.

En este caso, nos lo tendremos que currar un poco si queremos conseguir nuestro objetivo de desensamblar al objetivo. Para ello, si no tenemos ni idea lo primero que debemos hacer es leernos el magnífico tutorial "Descompresión manual con ProcDump" de Mr.Orange, en el que el presente tutorial se basa .

En ese tutorial, el problema se reducía a encontrar la última instrucción que se ejecutaba en la rutina descompresora (incluída en el ejecutable por el empaquetador) justo antes de comenzar a ejecutarse la primera instrucción del código original ya desempaquetado. Una vez localizado, se bloqueaba la ejecución del programa desde el SoftIce en esta última instrucción de la rutina descompresora, se realizaba un volcado completo con el ProcDump, y en el ejecutable creado se modificaba el punto de entrada para que apuntara a la primera instrucción del código desempaquetado. Ya, si querías hacer la jugada completa podías crear un script para automatizar la tarea con el ProcDump.

La búsqueda de este punto "crítico" en el que se acaba la rutina descompresora y comenzaba el código de la aplicación se realizaba traceando con el SoftIce y siguiendo la ejecución de la rutina desempaquetadora.

En este tutorial se presenta una técnica que en determinados casos puede ahorrarnos mucho tiempo al localizarnos automáticamente la dirección de la primera instrucción que se va a ejecutar de la aplicación original ya desempaquetada.

Breves apuntes de teoría de encabezados PE y empaquetadores

En esta sección solo se va a explicar brevemente lo estrictamente necesario para comprender la técnica que se va a explicar. Quién tenga interés en enterarse bien como funciona el formato PE (imprescindible si quieres ser un genio en la Ingeniería Inversa) que se lea mega-fantástico tutorial "Descabezando archivos PE (I)" de nuMIT_or.

Idealmente, en un ejecutable Win32 encontraremos una serie de secciones en las que se agrupan las instrucciones ejecutables, los datos necesarios para importar las funciones externas al programa, para exportar funciones, los recursos, etc... Las secciones, explicado de mala manera, no son más que "archivadores virtuales" en donde se guardan de manera ordenada y clasificada el contenido de los ejecutables.

Así, las instrucciones ejecutables se suelen guardar en una sección denominada .text, los recursos en .rsrc, las importaciones en .idata, etc... En el encabezamiento de cualquier archivo PE se almacena un índice con la organización interna del ejecutable. Entre muchas otras cosas, se almacena el punto de entrada al ejecutable (Entry Point) que indica al sistema operativo dónde está la primera instrucción del ejecutable. También se almacena en el encabezado una tabla con la descripción de todas las secciones presentes en el ejecutable, en qué parte del archivo se encuentran, su tamaño,...

Todos los empaquetadores, al comprimir un ejecutable deben adjuntarle las rutinas necesarias para deshacer esta compresión cuando el sistema operativo se dispone a cargar dicho ejecutable. La mayor parte de los empaquetadores lo que hacen es añadir una sección adicional en donde se almacena el código necesario para realizar la descompresión, y redirigir el punto de entrada del ejecutable hacia un punto de esta nueva sección, de forma que lo primero que ejecute el ordenador al cargar el ejecutable sea la rutina descompresora. La rutina descompresora se configura para que cuando finalice el desempaquetado salte al punto de entrada original del ejecutable, en donde ya debe encontrarse el código desempaquetado del ejecutable original.

En este tutorial, la misión será encontrar este punto de entrada original al que salta la rutina descompresora cuando ha finalizado su cometido.

Estableciendo los límites de la sección de código

Como hemos dicho, nuestro problema será localizar el punto de entrada original del ejecutable. ¿Qué sabemos sobre este punto?... Lo único que sabemos es que debe encontrarse en la sección de código, o en una de ellas si hay varias. Lo primero que debemos hacer es acotar los límites en los que se encontrarán las instrucciones de código una vez desempaquetado el ejecutable.

La tabla de secciones presente en el encabezado PE nos facilitará esta labor. Para ilustrar el procedimiento usaremos como "cobaya" un ejecutable comprimido con ASPack 2000. Para visualizar las secciones de cualquier ejecutable podemos utilizar muchas herramientas como el Gettyp o el mismo ProcDump. En mi ejemplo sale lo siguiente:

Name
Virt size
RVA
Phys size
Phys Ofs
.text
0009D000h
00001000h
0003CC00h
00000600h
.data
00020000h
0009E000h
00005400h
0003D200h
.tls
00001000h
000BE000h
00000200h
00042600h
.rdata
00001000h
000BF000h
00000200h
00042800h
.idata
00003000h
000C0000h
00001000h
00042A00h
.edata
00005000h
000C3000h
00004200h
00043A00h
.rsrc
001F1000h
000C8000h
0006B000h
00047C00h
.reloc
0000C000h
002B9000h
00000000h
000B2C00h
.aspack
00002000h
002C5000h
00001A00h
000B2C00h
.rsrc
00001000h
002C7000h
00000000h
000B4600h

 

En este caso he utilizado el Gettyp 2.52, que no ha podido reconocer el empaquetador utilizado, en parte porque el ASPack 2000 todavía no existía cuando se lanzó esta versión ;-)

¿Qué podemos sacar de esta información? Lo primero es que, a no ser que el programador nos quiera despistar, el ejecutable está comprimido con el ASPack... Tampoco hay que ser un lumbrera para sacar esta conclusión a la vista de los nombres de las secciones ¿verdad?

Bromas aparte, sabemos que está comprimido porque las secciones tienen un tamaño físico menor que su tamaño virtual. El tamaño virtual que aparece en la primera columna es el tamaño que ocupará cada una de las secciones en memoria, una vez cargado el ejecutable. El tamaño físico se muestra en la tercera columna e indica el espacio en bytes que ocupa dicha sección en el archivo físico, en el disco. Lógicamente, si el tamaño físico es mucho menor que el tamaño que nos va a ocupar en memoria una vez cargado es porque el ejecutable está comprimido.

Las otras dos columnas expresan a partir de dónde se encuentran cada una de las secciones. La cuarta columna expresa el desplazamiento físico, o lo que es lo mismo, a partir de qué offset del fichero comienza cada sección. Así, la información contenida en las dos últimas columnas nos acotan los límites de cada sección dentro del fichero.

La segunda columna es el dirección virtual relativa de memoria (Relative Virtual Address : RVA) donde comenzará cada sección una vez cargado el ejecutable. ¿Pero "relativa" a qué? Pues a la dirección base de la imagen del ejecutable en memoria (Image Base). Esta dirección viene definida en el encabezamiento PE e indica a partir de qué dirección virtual de memoria debe cargar el sistema operativo al ejecutable. Para ver cual es la Image Base de nuestro ejemplo podemos utilizar el ProcDump, con el que encontramos que la base es 00400000h.

Ya podemos acotar los límites de la sección .text en memoria, una vez descomprimido el ejecutable:

Inicio = Base + RVA = 400000h + 1000h = 401000h
Final = Inicio + Virt_size - 1 = 401000h + 9D000h = 49DFFFh

 

Apuntamos los límites... 401000h - 49DFFFh.

Amigo SoftIce

Acudimos al SoftIce para terminar la faena. De hecho será el SoftIce el que nos va a decir cuál es el punto de entrada original del ejecutable.

Puesto que conocemos entre que dos direcciones de memoria se va a encontrar este punto de entrada original, bastará con colocar breakpoints de ejecución en ese rango de memoria. En cuanto la aplicación intente ejecutar alguna de las instrucciones contenidas en ese rango, el SoftIce nos dará el control del asunto... Pero, ¿tenemos que llenar todo el rango con bpx's?

Afortunadamente no.. El procesador para ejecutar una instrucción tiene, lógicamente, que leerla antes. Y todos sabemos como cubrir todo un rango de memoria con breakpoints de lectura ¿a que sí?.

Este breakpoint debemos colocarlo desde el SoftIce, y una vez que estemos dentro del código de la rutina descompresora. El breakpoint mágico que buscamos es algo así como esto:

bpr Inicio Final r

Con esto colocamos un breakpoint de lectura a cualquiera de las direcciones comprendidas entre la dirección Inicio y Final. En el momento que la aplicación realice el primer acceso de lectura a alguna de las direcciones comprendidas dentro del rango, saltará el SoftIce.

Genial, ¿verdad?. Pues no. Esto tiene una pega y gorda. ¿Que ocurre si los datos de la sección comprimida se encuentran dentro de ese rango? Cada vez que la rutina descompresora accediera a los datos comprimidos para descomprimirlos nos saltaría el SoftIce...

Tenemos que asegurarnos que el breakpoint salte solo cuando se ejecute alguna de las instrucciones del rango, no cuando se accedan los datos en modo lectura. Gracias a NuMega, su SoftIce admite breakpoints condicionales. Tendremos que complicar un poquito más el breakpoint. El breakpoint definitivo quedaría tal que así:

bpr Inicio Final r if (eip>=Inicio)||(eip<=Final)

La condición del breakpoint obligará a que este no salte hasta que el contenido del registro EIP (puntero a programa) no contenga una dirección contenida entre inicio y el fina l de la sección .text. En nuestro ejemplo el breakpoint se introduciría así:

bpr 401000 49DFFF r if (eip>=401000)||(eip<=49DFFF)

Rematando la tarea

Ya tenemos ante nuestras narices la pantalla del SoftIce con la aplicación detenida en la primera instrucción que se va ejecutar del ejecutable original. Conviene tracear un poquito para asegurarse que la ejecución no regresa al la rutina descompresora. En el caso del ASPack 2000, la primera vez que salta el breakpoint es sobre una instrucción RET situada al inicio de la sección .text tras el que retorna nuevamente a la rutina descompresora. En este caso tendríamos que salir del SoftIce con CTRL+D y esperar a que vuelva a saltar el breakpoint.

¿Que cómo sabemos que la ejecución vuelve a la rutina descompresora? Al igual que hemos sacado los límites de la sección .text, también podríamos sacar los límites de la sección en la que se aloja la rutina descompresora, por lo que no tendríamos problemas para saber qué es lo que se está ejecutando en todo momento.

Bueno, suponemos que ya tenemos delante la pantalla del SoftIce con el EIP situado en el punto de entrada original de la aplicación. Tal y como se explicó en el tutorial de Mr.Orange, lo primero es bloquear la ejecución de la aplicación en el punto de entrada. Si suponemos que la dirección del punto de entrada es, por ejemplo, 401500h tendremos que ensamblar un:

401500 jmp 401500

o lo que es lo mismo:

401500 jmp eip

Antes de modificar tenemos que anotar los dos bytes que sobreescribiremos al introducir esta instrucción. Pulsamos CTRL+D, arrancamos el ProcDump y nuestra aplicación debe aparecer entre la lista de procesos activos que nos ofrece el ProcDump. Por supuesto la aplicación debe estar bloqueada, sin mostrar ventanas ni nada de nada...

Sobre nuestro proceso pulsamos el botón de la derecha, y en el menú seleccionamos "Dump (Full)". El siguiente paso es redirigir el punto de entrada del programa, que ahora mismo estará apuntando a la rutina descompresora, hacia el punto que hemos hallado nosotros. Esta tarea la podemos realizar con el mismo ProcDump, teniendo en cuenta que el punto de entrada debemos introducirlo relativo a la base del archivo. En nuestro ejemplo, si el punto de entrada localizado desde el SoftIce es 401500h, el punto de entrada que debemos introducir en el encabezado PE con el ProcDump es 1500h.

Aún nos queda solucionar un pequeño detalle. Si ejecutamos el fichero desempaquetado, se quedará bloqueado por culpa de aquel JMP que introducimos desde el SoftIce para bloquear el proceso. Para desbloquearlo usaremos nuestro editor hexadecimal preferido, aunque antes debemos localizar en qué offset del fichero se encuentra el punto de entrada. Tendremos que acudir a la tabla de secciones del nuevo ejecutable para solucionar este problemilla:

Name
Virt size
RVA
Phys size
Phys Ofs
.text
0009D000h
00001000h
0009E000h
00000600h

 

Sabemos que la sección .text comienza en la RVA 1000h, a la que le corresponde un offset en el fichero de 600h bytes. El punto que tratamos de localizar está en la RVA 1500h, luego en el fichero se encontrará en la posición:

Offset = 1500h - RVA(.text) + Phys_Ofs(.text)
Offset = 1500h - 1000h + 600h = 1100h

Tan solo nos queda abrir el nuevo ejecutable con el editor hexadecimal, ir al offset calculado en el que nos debemos encontrar los dos bytes del JMP bloqueante (EB FE) y sustituirlos por los dos originales. Grabamos, salimos, probamos y ....

Conclusiones

Puede ocurrir que nos encontremos con varias secciones en las que pudiera encontrarse el punto de entrada original, ya que nada impide que un programa tenga más de una sección de código. En este caso, deberán colocarse un breakpoint por cada una de las secciones sospechosas de contener el punto de entrada original.

La técnica presentada permitirá "batir" a la gran mayoría de compresores que nos podamos encontrar. Por supuesto esta técnica no es el fin de los compresores. El breakpoint condicional presentado será inútil cuando el compresor introduzca la rutina descompresora o parte de ella en la misma sección del código ejecutable. Además, si la rutina descompresora incluída con los ejecutables lleva algún tipo de protección anti-SoftIce, se nos fastidiará el SoftIce y el breakpoint milagroso... En estos casos, no habrá otra solución que utlizar otro debugger distinto contra el que la rutina descompresora no tenga nada que objetar o bien ponernos el traje de buceo y deshabilitar la protección anti-debugger.

El procedimiento seguido para determinar los límites de la sección de código puede fallar en determinados casos. El parámetro Virt_size presente en la tabla de secciones es modificable, lo cual significa que no es necesario que contenga el valor correcto para que el ejecutable funcione (gracias Crimson). Esto implica que en algunos casos el contenido de este parámetro puede que no tenga nada que ver con la realidad, y aún así el ejecutable funcionará correctamente. Este valor ha podido ser manipulado por el empaquetador, por el programador del programa o incluso puede que el compilador/enlazador utilizado en la creación del ejecutable original no lo haya generado correctamente. La determinación del tamaño de las secciones deberá realizarse por otros medios alternativos, por ejemplo teniendo en cuenta la distancia entre las RVA de dos secciones contiguas. En estos casos, a la hora de colocar el breakpoint, podremos suponer que una sección comienza en su RVA y termina en la RVA de la siguiente sección.

Por supuesto esta modificación artificial del tamaño de las secciones no puede realizarse a lo loco. En las pruebas realizadas se ha comprobado que en algunos ejecutables el parámetro Virt_size puede hacerse tan pequeño como se quiera sin perjudicar el correcto funcionamiento del ejecutable, pero a la hora de "simular" un tamaño mayor, si el nuevo tamaño es lo suficientemente grande como para que el final de la sección caiga en el interior de la siguiente se pueden producir problemas... aunque no siempre.

Otra limitación obvia es el mismo ProcDump, que si no es capaz de reconstruir adecuadamente el resto de secciones el ejecutable obtenido no será válido, aunque en este caso es posible que se desensamble sin problemas.

El breakpoint presentado no es la solución definitiva contra empaquetadores, pero ayudar.... ayuda ;-)

 

Agradecimientos:

  • Mr.Orange por su tutorial.
  • Mr.Crimson y Mr.Pink por su paciencia, por las aclaraciones sobre las posibles limitaciones de la técnica presentada... y por sus correcciones ;-)

Mr.Blue [WkT!]

[ 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