Powershell Check IOC

Found a script to check for indicators of compromise. Works w/Nagios, can run at command line. Unmodified with original authors comments.

Usage:

Copy the .ps1 to the system you are investigating.
Open Powershell as admin
Set-executionpolicy=bypass (y)

Navigate to the folder where you saved the file.

To run a check for the last 24 hours and export to a file called results.txt in the same folder as the file:
.\check_ioc.ps1 (60*24) > results.txt
or for the last 30min
.\check_ioc.ps1 30 > results.txt

(60*24) = 1440 minutes or 24 hours. Adjust as necessary.

#
# check_ioc.ps1 - v1.4 - 6November2016 - by Dallas Haselhorst
#	Look for the associated SANS Gold Paper to describe this work in greater detail as well if you'd like to learn more.
#	https://www.sans.org/reading-room/whitepapers/critical/uncovering-indicators-compromise-ioc-powershell-event-logs-traditional-monitorin-36352
#	The most up-to-date version of the Gold Paper may also be found on my blog post.
# 	http://linuxincluded.com/uncovering-indicators-of-compromise/
#	
#	This script attempts to locate indicators of compromise on Windows systems. Much of the legwork was performed by the
#	National Security Agency (NSA) in the white paper, "Spotting the Adversary with Windows Event Log Monitoring" (16Dec2013) so a
#	huge thank you to them. The various checks below are notated with the corresponding section from the white paper wherever valid.
#	Example: (4.1) ties to section 4.1 Application Whitelisting in the NSA white paper 
#
#	Heavily modified from the script, watch-eventlogs.ps1, found on Nagios Exchange and originally written by Aaron Wurthmann. 
#	Nonetheless, thanks for the framework and various bits of code Aaron!
#
#	If you are using this from a command line and not Nagios, simply type the script name followed by the amount of time from now
#	to check. Running in this mode will likely be helpful in incident handling and incident response to track down what changed in
#	the last 3 hours, 24 hours, or whatever timeframe you decide.
#	Examples: 
#	.\check_ioc.ps1 30 -- this line would search for the "selected" indicators of compromise (below) in the last 30 minutes 
#	.\check_ioc.ps1 30 > output.txt -- this line would do the same the above but send the output to an output file
#	.\check_ioc.ps1 (60*24) -- would search for the "selected" indicators of compromise (below) in the last 24 hours (too lazy for math)
#
#	For Nagios XI NCPA usage, the following line should be copied to the $ARG1$ text box
#	-t '<token>' -P <port number> -M 'agent/plugin/check_ioc.ps1/<ArgLastMinutes>'
#	For testing from the Nagios command line, add './check_ncpa.py -H <IP address>' (minus quotes) to the above line
#	Notes: 
#	ArgLastMinutes should be populated with the time to check in minutes, e.g. 30 (for 30 minutes), 120 (for 2 hours), etc.
#	Levels: 0 for all logs (LogAlways), 1 for Critical, 2 for Error, 3 for Warning, 4 for Informational, 5 for Verbose
#	Example:
#	-t 'TokenPass' -P 5693 -M 'agent/plugin/check_ioc.ps1/120' 
#	-- above line would search for the "selected" indicators of compromise (below) in the last 2 hours 
#
#	If running the pass-the-hash (PtH) checks on a domain controller this will work without any modifications
#	If running on a non-domain system, you will need to enable "Audit logon events" using the following steps
#	1) Click Start, click Run, type "gpedit.msc" and hit Enter 2) On the left hand side, navigate to Local Computer Policy > 
#	Computer Configuration > Windows Settings > Security Settings > Local Policies > Audit Policy 3) On the right hand side, double-click 
#	“Audit logon events” 4) Check the boxes for Success and Failure, click OK.
#	If in doubt, check your event viewer for Event IDs 4624 or 4625. If they are not there, it's either not turned on or it's logging 
#	elsewhere. ScheduledTaskCheck, RegKeyModCheck, and FileFolderModCheck all require "Audit object access" to be modified. This can be 
#	accomplished in the same manner "Audit logon events" is enabled above. 
#
#	If you are still reading this, great! For Nagios users, it may not make sense to check multiple IoC in single check. Instead, 
#	I would strongly suggest putting several logical selections together. For example, enable all the account-related tests and put them 
#	in their own separate check, enable all the kernel driver signing checks and put them in their own check, etc. Also, if you worried
# 	about log integrity, I would *strongly* recommend forwarding the logs and possibly even monitoring the event log service itself to 
#	ensure it does not get disabled in order to thwart all the log monitoring goodness of this script.
#

# 1 if you are running command for Nagios, comment out or change to 0 if you are running it interactively 
$Nagios = 0

