Implementing Remote LoadLibrary and Remote GetProcAddress Using PowerShell and Assembly

Recently I have been working on reflective DLL injection in to remote processes in PowerShell. I encountered a problem; I need to call LoadLibrary to load libraries in the remote process and then call GetProcAddress to get function addresses in the remote process.

This post will start off with some review of DLL injection in a 32bit process, which is straightforward. Then I will talk about loading a DLL in a 64bit process. Last I’ll talk about calling GetProcAddress in a remote process. I’ll also include DLL injection references at the bottom of the post, as there are already numerous posts online about basic DLL injection.

Invoke-CreateRemoteThread

First, I want to show how to create threads in remote processes using PowerShell. The technique depends on which version of Windows you are using. For Windows XP, you can create a remote thread using CreateRemoteThread. Windows Vista/7 introduced session isolation, which broke CreateRemoteThread if the target process is in a different session. The solution is to use an undocumented function, NtCreateThreadEx, which will work cross session. In Windows 8, CreateRemoteThread once again works cross session.

Function depending on OS:

  • CreateRemoteThread
    • Windows XP
    • Windows 8
  • NtCreateThreadEx
    • Windows Vista
    • Windows 7

Function Invoke-CreateRemoteThread
{
    Param(
    [Parameter(Position = 1, Mandatory = $true)]
    [IntPtr]
    $ProcessHandle,

    [Parameter(Position = 2, Mandatory = $true)]
    [IntPtr]
    $StartAddress,

    [Parameter(Position = 3, Mandatory = $false)]
    [IntPtr]
    $ArgumentPtr = [IntPtr]::Zero,

    [Parameter(Position = 4, Mandatory = $true)]
    [System.Object]
    $Win32Functions
    )

    [IntPtr]$RemoteThreadHandle = [IntPtr]::Zero

    $OSVersion = [Environment]::OSVersion.Version
    #Vista and Win7
    if (($OSVersion -ge (New-Object 'Version' 6,0)) -and ($OSVersion -lt (New-Object 'Version' 6,2)))
    {
        Write-Verbose "Windows Vista/7 detected, using NtCreateThreadEx. Address of thread: $StartAddress"
        $RetVal= $Win32Functions.NtCreateThreadEx.Invoke([Ref]$RemoteThreadHandle, 0x1FFFFF, [IntPtr]::Zero, $ProcessHandle, $StartAddress, $ArgumentPtr, $false, 0, 0xffff, 0xffff, [IntPtr]::Zero)
        $LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
        if ($RemoteThreadHandle -eq [IntPtr]::Zero)
        {
            Throw "Error in NtCreateThreadEx. Return value: $RetVal. LastError: $LastError"
        }
    }
    #XP/Win8
    else
    {
        Write-Verbose "Windows XP/8 detected, using CreateRemoteThread. Address of thread: $StartAddress"
        $RemoteThreadHandle = $Win32Functions.CreateRemoteThread.Invoke($ProcessHandle, [IntPtr]::Zero, [UIntPtr][UInt64]0xFFFF, $StartAddress, $ArgumentPtr, 0, [IntPtr]::Zero)
    }

    if ($RemoteThreadHandle -eq [IntPtr]::Zero)
    {
        Write-Verbose "Error creating remote thread, thread handle is null"
    }

    return $RemoteThreadHandle
}

This code will be used throughout the rest of this post.

32Bit Remote LoadLibrary:

For 32bit applications, calling LoadLibrary is relatively straightforward. CreateRemoteThread (and NtCreateThreadEx) take a function pointer (the initial address to begin thread execution) and a pointer to a single argument that can be passed to the function.

HANDLE WINAPI CreateRemoteThread(   _In_   HANDLE hProcess,
    _In_   LPSECURITY_ATTRIBUTES lpThreadAttributes,
    _In_   SIZE_T dwStackSize,
    _In_   LPTHREAD_START_ROUTINE lpStartAddress,
    _In_   LPVOID lpParameter,
    _In_   DWORD dwCreationFlags,
    _Out_  LPDWORD lpThreadId );
HMODULE WINAPI LoadLibrary(   _In_  LPCTSTR lpFileName );

LoadLibrary takes a single parameter, a pointer to a string that contains the name of the DLL to load. Therefore, to call LoadLibrary in a remote process, I do the following:

  1. Allocate memory in the remote process.
  2. Write the DLL name string to this allocated memory.
  3. Call Invoke-CreateRemoteThread with the address of the LoadLibrary function.  The memory address allocated is passed as the optional argument of CreateRemoteThread.
  4. Use WaitForSingleObject to wait until the thread finishes.
  5. Call GetExitCodeThread to get the return value of the thread, which is the return value of LoadLibrary. This gets the address the specified DLL was loaded to in the remote process.

One thing to note: For the above to work, I have to know the address of LoadLibrary in the remote process. As it turns out, Kernel32.dll is loaded to the same memory location in every process (it changes at reboot, but every process always loads it to the same spot). LoadLibrary is contained in Kernel32.dll, so I can simply call GetProcAddress in the PowerShell process, which will get the address of LoadLibrary in the PowerShell process and therefore the address of LoadLibrary in the remote process.

