|
|
 |
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 |
|
|
|
|
|