# Prevent PowerShell from auto-wrapping at 80 characters (for Nagios), cause newline characters on write-output otherwise
if ($Nagios -eq 1) {
	if( $Host -and $Host.UI -and $Host.UI.RawUI ) {
	$rawUI = $Host.UI.RawUI
	$oldSize = $rawUI.BufferSize
	$typeName = $oldSize.GetType( ).FullName
	$newSize = New-Object $typeName (500, $oldSize.Height)
	$rawUI.BufferSize = $newSize
	}
}

Function CreateEventsOutput # Should receive argument to reflect what it is working on. For example: SuccessfulPtH or FailedPtH
{

$EntryCount = 0
$LogCount = 0

	If ($Events) { 
	$LogCount = $Events.Count
	If ((($args -eq "FailedUserAcctLogin") -And ($LogCount -ge $FailedUserAcctThreshold)) -Or ($args -ne "FailedUserAcctLogin"))
	{ 
	# set global critical flag if events exist
	$script:CriticalFlag = 1 
	# separate out each of the checks so they can be read more easily
	$script:FullOutput+="
------------------$args Events------------------
	"
	# loop through the entries and format the output
    ForEach ($LogEntry in $Events) {
		$Level=$LogEntry.Level.ToString()
		$Message=$LogEntry.Message.Substring(0,[System.Math]::Min($EventMessageLength, $LogEntry.Message.Length)).TrimEnd().ToString()+'...' 
		$ProviderName=$LogEntry.ProviderName.ToString()
		$LogName=$LogEntry.LogName.ToString()
        $TimeCreated=$LogEntry.TimeCreated.ToString()
        $Id=$LogEntry.Id.ToString()
        $EntryCount++ 
         
                $script:EventResults=@"

$EntryCount - At: $TimeCreated
$EntryCount - LogName: $LogName  
$EntryCount - Level: $Level  
$EntryCount - Event ID: $Id
$EntryCount - Source: $ProviderName
$EntryCount - Message: $Message

$EventResults
"@
    }
	$script:FullOutput+=$EventResults 
	# empty the global eventresults 
	$script:EventResults = ""
	}
}

#If ($CondensedOutput) { $script:CondensedOutput+="; " }
If (($args -eq "FailedUserAcctLogin") -And ($LogCount -lt $FailedUserAcctThreshold))
{ $script:CondensedOutput+="$args` below threshold:$LogCount " }
Else { $script:CondensedOutput+="$args`:$EntryCount " }

}


# Pull in time argument if given, otherwise, default to [last] 30 mins 
$ArgLastMinutes = $args[0]
if (!$ArgLastMinutes) { $ArgLastMinutes = 30 }
$ArgTimeQuery = ($ArgLastMinutes*60*1000) # XML queries must be in micro seconds 
$EventMessageLength = 800 # main event information is about 30; should be around 800 to get a majority of details including the message text

# Change the values to 0 for items you do not want to check
$SuccessfulPtHCheck = 1 #(4.15) detects pass the hash attempts; may include false indicators in cases where remote desktop or remoteapp is utilized; added KeyLength to improve reliability (Thanks Dave Kennedy)
$FailedPtHCheck = 1 #(4.15) detects failed pass the hash attempts; same false indicator warning as PtH above
$LogClearCheck = 1 # (4.6) checks for all types of event log clears
$FirewallRuleModCheck = 1 # (4.5) checks for firewall rule adds, changes and deletions, may cause false indicators
$ServiceAddCheck = 1 # (4.7) checks for new Windows services
$AppErrorCheck = 0 # (4.2) may cause false indicators as applications do crash on their own; very useful in well-known environments
$AppHangCheck = 0 # (4.2) may cause false indicators as applications do crash on their own; very useful in well-known environments
$BSODCheck = 0 # (4.2) may cause false indicators as applications do crash on their own; very useful in well-known environments
$WindowsErrorReportingCheck = 0 # (4.2) may cause false indicators 
$ServiceFailCrashCheck = 0 # (4.3) may cause false indicators 
$AppLockerBlockCheck = 0 # (4.1) AppLocker must be configured
$AppLockerWarningCheck = 0 # (4.1) AppLocker must be configured
$SRPBlockCheck = 0 # (4.1) software restriction polices must be configured
$EMETCheck = 0 # (4.2) EMET must be configured and it will cause errors if you don't have it installed
$NewKernelFilterDriverCheck = 0 # (4.7) causes quite a few false indicators so you would have to add exceptions
$AppInstallCheck = 0 # (4.7) may cause false indicators Note: does not work in Win8 
$MSIInstallCheck = 0 # (4.7) may cause false indicators; very useful in well-known environments
$AccountLockoutCheck = 0 # (4.8) may cause false indicators if the # of invalid password attempts to lockout in group policy is low (recommend 25)
$UserAddPrivGroupCheck = 0 # (4.8) user added to privileged group
$SecEnabledGroupModCheck = 0 # (4.8) security-enabled group modification
$FailedUserAcctLoginCheck = 1 # (4.8) failed user account login, works with FailedUserAcctThreshold value below
# FailedUserAcctThreshold works in conjunction with the check above, FailedUserAcctLoginCheck
# Find out what a "standard" baseline number is for the period of time you are checking for and then add a bit more to avoid false indicators
$FailedUserAcctThreshold = 50 # threshold for the number of failed logins before 'critical' is triggered 
$InvalidImageHashFileCheck  = 1 # (4.9) kernel driver signing - detected an invalid image hash of a file
$InvalidPageHashFileCheck = 1 # (4.9) kernel driver signing - detected an invalid page hash of an image file
$CodeIntegrityCheck = 0 # (4.9) kernel driver signing - code integrity check
$FailedKernelDriverCheck = 1 # (4.9) kernel driver signing - failed kernel driver loading
$LSASAMPassChangesCheck = 0 # indicators code is loaded into LSA (Local Security Authority) or SAM (Security Account Manager) and watching for password changes (Thanks Jessica Payne)
$NewMassStorageInstallCheck = 0 # (4.13) new mass storage installation; this occurs every time the device is inserted (not just the first time)
# Recommend to disable all customer experience tasks to avoid false indicators with line below. Turn off in "Action Center" and then disable *ALL* jobs under 
# "Microsoft/Windows/Application Experience" and "Customer Experience Improvement Program" (and "Server" jobs if it exists under the latter library)
$ScheduledTaskCheck = 0 # checks for various changes to the scheduled tasks (for persistent access)
# auditing must be enabled/configured for registry keys you want to monitor, the check below is looking for "CurrentVersion" in the message
$RegKeyModCheck = 1 # checks for changes to the various registry keys that might be used for persistent access 
# auditing must be enabled/configured for the files or folders you want to monitor
#$FileFolderModCheck = 1 # checks for changes to high value files you specify. Note: Do not attempt to audit the entire c:\windows directory 