Example code for both 32bit and 64bit remote LoadLibrary are located further below.

64Bit Remote LoadLibrary:

Unfortunately this doesn’t work for 64bit processes. GetExitCodeThread returns a 32bit value; in a 64bit process, LoadLibrary will return a 64bit value.

Here’s the workaround I came up with:

  1. Write assembly code that calls LoadLibrary and writes the return value to a memory address in the remote process that I specify.
  2. Write this assembly code in to the remote process.
  3. Execute it using Invoke-CreateRemoteThread.
  4. Once the thread finished, read the memory address in the remote process that the assembly writes the return value to.

Here’s the assembly code:

[SECTION .text]
global _start
_start:
    ; Save rsp and setup stack for function call
    push rbx
    mov rbx, rsp
    sub rsp, 0x20
    and sp, 0xffc0

    ; Call LoadLibraryA
    mov rcx, 0x4141414141414141    ; Ptr to string of library, set by PS
    mov rdx, 0x4141414141414141    ; Address of LoadLibrary, set by PS
    call rdx

    mov rdx, 0x4141414141414141    ; Ptr to save result, set by PS
    mov [rdx], rax

    ; Fix stack
    mov rsp, rbx
    pop rbx
    ret

The PowerShell script changes the addresses labeled 0x4141414141414141 to the actual addresses in the remote process during execution.  The script allocates memory for the DLL name string, allocates memory for the return value of LoadLibrary, and gets the address of LoadLibrary, and writes these addresses in to the assembly.

Here’s example PowerShell script:

