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
andGetProcAddress
.
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 containingUnsafeNativeMethods
.Step 3: Retrieves a reference to
Microsoft.Win32.UnsafeNativeMethods
usingGetType()
.
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
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:Resolved memory address of
MessageBoxA
.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