Leveraging UnsafeNativeMethods

Using Dynamic Lookup

  • This approach dynamically resolves function addresses in memory.

  • It relies on two key Win32 API functions:

    • GetModuleHandle – Obtains a handle (base address) to a loaded DLL.

    • GetProcAddress – Resolves a function’s memory address within a DLL.

  • By using these APIs, we can locate functions without Add-Type, allowing pure in-memory execution.

Finding Preloaded Assemblies in PowerShell

Instead of creating a new assembly, we attempt to reuse existing ones. The following PowerShell script lists preloaded assemblies that may already contain the necessary methods:

$Assemblies = [AppDomain]::CurrentDomain.GetAssemblies() 
 
$Assemblies | 
  ForEach-Object { 
    $_.GetTypes() | 
      ForEach-Object { 
          $_ | Get-Member -Static | Where-Object { 
            $_.TypeName.Contains('Unsafe') 
          } 
      } 2> $null 
    } 
  • This script iterates through all loaded assemblies and filters for types containing Unsafe.

  • The goal is to identify an existing assembly that already contains GetModuleHandle and GetProcAddress.

Extracting Functions from System.dll

From the analysis, we discover that GetModuleHandle and GetProcAddress exist inside Microsoft.Win32.UnsafeNativeMethods, which is part of System.dll.

We retrieve a reference to this assembly dynamically:

$systemdll = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object {  
  $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }) 
   
$unsafeObj = $systemdll.GetType('Microsoft.Win32.UnsafeNativeMethods') 
  • Step 1: Filters assemblies from the Global Assembly Cache (GAC).

  • Step 2: Identifies System.dll as the assembly containing UnsafeNativeMethods.

  • Step 3: Retrieves a reference to Microsoft.Win32.UnsafeNativeMethods using GetType().

Resolving Function Addresses

Here we are finding the address of the user32.dll using GetModuleHandle

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

$GetModuleHandle.Invoke($null, @("user32.dll")) 

Now we try to find GetProcAddress

$GetModuleHandle = $unsafeObj.GetMethod('GetProcAddress')

 # this wont work due to a error
 
 
 # But when we use t search. 
$unsafeObj.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$_}} 


Name                       : GetProcAddress
DeclaringType              : Microsoft.Win32.UnsafeNativeMethods
ReflectedType              : Microsoft.Win32.UnsafeNativeMethods
MemberType                 : Method
MetadataToken              : 100663840
Module                     : System.dll
IsSecurityCritical         : True
IsSecuritySafeCritical     : True
IsSecurityTransparent      : False
MethodHandle               : System.RuntimeMethodHandle
Attributes                 : PrivateScope, Public, Static, HideBySig, PinvokeImpl
CallingConvention          : Standard
ReturnType                 : System.IntPtr
ReturnTypeCustomAttributes : IntPtr 
ReturnParameter            : IntPtr 
IsGenericMethod            : False
IsGenericMethodDefinition  : False
ContainsGenericParameters  : False 

Combining all together

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

$user32 = $GetModuleHandle.Invoke($null, @("user32.dll"))

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

Powershell Function to find a module.

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)) 
} 

Creating a Delegate Type Using Reflection

Since PowerShell does not support delegate, we manually define an assembly, module, and type in memory.

Step 1: Create an Assembly Object

We first create an in-memory assembly:

$MyAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate')
  • This assembly will hold our dynamically created delegate type​.

Step 2: Define a Dynamic Module

We set up the assembly to run in memory:

$Domain = [AppDomain]::CurrentDomain
$MyAssemblyBuilder = $Domain.DefineDynamicAssembly($MyAssembly,  
  [System.Reflection.Emit.AssemblyBuilderAccess]::Run)
$MyModuleBuilder = $MyAssemblyBuilder.DefineDynamicModule('InMemoryModule', $false)
  • No disk artifacts are created, increasing stealth​.

Step 3: Define a Custom Delegate Type

To match MessageBoxA's function signature, we create a custom type inheriting from MulticastDelegate:

$MyTypeBuilder = $MyModuleBuilder.DefineType('MyDelegateType',  
  'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
  • MulticastDelegate is used because it allows multiple parameters​.

Step 4: Define the Constructor

We now specify the function's expected arguments:

$MyConstructorBuilder = $MyTypeBuilder.DefineConstructor( 
  'RTSpecialName, HideBySig, Public',  
    [System.Reflection.CallingConventions]::Standard,  
      @([IntPtr], [String], [String], [int]))
$MyConstructorBuilder.SetImplementationFlags('Runtime, Managed')
  • This constructor defines MessageBoxA's expected arguments​.

Step 5: Define the Invoke Method

Next, we define the actual Invoke method to match MessageBoxA:

$MyMethodBuilder = $MyTypeBuilder.DefineMethod('Invoke',  
  'Public, HideBySig, NewSlot, Virtual',  
    [int],  
      @([IntPtr], [String], [String], [int]))
$MyMethodBuilder.SetImplementationFlags('Runtime, Managed')
  • This ensures that our delegate can be invoked just like a normal function​.

Step 6: Instantiate the Delegate Type

Finally, we create the delegate type:

$MyDelegateType = $MyTypeBuilder.CreateType()
  • This dynamically generates a new delegate without writing to disk​.


Using the Delegate Type to Call MessageBoxA

Now that we have a custom delegate, we can map the function pointer to it:

$MessageBoxA = LookupFunc user32.dll MessageBoxA
$MyFunction = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(
    $MessageBoxA, $MyDelegateType)
  • GetDelegateForFunctionPointer takes:

    1. Resolved memory address of MessageBoxA.

    2. Custom delegate type defining its argument structure.

To call MessageBoxA, we invoke our delegate:

$MyFunction.Invoke([IntPtr]::Zero, "Hello World", "This is My MessageBox", 0)
  • This produces a MessageBox popup, confirming that the function was invoked successfully​.

Final Code

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)) 
} 
 
$MessageBoxA = LookupFunc user32.dll MessageBoxA 
$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]) 
 
$MyConstructorBuilder = $MyTypeBuilder.DefineConstructor( 
  'RTSpecialName, HideBySig, Public',  
    [System.Reflection.CallingConventions]::Standard,  
      @([IntPtr], [String], [String], [int])) 
$MyConstructorBuilder.SetImplementationFlags('Runtime, Managed') 
$MyMethodBuilder = $MyTypeBuilder.DefineMethod('Invoke',  
  'Public, HideBySig, NewSlot, Virtual',  
    [int],  
      @([IntPtr], [String], [String], [int])) 
$MyMethodBuilder.SetImplementationFlags('Runtime, Managed') 
$MyDelegateType = $MyTypeBuilder.CreateType() 
 
$MyFunction = [System.Runtime.InteropServices.Marshal]:: 
    GetDelegateForFunctionPointer($MessageBoxA, $MyDelegateType) 
$MyFunction.Invoke([IntPtr]::Zero,"Hello World","This is My MessageBox",0)

Last updated