Function Import-DllInRemoteProcess
{
    Param(
    [Parameter(Position=0, Mandatory=$true)]
    [IntPtr]
    $RemoteProcHandle,

    [Parameter(Position=1, Mandatory=$true)]
    [IntPtr]
    $ImportDllPathPtr
    )

    $PtrSize = [System.Runtime.InteropServices.Marshal]::SizeOf([IntPtr])

    $ImportDllPath = [System.Runtime.InteropServices.Marshal]::PtrToStringAnsi($ImportDllPathPtr)
    $DllPathSize = [UIntPtr][UInt64]([UInt64]$ImportDllPath.Length + 1)
    $RImportDllPathPtr = $Win32Functions.VirtualAllocEx.Invoke($RemoteProcHandle, [IntPtr]::Zero, $DllPathSize, $Win32Constants.MEM_COMMIT -bor $Win32Constants.MEM_RESERVE, $Win32Constants.PAGE_READWRITE)
    if ($RImportDllPathPtr -eq [IntPtr]::Zero)
    {
        Throw "Unable to allocate memory in the remote process"
    }

    [UIntPtr]$NumBytesWritten = [UIntPtr]::Zero
    $Success = $Win32Functions.WriteProcessMemory.Invoke($RemoteProcHandle, $RImportDllPathPtr, $ImportDllPathPtr, $DllPathSize, [Ref]$NumBytesWritten)

    if ($Success -eq $false)
    {
        Throw "Unable to write DLL path to remote process memory"
    }
    if ($DllPathSize -ne $NumBytesWritten)
    {
        Throw "Didn't write the expected amount of bytes when writing a DLL path to load to the remote process"
    }

    $Kernel32Handle = $Win32Functions.GetModuleHandle.Invoke("kernel32.dll")
    $LoadLibraryAAddr = $Win32Functions.GetProcAddress.Invoke($Kernel32Handle, "LoadLibraryA") #Kernel32 loaded to the same address for all processes

    [IntPtr]$DllAddress = [IntPtr]::Zero
    #For 64bit DLL's, we can't use just CreateRemoteThread to call LoadLibrary because GetExitCodeThread will only give back a 32bit value, but we need a 64bit address
    #      Instead, write shellcode while calls LoadLibrary and writes the result to a memory address we specify. Then read from that memory once the thread finishes.
    if ($PEInfo.PE64Bit -eq $true)
    {
        #Allocate memory for the address returned by LoadLibraryA
        $LoadLibraryARetMem = $Win32Functions.VirtualAllocEx.Invoke($RemoteProcHandle, [IntPtr]::Zero, $DllPathSize, $Win32Constants.MEM_COMMIT -bor $Win32Constants.MEM_RESERVE, $Win32Constants.PAGE_READWRITE)
        if ($LoadLibraryARetMem -eq [IntPtr]::Zero)
        {
            Throw "Unable to allocate memory in the remote process for the return value of LoadLibraryA"
        }

        #Write Shellcode to the remote process which will call LoadLibraryA (Shellcode: LoadLibraryA.asm)
        $LoadLibrarySC1 = @(0x53, 0x48, 0x89, 0xe3, 0x48, 0x83, 0xec, 0x20, 0x66, 0x83, 0xe4, 0xc0, 0x48, 0xb9)
        $LoadLibrarySC2 = @(0x48, 0xba)
        $LoadLibrarySC3 = @(0xff, 0xd2, 0x48, 0xba)
        $LoadLibrarySC4 = @(0x48, 0x89, 0x02, 0x48, 0x89, 0xdc, 0x5b, 0xc3)

        $SCLength = $LoadLibrarySC1.Length + $LoadLibrarySC2.Length + $LoadLibrarySC3.Length + $LoadLibrarySC4.Length + ($PtrSize * 3)
        $SCPSMem = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($SCLength)
        $SCPSMemOriginal = $SCPSMem

        Write-BytesToMemory -Bytes $LoadLibrarySC1 -MemoryAddress $SCPSMem
        $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($LoadLibrarySC1.Length)
        [System.Runtime.InteropServices.Marshal]::StructureToPtr($RImportDllPathPtr, $SCPSMem, $false)
        $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($PtrSize)
        Write-BytesToMemory -Bytes $LoadLibrarySC2 -MemoryAddress $SCPSMem
        $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($LoadLibrarySC2.Length)
        [System.Runtime.InteropServices.Marshal]::StructureToPtr($LoadLibraryAAddr, $SCPSMem, $false)
        $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($PtrSize)
        Write-BytesToMemory -Bytes $LoadLibrarySC3 -MemoryAddress $SCPSMem
        $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($LoadLibrarySC3.Length)
        [System.Runtime.InteropServices.Marshal]::StructureToPtr($LoadLibraryARetMem, $SCPSMem, $false)
        $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($PtrSize)
        Write-BytesToMemory -Bytes $LoadLibrarySC4 -MemoryAddress $SCPSMem
        $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($LoadLibrarySC4.Length)

        $RSCAddr = $Win32Functions.VirtualAllocEx.Invoke($RemoteProcHandle, [IntPtr]::Zero, [UIntPtr][UInt64]$SCLength, $Win32Constants.MEM_COMMIT -bor $Win32Constants.MEM_RESERVE, $Win32Constants.PAGE_EXECUTE_READWRITE)
        if ($RSCAddr -eq [IntPtr]::Zero)
        {
            Throw "Unable to allocate memory in the remote process for shellcode"
        }

        $Success = $Win32Functions.WriteProcessMemory.Invoke($RemoteProcHandle, $RSCAddr, $SCPSMemOriginal, [UIntPtr][UInt64]$SCLength, [Ref]$NumBytesWritten)
        if (($Success -eq $false) -or ([UInt64]$NumBytesWritten -ne [UInt64]$SCLength))
        {
            Throw "Unable to write shellcode to remote process memory."
        }

        $RThreadHandle = Invoke-CreateRemoteThread -ProcessHandle $RemoteProcHandle -StartAddress $RSCAddr -Win32Functions $Win32Functions
        $Result = $Win32Functions.WaitForSingleObject.Invoke($RThreadHandle, 20000)
        if ($Result -ne 0)
        {
            Throw "Call to CreateRemoteThread to call GetProcAddress failed."
        }

        #The shellcode writes the DLL address to memory in the remote process at address $LoadLibraryARetMem, read this memory
        [IntPtr]$ReturnValMem = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($PtrSize)
        $Result = $Win32Functions.ReadProcessMemory.Invoke($RemoteProcHandle, $LoadLibraryARetMem, $ReturnValMem, [UIntPtr][UInt64]$PtrSize, [Ref]$NumBytesWritten)
        if ($Result -eq $false)
        {
            Throw "Call to ReadProcessMemory failed"
        }
        [IntPtr]$DllAddress = [System.Runtime.InteropServices.Marshal]::PtrToStructure($ReturnValMem, [IntPtr])

        $Win32Functions.VirtualFreeEx.Invoke($RemoteProcHandle, $LoadLibraryARetMem, [UIntPtr][UInt64]0, $Win32Constants.MEM_RELEASE) | Out-Null
        $Win32Functions.VirtualFreeEx.Invoke($RemoteProcHandle, $RSCAddr, [UIntPtr][UInt64]0, $Win32Constants.MEM_RELEASE) | Out-Null
    }
    else
    {
        [IntPtr]$RThreadHandle = Invoke-CreateRemoteThread -ProcessHandle $RemoteProcHandle -StartAddress $LoadLibraryAAddr -ArgumentPtr $RImportDllPathPtr -Win32Functions $Win32Functions
        $Result = $Win32Functions.WaitForSingleObject.Invoke($RThreadHandle, 20000)
        if ($Result -ne 0)
        {
            Throw "Call to CreateRemoteThread to call GetProcAddress failed."
        }

        [Int32]$ExitCode = 0
        $Result = $Win32Functions.GetExitCodeThread.Invoke($RThreadHandle, [Ref]$ExitCode)
        if (($Result -eq 0) -or ($ExitCode -eq 0))
        {
            Throw "Call to GetExitCodeThread failed"
        }

        [IntPtr]$DllAddress = [IntPtr]$ExitCode
    }

    $Win32Functions.VirtualFreeEx.Invoke($RemoteProcHandle, $RImportDllPathPtr, [UIntPtr][UInt64]0, $Win32Constants.MEM_RELEASE) | Out-Null

    return $DllAddress
}