# set default properties we will pull with each query
$Properties='Level','Message','ProviderName','TimeCreated','Id','LogName'

If ($SuccessfulPtHCheck -eq 1) {
$SuccessPtHQuery = @'
<QueryList> 
	<Query Id="0"> 
		<Select Path="Security"> 
		*[System[(EventID="4624")
		and 
		(Level=4 or Level=0) 
		and 
		TimeCreated[timediff(@SystemTime) <= 
'@
$SuccessPtHQuery += $ArgTimeQuery
$SuccessPtHQuery += @'
		] ]]
		and
		*[EventData[Data[@Name="LogonType"] and (Data="3")]]
		and
		*[EventData[Data[@Name="AuthenticationPackageName"] = "NTLM"]]
		and
		*[EventData[Data[@Name="KeyLength"] = "0"]]
		and
		*[EventData[Data[@Name="TargetUserName"] != "ANONYMOUS LOGON"]]
		</Select> 
		</Query> 
</QueryList>
'@
$Events = Get-winevent -FilterXml $SuccessPtHQuery -ea SilentlyContinue | Select-Object -Property $Properties
CreateEventsOutput "SuccessfulPtH"
}

If ($FailedPtHCheck -eq 1) {
$FailedPtHQuery = @'
<QueryList> 
	<Query Id="0"> 
		<Select Path="Security"> 
		*[System[(EventID="4625")
		and 
		(Level=4 or Level=0) 
		and 
		TimeCreated[timediff(@SystemTime) <= 
'@
$FailedPtHQuery += $ArgTimeQuery
$FailedPtHQuery += @'
		] ]]
		and
		*[EventData[Data[@Name="LogonType"] and (Data="3")]]
		and
		*[EventData[Data[@Name="AuthenticationPackageName"] = "NTLM"]]
		and
		*[EventData[Data[@Name="KeyLength"] = "0"]]
		and
		*[EventData[Data[@Name="TargetUserName"] != "ANONYMOUS LOGON"]]
		</Select> 
		</Query> 
</QueryList>
'@
$Events = Get-winevent -FilterXml $FailedPtHQuery -ea SilentlyContinue | Select-Object -Property $Properties
CreateEventsOutput "FailedPtH"
}

If ($LogClearCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='Security','System'; id=1102,104; Level=4; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "LogClear"
}

If ($FirewallRuleModCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='Microsoft-Windows-Windows Firewall With Advanced Security/Firewall'; id=2003,2004,2005,2006,2033; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "FirewallRuleMod"
}

If ($ServiceAddCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='System'; id=7045; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "ServiceAdd"
}

If ($AppErrorCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='Application'; ProviderName='Application Error'; id=1000; Level = 2; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "AppError"
}

If ($AppHangCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='Application'; ProviderName='Application Hang'; id=1002; Level = 2; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "AppHang"
}

