From 2fe7d135991948bda878472ed7f4cfc425356acf Mon Sep 17 00:00:00 2001 From: Joshua Wright Date: Sun, 28 Apr 2019 14:23:23 -0400 Subject: [PATCH] Add detector and event log to watch for Event Log Service stop/start as an indicator or event log tampering with eventlogedit --- DeepBlue.ps1 | 1087 ++++++++++++++++---------------- evtx/disablestop-eventlog.evtx | Bin 0 -> 69632 bytes 2 files changed, 552 insertions(+), 535 deletions(-) create mode 100644 evtx/disablestop-eventlog.evtx diff --git a/DeepBlue.ps1 b/DeepBlue.ps1 index 6e1ed34..bc0eb34 100644 --- a/DeepBlue.ps1 +++ b/DeepBlue.ps1 @@ -1,535 +1,552 @@ -<# -.SYNOPSIS - -A PowerShell module for hunt teaming via Windows event logs -.DESCRIPTION - -DeepBlueCLI can automatically determine events that are typically triggered during a majority of successful breaches, including use of malicious command lines including PowerShell. -.Example - -Process local Windows security event log: -.\DeepBlue.ps1 -.\DeepBlue.ps1 -log security -.Example -Process local Windows system event log: - -.\DeepBlue.ps1 -log system -.\DeepBlue.ps1 "" system -.Example -Process evtx file: - -.\DeepBlue.ps1 .\evtx\new-user-security.evtx -.\DeepBlue.ps1 -file .\evtx\new-user-security.evtx -.LINK -https://github.com/sans-blue-team/DeepBlueCLI - -#> - -# DeepBlueCLI 1.9 pre-DerbyCon -# Eric Conrad, Backshore Communications, LLC -# deepblue backshore net -# Twitter: @eric_conrad -# http://ericconrad.com -# - -param ([string]$file=$env:file,[string]$log=$env:log) - -function Main { - # Set up the global variables - $text="" # Temporary scratch pad variable to hold output text - $minlength=1000 # Minimum length of command line to alert - # Load cmd match regexes from csv file, ignore comments - $regexes = Get-Content ".\regexes.txt" | Select-String '^[^#]' | ConvertFrom-Csv - # Load cmd whitelist regexes from csv file, ignore comments - $whitelist = Get-Content ".\whitelist.txt" | Select-String '^[^#]' | ConvertFrom-Csv - $logname=Check-Options $file $log - #"Processing the " + $logname + " log..." - $filter=Create-Filter $file $logname - $maxfailedlogons=25 # Alert after this many failed logons - $failedlogins=@{} # HashTable of failed logins per user - # Obfuscation variables: - $minpercent=.65 # minimum percentage of alphanumeric and common symbols - $maxbinary=.50 # Maximum percentage of zeros and ones to detect binary encoding - # - # Sysmon variables: - # Check for unsigned EXEs/DLLs. This can be very chatty, so it's disabled. - # Set $checkunsigned to 1 to enable: - $checkunsigned = 0 - # - # Get the events: - try{ - $events = iex "Get-WinEvent $filter -ErrorAction Stop" - } - catch { - Write-Host "Get-WinEvent $filter -ErrorAction Stop" - Write-Host "Get-WinEvent error: " $_.Exception.Message "`n" - Write-Host "Exiting...`n" - exit - } - ForEach ($event in $events) { - # Custom reporting object: - $obj = [PSCustomObject]@{ - Date = $event.TimeCreated - Log = $logname - EventID = $event.id - Message = "" - Results = "" - Command = "" - Decoded = "" - } - $eventXML = [xml]$event.ToXml() - $servicecmd=0 # CLIs via service creation get extra checks, this defaults to 0 (no extra checks) - if ($logname -eq "Security"){ - if ($event.id -eq 4688){ - # A new process has been created. (Command Line Logging) - $commandline=$eventXML.Event.EventData.Data[8]."#text" # Process Command Line - $creator=$eventXML.Event.EventData.Data[13]."#text" # Creator Process Name - if ($commandline){ - Check-Command - } - } - ElseIf ($event.id -eq 4720){ - # A user account was created. - $username=$eventXML.Event.EventData.Data[0]."#text" - $securityid=$eventXML.Event.EventData.Data[2]."#text" - $obj.Message = "New User Created" - $obj.Results = "Username: $username`n" - $obj.Results += "User SID: $securityid`n" - Write-Output $obj - } - ElseIf(($event.id -eq 4728) -or ($event.id -eq 4732)){ - # A member was added to a security-enabled (global|local) group. - $groupname=$eventXML.Event.EventData.Data[2]."#text" - # Check if group is Administrators, may later expand to all groups - if ($groupname -eq "Administrators"){ - $username=$eventXML.Event.EventData.Data[0]."#text" - $securityid=$eventXML.Event.EventData.Data[1]."#text" - switch ($event.id){ - 4728 {$obj.Message = "User added to global $groupname group"} - 4732 {$obj.Message = "User added to local $groupname group"} - } - $obj.Results = "Username: $username`n" - $obj.Results += "User SID: $securityid`n" - Write-Output $obj - } - } - ElseIf($event.id -eq 4625){ - # An account failed to log on. - # Requires auditing logon failures - # https://technet.microsoft.com/en-us/library/cc976395.aspx - $username=$eventXML.Event.EventData.Data[5]."#text" - if($failedlogins.ContainsKey($username)){ - $count=$failedlogins.Get_Item($username) - $failedlogins.Set_Item($username,$count+1) - } - Else{ - $failedlogins.Set_Item($username,1) - } - } - } - ElseIf ($logname -eq "System"){ - if ($event.id -eq 7045){ - # A service was installed in the system. - $servicename=$eventXML.Event.EventData.Data[0]."#text" - $commandline=$eventXML.Event.EventData.Data[1]."#text" - # Check for suspicious service name - $text = (Check-Regex $servicename 1) - if ($text){ - $obj.Message = "New Service Created" - $obj.Command = $commandline - $obj.Results = "Service name: $servicename`n" - $obj.Results +=$text - Write-Output $obj - } - # Check for suspicious cmd - if ($commandline){ - $servicecmd=1 # CLIs via service creation get extra checks - Check-Command - } - } - ElseIf ($event.id -eq 7030){ - # The ... service is marked as an interactive service. However, the system is configured - # to not allow interactive services. This service may not function properly. - $servicename=$eventXML.Event.EventData.Data."#text" - $obj.Message = "Interactive service warning" - $obj.Results = "Service name: $servicename`n" - $obj.Results += "Malware (and some third party software) trigger this warning" - # Check for suspicious service name - $servicecmd=1 # CLIs via service creation get extra check - $obj.Results += (Check-Regex $servicename 1) - Write-Output $obj - } - ElseIf ($event.id -eq 7036){ - # The ... service entered the stopped|running state. - $servicename=$eventXML.Event.EventData.Data[0]."#text" - $text = (Check-Regex $servicename 1) - if ($text){ - $obj.Message = "Suspicious Service Name" - $obj.Results = "Service name: $servicename`n" - $obj.Results += $text - Write-Output $obj - } - } - } - ElseIf ($logname -eq "Application"){ - if (($event.id -eq 2) -and ($event.Providername -eq "EMET")){ - # EMET Block - $obj.Message="EMET Block" - if ($event.Message){ - # EMET Message is a blob of text that looks like this: - ######################################################### - # EMET detected HeapSpray mitigation and will close the application: iexplore.exe - # - # HeapSpray check failed: - # Application : C:\Program Files (x86)\Internet Explorer\iexplore.exe - # User Name : WIN-CV6AHH1BNU9\Instructor - # Session ID : 1 - # PID : 0xBA8 (2984) - # TID : 0x9E8 (2536) - # Module : mshtml.dll - # Address : 0x6FBA7512, pull out relevant parts - $array = $event.message -split '\n' # Split each line of the message into an array - $text = $array[0] - $application = Remove-Spaces($array[3]) - $command= $application -Replace "^Application: ","" - $username = Remove-Spaces($array[4]) - $obj.Message="EMET Block" - $obj.Command = "$command" - $obj.Results = "$text`n" - $obj.Results += "$username`n" - } - Else{ - # If the message is blank: EMET is not installed locally. - # This occurs when parsing remote event logs sent from systems with EMET installed - $obj.Message="Warning: EMET Message field is blank. Install EMET locally to see full details of this alert" - } - Write-Output $obj - } - } - ElseIf ($logname -eq "Applocker"){ - if ($event.id -eq 8003){ - # ...was allowed to run but would have been prevented from running if the AppLocker policy were enforced. - $obj.Message="Applocker Warning" - $command = $event.message -Replace " was .*$","" - $obj.Command=$command - $obj.Results = $event.message - Write-Output $obj - } - ElseIf ($event.id -eq 8004){ - $obj.Message="Applocker Block" - # ...was prevented from running. - $command = $event.message -Replace " was .*$","" - $obj.Command=$command - $obj.Results = $event.message - Write-Output $obj - } - } - ElseIf ($logname -eq "PowerShell"){ - if ($event.id -eq 4103){ - $commandline= $eventXML.Event.EventData.Data[2]."#text" - if ($commandline -Match "Host Application"){ - # Multiline replace, remove everything before "Host Application = " - $commandline = $commandline -Replace "(?ms)^.*Host.Application = ","" - # Remove every line after the "Host Application = " line. - $commandline = $commandline -Replace "(?ms)`n.*$","" - if ($commandline){ - Check-Command - } - } - } - ElseIf ($event.id -eq 4104){ - # This section requires PowerShell command logging for event 4104 , which seems to be default with - # Windows 10, but may not not the default with older Windows versions (which may log the script - # block but not the command that launched it). - # Caveats included because more testing of various Windows versions is needed - # - # If the command itself is not being logged: - # Add the following to \Windows\System32\WindowsPowerShell\v1.0\profile.ps1 - # $LogCommandHealthEvent = $true - # $LogCommandLifecycleEvent = $true - # - # See the following for more information: - # - # https://logrhythm.com/blog/powershell-command-line-logging/ - # http://hackerhurricane.blogspot.com/2014/11/i-powershell-logging-what-everyone.html - # - # Thank you: @heinzarelli and @HackerHurricane - # - # The command's path is $eventxml.Event.EventData.Data[4] - # - # Blank path means it was run as a commandline. CLI parsing is *much* simpler than - # script parsing. See Revoke-Obfuscation for parsing the script blocks: - # - # https://github.com/danielbohannon/Revoke-Obfuscation - # - # Thanks to @danielhbohannon and @Lee_Holmes - # - # This ignores scripts and grabs PowerShell CLIs - if (-not ($eventxml.Event.EventData.Data[4]."#text")){ - $commandline=$eventXML.Event.EventData.Data[2]."#text" - if ($commandline){ - Check-Command - } - } - } - } - ElseIf ($logname -eq "Sysmon"){ - # Check command lines - if ($event.id -eq 1){ - $creator=$eventXML.Event.EventData.Data[14]."#text" - $commandline=$eventXML.Event.EventData.Data[4]."#text" - if ($commandline){ - Check-Command - } - } - ElseIf ($event.id -eq 7){ - # Check for unsigned EXEs/DLLs: - # This can be very chatty, so it's disabled. - # Set $checkunsigned to 1 (global variable section) to enable: - if ($checkunsigned){ - if ($eventXML.Event.EventData.Data[6]."#text" -eq "false"){ - $obj.Message="Unsigned Image (DLL)" - $image=$eventXML.Event.EventData.Data[3]."#text" - $imageload=$eventXML.Event.EventData.Data[4]."#text" - # $hash=$eventXML.Event.EventData.Data[5]."#text" - $obj.Command=$imageload - $obj.Results= "Loaded by: $image" - Write-Output $obj - } - } - } - } - } - # Iterate through failed logins hashtable (key is $username) - foreach ($username in $failedlogins.Keys) { - $count=$failedlogins.Get_Item($username) - if ($count -gt $maxfailedlogons){ - $obj.Message="High number of failed logons" - $obj.Results= "Username: $username`n" - $obj.Results += "$count failed logons" - Write-Output $obj - } - } -} - -function Check-Options($file, $log) -{ - $log_error="Unknown and/or unsupported log type" - $logname="" - # Checks the command line options, return logname to parse - if($file -eq ""){ # No filename provided, parse local logs - if(($log -eq "") -or ($log -eq "Security")){ # Parse the security log if no log was selected - $logname="Security" - } - ElseIf ($log -eq "System"){ - $logname="System" - } - ElseIf ($log -eq "Application"){ - $logname="Application" - } - ElseIf ($log -eq "Sysmon"){ - $logname="Sysmon" - } - ElseIf ($log -eq "Powershell"){ - $logname="Powershell" - } - Else{ - write-host $log_error - exit 1 - } - } - else{ # Filename provided, check if it exists: - if (Test-Path $file){ # File exists. Todo: verify it is an evtx file. - # Get-WinEvent will generate this error for non-evtx files: "...file does not appear to be a valid log file. - # Specify only .evtx, .etl, or .evt filesas values of the Path parameter." - # - # Check the LogName of the first event - try{ - $event=Get-WinEvent -path $file -max 1 -ErrorAction Stop - } - catch - { - Write-Host "Get-WinEvent error: " $_.Exception.Message "`n" - Write-Host "Exiting...`n" - exit - } - switch ($event.LogName){ - "Security" {$logname="Security"} - "System" {$logname="System"} - "Application" {$logname="Application"} - "Microsoft-Windows-AppLocker/EXE and DLL" {$logname="Applocker"} - "Microsoft-Windows-PowerShell/Operational" {$logname="Powershell"} - "Microsoft-Windows-Sysmon/Operational" {$logname="Sysmon"} - default {"Logic error 3, should not reach here...";Exit 1} - } - } - else{ # Filename does not exist, exit - Write-host "Error: no such file. Exiting..." - exit 1 - } - } - return $logname -} - -function Create-Filter($file, $logname) -{ - # Return the Get-Winevent filter - # - $sys_events="7030,7036,7045" - $sec_events="4688,4720,4728,4732,4625" - $app_events="2" - $applocker_events="8003,8004,8006,8007" - $powershell_events="4103,4104" - $sysmon_events="1,7" - if ($file -ne ""){ - switch ($logname){ - "Security" {$filter="@{path=""$file"";ID=$sec_events}"} - "System" {$filter="@{path=""$file"";ID=$sys_events}"} - "Application" {$filter="@{path=""$file"";ID=$app_events}"} - "Applocker" {$filter="@{path=""$file"";ID=$applocker_events}"} - "Powershell" {$filter="@{path=""$file"";ID=$powershell_events}"} - "Sysmon" {$filter="@{path=""$file"";ID=$sysmon_events}"} - default {"Logic error 1, should not reach here...";Exit 1} - } - } - else{ - switch ($logname){ - "Security" {$filter="@{Logname=""Security"";ID=$sec_events}"} - "System" {$filter="@{Logname=""System"";ID=$sys_events}"} - "Application" {$filter="@{Logname=""Application"";ID=$app_events}"} - "Applocker" {$filter="@{logname=""Microsoft-Windows-AppLocker/EXE and DLL"";ID=$applocker_events}"} - "Powershell" {$filter="@{logname=""Microsoft-Windows-PowerShell/Operational"";ID=$powershell_events}"} - "Sysmon" {$filter="@{logname=""Microsoft-Windows-Sysmon/Operational"";ID=$sysmon_events}"} - default {"Logic error 2, should not reach here...";Exit 1} - } - } - return $filter -} - - -function Check-Command(){ - $text="" - $base64="" - # Check to see if command is whitelisted - foreach ($entry in $whitelist) { - if ($commandline -Match $entry.regex) { - # Command is whitelisted, return nothing - return - } - } - if ($commandline.length -gt $minlength){ - $text += "Long Command Line: greater than $minlength bytes`n" - } - $text += (Check-Obfu $commandline) - $text += (Check-Regex $commandline 0) - $text += (Check-Creator $commandline $creator) - # Check for base64 encoded function, decode and print if found - # This section is highly use case specific, other methods of base64 encoding and/or compressing may evade these checks - if ($commandline -Match "\-enc.*[A-Za-z0-9/+=]{100}"){ - $base64= $commandline -Replace "^.* \-Enc(odedCommand)? ","" - } - ElseIf ($commandline -Match ":FromBase64String\("){ - $base64 = $commandline -Replace "^.*:FromBase64String\(\'*","" - $base64 = $base64 -Replace "\'.*$","" - } - if ($base64){ - if ($commandline -Match "Compression.GzipStream.*Decompress"){ - # Metasploit-style compressed and base64-encoded function. Uncompress it. - $decoded=New-Object IO.MemoryStream(,[Convert]::FromBase64String($base64)) - $uncompressed=(New-Object IO.StreamReader(((New-Object IO.Compression.GzipStream($decoded,[IO.Compression.CompressionMode]::Decompress))),[Text.Encoding]::ASCII)).ReadToEnd() - $obj.Decoded=$uncompressed - $text += "Base64-encoded and compressed function`n" - } - else{ - $decoded = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($base64)) - $obj.Decoded=$decoded - $text += "Base64-encoded function`n" - $text += (Check-Obfu $decoded) - $text += (Check-Regex $decoded 0) - } - } - if ($text){ - if ($servicecmd){ - $obj.Message = "Suspicious Service Command" - $obj.Results = "Service name: $servicename`n" - } - Else{ - $obj.Message = "Suspicious Command Line" - } - $obj.Command = $commandline - $obj.Results += $text - Write-Output $obj - } - return -} - -function Check-Regex($string,$type){ - $regextext="" # Local variable for return output - foreach ($regex in $regexes){ - if ($regex.Type -eq $type) { # Type is 0 for Commands, 1 for services. Set in regexes.csv - if ($string -Match $regex.regex) { - $regextext += $regex.String + "`n" - } - } - } - #if ($regextext){ - # $regextext = $regextext.Substring(0,$regextext.Length-1) # Remove final newline. - #} - return $regextext -} - -function Check-Obfu($string){ - # Check for special characters in the command. Inspired by Invoke-Obfuscation: https://twitter.com/danielhbohannon/status/778268820242825216 - # - $obfutext="" # Local variable for return output - $lowercasestring=$string.ToLower() - $length=$lowercasestring.length - $noalphastring = $lowercasestring -replace "[a-z0-9/\;:|.]" - $nobinarystring = $lowercasestring -replace "[01]" # To catch binary encoding - # Calculate the percent alphanumeric/common symbols - if ($length -gt 0){ - $percent=(($length-$noalphastring.length)/$length) - # Adjust minpercent for very short commands, to avoid triggering short warnings - if (($length/100) -lt $minpercent){ - $minpercent=($length/100) - } - if ($percent -lt $minpercent){ - $percent = "{0:P0}" -f $percent # Convert to a percent - $obfutext += "Possible command obfuscation: only $percent alphanumeric and common symbols`n" - } - # Calculate the percent of binary characters - $percent=(($nobinarystring.length-$length/$length)/$length) - $binarypercent = 1-$percent - if ($binarypercent -gt $maxbinary){ - #$binarypercent = 1-$percent - $binarypercent = "{0:P0}" -f $binarypercent # Convert to a percent - $obfutext += "Possible command obfuscation: $binarypercent zeroes and ones (possible numeric or binary encoding)`n" - } - } - return $obfutext -} - -function Check-Creator($command,$creator){ - $creatortext="" # Local variable for return output - if ($creator){ - if ($command -Match "powershell"){ - if ($creator -Match "PSEXESVC"){ - $creatortext += "PowerShell launched via PsExec: $creator`n" - } - ElseIf($creator -Match "WmiPrvSE"){ - $creatortext += "PowerShell launched via WMI: $creator`n" - } - } - } - return $creatortext -} - -function Remove-Spaces($string){ - # Changes this: Application : C:\Program Files (x86)\Internet Explorer\iexplore.exe - # to this: Application: C:\Program Files (x86)\Internet Explorer\iexplore.exe - $string = $string.trim() -Replace "\s+:",":" - return $string -} - -. Main - +<# +.SYNOPSIS + +A PowerShell module for hunt teaming via Windows event logs +.DESCRIPTION + +DeepBlueCLI can automatically determine events that are typically triggered during a majority of successful breaches, including use of malicious command lines including PowerShell. +.Example + +Process local Windows security event log: +.\DeepBlue.ps1 +.\DeepBlue.ps1 -log security +.Example +Process local Windows system event log: + +.\DeepBlue.ps1 -log system +.\DeepBlue.ps1 "" system +.Example +Process evtx file: + +.\DeepBlue.ps1 .\evtx\new-user-security.evtx +.\DeepBlue.ps1 -file .\evtx\new-user-security.evtx +.LINK +https://github.com/sans-blue-team/DeepBlueCLI + +#> + +# DeepBlueCLI 1.9 pre-DerbyCon +# Eric Conrad, Backshore Communications, LLC +# deepblue backshore net +# Twitter: @eric_conrad +# http://ericconrad.com +# + +param ([string]$file=$env:file,[string]$log=$env:log) + +function Main { + # Set up the global variables + $text="" # Temporary scratch pad variable to hold output text + $minlength=1000 # Minimum length of command line to alert + # Load cmd match regexes from csv file, ignore comments + $regexes = Get-Content ".\regexes.txt" | Select-String '^[^#]' | ConvertFrom-Csv + # Load cmd whitelist regexes from csv file, ignore comments + $whitelist = Get-Content ".\whitelist.txt" | Select-String '^[^#]' | ConvertFrom-Csv + $logname=Check-Options $file $log + #"Processing the " + $logname + " log..." + $filter=Create-Filter $file $logname + $maxfailedlogons=25 # Alert after this many failed logons + $failedlogins=@{} # HashTable of failed logins per user + # Obfuscation variables: + $minpercent=.65 # minimum percentage of alphanumeric and common symbols + $maxbinary=.50 # Maximum percentage of zeros and ones to detect binary encoding + # + # Sysmon variables: + # Check for unsigned EXEs/DLLs. This can be very chatty, so it's disabled. + # Set $checkunsigned to 1 to enable: + $checkunsigned = 0 + # + # Get the events: + try{ + $events = iex "Get-WinEvent $filter -ErrorAction Stop" + } + catch { + Write-Host "Get-WinEvent $filter -ErrorAction Stop" + Write-Host "Get-WinEvent error: " $_.Exception.Message "`n" + Write-Host "Exiting...`n" + exit + } + ForEach ($event in $events) { + # Custom reporting object: + $obj = [PSCustomObject]@{ + Date = $event.TimeCreated + Log = $logname + EventID = $event.id + Message = "" + Results = "" + Command = "" + Decoded = "" + } + $eventXML = [xml]$event.ToXml() + $servicecmd=0 # CLIs via service creation get extra checks, this defaults to 0 (no extra checks) + if ($logname -eq "Security"){ + if ($event.id -eq 4688){ + # A new process has been created. (Command Line Logging) + $commandline=$eventXML.Event.EventData.Data[8]."#text" # Process Command Line + $creator=$eventXML.Event.EventData.Data[13]."#text" # Creator Process Name + if ($commandline){ + Check-Command + } + } + ElseIf ($event.id -eq 4720){ + # A user account was created. + $username=$eventXML.Event.EventData.Data[0]."#text" + $securityid=$eventXML.Event.EventData.Data[2]."#text" + $obj.Message = "New User Created" + $obj.Results = "Username: $username`n" + $obj.Results += "User SID: $securityid`n" + Write-Output $obj + } + ElseIf(($event.id -eq 4728) -or ($event.id -eq 4732)){ + # A member was added to a security-enabled (global|local) group. + $groupname=$eventXML.Event.EventData.Data[2]."#text" + # Check if group is Administrators, may later expand to all groups + if ($groupname -eq "Administrators"){ + $username=$eventXML.Event.EventData.Data[0]."#text" + $securityid=$eventXML.Event.EventData.Data[1]."#text" + switch ($event.id){ + 4728 {$obj.Message = "User added to global $groupname group"} + 4732 {$obj.Message = "User added to local $groupname group"} + } + $obj.Results = "Username: $username`n" + $obj.Results += "User SID: $securityid`n" + Write-Output $obj + } + } + ElseIf($event.id -eq 4625){ + # An account failed to log on. + # Requires auditing logon failures + # https://technet.microsoft.com/en-us/library/cc976395.aspx + $username=$eventXML.Event.EventData.Data[5]."#text" + if($failedlogins.ContainsKey($username)){ + $count=$failedlogins.Get_Item($username) + $failedlogins.Set_Item($username,$count+1) + } + Else{ + $failedlogins.Set_Item($username,1) + } + } + } + ElseIf ($logname -eq "System"){ + if ($event.id -eq 7045){ + # A service was installed in the system. + $servicename=$eventXML.Event.EventData.Data[0]."#text" + $commandline=$eventXML.Event.EventData.Data[1]."#text" + # Check for suspicious service name + $text = (Check-Regex $servicename 1) + if ($text){ + $obj.Message = "New Service Created" + $obj.Command = $commandline + $obj.Results = "Service name: $servicename`n" + $obj.Results +=$text + Write-Output $obj + } + # Check for suspicious cmd + if ($commandline){ + $servicecmd=1 # CLIs via service creation get extra checks + Check-Command + } + } + ElseIf ($event.id -eq 7030){ + # The ... service is marked as an interactive service. However, the system is configured + # to not allow interactive services. This service may not function properly. + $servicename=$eventXML.Event.EventData.Data."#text" + $obj.Message = "Interactive service warning" + $obj.Results = "Service name: $servicename`n" + $obj.Results += "Malware (and some third party software) trigger this warning" + # Check for suspicious service name + $servicecmd=1 # CLIs via service creation get extra check + $obj.Results += (Check-Regex $servicename 1) + Write-Output $obj + } + ElseIf ($event.id -eq 7036){ + # The ... service entered the stopped|running state. + $servicename=$eventXML.Event.EventData.Data[0]."#text" + $text = (Check-Regex $servicename 1) + if ($text){ + $obj.Message = "Suspicious Service Name" + $obj.Results = "Service name: $servicename`n" + $obj.Results += $text + Write-Output $obj + } + } + ElseIf ($event.id -eq 7040){ + # The start type of the Windows Event Log service was changed from auto start to disabled. + $servicename=$eventXML.Event.EventData.Data[0]."#text" + $action = $eventXML.Event.EventData.Data[1]."#text" + if ($servicename -ccontains "Windows Event Log") { + $obj.Results = "Service name: $servicename`n" + $obj.Results += $text + if ($action -eq "disabled") { + $obj.Message = "Event Log Service Stopped" + $obj.Results += "Selective event log manipulation may follow this event." + } elseIf ($action -eq "auto start") { + $obj.Message = "Event Log Service Started" + $obj.Results += "Selective event log manipulation may precede this event." + } + Write-Output $obj + } + } + } + ElseIf ($logname -eq "Application"){ + if (($event.id -eq 2) -and ($event.Providername -eq "EMET")){ + # EMET Block + $obj.Message="EMET Block" + if ($event.Message){ + # EMET Message is a blob of text that looks like this: + ######################################################### + # EMET detected HeapSpray mitigation and will close the application: iexplore.exe + # + # HeapSpray check failed: + # Application : C:\Program Files (x86)\Internet Explorer\iexplore.exe + # User Name : WIN-CV6AHH1BNU9\Instructor + # Session ID : 1 + # PID : 0xBA8 (2984) + # TID : 0x9E8 (2536) + # Module : mshtml.dll + # Address : 0x6FBA7512, pull out relevant parts + $array = $event.message -split '\n' # Split each line of the message into an array + $text = $array[0] + $application = Remove-Spaces($array[3]) + $command= $application -Replace "^Application: ","" + $username = Remove-Spaces($array[4]) + $obj.Message="EMET Block" + $obj.Command = "$command" + $obj.Results = "$text`n" + $obj.Results += "$username`n" + } + Else{ + # If the message is blank: EMET is not installed locally. + # This occurs when parsing remote event logs sent from systems with EMET installed + $obj.Message="Warning: EMET Message field is blank. Install EMET locally to see full details of this alert" + } + Write-Output $obj + } + } + ElseIf ($logname -eq "Applocker"){ + if ($event.id -eq 8003){ + # ...was allowed to run but would have been prevented from running if the AppLocker policy were enforced. + $obj.Message="Applocker Warning" + $command = $event.message -Replace " was .*$","" + $obj.Command=$command + $obj.Results = $event.message + Write-Output $obj + } + ElseIf ($event.id -eq 8004){ + $obj.Message="Applocker Block" + # ...was prevented from running. + $command = $event.message -Replace " was .*$","" + $obj.Command=$command + $obj.Results = $event.message + Write-Output $obj + } + } + ElseIf ($logname -eq "PowerShell"){ + if ($event.id -eq 4103){ + $commandline= $eventXML.Event.EventData.Data[2]."#text" + if ($commandline -Match "Host Application"){ + # Multiline replace, remove everything before "Host Application = " + $commandline = $commandline -Replace "(?ms)^.*Host.Application = ","" + # Remove every line after the "Host Application = " line. + $commandline = $commandline -Replace "(?ms)`n.*$","" + if ($commandline){ + Check-Command + } + } + } + ElseIf ($event.id -eq 4104){ + # This section requires PowerShell command logging for event 4104 , which seems to be default with + # Windows 10, but may not not the default with older Windows versions (which may log the script + # block but not the command that launched it). + # Caveats included because more testing of various Windows versions is needed + # + # If the command itself is not being logged: + # Add the following to \Windows\System32\WindowsPowerShell\v1.0\profile.ps1 + # $LogCommandHealthEvent = $true + # $LogCommandLifecycleEvent = $true + # + # See the following for more information: + # + # https://logrhythm.com/blog/powershell-command-line-logging/ + # http://hackerhurricane.blogspot.com/2014/11/i-powershell-logging-what-everyone.html + # + # Thank you: @heinzarelli and @HackerHurricane + # + # The command's path is $eventxml.Event.EventData.Data[4] + # + # Blank path means it was run as a commandline. CLI parsing is *much* simpler than + # script parsing. See Revoke-Obfuscation for parsing the script blocks: + # + # https://github.com/danielbohannon/Revoke-Obfuscation + # + # Thanks to @danielhbohannon and @Lee_Holmes + # + # This ignores scripts and grabs PowerShell CLIs + if (-not ($eventxml.Event.EventData.Data[4]."#text")){ + $commandline=$eventXML.Event.EventData.Data[2]."#text" + if ($commandline){ + Check-Command + } + } + } + } + ElseIf ($logname -eq "Sysmon"){ + # Check command lines + if ($event.id -eq 1){ + $creator=$eventXML.Event.EventData.Data[14]."#text" + $commandline=$eventXML.Event.EventData.Data[4]."#text" + if ($commandline){ + Check-Command + } + } + ElseIf ($event.id -eq 7){ + # Check for unsigned EXEs/DLLs: + # This can be very chatty, so it's disabled. + # Set $checkunsigned to 1 (global variable section) to enable: + if ($checkunsigned){ + if ($eventXML.Event.EventData.Data[6]."#text" -eq "false"){ + $obj.Message="Unsigned Image (DLL)" + $image=$eventXML.Event.EventData.Data[3]."#text" + $imageload=$eventXML.Event.EventData.Data[4]."#text" + # $hash=$eventXML.Event.EventData.Data[5]."#text" + $obj.Command=$imageload + $obj.Results= "Loaded by: $image" + Write-Output $obj + } + } + } + } + } + # Iterate through failed logins hashtable (key is $username) + foreach ($username in $failedlogins.Keys) { + $count=$failedlogins.Get_Item($username) + if ($count -gt $maxfailedlogons){ + $obj.Message="High number of failed logons" + $obj.Results= "Username: $username`n" + $obj.Results += "$count failed logons" + Write-Output $obj + } + } +} + +function Check-Options($file, $log) +{ + $log_error="Unknown and/or unsupported log type" + $logname="" + # Checks the command line options, return logname to parse + if($file -eq ""){ # No filename provided, parse local logs + if(($log -eq "") -or ($log -eq "Security")){ # Parse the security log if no log was selected + $logname="Security" + } + ElseIf ($log -eq "System"){ + $logname="System" + } + ElseIf ($log -eq "Application"){ + $logname="Application" + } + ElseIf ($log -eq "Sysmon"){ + $logname="Sysmon" + } + ElseIf ($log -eq "Powershell"){ + $logname="Powershell" + } + Else{ + write-host $log_error + exit 1 + } + } + else{ # Filename provided, check if it exists: + if (Test-Path $file){ # File exists. Todo: verify it is an evtx file. + # Get-WinEvent will generate this error for non-evtx files: "...file does not appear to be a valid log file. + # Specify only .evtx, .etl, or .evt filesas values of the Path parameter." + # + # Check the LogName of the first event + try{ + $event=Get-WinEvent -path $file -max 1 -ErrorAction Stop + } + catch + { + Write-Host "Get-WinEvent error: " $_.Exception.Message "`n" + Write-Host "Exiting...`n" + exit + } + switch ($event.LogName){ + "Security" {$logname="Security"} + "System" {$logname="System"} + "Application" {$logname="Application"} + "Microsoft-Windows-AppLocker/EXE and DLL" {$logname="Applocker"} + "Microsoft-Windows-PowerShell/Operational" {$logname="Powershell"} + "Microsoft-Windows-Sysmon/Operational" {$logname="Sysmon"} + default {"Logic error 3, should not reach here...";Exit 1} + } + } + else{ # Filename does not exist, exit + Write-host "Error: no such file. Exiting..." + exit 1 + } + } + return $logname +} + +function Create-Filter($file, $logname) +{ + # Return the Get-Winevent filter + # + $sys_events="7030,7036,7045,7040" + $sec_events="4688,4720,4728,4732,4625" + $app_events="2" + $applocker_events="8003,8004,8006,8007" + $powershell_events="4103,4104" + $sysmon_events="1,7" + if ($file -ne ""){ + switch ($logname){ + "Security" {$filter="@{path=""$file"";ID=$sec_events}"} + "System" {$filter="@{path=""$file"";ID=$sys_events}"} + "Application" {$filter="@{path=""$file"";ID=$app_events}"} + "Applocker" {$filter="@{path=""$file"";ID=$applocker_events}"} + "Powershell" {$filter="@{path=""$file"";ID=$powershell_events}"} + "Sysmon" {$filter="@{path=""$file"";ID=$sysmon_events}"} + default {"Logic error 1, should not reach here...";Exit 1} + } + } + else{ + switch ($logname){ + "Security" {$filter="@{Logname=""Security"";ID=$sec_events}"} + "System" {$filter="@{Logname=""System"";ID=$sys_events}"} + "Application" {$filter="@{Logname=""Application"";ID=$app_events}"} + "Applocker" {$filter="@{logname=""Microsoft-Windows-AppLocker/EXE and DLL"";ID=$applocker_events}"} + "Powershell" {$filter="@{logname=""Microsoft-Windows-PowerShell/Operational"";ID=$powershell_events}"} + "Sysmon" {$filter="@{logname=""Microsoft-Windows-Sysmon/Operational"";ID=$sysmon_events}"} + default {"Logic error 2, should not reach here...";Exit 1} + } + } + return $filter +} + + +function Check-Command(){ + $text="" + $base64="" + # Check to see if command is whitelisted + foreach ($entry in $whitelist) { + if ($commandline -Match $entry.regex) { + # Command is whitelisted, return nothing + return + } + } + if ($commandline.length -gt $minlength){ + $text += "Long Command Line: greater than $minlength bytes`n" + } + $text += (Check-Obfu $commandline) + $text += (Check-Regex $commandline 0) + $text += (Check-Creator $commandline $creator) + # Check for base64 encoded function, decode and print if found + # This section is highly use case specific, other methods of base64 encoding and/or compressing may evade these checks + if ($commandline -Match "\-enc.*[A-Za-z0-9/+=]{100}"){ + $base64= $commandline -Replace "^.* \-Enc(odedCommand)? ","" + } + ElseIf ($commandline -Match ":FromBase64String\("){ + $base64 = $commandline -Replace "^.*:FromBase64String\(\'*","" + $base64 = $base64 -Replace "\'.*$","" + } + if ($base64){ + if ($commandline -Match "Compression.GzipStream.*Decompress"){ + # Metasploit-style compressed and base64-encoded function. Uncompress it. + $decoded=New-Object IO.MemoryStream(,[Convert]::FromBase64String($base64)) + $uncompressed=(New-Object IO.StreamReader(((New-Object IO.Compression.GzipStream($decoded,[IO.Compression.CompressionMode]::Decompress))),[Text.Encoding]::ASCII)).ReadToEnd() + $obj.Decoded=$uncompressed + $text += "Base64-encoded and compressed function`n" + } + else{ + $decoded = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($base64)) + $obj.Decoded=$decoded + $text += "Base64-encoded function`n" + $text += (Check-Obfu $decoded) + $text += (Check-Regex $decoded 0) + } + } + if ($text){ + if ($servicecmd){ + $obj.Message = "Suspicious Service Command" + $obj.Results = "Service name: $servicename`n" + } + Else{ + $obj.Message = "Suspicious Command Line" + } + $obj.Command = $commandline + $obj.Results += $text + Write-Output $obj + } + return +} + +function Check-Regex($string,$type){ + $regextext="" # Local variable for return output + foreach ($regex in $regexes){ + if ($regex.Type -eq $type) { # Type is 0 for Commands, 1 for services. Set in regexes.csv + if ($string -Match $regex.regex) { + $regextext += $regex.String + "`n" + } + } + } + #if ($regextext){ + # $regextext = $regextext.Substring(0,$regextext.Length-1) # Remove final newline. + #} + return $regextext +} + +function Check-Obfu($string){ + # Check for special characters in the command. Inspired by Invoke-Obfuscation: https://twitter.com/danielhbohannon/status/778268820242825216 + # + $obfutext="" # Local variable for return output + $lowercasestring=$string.ToLower() + $length=$lowercasestring.length + $noalphastring = $lowercasestring -replace "[a-z0-9/\;:|.]" + $nobinarystring = $lowercasestring -replace "[01]" # To catch binary encoding + # Calculate the percent alphanumeric/common symbols + if ($length -gt 0){ + $percent=(($length-$noalphastring.length)/$length) + # Adjust minpercent for very short commands, to avoid triggering short warnings + if (($length/100) -lt $minpercent){ + $minpercent=($length/100) + } + if ($percent -lt $minpercent){ + $percent = "{0:P0}" -f $percent # Convert to a percent + $obfutext += "Possible command obfuscation: only $percent alphanumeric and common symbols`n" + } + # Calculate the percent of binary characters + $percent=(($nobinarystring.length-$length/$length)/$length) + $binarypercent = 1-$percent + if ($binarypercent -gt $maxbinary){ + #$binarypercent = 1-$percent + $binarypercent = "{0:P0}" -f $binarypercent # Convert to a percent + $obfutext += "Possible command obfuscation: $binarypercent zeroes and ones (possible numeric or binary encoding)`n" + } + } + return $obfutext +} + +function Check-Creator($command,$creator){ + $creatortext="" # Local variable for return output + if ($creator){ + if ($command -Match "powershell"){ + if ($creator -Match "PSEXESVC"){ + $creatortext += "PowerShell launched via PsExec: $creator`n" + } + ElseIf($creator -Match "WmiPrvSE"){ + $creatortext += "PowerShell launched via WMI: $creator`n" + } + } + } + return $creatortext +} + +function Remove-Spaces($string){ + # Changes this: Application : C:\Program Files (x86)\Internet Explorer\iexplore.exe + # to this: Application: C:\Program Files (x86)\Internet Explorer\iexplore.exe + $string = $string.trim() -Replace "\s+:",":" + return $string +} + +. Main + diff --git a/evtx/disablestop-eventlog.evtx b/evtx/disablestop-eventlog.evtx new file mode 100644 index 0000000000000000000000000000000000000000..5e7ce922ee681277c8c8d82413bb9ba944caef54 GIT binary patch literal 69632 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 M5fA|p_$U$hKS&zPvj6}9 literal 0 HcmV?d00001