So, using PowerShell and a little bit of assembly I can load libraries in both 32bit and 64bit remote processes. Time to move on to remote GetProcAddress.

Remote GetProcAddress:

Calling GetProcAddress in remote processes is something I haven’t found a ton of information about online, and I didn’t like the solutions I saw. Unfortunately, you cannot use the CreateRemoteThread trick for 32bit processes because GetProcAddress takes 2 parameters (and you can only pass 1 parameter through CreateRemoteThread). I was already writing assembly for other functionality, and I figured this would be pretty easy to write in assembly as well. Unlike with LoadLibrary, assembly must be written for both 32bit and 64bit processes.

General steps:

  1. Write assembly code that calls GetProcAddress and writes the return value to a memory address in the remote process that I specify.
  2. Write this assembly code in to the remote process.
  3. Execute it using Invoke-CreateRemoteThread.
  4. Once the thread finished, read the memory address in the remote process that the assembly writes the return value to.

As you can see, these steps are basically identical to the LoadLibrary assembly steps.

64Bit Assembly:

[SECTION .text]

global _start

_start:
    ; Save state of rbx and stack
    push rbx
    mov rbx, rsp

    ; Set up stack for function call to GetProcAddress
    sub rsp, 0x20
    and sp, 0xffc0

    ; Call getprocaddress
    mov rcx, 0x4141414141414141    ; DllHandle, set by PS
    mov rdx, 0x4141414141414141    ; Ptr to FuncName string, set by PS
    mov rax, 0x4141414141414141    ; GetProcAddress address, set by PS
    call rax

    ; Store the result
    mov rcx, 0x4141414141414141    ; Ptr to buffer to save result,set by PS
    mov [rcx], rax

    ; Restore stack
    mov rsp, rbx
    pop rbx
    ret

32Bit Assembly:

[SECTION .text]

global _start

_start:
    ; Save state of ebx and stack
    push ebx
    mov ebx, esp

    ; Align stack
    and esp, 0xffffffc0

    ; Call GetProcAddress
    mov eax, 0x41414141          ; DllHandle, supplied by PS
    mov ecx, 0x41414141          ; Function name, supplied by PS
    push ecx
    push eax
    mov eax, 0x41414141          ; GetProcAddress address, supplied by PS
    call eax

    ; Write GetProcAddress return value to an address supplied by PS
    mov ecx, 0x41414141          ; Address supplied by PS
    mov [ecx], eax

    ; Fix stack
    mov esp, ebx
    pop ebx
    ret

If you think I suck at writing assembly, you are probably correct :-). But it gets the job done.

Here’s the PowerShell code for Invoke-GetRemoteProcAddress:

