├── README.md ├── SharpHoundAuto.ps1 ├── SPF_Domain_Enumerator.ps1 ├── Pyacscan - Remote Handler.ps1 ├── GetUACStatusParallelv1.0.ps1 ├── InstallVelociraptor.ps1 ├── GetLocalGroupMembersParallel v1.0.ps1 ├── Yara Remote PowerShell Scanner.ps1 └── VirusTotal PowerShell Scanner.ps1 /README.md: -------------------------------------------------------------------------------- 1 | # PowershellAdmin 2 | Some Powershell scripts developed during my security consulting work. Hopefully they are useful to you too! 3 | Please be sure to test and review these scripts before use in your own environments. Liability != mine :) 4 | -------------------------------------------------------------------------------- /SharpHoundAuto.ps1: -------------------------------------------------------------------------------- 1 | #Requires -version 2.0 2 | #Author: David Cottingham 3 | #This script reads a list of domain names from a file called 'targetdomains.txt' 4 | #It then passes the commands to SharpHound to collect all information and places the results in named subdirectories. 5 | 6 | #setup working directories and paths 7 | $working = Get-Location 8 | $sharphoundlocation = "$working\sharphound.exe" 9 | $domainlist = "$working\targetdomains.txt" 10 | 11 | #check for administrator privileges 12 | If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) 13 | { 14 | write-host "This script requires administrative privileges which have not been detected. This script will now exit.`r`nPlease start a powershell prompt as an administrator and run the script again. `r`n" -ForegroundColor Yellow 15 | Pause 16 | Break 17 | } 18 | 19 | #ensure sharphound exists in the current working directory 20 | If (Test-Path -Path $sharphoundlocation -ErrorAction SilentlyContinue) 21 | { 22 | write-host "Sharphound found $sharphoundlocation" 23 | 24 | #ensure a domain list exists in the current working directory 25 | If (Test-Path -Path $domainlist -ErrorAction SilentlyContinue) 26 | { 27 | write-host "Domain list found $domainlist" 28 | #read in the domain list from the text file and loop through all domains 29 | Get-Content $domainlist | ForEach-Object{ 30 | write-host "Commencing collection for $_" -ForegroundColor Magenta 31 | 32 | #send commands to sharphound 33 | Invoke-Expression "./sharphound.exe -d $_ -c All" 34 | write-host "Collection of $_ complete, files written to $working\" -ForegroundColor Green 35 | } 36 | } 37 | else 38 | { 39 | write-host "Domain List Not Found, expected $domainlist" -ForegroundColor Red 40 | pause 41 | } 42 | } 43 | else 44 | { 45 | write-host "Sharphound Not Found, expected $sharphoundlocation" -ForegroundColor Red 46 | pause 47 | } 48 | 49 | -------------------------------------------------------------------------------- /SPF_Domain_Enumerator.ps1: -------------------------------------------------------------------------------- 1 | #Get the current working directory 2 | $WorkingDir = Get-Location 3 | 4 | #Check to see if the domain list is located in the current working directory 5 | If (Test-Path -path "$WorkingDir\domainlist.csv" -ErrorAction SilentlyContinue) 6 | { 7 | $SitestoScan = Import-CSV -Path "$WorkingDir\domainlist.csv" 8 | } 9 | else 10 | { 11 | $SiteCSV = Read-Host "Please type the full path to the CSV containing the sites you wish to scan. e.g. C:\domainlist.csv (Note: This CSV must have a header row called Sites)" 12 | $SitestoScan = Import-CSV -Path $SiteCSV 13 | } 14 | 15 | #Abort the script is a valid domain list is unable to be found or populated 16 | If ($SitestoScan.Sites -eq $null) 17 | { 18 | Write-Output "The CSV is not valid or has been incorrectly formatted. Please ensure the CSV has a header row of Sites and each site you want to scan on a new line in the file" 19 | Pause 20 | break 21 | } 22 | 23 | 24 | #The main function to perform SPF Validation 25 | function Validate-SPF ($domain) 26 | { 27 | #null out the variables in the event this is run in powershell ISE 28 | $record = $null 29 | $y = $null 30 | $res = $null 31 | 32 | #Call the dns function to check for SPF Records 33 | $DNSResult = Get-DNS $domain 34 | $y = $DNSResult | where { $_.strings -like "*=spf*" } | select name, strings, Type 35 | Write-Host "Querying Records for $domain" 36 | 37 | #Create an array called 'res' and put the domain into it 38 | $res = "" | select domain, result, record, Type, NameExchange,MatchHandler 39 | #$res = "" | select domain, result, message, txt, record, Type 40 | $res.domain = $domain 41 | 42 | If ($DNSResult.Type -eq "MX") 43 | { 44 | [string]$MXRecord = "MX" 45 | } 46 | elseif ($DNSResult.Type -eq "TXT") 47 | { 48 | [string]$MXRecord = "TXT" 49 | } 50 | elseif ($DNSResult -eq $null) 51 | { 52 | [string]$MXRecord = "No Result" 53 | } 54 | elseif ($DNSResult -eq "No DNS Record Found") 55 | { 56 | [string]$MXRecord = "No DNS Record Found" 57 | } 58 | else 59 | { 60 | [string]$MXRecord = $DNSResult.Type 61 | } 62 | 63 | #Check to see if there is an SPF Result 64 | if ($y -ne $null) 65 | { 66 | #Print to the console that an SPF Exists 67 | Write-Host "SPF present: $($y.strings). Checking validity ..." -ForegroundColor Green 68 | 69 | #Launch the kitterman web check and setup the request 70 | $web = Invoke-WebRequest -Uri http://www.kitterman.com/spf/validate.html 71 | $web.forms[0].fields.domain = "$($y.name)" 72 | 73 | #Get the name of the domain to scan and send the data to kitterman, get and format the response 74 | $result = Invoke-RestMethod http://www.kitterman.com/getspf2.py -Body $web.forms[0].fields 75 | $message = $result.replace("`r`n", "--") 76 | 77 | #populate the array 78 | #$res.message = $result 79 | #$res.txt = $message 80 | 81 | #this is done because using |Out-String puts a carriage return at the end of each SPF record, it's frustrating looking at the CSV. 82 | [string]$record = $y.strings.replace("`r`n", " ") 83 | $res.record = $record 84 | $res.Type = $MXRecord 85 | [string]$NameExchange = $DNSResult.NameExchange 86 | $res.NameExchange = $NameExchange 87 | 88 | #This section of the script attempts to determine how the match handler -all is formed, it will also match a null SPF record. Does it hard fail mail? Does it Soft Fail? etc? 89 | If ($($y.strings) -like '*v=spf1 -all*') 90 | { 91 | $res.MatchHandler = "Null SPF (Non-Mail Sending Domain)" 92 | } 93 | ElseIf ($($y.strings) -like '*-all*') 94 | { 95 | $res.MatchHandler = "Hard Fail" 96 | } 97 | ElseIf ($($y.strings) -like '*~all*') 98 | { 99 | $res.MatchHandler = "Soft Fail" 100 | } 101 | ElseIf ($($y.strings) -like '*\?all*') 102 | { 103 | $res.MatchHandler = "Neutral" 104 | } 105 | Else 106 | { 107 | $res.MatchHandler = "Pass (SPF Will Pass All Mail)" 108 | } 109 | 110 | 111 | #Scan for the result in the message 112 | if ($message -like "*passed*") 113 | { 114 | $res.result = "Passed - This record is valid" 115 | } 116 | else 117 | { 118 | $res.result = "FAIL - This record will be ignored" 119 | } 120 | } 121 | #If there was no SPF Record found run the following 122 | else 123 | { 124 | #populate the array with dummy values 125 | #$res.message = "N/A" 126 | $res.result = "No SPF Record" 127 | $res.MatchHandler = "-" 128 | [string]$NameExchange = $DNSResult.NameExchange 129 | $res.NameExchange = $NameExchange 130 | #$res.txt = "N/A" 131 | 132 | If ($y.Type -eq $null) 133 | { 134 | $type = "-" 135 | $res.Type = $MXRecord 136 | } 137 | 138 | If ($y.strings -eq $null) 139 | { 140 | $record = "-" 141 | $res.record = $record 142 | } 143 | } 144 | return $res 145 | } 146 | 147 | 148 | function Get-DNS ([String]$domain) 149 | { 150 | try { resolve-dnsname $domain -type MX -ErrorAction Stop} 151 | catch { Write-Output "No DNS Record Found" } 152 | try { resolve-dnsname $domain -type TXT -ErrorAction Stop} 153 | catch { Write-Output "No DNS Record Found" } 154 | } 155 | 156 | #loop through the sites to scan 157 | $SiteResults = @() 158 | $SitestoScan | ForEach-Object{ 159 | $SiteResults += Validate-SPF -domain $_.Sites 160 | } 161 | 162 | #collect the results and output to CSV 163 | $SiteResults | Export-Csv -Path "$WorkingDir\results.csv" 164 | Write-Host "Report has been written to $WorkingDir\results.csv" 165 | -------------------------------------------------------------------------------- /Pyacscan - Remote Handler.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .NOTES 3 | =========================================================================== 4 | Created by: David Cottingham 5 | Purpose: This powershell script facilitates remote computer scanning using the ACSC Pyacscan.exe utility (this utility is local scanning only). 6 | You can download this utility here: https://cyber.gov.au/government/news/parliament-house-network-compromise/ 7 | 8 | This script requires PowerShell remoting, remote administrative access and SMB share access to the target C drive admin share. 9 | 10 | Scan results will be returned to the local scan directory. I hope it helps :) 11 | =========================================================================== 12 | #> 13 | 14 | Param ($MaxThreads = 50, 15 | $SleepTimer = 500, 16 | $MaxWaitAtEnd = 168000) 17 | 18 | #check for administrator privileges 19 | If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) 20 | { 21 | write-host "This script requires administrative privileges which have not been detected. This script will now exit.`r`nPlease start a powershell prompt as an administrator and run the script again. `r`n" -ForegroundColor Yellow 22 | Pause 23 | Break 24 | } 25 | 26 | $ComputerList = $(Read-Host "Please enter the path to a list of computers you wish to scan, for example C:\Results\DetailedResults.txt") 27 | If (Test-Path -Path $ComputerList -ErrorAction SilentlyContinue) 28 | { 29 | #Do nothing 30 | } 31 | else 32 | { 33 | write-host "ComputerList Not Found, please check the path you entered" -ForegroundColor Red 34 | pause 35 | break 36 | } 37 | 38 | $pyacscanpath = $(Read-Host "Please enter the path to pyacscan.exe, for example C:\temp\pyacscan.exe") 39 | If (Test-Path -Path $pyacscanpath -ErrorAction SilentlyContinue) 40 | { 41 | #Do nothing 42 | } 43 | else 44 | { 45 | write-host "Pyacscan.exe not found, please check the path you entered" -ForegroundColor Red 46 | pause 47 | break 48 | } 49 | 50 | $ResultsPath = $(Read-Host "Please enter the directory you want to output scan results to, for example C:\temp\results (the directory must exist! no trailing \ character required)") 51 | If (Test-Path -Path $ResultsPath -ErrorAction SilentlyContinue) 52 | { 53 | #Do nothing 54 | } 55 | else 56 | { 57 | write-host "The output directory does not exist, please check the path you entered" -ForegroundColor Red 58 | pause 59 | break 60 | } 61 | 62 | Write-Host "Note, this script uses PowerShell remoting, requires remote administrative access and SMB read-write to the C drive admin share." -foregroundcolor Yellow 63 | 64 | $servers = Get-Content $ComputerList | Sort-Object | Get-Unique 65 | $numcomps = $servers.Count 66 | write-host "There are $numcomps endpoints queued for scanning" -foregroundcolor "green" 67 | 68 | Write-Host "Killing existing jobs..." 69 | Get-Job | Remove-Job -Force 70 | Write-Host "Done" 71 | 72 | write-host "Ready to scan?" -foregroundcolor Yellow 73 | Pause 74 | 75 | 76 | $sb = { 77 | param ([string]$server,[string]$pyacscanpath,[string]$ResultsPath) 78 | 79 | If (!(Test-Connection -comp $server -count 1 -ea 0 -quiet)) 80 | { 81 | 82 | Write-Warning -Message "Could not ping $server assuming offline, skipping" 83 | Write-Output "$server offline" | Out-File "$ResultsPath\$server.couldnotping.txt" 84 | } 85 | else 86 | { 87 | 88 | try { Copy-Item -Path $pyacscanpath -Destination "\\$server\c$\pyacscan.exe" -ErrorAction Stop} 89 | catch { "Error Copying File To Remote Location, Likely Access Denied" | Out-File "$ResultsPath\$server.accessdenied.txt" 90 | break 91 | } 92 | 93 | Invoke-Command -ComputerName $server -ScriptBlock { & cmd.exe /c "cd c:\ && pyacscan.exe" } 94 | 95 | $file = "\\$server\c$\scan.txt" 96 | 97 | Get-Content $file | Out-File "$ResultsPath\$server.txt" 98 | 99 | Remove-Item -Path "\\$server\c$\pyacscan.exe" 100 | Remove-Item -Path "\\$server\c$\scan.txt" 101 | } 102 | } 103 | 104 | $i = 0 105 | 106 | ForEach ($server in $servers) 107 | { 108 | While ($(Get-Job -state running).count -ge $MaxThreads) 109 | { 110 | Write-Progress "Scanning In Progress" 111 | write-output "$i threads created - $($(Get-Job -state running).count) threads open, waiting for threads to close before starting more" 112 | write-output "$($i / $servers.count * 100) $("% Complete")" 113 | Start-Sleep -Milliseconds $SleepTimer 114 | } 115 | 116 | #"Starting job - $Computer" 117 | $i++ 118 | Start-Job -ScriptBlock $sb -ArgumentList $server, $pyacscanpath, $ResultsPath | Out-Null 119 | Write-Progress "Scanning In Progress" 120 | write-output CurrentOperation "$i threads created - $($(Get-Job -state running).count) threads open, scanning $server" 121 | write-output "$($i / $servers.count * 100) $("% Complete")" 122 | 123 | } 124 | 125 | $Complete = Get-date 126 | 127 | While ($(Get-Job -State Running).count -gt 0) 128 | { 129 | $ComputersStillRunning = "" 130 | ForEach ($server in $(Get-Job -state running)) { $ComputersStillRunning += ", $($server.name)" } 131 | $ComputersStillRunning = $ComputersStillRunning.Substring(2) 132 | Write-Progress "Nearly Done, Waiting For Last Jobs To Finish" 133 | write-output "$($(Get-Job -State Running).count) threads remaining" 134 | write-output "$ComputersStillRunning" 135 | write-output "$($(Get-Job -State Completed).count / $(Get-Job).count * 100)$("% Complete")" 136 | If ($(New-TimeSpan $Complete $(Get-Date)).totalseconds -ge $MaxWaitAtEnd) { "Killing all jobs still running . . ."; Get-Job -State Running | Remove-Job -Force } 137 | Start-Sleep -Milliseconds $SleepTimer 138 | } 139 | 140 | "Reading all jobs" 141 | 142 | #This section reads the results from jobs in the script block 143 | 144 | ForEach ($Job in Get-Job) 145 | { 146 | Receive-Job $Job 147 | } 148 | -------------------------------------------------------------------------------- /GetUACStatusParallelv1.0.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script gets the current status of User Account Control (UAC) on a computer 4 | 5 | .DESCRIPTION 6 | This script runs a UAC registry check across numerous computers using parallel jobs. By default the job threads are fixed at 50, change $MaxThreads to increase. 7 | 8 | This script accepts computer names from an input text file, with each computername on a new line (the script does automatic deduplication of computernames). 9 | 10 | This script will output the results to a defined CSV formatted file as prompted. 11 | 12 | This script has been made using code portions from the following scripts: 13 | http://www.ehloworld.com/1026 Pat Richard (pat@innervation.com) 14 | http://www.get-blog.com/?p=22 Ryan Witschger 15 | 16 | .USAGE 17 | This script is designed to accept user input at run time and requires no command line flags. Simply run the script :) 18 | 19 | .NOTES 20 | Version : 1.0 21 | Rights Required : Local admin over remote computer, remote registry service required 22 | Author(s) : David Cottingham (david@airlockdigital.com) 23 | Disclaimer : Please test every script before running it in production! 24 | 25 | 26 | .INPUTS 27 | None. You cannot pipe objects to this script. 28 | 29 | #Requires -Version 3.0 30 | #> 31 | 32 | Param($ComputerList = $(Read-Host "Enter the Location of the target computerlist, this must be text formatted with one computer name per line"), 33 | $OutputResults = $(Read-Host "Enter the desired location for results to be written e.g. C:\Results\UACResults.csv"), 34 | $MaxThreads = 50, 35 | $SleepTimer = 500, 36 | $MaxWaitAtEnd = 600, 37 | $OutputType = "Text") 38 | 39 | #This block checks that the computer list exists and the user has not made a typo 40 | 41 | $TestPathResult = Test-Path $ComputerList 42 | 43 | If ($TestPathResult -notmatch 'True') 44 | { 45 | Write-Warning "The entered computer list file path '$ComputerList' is not valid. Please enter a valid file path to a .txt file containing a list of computers you want to scan e.g. C:\toscan\computers.txt" 46 | } 47 | 48 | #This block loads the computer list, sorts it and only outputs unique values. It then counts the number of computers for scanning and displays this to the user. 49 | 50 | $Computers = Get-Content $ComputerList | Sort-Object | Get-Unique 51 | $numcomps = $computers.Count 52 | write-host "There are $numcomps endpoints queued for scanning" -foregroundcolor "green" 53 | 54 | #This makes sure that there are no existing powershell jobs running before commencing the script 55 | 56 | "Killing existing jobs..." 57 | Get-Job | Remove-Job -Force 58 | "Done" 59 | 60 | 61 | #This calls the pause function and waits for the user to press a key before continuing the script. 62 | write-host "Ready to scan?" -foregroundcolor Yellow 63 | Pause 64 | 65 | $sb = { 66 | param ([string] $Computer) 67 | 68 | If(!(Test-Connection -comp $Computer -count 1 -ea 0 -quiet)) 69 | 70 | { 71 | $Joboutput = "$Computer,Can't Ping (Offline)" 72 | return $Joboutput 73 | } 74 | 75 | ELSE 76 | 77 | { 78 | [string]$RegistryValue = "EnableLUA" 79 | [string]$RegistryPath = "Software\Microsoft\Windows\CurrentVersion\Policies\System" 80 | [bool]$UACStatus = $false 81 | $error.clear() 82 | 83 | Try 84 | { 85 | $OpenRegistry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine,$Computer) 86 | } 87 | Catch 88 | { 89 | $Joboutput = "$Computer,Unable to access the Remote Registry" 90 | return $Joboutput 91 | } 92 | 93 | If(!$error) 94 | { 95 | $Subkey = $OpenRegistry.OpenSubKey($RegistryPath,$false) 96 | $Subkey.ToString() | Out-Null 97 | $UACStatus = ($Subkey.GetValue($RegistryValue) -eq 1) 98 | $Joboutput = "$Computer,$UACStatus" 99 | return $Joboutput 100 | } 101 | 102 | } 103 | } 104 | 105 | $i = 0 106 | 107 | ForEach ($Computer in $Computers){ 108 | While ($(Get-Job -state running).count -ge $MaxThreads){ 109 | Write-Progress "Scanning In Progress" 110 | write-output "$i threads created - $($(Get-Job -state running).count) threads open, waiting for threads to close before starting more" 111 | write-output "$($i / $Computers.count * 100) $("% Complete")" 112 | Start-Sleep -Milliseconds $SleepTimer 113 | } 114 | 115 | #"Starting job - $Computer" 116 | $i++ 117 | Start-Job -ScriptBlock $sb -ArgumentList $computer | Out-Null 118 | Write-Progress "Scanning In Progress" 119 | write-output CurrentOperation "$i threads created - $($(Get-Job -state running).count) threads open, scanning $computer" 120 | write-output "$($i / $Computers.count * 100) $("% Complete")" 121 | 122 | } 123 | 124 | $Complete = Get-date 125 | 126 | While ($(Get-Job -State Running).count -gt 0){ 127 | $ComputersStillRunning = "" 128 | ForEach ($System in $(Get-Job -state running)){$ComputersStillRunning += ", $($System.name)"} 129 | $ComputersStillRunning = $ComputersStillRunning.Substring(2) 130 | Write-Progress "Nearly Done, Waiting For Last Jobs To Finish" 131 | write-output "$($(Get-Job -State Running).count) threads remaining" 132 | write-output "$ComputersStillRunning" 133 | write-output "$($(Get-Job -State Completed).count / $(Get-Job).count * 100 )$("% Complete")" 134 | If ($(New-TimeSpan $Complete $(Get-Date)).totalseconds -ge $MaxWaitAtEnd){"Killing all jobs still running . . .";Get-Job -State Running | Remove-Job -Force} 135 | Start-Sleep -Milliseconds $SleepTimer 136 | } 137 | 138 | "Reading all jobs" 139 | 140 | #This section reads the results from jobs in the script block, writes the file header. 141 | Write-output "#Computer,#UACEnabled" | Out-File -filepath $OutputResults 142 | 143 | ForEach($Job in Get-Job) 144 | { 145 | Receive-Job $Job | Out-File -filepath $OutputResults -Append 146 | } 147 | 148 | write-host "Please see $OutputResults for your data" -foregroundcolor "yellow" 149 | 150 | function PauseFinal 151 | 152 | { 153 | Read-Host 'Scanning Complete, press any key to exit' | Out-Null 154 | } 155 | 156 | PauseFinal 157 | -------------------------------------------------------------------------------- /InstallVelociraptor.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .NOTES 3 | =========================================================================== 4 | Created by: David Cottingham 5 | Purpose: This powershell script facilitates remote installation of the velociraptor MSI. 6 | You can download the latest version of velociraptor 7 | here: https://github.com/Velocidex/velociraptor/releases 8 | 9 | This script requires PowerShell remoting, remote administrative access and SMB share access to the target C drive admin share. 10 | 11 | Scan results will be returned to the local scan directory. I hope it helps :) 12 | =========================================================================== 13 | #> 14 | 15 | Param ($MaxThreads = 5, 16 | $SleepTimer = 500, 17 | $MaxWaitAtEnd = 168000) 18 | 19 | #check for administrator privileges 20 | If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) 21 | { 22 | write-host "This script requires administrative privileges which have not been detected. This script will now exit.`r`nPlease start a powershell prompt as an administrator and run the script again. `r`n" -ForegroundColor Yellow 23 | Pause 24 | Break 25 | } 26 | 27 | $msipath = $(Read-Host "Please enter the path to the main velociraptor MSI installer, for example C:\temp\velociraptor.msi") 28 | If (Test-Path -Path $msipath -ErrorAction SilentlyContinue) 29 | { 30 | #Do nothing 31 | } 32 | else 33 | { 34 | write-host "velociraptor msi not found, please check the path you entered" -ForegroundColor Red 35 | pause 36 | break 37 | } 38 | 39 | $ComputerList = $(Read-Host "Please enter the path to a list of computers you wish to isntall velociraptor on, for example C:\Results\toinstall.txt") 40 | If (Test-Path -Path $ComputerList -ErrorAction SilentlyContinue) 41 | { 42 | #Do nothing 43 | } 44 | else 45 | { 46 | write-host "ComputerList Not Found, please check the path you entered" -ForegroundColor Red 47 | pause 48 | break 49 | } 50 | 51 | 52 | $workingdir = Split-Path -Path $msipath 53 | write-host "`nChecking if a velociraptor client configuration file is already in the specified msi directory" 54 | 55 | If ((Get-ChildItem -Path $workingdir -Filter Velociraptor.config.yaml -File -Name) -ne $null) 56 | { 57 | $vconfig = Get-ChildItem -Path $workingdir -Filter Velociraptor.config.yaml -File -Name 58 | 59 | write-host "Velociraptor configuration file found, using $vconfig" 60 | 61 | } 62 | else 63 | { 64 | $vconfig = $(Read-Host "No file found, please enter the path to the velociraptor configuration file you wish to use, for example C:\temp\velociraptor.config.yaml") 65 | If (Test-Path -Path $vconfig -ErrorAction SilentlyContinue) 66 | { 67 | #Do nothing 68 | } 69 | else 70 | { 71 | write-host "velociraptor configuration file not found, please check the path you entered" -ForegroundColor Red 72 | pause 73 | break 74 | } 75 | } 76 | 77 | $ResultsPath = $(Read-Host "`nPlease enter the directory you want to output scan results to, for example C:\temp\results (the directory must exist! no trailing \ character required)") 78 | If (Test-Path -Path $ResultsPath -ErrorAction SilentlyContinue) 79 | { 80 | #Do nothing 81 | } 82 | else 83 | { 84 | write-host "The output directory does not exist, please check the path you entered" -ForegroundColor Red 85 | pause 86 | break 87 | } 88 | 89 | 90 | 91 | Write-Host "Note, this script uses PowerShell remoting, requires remote administrative access and SMB read-write to the C drive admin share." -foregroundcolor Yellow 92 | 93 | $servers = Get-Content $ComputerList | Sort-Object | Get-Unique 94 | $numcomps = $servers.Count 95 | write-host "There are $numcomps endpoints queued for scanning" -foregroundcolor "green" 96 | 97 | Write-Host "Killing existing jobs..." 98 | Get-Job | Remove-Job -Force 99 | Write-Host "Done" 100 | 101 | write-host "Ready to scan?" -foregroundcolor Yellow 102 | Pause 103 | 104 | 105 | $sb = { 106 | param ([string]$server,[string]$msipath,[string]$vconfig,[string]$ResultsPath) 107 | 108 | If (!(Test-Connection -comp $server -count 1 -ea 0 -quiet)) 109 | { 110 | 111 | Write-Warning -Message "Could not ping $server assuming offline, skipping" 112 | Write-Output "$server offline" | Out-File "$ResultsPath\$server.couldnotping.txt" 113 | } 114 | else 115 | { 116 | 117 | try { Copy-Item -Path $msipath -Destination "\\$server\c$\velociraptor.msi" -ErrorAction Stop} 118 | catch { "Error Copying File To Remote Location, Likely Access Denied" | Out-File "$ResultsPath\$server.accessdenied.txt" 119 | break 120 | } 121 | 122 | 123 | try { wmic product call install true,"" , "velociraptor.msi"} 124 | catch { "Error installing MSI" | Out-File "$ResultsPath\$server.msiinstallfailure.txt" 125 | break 126 | } 127 | 128 | 129 | 130 | try { Copy-Item -Path $vconfig -Destination "\\$server\c$\Program Files\Velociraptor\Velociraptor.config.yaml" -ErrorAction Stop} 131 | catch { "Error Copying velociraptor config file, agent will need manual intervention to work" | Out-File "$ResultsPath\$server.configcopyfail.txt" 132 | break 133 | } 134 | 135 | 136 | 137 | Write-Output "Installed ok" | Out-File "$ResultsPath\$server.txt" 138 | 139 | Remove-Item -Path "\\$server\c$\velociraptor.msi" 140 | } 141 | } 142 | 143 | $i = 0 144 | 145 | ForEach ($server in $servers) 146 | { 147 | While ($(Get-Job -state running).count -ge $MaxThreads) 148 | { 149 | Write-Progress "Scanning In Progress" 150 | write-output "$i threads created - $($(Get-Job -state running).count) threads open, waiting for threads to close before starting more" 151 | write-output "$($i / $servers.count * 100) $("% Complete")" 152 | Start-Sleep -Milliseconds $SleepTimer 153 | } 154 | 155 | #"Starting job - $Computer" 156 | $i++ 157 | Start-Job -ScriptBlock $sb -ArgumentList $server, $msipath, $vconfig, $ResultsPath | Out-Null 158 | Write-Progress "Scanning In Progress" 159 | write-output CurrentOperation "$i threads created - $($(Get-Job -state running).count) threads open, scanning $server" 160 | write-output "$($i / $servers.count * 100) $("% Complete")" 161 | 162 | } 163 | 164 | $Complete = Get-date 165 | 166 | While ($(Get-Job -State Running).count -gt 0) 167 | { 168 | $ComputersStillRunning = "" 169 | ForEach ($server in $(Get-Job -state running)) { $ComputersStillRunning += ", $($server.name)" } 170 | $ComputersStillRunning = $ComputersStillRunning.Substring(2) 171 | Write-Progress "Nearly Done, Waiting For Last Jobs To Finish" 172 | write-output "$($(Get-Job -State Running).count) threads remaining" 173 | write-output "$ComputersStillRunning" 174 | write-output "$($(Get-Job -State Completed).count / $(Get-Job).count * 100)$("% Complete")" 175 | If ($(New-TimeSpan $Complete $(Get-Date)).totalseconds -ge $MaxWaitAtEnd) { "Killing all jobs still running . . ."; Get-Job -State Running | Remove-Job -Force } 176 | Start-Sleep -Milliseconds $SleepTimer 177 | } 178 | 179 | "Reading all jobs" 180 | 181 | #This section reads the results from jobs in the script block 182 | 183 | ForEach ($Job in Get-Job) 184 | { 185 | Receive-Job $Job 186 | } -------------------------------------------------------------------------------- /GetLocalGroupMembersParallel v1.0.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script retrieves the BUILTIN user groups from a computer and their memberships. This allows you to determine which domain groups may have specific rights over a remote machine. 4 | 5 | .DESCRIPTION 6 | This script remotely connects to computers and retrieves the BUILTIN user memberships. It also outputs 'de-duplicated' administrative group information allowing you to determine which domain groups or users have administrative rights on machines. 7 | 8 | This script accepts computer names from an input text file, with each computername on a new line (the script does automatic deduplication of computernames). 9 | 10 | This script will output the results to two user defined CSV Files: 11 | Detailed Results CSV: Contains the full group information and computer information for analysis; 12 | Administrative Group Results CSV: Contains de-duplicated administrative group information, this file may need to be cross referenced with Detailed Results CSV for specific computer information. 13 | 14 | This script has been made using code portions from the following scripts: 15 | https://gallery.technet.microsoft.com/scriptcenter/Get-LocalGroupMembers-b714517d Piotr Lewandowski 16 | http://www.get-blog.com/?p=22 Ryan Witschger 17 | 18 | .NOTES 19 | Version : 1.0 20 | Rights Required : Local admin rights over remote computer 21 | Author(s) : David Cottingham (david@airlockdigital.com) 22 | Disclaimer : Please test every script before running it in production! 23 | 24 | 25 | .INPUTS 26 | None. You cannot pipe objects to this script. 27 | 28 | #Requires -Version 3.0 29 | #> 30 | 31 | Param($ComputerList = $(Read-Host "Enter the Location of the target computerlist, this must be text formatted with one computer name per line"), 32 | $OutputResults = $(Read-Host "Enter the file path for detailed results to be written e.g. C:\Results\DetailedResults.csv"), 33 | $AdminGroups = $(Read-Host "Enter the file path for Administrative Group Results (deduplicated) to be written e.g. C:\Result\AdminResults.csv"), 34 | $GroupName = $(Read-Host "Enter the name of the group you wish to scan for e.g Administrators (leave blank and press enter for all groups)"), 35 | $MaxThreads = 50, 36 | $SleepTimer = 500, 37 | $MaxWaitAtEnd = 600, 38 | $OutputType = "Text") 39 | 40 | #This block checks that the computer list exists and the user has not made a typo 41 | 42 | $TestPathResult = Test-Path $ComputerList 43 | 44 | If ($TestPathResult -notmatch 'True') 45 | { 46 | Throw 'The entered file path is not valid. Please enter a valid file path to a .txt file containing a list of computers you want to scan e.g. C:\toscan\computers.txt' 47 | } 48 | 49 | #This block loads the computer list, sorts it and only outputs unique values. It then counts the number of computers for scanning and displays this to the user. 50 | 51 | $servers = Get-Content $ComputerList | Sort-Object | Get-Unique 52 | $numcomps = $servers.Count 53 | write-host "There are $numcomps endpoints queued for scanning" -foregroundcolor "green" 54 | 55 | #This makes sure that there are no existing powershell jobs running before commencing the script 56 | 57 | "Killing existing jobs..." 58 | Get-Job | Remove-Job -Force 59 | "Done" 60 | 61 | 62 | #This calls the pause function and waits for the user to press a key before continuing the script. 63 | write-host "Ready to scan?" -foregroundcolor Yellow 64 | Pause 65 | 66 | $sb = { 67 | param ([string] $server, $GroupName) 68 | 69 | If(!(Test-Connection -comp $server -count 1 -ea 0 -quiet)) 70 | 71 | { 72 | 73 | Write-Warning -Message "Could not ping $server assuming offline, skipping" 74 | 75 | } 76 | 77 | ELSE 78 | 79 | { 80 | $finalresult = @() 81 | $computer = [ADSI]"WinNT://$server" 82 | 83 | if (!($groupName)) 84 | { 85 | $Groups = $computer.psbase.Children | Where {$_.psbase.schemaClassName -eq "group"} | select -expand name 86 | } 87 | else 88 | { 89 | $groups = $groupName 90 | } 91 | $CurrentDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().GetDirectoryEntry() | select name,objectsid 92 | $domain = $currentdomain.name 93 | $SID=$CurrentDomain.objectsid 94 | $DomainSID = (New-Object System.Security.Principal.SecurityIdentifier($sid[0], 0)).value 95 | 96 | 97 | foreach ($group in $groups) 98 | { 99 | $gmembers = $null 100 | $LocalGroup = [ADSI]("WinNT://$server/$group,group") 101 | 102 | 103 | $GMembers = $LocalGroup.psbase.invoke("Members") 104 | $GMemberProps = @{Server="$server";"Local Group"=$group;Name="";Type="";ADSPath="";Domain="";SID=""} 105 | $MemberResult = @() 106 | 107 | 108 | if ($gmembers) 109 | { 110 | foreach ($gmember in $gmembers) 111 | { 112 | $membertable = new-object psobject -Property $GMemberProps 113 | $name = $gmember.GetType().InvokeMember("Name",'GetProperty', $null, $gmember, $null) 114 | $sid = $gmember.GetType().InvokeMember("objectsid",'GetProperty', $null, $gmember, $null) 115 | $UserSid = New-Object System.Security.Principal.SecurityIdentifier($sid, 0) 116 | $class = $gmember.GetType().InvokeMember("Class",'GetProperty', $null, $gmember, $null) 117 | $ads = $gmember.GetType().InvokeMember("adspath",'GetProperty', $null, $gmember, $null) 118 | $MemberTable.name= "$name" 119 | $MemberTable.type= "$class" 120 | $MemberTable.adspath="$ads" 121 | $membertable.sid=$usersid.value 122 | 123 | 124 | if ($userSID -like "$domainsid*") 125 | { 126 | $MemberTable.domain = "$domain" 127 | } 128 | 129 | $MemberResult += $MemberTable 130 | } 131 | 132 | } 133 | $finalresult += $MemberResult 134 | } 135 | $finalresult | select server,"local group",name,type,domain,sid 136 | } 137 | } 138 | 139 | 140 | $i = 0 141 | 142 | ForEach ($server in $servers){ 143 | While ($(Get-Job -state running).count -ge $MaxThreads){ 144 | Write-Progress "Scanning In Progress" 145 | write-output "$i threads created - $($(Get-Job -state running).count) threads open, waiting for threads to close before starting more" 146 | write-output "$($i / $servers.count * 100) $("% Complete")" 147 | Start-Sleep -Milliseconds $SleepTimer 148 | } 149 | 150 | #"Starting job - $Computer" 151 | $i++ 152 | Start-Job -ScriptBlock $sb -ArgumentList $server | Out-Null 153 | Write-Progress "Scanning In Progress" 154 | write-output CurrentOperation "$i threads created - $($(Get-Job -state running).count) threads open, scanning $server" 155 | write-output "$($i / $servers.count * 100) $("% Complete")" 156 | 157 | } 158 | 159 | $Complete = Get-date 160 | 161 | While ($(Get-Job -State Running).count -gt 0){ 162 | $ComputersStillRunning = "" 163 | ForEach ($System in $(Get-Job -state running)){$ComputersStillRunning += ", $($System.name)"} 164 | $ComputersStillRunning = $ComputersStillRunning.Substring(2) 165 | Write-Progress "Nearly Done, Waiting For Last Jobs To Finish" 166 | write-output "$($(Get-Job -State Running).count) threads remaining" 167 | write-output "$ComputersStillRunning" 168 | write-output "$($(Get-Job -State Completed).count / $(Get-Job).count * 100 )$("% Complete")" 169 | If ($(New-TimeSpan $Complete $(Get-Date)).totalseconds -ge $MaxWaitAtEnd){"Killing all jobs still running . . .";Get-Job -State Running | Remove-Job -Force} 170 | Start-Sleep -Milliseconds $SleepTimer 171 | } 172 | 173 | "Reading all jobs" 174 | 175 | #This section reads the results from jobs in the script block 176 | 177 | ForEach($Job in Get-Job) 178 | { 179 | Receive-Job $Job | Export-CSV -Path $OutputResults -Append -NoTypeInformation -Force 180 | } 181 | 182 | #This section parses the job results and generates the Administrative Group Results CSV 183 | $ParseCSV = Import-CSV $OutputResults 184 | Add-Content $AdminGroups '"Count","Name","User/Group","Domain/Local(Blank)"' 185 | $ParseCSV |where-object {$_.'Local Group' -eq "Administrators"} | Select-Object -Property Name,Type,Domain|Group-Object -Property Name,Type,Domain -NoElement | Select-Object Count,Name |Export-Csv $AdminGroups -NoTypeInformation -Append -Force 186 | (Get-Content $AdminGroups).replace(', ', '","')| Set-Content $AdminGroups 187 | 188 | write-host "Please see $OutputResults for detailed information" -foregroundcolor Yellow 189 | write-host "Please see $AdminGroups for unique groups or users that have administrative access (assumes Administrators was searched for)" -foregroundcolor Green 190 | 191 | function PauseFinal 192 | 193 | { 194 | Read-Host 'Scanning Complete, press any key to exit' | Out-Null 195 | } 196 | 197 | PauseFinal 198 | -------------------------------------------------------------------------------- /Yara Remote PowerShell Scanner.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .NOTES 3 | =========================================================================== 4 | Created by: David Cottingham 5 | Purpose: This powershell script facilitates remote computer scanning using yara.exe and a set of specified yara rules. 6 | You can download the latest version of yara here: https://github.com/VirusTotal/yara/releases 7 | 8 | This script requires PowerShell remoting, remote administrative access and SMB share access to the target C drive admin share. 9 | 10 | Scan results will be returned to the local scan directory. I hope it helps :) 11 | =========================================================================== 12 | #> 13 | 14 | Param ($MaxThreads = 10, 15 | $SleepTimer = 500, 16 | $MaxWaitAtEnd = 168000) 17 | 18 | #check for administrator privileges 19 | If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) 20 | { 21 | write-host "This script requires administrative privileges which have not been detected. This script will now exit.`r`nPlease start a powershell prompt as an administrator and run the script again. `r`n" -ForegroundColor Yellow 22 | Pause 23 | Break 24 | } 25 | 26 | $yarascanpath = $(Read-Host "Please enter the path to the main yara executable, for example C:\temp\yara.exe") 27 | If (Test-Path -Path $yarascanpath -ErrorAction SilentlyContinue) 28 | { 29 | #Do nothing 30 | } 31 | else 32 | { 33 | write-host "yara binary not found, please check the path you entered" -ForegroundColor Red 34 | pause 35 | break 36 | } 37 | 38 | $ComputerList = $(Read-Host "Please enter the path to a list of computers you wish to scan, for example C:\Results\DetailedResults.txt") 39 | If (Test-Path -Path $ComputerList -ErrorAction SilentlyContinue) 40 | { 41 | #Do nothing 42 | } 43 | else 44 | { 45 | write-host "ComputerList Not Found, please check the path you entered" -ForegroundColor Red 46 | pause 47 | break 48 | } 49 | 50 | 51 | $workingdir = Split-Path -Path $yarascanpath 52 | write-host "`nChecking if a .yar run file is already in the yara working directory" 53 | 54 | If ((Get-ChildItem -Path $workingdir -Filter *.yar -File -Name) -ne $null) 55 | { 56 | $yarafile = Get-ChildItem -Path $workingdir -Filter *.yar -File -Name 57 | $yaracount = $yarafile | Measure-Object 58 | 59 | If ($yaracount.Count -eq "1") 60 | { 61 | $yaraiocpath = $workingdir + "\" + $yarafile 62 | write-host "Yar rule file found, using $yaraiocpath" 63 | } 64 | elseif ($yaracount.Count -ne "0") 65 | { 66 | $yarafile = $yarafile[0] 67 | $yaraiocpath = $workingdir + "\" + $yarafile 68 | write-host "Multiple Yar rule files found in the directory, using the first yar file $yaraiocpath" 69 | } 70 | } 71 | else 72 | { 73 | $yaraiocpath = $(Read-Host "No file found, please enter the path to the yara ioc file you wish to use, for example C:\temp\toscan.yar") 74 | If (Test-Path -Path $yaraiocpath -ErrorAction SilentlyContinue) 75 | { 76 | #Do nothing 77 | } 78 | else 79 | { 80 | write-host "yara ioc file not found, please check the path you entered" -ForegroundColor Red 81 | pause 82 | break 83 | } 84 | } 85 | 86 | Write-Host "`nChecking for VC Runtime Dependency" 87 | 88 | If ((Get-ChildItem -Path $workingdir -Filter vcruntime140.dll -File -Name) -ne $null) 89 | { 90 | $vcruntime = $workingdir + "\" + "vcruntime140.dll" 91 | write-host "vcruntime140 dependency found, using $vcruntime" 92 | } 93 | elseif (Test-Path -Path C:\Windows\System32\vcruntime140.dll) 94 | { 95 | $vcruntime = "C:\Windows\System32\vcruntime140.dll" 96 | Write-Host "Using system provided vcruntime140.dll located at $vcruntime" 97 | } 98 | else 99 | { 100 | $vcruntime = $(Read-Host "No VC Runtime dependency found, please enter the path to the vcruntime140.dll (this adds support for hosts that don't have C++ Runtime installed), for example C:\Windows\System32\vcruntime140.dll") 101 | If (Test-Path -Path $vcruntime -ErrorAction SilentlyContinue) 102 | { 103 | #Do nothing 104 | } 105 | else 106 | { 107 | write-host "vcruntime140.dll not found, please check the path you entered`n" -ForegroundColor Red 108 | pause 109 | break 110 | } 111 | } 112 | 113 | $ResultsPath = $(Read-Host "`nPlease enter the directory you want to output scan results to, for example C:\temp\results (the directory must exist! no trailing \ character required)") 114 | If (Test-Path -Path $ResultsPath -ErrorAction SilentlyContinue) 115 | { 116 | #Do nothing 117 | } 118 | else 119 | { 120 | write-host "The output directory does not exist, please check the path you entered" -ForegroundColor Red 121 | pause 122 | break 123 | } 124 | 125 | Write-Host "Note, this script uses PowerShell remoting, requires remote administrative access and SMB read-write to the C drive admin share." -foregroundcolor Yellow 126 | 127 | $servers = Get-Content $ComputerList | Sort-Object | Get-Unique 128 | $numcomps = $servers.Count 129 | write-host "There are $numcomps endpoints queued for scanning" -foregroundcolor "green" 130 | 131 | Write-Host "Killing existing jobs..." 132 | Get-Job | Remove-Job -Force 133 | Write-Host "Done" 134 | 135 | write-host "Ready to scan?" -foregroundcolor Yellow 136 | Pause 137 | 138 | 139 | $sb = { 140 | param ([string]$server,[string]$yarascanpath,[string]$yaraiocpath,[string]$vcruntime,[string]$ResultsPath) 141 | 142 | If (!(Test-Connection -comp $server -count 1 -ea 0 -quiet)) 143 | { 144 | 145 | Write-Warning -Message "Could not ping $server assuming offline, skipping" 146 | Write-Output "$server offline" | Out-File "$ResultsPath\$server.couldnotping.txt" 147 | } 148 | else 149 | { 150 | 151 | try { Copy-Item -Path $yarascanpath -Destination "\\$server\c$\yara.exe" -ErrorAction Stop} 152 | catch { "Error Copying File To Remote Location, Likely Access Denied" | Out-File "$ResultsPath\$server.accessdenied.txt" 153 | break 154 | } 155 | 156 | try { Copy-Item -Path $yaraiocpath -Destination "\\$server\c$\toscan.yar" -ErrorAction Stop} 157 | catch { "Error Copying File To Remote Location, Likely Access Denied" | Out-File "$ResultsPath\$server.accessdenied.txt" 158 | break 159 | } 160 | 161 | try { Copy-Item -Path $vcruntime -Destination "\\$server\c$\vcruntime140.dll" -ErrorAction Stop} 162 | catch { "Error Copying vcruntime140.dll To Remote Location, Likely Access Denied" | Out-File "$ResultsPath\$server.accessdenied.txt" 163 | break 164 | } 165 | 166 | try { $diskstoscan = Get-WmiObject Win32_Logicaldisk -Namespace "root\cimv2" -Computer $server | where {($_.DriveType -match '3')} | Select-Object DeviceID} 167 | catch { "Error Quering Remote drives via WMI, Likely Access Denied" | Out-File "$ResultsPath\$server.wmifailure.txt" 168 | break 169 | } 170 | 171 | ForEach ($disk in $diskstoscan) 172 | { 173 | $disk = $disk.DeviceID + "\" 174 | Invoke-Command -ComputerName $server -ScriptBlock {param($diskinblock) & cmd.exe /c "cd c:\ && yara.exe --recursive --threads=1 C:\toscan.yar $diskinblock >> scan.txt" } -ArgumentList $disk 175 | } 176 | 177 | $file = "\\$server\c$\scan.txt" 178 | 179 | Get-Content $file | Out-File "$ResultsPath\$server.txt" 180 | 181 | Remove-Item -Path "\\$server\c$\yara.exe" 182 | Remove-Item -Path "\\$server\c$\toscan.yar" 183 | Remove-Item -Path "\\$server\c$\scan.txt" 184 | } 185 | } 186 | 187 | $i = 0 188 | 189 | ForEach ($server in $servers) 190 | { 191 | While ($(Get-Job -state running).count -ge $MaxThreads) 192 | { 193 | Write-Progress "Scanning In Progress" 194 | write-output "$i threads created - $($(Get-Job -state running).count) threads open, waiting for threads to close before starting more" 195 | write-output "$($i / $servers.count * 100) $("% Complete")" 196 | Start-Sleep -Milliseconds $SleepTimer 197 | } 198 | 199 | #"Starting job - $Computer" 200 | $i++ 201 | Start-Job -ScriptBlock $sb -ArgumentList $server, $yarascanpath, $yaraiocpath, $vcruntime, $ResultsPath | Out-Null 202 | Write-Progress "Scanning In Progress" 203 | write-output CurrentOperation "$i threads created - $($(Get-Job -state running).count) threads open, scanning $server" 204 | write-output "$($i / $servers.count * 100) $("% Complete")" 205 | 206 | } 207 | 208 | $Complete = Get-date 209 | 210 | While ($(Get-Job -State Running).count -gt 0) 211 | { 212 | $ComputersStillRunning = "" 213 | ForEach ($server in $(Get-Job -state running)) { $ComputersStillRunning += ", $($server.name)" } 214 | $ComputersStillRunning = $ComputersStillRunning.Substring(2) 215 | Write-Progress "Nearly Done, Waiting For Last Jobs To Finish" 216 | write-output "$($(Get-Job -State Running).count) threads remaining" 217 | write-output "$ComputersStillRunning" 218 | write-output "$($(Get-Job -State Completed).count / $(Get-Job).count * 100)$("% Complete")" 219 | If ($(New-TimeSpan $Complete $(Get-Date)).totalseconds -ge $MaxWaitAtEnd) { "Killing all jobs still running . . ."; Get-Job -State Running | Remove-Job -Force } 220 | Start-Sleep -Milliseconds $SleepTimer 221 | } 222 | 223 | "Reading all jobs" 224 | 225 | #This section reads the results from jobs in the script block 226 | 227 | ForEach ($Job in Get-Job) 228 | { 229 | Receive-Job $Job 230 | } -------------------------------------------------------------------------------- /VirusTotal PowerShell Scanner.ps1: -------------------------------------------------------------------------------- 1 | #Requires -version 4.0 2 | #This powershell script is designed to scan a drive or directory location for files and check the file hashes against VirusTotal. 3 | #It has the capability to detect the inserting of a USB device into the pc and automatically commence scanning of the volume. 4 | #To stop the cookie errors run the following command line command (note this will reduce IE cookie security) 'reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\3" /t REG_DWORD /v 1A10 /f /d 0' 5 | #Adapted from Emin's codebase which can be found here https://p0w3rsh3ll.wordpress.com/2014/04/05/analysing-files-with-virustotal-com-public-api/ 6 | #Author: David Cottingham 7 | 8 | $scantype = $(Read-Host "Please enter '1' for USB automatic detection or '2' for manual scan") 9 | 10 | If ($scantype -eq 1) { 11 | 12 | #To ensure multiple events do not get registered, we first call to unregister any exising event called volumeChange. Errors are supressed. 13 | Unregister-Event -SourceIdentifier volumeChange -ErrorAction SilentlyContinue 14 | 15 | #Register drive monitoring event and wait 16 | Register-WmiEvent -Class win32_VolumeChangeEvent -SourceIdentifier volumeChange 17 | Write-Host "Waiting for drive attachment" -ForegroundColor Green 18 | $newEvent = Wait-Event -SourceIdentifier volumeChange 19 | 20 | #This while statement ensures the program only runs upon USB device insert. It checks for the insert type, if it is any other type it removes and adds the event again and loops. 21 | while ($newEvent.SourceEventArgs.NewEvent.EventType -ne 2){ 22 | Remove-Event -SourceIdentifier volumeChange 23 | $newEvent = Wait-Event -SourceIdentifier volumeChange 24 | } 25 | 26 | Write-Host "Commencing scan of $($newEvent.SourceEventArgs.NewEvent.DriveName) drive" -ForegroundColor yellow 27 | 28 | $Path = $($newEvent.SourceEventArgs.NewEvent.DriveName) 29 | #Remove the WMI event results as we no longer require it. This also prevents old events from firing upon subsequent script runs. 30 | Remove-Event -SourceIdentifier volumeChange 31 | 32 | } else { 33 | 34 | $path = $(Read-Host "Please enter the drive or path you wish to scan, this can be a drive or directory i.e. 'E:\' or 'E:\Scan\' Directories are searched recursively") 35 | 36 | } 37 | 38 | $allfiles = @() 39 | # We first clear all errors in the automatic variable 40 | $Error.Clear() 41 | # We capture all files and let error happen silently and being logged into the $error automatic variable 42 | $allfiles = Get-ChildItem -Path $Path -Recurse -Force -Include * -File -ErrorAction SilentlyContinue 43 | 44 | # Let us know what happen 45 | $Error | Where { $_.CategoryInfo.Reason -eq "PathTooLongException" } | ForEach-Object -Begin{ 46 | Write-Verbose -Message "The following folders contain a file longer than 260 characters" 47 | # Get-ChildItem : The specified path, file name, or both are too long. 48 | # The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters. 49 | } -Process { 50 | $_.CategoryInfo.TargetName 51 | } 52 | 53 | Write-Host ("There's a total of {0} files" -f $allfiles.Count) -ForegroundColor Green 54 | 55 | # Show extensions by occurence 56 | $allfiles | Group -NoElement -Property Extension | Sort -Property Count -Descending 57 | 58 | Start-Sleep -Seconds 1 59 | 60 | $totalzip = ($allfiles | Where { $_.Extension }).Count 61 | $filecount = 0 62 | $knownmalicious = 0 63 | 64 | # Select only files with a zip extension 65 | $results = @() 66 | $allfiles | Where { $_.Extension } | 67 | ForEach-Object { 68 | 69 | $filecount++ 70 | 71 | $hash = $res = $page = $checksum = $obj = $outtext = $null 72 | 73 | Start-Sleep -Milliseconds (Get-Random -Maximum 750 -Minimum 500) 74 | 75 | $hash = (Get-FileHash -Path $_.FullName -Algorithm SHA256).Hash 76 | 77 | Write-Host ('Searching file {2}/{3} on {0} with sha256 {1}' -f $_.FullName,$hash,$filecount,$totalzip) 78 | 79 | # Append a SHA256 80 | $_ | Add-Member -MemberType NoteProperty -Name SHA256 -Value $hash -Force 81 | 82 | # Search virustotal by SHA256 83 | $res = (Invoke-WebRequest -Uri 'https://www.virustotal.com/en/search/' -Method Post -Body "query=$hash" -MaximumRedirection 0 -ErrorAction SilentlyContinue -UseBasicParsing) 84 | 85 | if ($res.StatusCode -eq 302 ) { 86 | if ($res.Headers.Location) { 87 | try { 88 | $page = Invoke-WebRequest -Method GET -Uri $res.Headers.Location -ErrorAction SilentlyContinue -MaximumRedirection 0 89 | } catch { 90 | Write-Warning -Message "The request on $($res.Headers.Location) returned $($_.Exception.Message)" 91 | Write-Host "The server has returned an error, please accept captcha and press any key to continue. If no captcha is deplayed you have been temporarily banned :'(" -foregroundcolor Yellow 92 | start $res.Headers.Location 93 | Pause 94 | } 95 | if ($page.Headers.Location -notmatch "file/not/found/") { 96 | try { 97 | $obj = New-Object -TypeName PSObject 98 | $outtext = ($page.AllElements | Where { ($_.TagName -eq 'TBODY') -and ($_.outerHTML -match "$hash") -and ($_.outerText -match "Detection\sratio") }).OuterText 99 | if ($outtext) { 100 | $outtext -split "`n" | ForEach-Object { 101 | if ($_ -match ":") { 102 | $obj | Add-Member -MemberType NoteProperty -Name ($_ -split ":")[0] -Value (-join($_ -split ":" )[1..($_ -split ":" ).count]) -Force 103 | } else { 104 | Write-Warning -Message "Mismatch with ':'" 105 | $_ 106 | } 107 | } 108 | # Analysis tab 109 | $count = 0 110 | $analysisar = @() 111 | $AVName = $DetectionDate = $DetectionRate = $null 112 | 113 | ([regex]'|\stext\-green">)(?.*)("\sclass=icon\-ok\-sign\sdata\-toggle="tooltip">|\s)').Matches( 114 | $page.AllElements.FindById('antivirus-results').outerHTML) | ForEach-Object -Process { 115 | $count++ 116 | switch ((@($_.Groups))[-1]) { 117 | {$_ -match '\d{8}'} { $DetectionDate = $_ ; break} 118 | {$_ -match '(^\-$|^File\snot\sdetected$)'}{ $DetectionRate = '-' ; break } 119 | {$_ -match '.*'} { 120 | if ($count -eq 1) { 121 | $AVName = $_ 122 | } else { 123 | $DetectionRate = $_ 124 | } 125 | ; break 126 | } 127 | } 128 | if ($count -eq 3) { 129 | $count = 0 130 | $analysisar += New-Object -TypeName PSObject -Property @{ 131 | Update = $DetectionDate 132 | Result = $DetectionRate 133 | Antivirus = $AVName 134 | } 135 | } 136 | } 137 | $obj | Add-Member -MemberType NoteProperty -Name Analysis -Value $analysisar -Force 138 | $obj 139 | 140 | if ($obj.'Detection ratio' -notmatch '0 / ??'){ 141 | $knownmalicious++ 142 | } 143 | 144 | $_ | Add-Member -MemberType NoteProperty -Name VTResults -Value $obj -Force 145 | } else { 146 | # Write-Warning -Message "$outtext # is because the file has probably been never submitted" 147 | # it shouldn't happen but... who knows 148 | $_ | Add-Member -MemberType NoteProperty -Name VTResults -Value ([string]::Empty) -Force 149 | } 150 | } catch { 151 | $_ 152 | } 153 | } else { 154 | Write-Warning -Message "the file was not found" 155 | $_ | Add-Member -MemberType NoteProperty -Name VTResults -Value "Unknown by VT" -Force 156 | } 157 | } else { 158 | Write-Warning -Message "the location in the header is empty" 159 | $_ | Add-Member -MemberType NoteProperty -Name VTResults -Value "Header empty issue" -Force 160 | } 161 | } else { 162 | Write-Warning -Message "the page returned a $($res.StatusCode) status code" 163 | $_ | Add-Member -MemberType NoteProperty -Name VTResults -Value "Status code issue" -Force 164 | } 165 | $results += $_ 166 | } 167 | 168 | Write-Host ("There's a total of {0} results" -f $results.Count) -ForegroundColor Magenta 169 | 170 | $unknowncount = ("{0}" -f ( 171 | $results | Where 'VTResults' -eq "Unknown by VT").Count 172 | ) 173 | 174 | Write-Host "There's a total of $unknowncount unknown files" -ForegroundColor Magenta 175 | 176 | # Export results to CSV 177 | $outputpath = "$($env:USERPROFILE)\Documents\VT.analysis.$(get-date -f yyyy-MM-dd-hh-mm).csv" 178 | 179 | # First unknown files 180 | ($results | Where 'VTResults' -eq "Unknown by VT") | 181 | Select Name,FullName,SHA256, 182 | @{l='Ratio';e={'Unknown by VT'}}, 183 | @{l='MalwareName';e={[string]::Empty}} | 184 | Export-Csv -Path "$outputpath" -Encoding UTF8 -NoTypeInformation -Delimiter "," 185 | 186 | # Then export files that are identified as malware 187 | $knowncount = ("{0}" -f ( 188 | $results | Where 'VTResults' -notin @("Unknown by VT","Header empty issue","Status code issue")).Count 189 | ) 190 | 191 | Write-Host "There's a total of $knowncount known files" -ForegroundColor Magenta 192 | Write-Host "There's a total of $knownmalicious potentially malicious files" -ForegroundColor Magenta 193 | 194 | ($results | Where 'VTResults' -notin @("Unknown by VT","Header empty issue","Status code issue")) | 195 | Select Name,FullName,SHA256, 196 | @{l='Ratio';e={ 197 | $_.VTResults.'Detection Ratio' -replace '\s','' 198 | }}, 199 | @{l='MalwareName';e={ 200 | ($_.VTResults.Analysis | Where { $_.Result -notmatch "^\-$"}).Result -as [string[]] 201 | }} | Export-Csv -Path $outputpath -Encoding UTF8 -Append -NoTypeInformation -Delimiter "," 202 | 203 | If ($knownmalicious -ne 0) { 204 | 205 | Invoke-Item $outputpath 206 | 207 | Write-Host "Launching results file $outputpath" -ForegroundColor Green 208 | 209 | } else { 210 | 211 | Write-Host "There were no detections with a ratio count higher than 0, Note: there were $unknowncount unknown files. Further detailed results can be found in $outputpath" -ForegroundColor Green 212 | 213 | } 214 | --------------------------------------------------------------------------------