If ($BSODCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='System'; id=1001; Level = 2; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "BSOD"
}

If ($WindowsErrorReportingCheck -eq 1 ) {
$Events = Get-winevent -FilterHashtable @{logname='Application'; id=1001; Level = 4; ProviderName='Windows Error Reporting'; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "WindowsErrorReporting"
}

If ($ServiceFailCrashCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='System'; id=7022,7023,7024,7026,7031,7032,7034; Level = 2; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "ServiceFailsCrash"
}

If ($AppLockerBlockCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='Microsoft-Windows-AppLocker/EXE and DLL'; id=8003,8004; Level = 2,3; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "AppLockerBlock"
}

If ($AppLockerWarningCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='Microsoft-Windows-AppLocker/MSI and Script'; id=8006,8007; Level = 2,3; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "AppLockerWarning"
}

If ($EMETCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='Application'; ProviderName='EMET'; id=1,2; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "EMET"
}

If ($NewKernelFilterDriverCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='System'; id=6; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "NewKernelFilterDriver"
}

If ($AppInstallCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='Microsoft-Windows-Application-Experience/Program-Inventory'; id=903,904; ProviderName='Microsoft-Windows-Application-Experience'; Level=4; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "AppInstall"
}

If ($MSIInstallCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='Application'; id=1022,1033; ProviderName='MsiInstaller'; Level=4; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "MSIInstall"
}

If ($AccountLockoutCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='Security'; id=4740; ProviderName='Microsoft-Windows-Security-Auditing'; Level=4; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "AccountLockout"
}

If ($UserAddPrivGroupCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='Security'; id=4728,4732,4756; ProviderName='Microsoft-Windows-Security-Auditing'; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "UserAddPrivGroup"
}

If ($SecEnabledGroupModCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='Security'; id=4735; ProviderName='Microsoft-Windows-Security-Auditing'; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "SecEnabledGroupMod"
}

If ($FailedUserAcctLoginCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='Security'; id=4625; ProviderName='Microsoft-Windows-Security-Auditing'; Level=0; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "FailedUserAcctLogin"
}

If ($InvalidImageHashFileCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='Security'; id=5038; ProviderName='Microsoft-Windows-Security-Auditing'; Level=0; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "InvalidImageHashFile"
}

If ($InvalidPageHashFileCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='Security'; id=6281; ProviderName='Microsoft-Windows-Security-Auditing'; Level=0; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "InvalidPageHashFile"
}

If ($CodeIntegrityCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='Microsoft-Windows-CodeIntegrity/Operational'; id=3001,3002,3003,3004,3010,3023; ProviderName='Microsoft-Windows-CodeIntegrity'; Level=2,3; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "CodeIntegrity"
}

If ($FailedKernelDriverCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='System'; id=219; ProviderName='Microsoft-Windows-Kernel-PnP'; Level=3; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "FailedKernelDriver"
}

If ($LSASAMPassChangesCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='Security'; id=4610,4611,4614,4622; Level=0; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "LSASAMPassChanges"
}

If ($NewMassStorageInstallCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='Microsoft-Windows-Kernel-PnP/Configuration'; id=400,410; ProviderName='Microsoft-Windows-Kernel-PnP'; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "NewMassStorageInstall"
}

If ($ScheduledTaskCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='Security'; id=4698,4699,4700,4701,4702; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | Select-Object -Property $Properties 
CreateEventsOutput "ScheduledTask"
}

If ($RegKeyModCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='Security'; id=4657; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | where-object {$_.Message -like "*CurrentVersion*" } | Select-Object -Property $Properties 
CreateEventsOutput "RegKeyMod"
}
<#
If ($FileFolderModCheck -eq 1) {
$Events = Get-winevent -FilterHashtable @{logname='Security'; id=4656,4658; StartTime = (Get-Date).AddMinutes(-$ArgLastMinutes) } -ea SilentlyContinue | where-object {$_. -eq "" } | Select-Object -Property $Properties 
CreateEventsOutput "FileFolderMod"
}
#>

If ($CriticalFlag -eq 1) { 
# write the "summary" at the top and bottom so you don't have to hunt for it
# write-host is used first so it returns one line to Nagios without newline characters
# write-output allows us to easily re-direct the script from the command line to a separate file 
$OutputString = "Critical: $CondensedOutput"
$OutputString += "in the last $ArgLastMinutes minutes" 
write-output $OutputString
write-output $FullOutput 
write-output $OutputString
exit 2 # the 2 returns to Nagios as a critical event
} 
else { 
$OutputString = "OK: $Status$CondensedOutput"
$OutputString += "in the last $ArgLastMinutes minutes" 
write-output $OutputString
exit 0 # the 0 returns to Nagios as all is well 
}

You may also like...