Function Get-RemoteProcAddress
{
    Param(
    [Parameter(Position=0, Mandatory=$true)]
    [IntPtr]
    $RemoteProcHandle,

    [Parameter(Position=1, Mandatory=$true)]
    [IntPtr]
    $RemoteDllHandle,

    [Parameter(Position=2, Mandatory=$true)]
    [String]
    $FunctionName
    )

    $PtrSize = [System.Runtime.InteropServices.Marshal]::SizeOf([IntPtr])
    $FunctionNamePtr = [System.Runtime.InteropServices.Marshal]::StringToHGlobalAnsi($FunctionName)

    #Write FunctionName to memory (will be used in GetProcAddress)
    $FunctionNameSize = [UIntPtr][UInt64]([UInt64]$FunctionName.Length + 1)
    $RFuncNamePtr = $Win32Functions.VirtualAllocEx.Invoke($RemoteProcHandle, [IntPtr]::Zero, $FunctionNameSize, $Win32Constants.MEM_COMMIT -bor $Win32Constants.MEM_RESERVE, $Win32Constants.PAGE_READWRITE)
    if ($RFuncNamePtr -eq [IntPtr]::Zero)
    {
        Throw "Unable to allocate memory in the remote process"
    }

    [UIntPtr]$NumBytesWritten = [UIntPtr]::Zero
    $Success = $Win32Functions.WriteProcessMemory.Invoke($RemoteProcHandle, $RFuncNamePtr, $FunctionNamePtr, $FunctionNameSize, [Ref]$NumBytesWritten)
    [System.Runtime.InteropServices.Marshal]::FreeHGlobal($FunctionNamePtr)
    if ($Success -eq $false)
    {
        Throw "Unable to write DLL path to remote process memory"
    }
    if ($FunctionNameSize -ne $NumBytesWritten)
    {
        Throw "Didn't write the expected amount of bytes when writing a DLL path to load to the remote process"
    }

    #Get address of GetProcAddress
    $Kernel32Handle = $Win32Functions.GetModuleHandle.Invoke("kernel32.dll")
    $GetProcAddressAddr = $Win32Functions.GetProcAddress.Invoke($Kernel32Handle, "GetProcAddress") #Kernel32 loaded to the same address for all processes

    #Allocate memory for the address returned by GetProcAddress
    $GetProcAddressRetMem = $Win32Functions.VirtualAllocEx.Invoke($RemoteProcHandle, [IntPtr]::Zero, [UInt64][UInt64]$PtrSize, $Win32Constants.MEM_COMMIT -bor $Win32Constants.MEM_RESERVE, $Win32Constants.PAGE_READWRITE)
    if ($GetProcAddressRetMem -eq [IntPtr]::Zero)
    {
        Throw "Unable to allocate memory in the remote process for the return value of GetProcAddress"
    }

    #Write Shellcode to the remote process which will call GetProcAddress
    #Shellcode: GetProcAddress.asm
    #todo: need to have detection for when to get by ordinal
    [Byte[]]$GetProcAddressSC = @()
    if ($PEInfo.PE64Bit -eq $true)
    {
        $GetProcAddressSC1 = @(0x53, 0x48, 0x89, 0xe3, 0x48, 0x83, 0xec, 0x20, 0x66, 0x83, 0xe4, 0xc0, 0x48, 0xb9)
        $GetProcAddressSC2 = @(0x48, 0xba)
        $GetProcAddressSC3 = @(0x48, 0xb8)
        $GetProcAddressSC4 = @(0xff, 0xd0, 0x48, 0xb9)
        $GetProcAddressSC5 = @(0x48, 0x89, 0x01, 0x48, 0x89, 0xdc, 0x5b, 0xc3)
    }
    else
    {
        $GetProcAddressSC1 = @(0x53, 0x89, 0xe3, 0x83, 0xe4, 0xc0, 0xb8)
        $GetProcAddressSC2 = @(0xb9)
        $GetProcAddressSC3 = @(0x51, 0x50, 0xb8)
        $GetProcAddressSC4 = @(0xff, 0xd0, 0xb9)
        $GetProcAddressSC5 = @(0x89, 0x01, 0x89, 0xdc, 0x5b, 0xc3)
    }
    $SCLength = $GetProcAddressSC1.Length + $GetProcAddressSC2.Length + $GetProcAddressSC3.Length + $GetProcAddressSC4.Length + $GetProcAddressSC5.Length + ($PtrSize * 4)
    $SCPSMem = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($SCLength)
    $SCPSMemOriginal = $SCPSMem

    Write-BytesToMemory -Bytes $GetProcAddressSC1 -MemoryAddress $SCPSMem
    $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($GetProcAddressSC1.Length)
    [System.Runtime.InteropServices.Marshal]::StructureToPtr($RemoteDllHandle, $SCPSMem, $false)
    $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($PtrSize)
    Write-BytesToMemory -Bytes $GetProcAddressSC2 -MemoryAddress $SCPSMem
    $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($GetProcAddressSC2.Length)
    [System.Runtime.InteropServices.Marshal]::StructureToPtr($RFuncNamePtr, $SCPSMem, $false)
    $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($PtrSize)
    Write-BytesToMemory -Bytes $GetProcAddressSC3 -MemoryAddress $SCPSMem
    $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($GetProcAddressSC3.Length)
    [System.Runtime.InteropServices.Marshal]::StructureToPtr($GetProcAddressAddr, $SCPSMem, $false)
    $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($PtrSize)
    Write-BytesToMemory -Bytes $GetProcAddressSC4 -MemoryAddress $SCPSMem
    $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($GetProcAddressSC4.Length)
    [System.Runtime.InteropServices.Marshal]::StructureToPtr($GetProcAddressRetMem, $SCPSMem, $false)
    $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($PtrSize)
    Write-BytesToMemory -Bytes $GetProcAddressSC5 -MemoryAddress $SCPSMem
    $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($GetProcAddressSC5.Length)

    $RSCAddr = $Win32Functions.VirtualAllocEx.Invoke($RemoteProcHandle, [IntPtr]::Zero, [UIntPtr][UInt64]$SCLength, $Win32Constants.MEM_COMMIT -bor $Win32Constants.MEM_RESERVE, $Win32Constants.PAGE_EXECUTE_READWRITE)
    if ($RSCAddr -eq [IntPtr]::Zero)
    {
        Throw "Unable to allocate memory in the remote process for shellcode"
    }

    $Success = $Win32Functions.WriteProcessMemory.Invoke($RemoteProcHandle, $RSCAddr, $SCPSMemOriginal, [UIntPtr][UInt64]$SCLength, [Ref]$NumBytesWritten)
    if (($Success -eq $false) -or ([UInt64]$NumBytesWritten -ne [UInt64]$SCLength))
    {
        Throw "Unable to write shellcode to remote process memory."
    }

    $RThreadHandle = Invoke-CreateRemoteThread -ProcessHandle $RemoteProcHandle -StartAddress $RSCAddr -Win32Functions $Win32Functions
    $Result = $Win32Functions.WaitForSingleObject.Invoke($RThreadHandle, 20000)
    if ($Result -ne 0)
    {
        Throw "Call to CreateRemoteThread to call GetProcAddress failed."
    }

    #The process address is written to memory in the remote process at address $GetProcAddressRetMem, read this memory
    [IntPtr]$ReturnValMem = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($PtrSize)
    $Result = $Win32Functions.ReadProcessMemory.Invoke($RemoteProcHandle, $GetProcAddressRetMem, $ReturnValMem, [UIntPtr][UInt64]$PtrSize, [Ref]$NumBytesWritten)
    if (($Result -eq $false) -or ($NumBytesWritten -eq 0))
    {
        Throw "Call to ReadProcessMemory failed"
    }
    [IntPtr]$ProcAddress = [System.Runtime.InteropServices.Marshal]::PtrToStructure($ReturnValMem, [IntPtr])

    $Win32Functions.VirtualFreeEx.Invoke($RemoteProcHandle, $RSCAddr, [UIntPtr][UInt64]0, $Win32Constants.MEM_RELEASE) | Out-Null
    $Win32Functions.VirtualFreeEx.Invoke($RemoteProcHandle, $RFuncNamePtr, [UIntPtr][UInt64]0, $Win32Constants.MEM_RELEASE) | Out-Null
    $Win32Functions.VirtualFreeEx.Invoke($RemoteProcHandle, $GetProcAddressRetMem, [UIntPtr][UInt64]0, $Win32Constants.MEM_RELEASE) | Out-Null

    return $ProcAddress
}

