-
Notifications
You must be signed in to change notification settings - Fork 4
/
Hash-Huntress.ps1
134 lines (105 loc) · 5.32 KB
/
Hash-Huntress.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
##############################################################################
# Script: Hash-Huntress.ps1
# Date: 2021.03.20
# Version: 3.5
# Author: Blake Regan @crash0ver1d3
# Purpose: Hunt for files hash or process hash matching IOCs you provide, at scale across domain.
# Legal: Script provided "AS IS" without warranties or guarantees of any
# kind. USE AT YOUR OWN RISK. Public domain, no rights reserved.
#NOTE: This script requires Windows RM to be enabled and run with an account with elevated privileges on target for domain use.
#By default, this will work with a account that is a memeber of the local administrators group on the targets
#If you do not want to use domain admin to perform this, you can establish a lower privilege domain account as a member of local adminisrators using
#Restricted groups and applying via Group Policy
function Hash-Huntress {
#Starts transcript, to capture output to review later in IR phases
$timestamp=(Get-Date -UFormat "%Y%m%d_%H-%M-%S")
$TranscriptFile=(Get-item ".\").FullName + "\Hash-Huntress_Transcript$timestamp.txt"
#start transcription to defined file with timestamp. Allows multiple unique files to be stored in same directory
Start-Transcript -Path $TranscriptFile
#start defintion of script block, to be passed via the Invoke-Command -ScriptBlock command
#allows locally defined criteria to be passed to remote host(s)
$Hash_Huntress ={
#Define Path1 and Hash1 to search for, and gather hashes to loop through from specified directory. SHA-256 is default Algo
$IOCPath1="C:\Users\Public\Downloads"
$IOCHash1="23A243A1CE474C4DA90B1003FFCBAF9A3FF25E0787844BFE74C21671FDD8B269"
#verify if $IOCPath1 exists, and if so gather hashes and store to array, if not, notify and move on, set $IOCPath1_Check to false
if (Test-Path -Path $IOCPath1)
{
write-host
write-host "Path $IOCPath1 exists, continuing..."
$IOC1DirContents=(dir -Path $IOCPath1 | Get-FileHash)
}
#If path does not exists on host, skip attempts to process
else
{
write-host
write-host "Path $IOCPath1 does not exist, skipping..."
$IOCPath1_Check=$false
}
#Enumerate through each hashed file in the specified directory, and compare for match
if (!($IOCPath1_Check))
{
#For each of the hashes contained in the defined directory
foreach ($IOC1DirContentHash in $IOC1DirContents)
{
#Evaluate whether current element in the array matches the defined IOC, if true declare match and identify path and filename, and host
if ($IOC1DirContentHash.Hash -eq $IOCHash1)
{
write-host
write-host "$($IOC1DirContentHash.Hash) matches IOC1!!! Discovered $($IOC1DirContentHash.Path) on $ServerName"
}
}
}
#Define Path2 and Hash2 to search for and gather hashes to loop through from specified directory. SHA-256 is default Algo
$IOCPath2="C:\Windows\system32"
$IOCHash2 = "E9E646A9DBA31A8E3DEBF4202ED34B0B22C483F1ACA75FFA43E684CB417837FA"
#verify if $IOCPath2 exists, and if so gather hashes and store to array, if not, notify and move on, set $IOCPath2_Check to false
if (Test-Path -Path $IOCPath2)
{
write-host
write-host "Path $IOCPath2 exists, continuing..."
$IOC2DirContents=(dir -Path $IOCPath2 | Get-FileHash)
}
#If path does not exists on host, skip attempts to process
else
{
write-host
write-host "Path $IOCPath2 does not exist, skipping..."
$IOCPath2_Check=$false
}
#evaluate if $IOCPath2_Check, if false, skip attempt to process
if (!($IOCPath2_Check))
{
#Enumerate through each hashed file in the specified directory, and compare for match
foreach ($IOC2DirContentHash in $IOC2DirContents)
{
#Evaluate whether current element in the array matches the defined IOC, if true declare match and identify path and filename, and host
if ($IOC2DirContentHash.Hash -eq $IOCHash2)
{
write-host
write-host "$($IOC2DirContentHash.Hash) matches IOC2!!! Discovered $($IOC2DirContentHash.Path) on $ServerName"
}
}
}
#end of $Hash_Huntress script block statement
}
#dynamically identify Default Domain Naming context and assign to a variable to use for distinguishedname of OU to query member servers/workstations
$root = [ADSI]"LDAP://RootDSE"
$DOMAIN = $root.defaultNamingContext
<#Main Action of the script#>
#gather specified members from OU, and pass the $Hash-Huntress script block to them
$Servers=Get-ADComputer -filter * -SearchBase "OU=Servers,$DOMAIN" -Properties Name,OperatingSystem | where-object {$_.OperatingSystem -like "Windows Server 2012*" -or $_.OperatingSystem -like "Windows Server 2016*" -or $_.OperatingSystem -like "Windows Server 2019*"} | select-object -Property Name, OperatingSystem
#For each member server represented in the array defined as $Servers, execute the $Hash_Huntress script block
foreach ($Server in $Servers)
{
Invoke-Command -ComputerName $Server.Name -ScriptBlock $Hash_Huntress
}
}
#Stops transcript, to capture output to review later in IR phases
#If you just define Stop-Transcript, when the script is loaded as a function
#an error will be thrown, stating no transcription is taking place currently.
#Checking the value of the $StarTranscript variable, which contains the file that we defined at the top of the script ;)
If ($StartTranscript -ne $null)
{
Stop-Transcript
}