And there you have it. Using this code you can get the addresses to functions in remote 32bit and 64bit processes.

You might have noticed the code references the variables Win32Functions and Win32Constants. I define these in other functions. Here is some of the code from Win32Functions. You might have a define a few functions yourself, because I am extracting this out of a much larger script and might miss a few things. I recommend checking out this blog post for information on reflectively defining structures in PowerShell: http://www.exploit-monday.com/2012/07/structs-and-enums-using-reflection.html.

Function Get-Win32Functions
{
    $Win32Functions = New-Object System.Object

    $VirtualAllocAddr = Get-ProcAddress kernel32.dll VirtualAlloc
    $VirtualAllocDelegate = Get-DelegateType @([IntPtr], [UIntPtr], [UInt32], [UInt32]) ([IntPtr])
    $VirtualAlloc = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($VirtualAllocAddr, $VirtualAllocDelegate)
    $Win32Functions | Add-Member NoteProperty -Name VirtualAlloc -Value $VirtualAlloc

    $VirtualAllocExAddr = Get-ProcAddress kernel32.dll VirtualAllocEx
    $VirtualAllocExDelegate = Get-DelegateType @([IntPtr], [IntPtr], [UIntPtr], [UInt32], [UInt32]) ([IntPtr])
    $VirtualAllocEx = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($VirtualAllocExAddr, $VirtualAllocExDelegate)
    $Win32Functions | Add-Member NoteProperty -Name VirtualAllocEx -Value $VirtualAllocEx

    $memcpyAddr = Get-ProcAddress msvcrt.dll memcpy
    $memcpyDelegate = Get-DelegateType @([IntPtr], [IntPtr], [UIntPtr]) ([IntPtr])
    $memcpy = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($memcpyAddr, $memcpyDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name memcpy -Value $memcpy

    $memsetAddr = Get-ProcAddress msvcrt.dll memset
    $memsetDelegate = Get-DelegateType @([IntPtr], [Int32], [IntPtr]) ([IntPtr])
    $memset = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($memsetAddr, $memsetDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name memset -Value $memset

    $LoadLibraryAddr = Get-ProcAddress kernel32.dll LoadLibraryA
    $LoadLibraryDelegate = Get-DelegateType @([String]) ([IntPtr])
    $LoadLibrary = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($LoadLibraryAddr, $LoadLibraryDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name LoadLibrary -Value $LoadLibrary

    $GetProcAddressAddr = Get-ProcAddress kernel32.dll GetProcAddress
    $GetProcAddressDelegate = Get-DelegateType @([IntPtr], [String]) ([IntPtr])
    $GetProcAddress = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetProcAddressAddr, $GetProcAddressDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name GetProcAddress -Value $GetProcAddress

    $GetProcAddressOrdinalAddr = Get-ProcAddress kernel32.dll GetProcAddress
    $GetProcAddressOrdinalDelegate = Get-DelegateType @([IntPtr], [IntPtr]) ([IntPtr])
    $GetProcAddressOrdinal = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetProcAddressOrdinalAddr, $GetProcAddressOrdinalDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name GetProcAddressOrdinal -Value $GetProcAddressOrdinal

    $VirtualFreeAddr = Get-ProcAddress kernel32.dll VirtualFree
    $VirtualFreeDelegate = Get-DelegateType @([IntPtr], [UIntPtr], [UInt32]) ([Bool])
    $VirtualFree = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($VirtualFreeAddr, $VirtualFreeDelegate)
    $Win32Functions | Add-Member NoteProperty -Name VirtualFree -Value $VirtualFree

    $VirtualFreeExAddr = Get-ProcAddress kernel32.dll VirtualFreeEx
    $VirtualFreeExDelegate = Get-DelegateType @([IntPtr], [IntPtr], [UIntPtr], [UInt32]) ([Bool])
    $VirtualFreeEx = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($VirtualFreeExAddr, $VirtualFreeExDelegate)
    $Win32Functions | Add-Member NoteProperty -Name VirtualFreeEx -Value $VirtualFreeEx

    $VirtualProtectAddr = Get-ProcAddress kernel32.dll VirtualProtect
    $VirtualProtectDelegate = Get-DelegateType @([IntPtr], [UIntPtr], [UInt32], [UInt32].MakeByRefType()) ([Bool])
    $VirtualProtect = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($VirtualProtectAddr, $VirtualProtectDelegate)
    $Win32Functions | Add-Member NoteProperty -Name VirtualProtect -Value $VirtualProtect

    $GetModuleHandleAddr = Get-ProcAddress kernel32.dll GetModuleHandleA
    $GetModuleHandleDelegate = Get-DelegateType @([String]) ([IntPtr])
    $GetModuleHandle = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetModuleHandleAddr, $GetModuleHandleDelegate)
    $Win32Functions | Add-Member NoteProperty -Name GetModuleHandle -Value $GetModuleHandle

    $FreeLibraryAddr = Get-ProcAddress kernel32.dll FreeLibrary
    $FreeLibraryDelegate = Get-DelegateType @([Bool]) ([IntPtr])
    $FreeLibrary = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($FreeLibraryAddr, $FreeLibraryDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name FreeLibrary -Value $FreeLibrary

    $OpenProcessAddr = Get-ProcAddress kernel32.dll OpenProcess
    $OpenProcessDelegate = Get-DelegateType @([UInt32], [Bool], [UInt32]) ([IntPtr])
    $OpenProcess = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($OpenProcessAddr, $OpenProcessDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name OpenProcess -Value $OpenProcess

    $WaitForSingleObjectAddr = Get-ProcAddress kernel32.dll WaitForSingleObject
    $WaitForSingleObjectDelegate = Get-DelegateType @([IntPtr], [UInt32]) ([UInt32])
    $WaitForSingleObject = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($WaitForSingleObjectAddr, $WaitForSingleObjectDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name WaitForSingleObject -Value $WaitForSingleObject

    $WriteProcessMemoryAddr = Get-ProcAddress kernel32.dll WriteProcessMemory
    $WriteProcessMemoryDelegate = Get-DelegateType @([IntPtr], [IntPtr], [IntPtr], [UIntPtr], [UIntPtr].MakeByRefType()) ([Bool])
    $WriteProcessMemory = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($WriteProcessMemoryAddr, $WriteProcessMemoryDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name WriteProcessMemory -Value $WriteProcessMemory

    $ReadProcessMemoryAddr = Get-ProcAddress kernel32.dll ReadProcessMemory
    $ReadProcessMemoryDelegate = Get-DelegateType @([IntPtr], [IntPtr], [IntPtr], [UIntPtr], [UIntPtr].MakeByRefType()) ([Bool])
    $ReadProcessMemory = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($ReadProcessMemoryAddr, $ReadProcessMemoryDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name ReadProcessMemory -Value $ReadProcessMemory

    $CreateRemoteThreadAddr = Get-ProcAddress kernel32.dll CreateRemoteThread
    $CreateRemoteThreadDelegate = Get-DelegateType @([IntPtr], [IntPtr], [UIntPtr], [IntPtr], [IntPtr], [UInt32], [IntPtr]) ([IntPtr])
    $CreateRemoteThread = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($CreateRemoteThreadAddr, $CreateRemoteThreadDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name CreateRemoteThread -Value $CreateRemoteThread

    $GetExitCodeThreadAddr = Get-ProcAddress kernel32.dll GetExitCodeThread
    $GetExitCodeThreadDelegate = Get-DelegateType @([IntPtr], [Int32].MakeByRefType()) ([Bool])
    $GetExitCodeThread = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetExitCodeThreadAddr, $GetExitCodeThreadDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name GetExitCodeThread -Value $GetExitCodeThread

    $NtCreateThreadExAddr = Get-ProcAddress NtDll.dll NtCreateThreadEx
    $NtCreateThreadExDelegate = Get-DelegateType @([IntPtr].MakeByRefType(), [UInt32], [IntPtr], [IntPtr], [IntPtr], [IntPtr], [Bool], [UInt32], [UInt32], [UInt32], [IntPtr]) ([UInt32])
    $NtCreateThreadEx = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($NtCreateThreadExAddr, $NtCreateThreadExDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name NtCreateThreadEx -Value $NtCreateThreadEx

    return $Win32Functions
}

And here’s a bunch of Win32 Constants I have defined:

Function Get-Win32Constants
{
    $Win32Constants = New-Object System.Object

    $Win32Constants | Add-Member -MemberType NoteProperty -Name MEM_COMMIT -Value 0x00001000
    $Win32Constants | Add-Member -MemberType NoteProperty -Name MEM_RESERVE -Value 0x00002000
    $Win32Constants | Add-Member -MemberType NoteProperty -Name PAGE_NOACCESS -Value 0x01
    $Win32Constants | Add-Member -MemberType NoteProperty -Name PAGE_READONLY -Value 0x02
    $Win32Constants | Add-Member -MemberType NoteProperty -Name PAGE_READWRITE -Value 0x04
    $Win32Constants | Add-Member -MemberType NoteProperty -Name PAGE_WRITECOPY -Value 0x08
    $Win32Constants | Add-Member -MemberType NoteProperty -Name PAGE_EXECUTE -Value 0x10
    $Win32Constants | Add-Member -MemberType NoteProperty -Name PAGE_EXECUTE_READ -Value 0x20
    $Win32Constants | Add-Member -MemberType NoteProperty -Name PAGE_EXECUTE_READWRITE -Value 0x40
    $Win32Constants | Add-Member -MemberType NoteProperty -Name PAGE_EXECUTE_WRITECOPY -Value 0x80
    $Win32Constants | Add-Member -MemberType NoteProperty -Name PAGE_NOCACHE -Value 0x200
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_REL_BASED_ABSOLUTE -Value 0
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_REL_BASED_HIGHLOW -Value 3
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_REL_BASED_DIR64 -Value 10
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_SCN_MEM_DISCARDABLE -Value 0x02000000
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_SCN_MEM_EXECUTE -Value 0x20000000
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_SCN_MEM_READ -Value 0x40000000
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_SCN_MEM_WRITE -Value 0x80000000
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_SCN_MEM_NOT_CACHED -Value 0x04000000
    $Win32Constants | Add-Member -MemberType NoteProperty -Name MEM_DECOMMIT -Value 0x4000
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_FILE_EXECUTABLE_IMAGE -Value 0x0002
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_FILE_DLL -Value 0x2000
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE -Value 0x40
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_DLLCHARACTERISTICS_NX_COMPAT -Value 0x100
    $Win32Constants | Add-Member -MemberType NoteProperty -Name MEM_RELEASE -Value 0x8000
    $Win32Constants | Add-Member -MemberType NoteProperty -Name TOKEN_QUERY -Value 0x0008
    $Win32Constants | Add-Member -MemberType NoteProperty -Name TOKEN_ADJUST_PRIVILEGES -Value 0x0020
    $Win32Constants | Add-Member -MemberType NoteProperty -Name SE_PRIVILEGE_ENABLED -Value 0x2
    $Win32Constants | Add-Member -MemberType NoteProperty -Name ERROR_NO_TOKEN -Value 0x3f0

    return $Win32Constants
}

I think this should give you most of the information and code you need. Let me know if you’d like to see more material like this, and keep your eyes out for an update to my PE injection which allows reflectively injecting DLL’s in to remote processes.

References:

About these ads
Tagged with:
Posted in Hacking
8 comments on “Implementing Remote LoadLibrary and Remote GetProcAddress Using PowerShell and Assembly
  1. Matt Graeber says:

    Awesome post! I’m glad there’s finally someone else out there interested in low-level hacking with PowerShell.

    FYI, you can just call GetProcAddress in the running PowerShell process and be guaranteed that the address returned will be the same address in the remote process (as long as the module is loaded in the remote process, of course). Once one process loads a module, its address will remain fixed across all processes. For example, if process #1 was the first to load dbghelp.dll and it loaded at base address 0x40000000, then if process #2 loaded dbghelp.dll, it would be loaded at the same base address. I rely upon this trick in my Invoke-DllInjection function. You could validate my claim with the following short script:

    Get-Process | % { $Id = $_.Id; $_.Modules } |
    ? {$_.ModuleName -eq ‘kernel32.dll’} |
    % { “Kernel32.dll Base: 0x$($_.BaseAddress.ToString(“X$([IntPtr]::Size * 2)”)) (PID: $Id)” }

    That will display the loaded base address of kernel32.dll. You’l find that they are all loaded at the same base address.

    Keep up the good work and thanks for the link to my blog! :D

    Happy hacking,
    Matt

    • clymb3r says:

      Interesting, for some reason I thought only kernel32.dll and ntdll.dll loaded to the same address across processes. Guess I never verified, heh.

      I do think this method is a little more sneaky though, as it doesn’t require loading DLL’s in to PowerShell. Maybe there’s other benefits or drawbacks that I haven’t thought of.

      In any case, I’ll try to update the post to reflect this info when I get a chance.

      Thanks for the feedback!
      Joe

  2. Interesting blog! Is your theme custom made or did you
    download it from somewhere? A design like yours with
    a few simple tweeks would really make my blog jump out.

    Please let me know where you got your theme.
    Cheers

  3. It’s in point of fact a nice and useful piece of information.
    I’m satisfied that you just shared this useful information with us.
    Please keep us up to date like this. Thanks for sharing.

  4. At this time I am going to do my breakfast, when having myy breakfast coming over again to read more
    news.

  5. Ronny says:

    What’s up, after reading this remarkable post i am as well glad to
    share my familiarity here with mates.

  6. Really when someone doesn’t understand afterward its up to other viewers that they will help, so here
    it occurs.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: