├── 0001-logstash-input-syslog.conf ├── 0099-logstash-copy-original-message.conf ├── 0420-logstash-windows-wef.conf ├── 0511-logstash-powershell.conf ├── 9998-logstash-parse-failures.conf ├── 9999-logstash-output.conf ├── ARP Table to Event ├── ETW Tracing Consumption Example ├── README.md ├── WMI Events To Windows Events ├── add-cuckoo-id-to-winlogs.txt ├── dns-debug-log-gpo-steps.txt ├── enable-wmi-tracing-via-gpo.txt ├── install-and-or-upgrade-sysmon-and-config.ps1 └── nxlog.conf /0001-logstash-input-syslog.conf: -------------------------------------------------------------------------------- 1 | input { 2 | 3 | udp { 4 | port => 8530 5 | type => "windows-dns" 6 | } 7 | 8 | tcp { 9 | port => 8530 10 | type => "windows-dns" 11 | } 12 | 13 | tcp { 14 | port => 8531 15 | type => "windows-wef" 16 | } 17 | 18 | udp { 19 | port => 8531 20 | type => "windows-wef" 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /0099-logstash-copy-original-message.conf: -------------------------------------------------------------------------------- 1 | filter { 2 | # Keep the base message before any edits, in case we have a parse failure / error in logstash config therefore we only want to keep this 3 | mutate { 4 | copy => { "message" => "_original_message" } 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /0420-logstash-windows-wef.conf: -------------------------------------------------------------------------------- 1 | filter { 2 | 3 | if [type] == "windows-wef" { 4 | 5 | json { 6 | source => "message" 7 | tag_on_failure => "_jsonparsefailure" 8 | } 9 | 10 | mutate { 11 | # Base 12 | add_field => { 13 | # Since it may be possible for windows time to be changed many hours, days, weeks, months, or years in the past to avoid time based monitoring (ie:in in a SIEM where looking for last 48 hours for suspicious events or etc) lets use the timestamp that the event was received.. This will also allow us to not to have to worry about converting certain sites that are not UTC and different time zones all over back to UTC too. 14 | "[@meta][log][timestamp]" => "%{@timestamp}" 15 | "[@meta][log][type]" => "windows-wef" 16 | "[@meta][event_type]" => "endpoint" 17 | "[@meta][log][host_ip]" => "%{[@meta][log][host_name]}" 18 | } 19 | 20 | # Remove NXLog fields 21 | # Remove other windows field we do not want 22 | remove_field => [ 23 | "port", 24 | "SourceModuleType", 25 | "type", 26 | "message", 27 | "SourceModuleName", 28 | "SeverityValue", 29 | "SourceName", 30 | "Keywords", 31 | "OpcodeValue", 32 | "ProviderGuid", 33 | "SeverityValue", 34 | "Version" 35 | ] 36 | # Rename some base fields 37 | rename => { 38 | "Hostname" => "[@meta][log][host_name]" 39 | "RecordNumber" => "[win][log][record_num]" 40 | "EventType" => "[win][log][type]" 41 | "Opcode" => "[win][log][opcode]" 42 | "host" => "[@meta][log][forwarder]" 43 | "Severity" => "[@meta][log][level]" 44 | } 45 | lowercase => [ 46 | "[@meta][log][host_name]", 47 | "[win][log][type]", 48 | "[win][log][opcode]", 49 | "[@meta][log][level]" 50 | ] 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /0511-logstash-powershell.conf: -------------------------------------------------------------------------------- 1 | filter { 2 | 3 | if [@meta][log][type] == "windows-wef" { 4 | 5 | # PowerShell Operational Only 6 | if [Channel] == "Microsoft-Windows-PowerShell/Operational" { 7 | 8 | # EventID 4103 9 | if [EventID] == 4103 { 10 | 11 | mutate { 12 | 13 | add_field => { 14 | "PayLoadInvocation" => "%{Payload}" 15 | "PayLoadParams" => "%{Payload}" 16 | } 17 | 18 | gsub => [ 19 | # Normalize ContextInfo 20 | "ContextInfo", " ", "", 21 | "ContextInfo", " = ", "=" 22 | ] 23 | 24 | } 25 | 26 | # Parse ContextInfo 27 | kv { 28 | 29 | source => "ContextInfo" 30 | field_split => "\r\n" 31 | value_split => "=" 32 | remove_char_key => " " 33 | allow_duplicate_values => false 34 | # Set only allowed keys/fields incase ever an error parsing where something could contain a similar value_split of "=" 35 | include_keys => [ "Severity", "HostName", "HostVersion", "HostID", "HostApplication", "EngineVersion", "RunspaceID", "PipelineID", "CommandName", "CommandType", "ScriptName", "CommandPath", "SequenceNumber", "User", "ConnectedUser", "ShellID" ] 36 | 37 | } 38 | 39 | mutate { 40 | 41 | gsub => [ 42 | # Prepare Payload CommandInvocation parsing 43 | "PayLoadInvocation", "CommandInvocation\(.*\)", "CommandInvocation", 44 | "PayLoadInvocation", "ParameterBinding.*\r\n", "", 45 | 46 | # Prepare Payload ParameterBinding parsing 47 | "PayLoadParams", "CommandInvocation.*\r\n", "", 48 | "PayLoadParams", "ParameterBinding\(\S+\): ", "|||SPLITMEHEHE|||", 49 | 50 | # Remove any commandinvocation and parameterbinding and any other known fields/keys and leave a remaining Payload field 51 | "Payload", "CommandInvocation.*\r\n", "", 52 | "Payload", "ParameterBinding.*\r\n", "" 53 | ] 54 | 55 | } 56 | 57 | # Parse payload field for all CommandInvocations 58 | # Also add a field for non alphanumeric characters via https://twitter.com/jackcr/status/884875719972728833 59 | kv { 60 | 61 | source => "PayLoadInvocation" 62 | field_split => "\n" 63 | value_split => ":" 64 | allow_duplicate_values => false 65 | target => "[ps]" 66 | include_keys => [ "CommandInvocation" ] 67 | 68 | } 69 | 70 | ruby { 71 | 72 | code => " 73 | params_split = event.get('PayLoadParams').split('|||SPLITMEHEHE|||') 74 | params_split = params_split.drop(1) 75 | params_split_length = params_split.length 76 | all_names = Array.new 77 | all_values = Array.new 78 | all_values_non_alphanumeric = Array.new 79 | 80 | for param in params_split 81 | slice_and_dice = param.index('; value=') 82 | name = param.slice(6..slice_and_dice-2) 83 | value = param.slice(param.index('value=')..-1)[6..-1] 84 | value = value.strip 85 | value[0] = '' 86 | value[-1] = '' 87 | value_non_alphanumeric = value.gsub(/[A-Za-z0-9\s]+/i, '') 88 | all_names.push(name) 89 | all_values.push(value) 90 | all_values_non_alphanumeric.push(value_non_alphanumeric) 91 | end 92 | 93 | all_names = all_names.uniq 94 | all_values = all_values.uniq 95 | event.set('[ps][param][name]', all_names) 96 | event.set('[ps][param][value]', all_values) 97 | event.set('[ps][param][value_nonalphanumeric]', all_values_non_alphanumeric) 98 | " 99 | 100 | } 101 | 102 | # Cleanup and Conversions 103 | mutate { 104 | 105 | # Normalize ContextInfo field names 106 | rename => { 107 | "CommandName" => "[ps][command][name]" 108 | "CommandPath" => "[ps][command][path]" 109 | "CommandType" => "[ps][command][type]" 110 | "ConnectedUser" => "[ps][connected_user][full]" 111 | "EngineVersion" => "[ps][version][full]" 112 | "HostApplication" => "[ps][src][application]" 113 | "HostID" => "[ps][src][host_id]" 114 | "HostName" => "[ps][src][name]" 115 | "HostVersion" => "[ps][src][version]" 116 | "PipelineID" => "[ps][pipeline_id]" 117 | "RunspaceID" => "[ps][runspace_id]" 118 | "ScriptName" => "[file][name]" 119 | "SequenceNumber" => "[ps][seq_num]" 120 | "ShellID" => "[ps][src][id]" 121 | "User" => "[ps][user][full]" 122 | "[ps][CommandInvocation]" => "[ps][invocation]" 123 | "Payload" => "[ps][remaining_payload]" 124 | } 125 | 126 | # Remove unwanted fields 127 | remove_field => [ 128 | "Severity", 129 | "EventType", 130 | "Keywords", 131 | "message", 132 | "Message", 133 | "Opcode", 134 | "port", 135 | "SeverityValue", 136 | "SourceModuleName", 137 | "SourceModuleType", 138 | "Version", 139 | "ContextInfo", 140 | "PayLoadInvocation", 141 | "PayLoadParams" 142 | ] 143 | 144 | # Set correct value types 145 | convert => { "[ps][pipeline_id]" => "integer" } 146 | convert => { "[ps][seq_num]" => "integer" } 147 | lowercase => [ 148 | "[ps][command][name]", 149 | "[ps][command][type]", 150 | "[ps][src][application]", 151 | "[ps][src][id]", 152 | "[ps][src][name]", 153 | "[ps][user][full]" 154 | ] 155 | 156 | } 157 | 158 | } 159 | 160 | # EventID 4104 161 | else if [EventID] == 4104 { 162 | 163 | # Sometimes ScriptBlockText will not be parsed from the Message field. When this happens the other parameters (appear) to also never be parsed (ie: ScriptBlockId etc) 164 | # So check if ScriptBlockText exists and if it does not then we will want to parse the parameters from the Message field 165 | if [ScriptBlockText] { 166 | mutate { 167 | remove_field => [ 168 | "Message" 169 | ] 170 | } 171 | } 172 | else { 173 | # Lets use GSUB to make sure we can get things to split on / make it easier more efficient to split on 174 | grok { 175 | match => { 176 | "Message" => "^Creating Scriptblock text \(%{INT:MessageNumber} of %{INT:MessageTotal}\):\r\n%{GREEDYDATA:ScriptBlockText}\r\n\r\nScriptBlock ID: %{UUID:ScriptBlockId}\r\nPath: %{DATA:Path}$" 177 | } 178 | break_on_match => true 179 | keep_empty_captures => false 180 | named_captures_only => true 181 | tag_on_failure => [ "_grokparsefailure", "_parsefailure" ] 182 | tag_on_timeout => "_groktimeout" 183 | # Timeout 1.5 seconds 184 | timeout_millis => 1500 185 | remove_field => [ "Message" ] 186 | } 187 | } 188 | 189 | mutate { 190 | rename => { 191 | "Path" => "[file][name]" 192 | "ScriptBlockText" => "[ps][script_block][text]" 193 | "ScriptBlockId" => "[ps][script_block][id]" 194 | "MessageNumber" => "[ps][script_block][msg_num]" 195 | "MessageTotal" => "[ps][script_block][msg_total]" 196 | } 197 | copy => { "Domain" => "[src_user][domain]"} 198 | } 199 | # Fingerprint the Script Block Text ---- useful for finding reoccuring scripts we want to exclude 200 | fingerprint { 201 | source => [ "[ps][script_block][text]" ] 202 | method => "SHA1" 203 | target => "[@meta][fp][ps][script_block][sha1]" 204 | key => "logstash" 205 | } 206 | # Fingerprint Script Block Text and UserID/[@meta][src_user][sid] ---- because sometimes certain accounts should not run certain scripts, so filtering just Script Block Text could be a problem. Also, don't want to use AccountName because a local user with $X name could have same name as a domain user! 207 | fingerprint { 208 | source => [ "[ps][script_block][text]", "[@meta][src_user][sid]" ] 209 | concatenate_sources => true 210 | method => "SHA1" 211 | target => "[@meta][fp][ps][script_block_and_sid][sha1]" 212 | key => "logstash" 213 | } 214 | # Fingerprint UserID/[@meta][src_user][sid] and FileName---- because ScriptBlockText gets chopped up sometimes. Use this with caution for filtering 215 | if [file][name] { 216 | fingerprint { 217 | source => [ "[file][name]", "[@meta][src_user][sid]" ] 218 | concatenate_sources => true 219 | method => "SHA1" 220 | target => "[@meta][fp][ps][file_name_and_sid][sha1]" 221 | key => "logstash" 222 | } 223 | } 224 | } 225 | } 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /9998-logstash-parse-failures.conf: -------------------------------------------------------------------------------- 1 | filter { 2 | # In case [tags] are being used for something else and or geoip lookup failures, then we do NOT want to just assume [tags] means something we don't want to index. 3 | # However, we also do NOT want to perform the expensive multi or statement on everything that does not have [tags] 4 | if [tags] { 5 | if "_parsefailure" in [tags] or "_jsonparsefailure" in [tags] or "_grokparsefailure" in [tags] or "_dissectfailure" in [tags] or "_groktimeout" in [tags] or "_rubyexception" in [tags] or "_dateparsefailure" in [tags] or "_jdbcstreamingfailure" in [tags] or "_elasticsearch_lookup_failure" in [tags] or "_urldecodefailure" in [tags] or "_csvparsefailure" in [tags] or "_xmlparsefailure" in [tags] { 6 | # After setting the event as a parse failure, then we need to remove all events except for what was set in 00*-.conf and above 7 | prune { 8 | whitelist_names => [ "$@timestamp^", "@timestamp", "$_original_message^", "_original_message", "^tags$", "$%{[@meta][event_type]}^", "${[@meta][log][timestamp]}^", "$[@meta][log][level]^", "$[@meta][log][timestamp]^" ] 9 | } 10 | mutate { 11 | add_field => { 12 | "[@meta][event_type]" => "_parsefailure" 13 | "[@meta][log][timestamp]" => "%{@timestamp}" 14 | } 15 | } 16 | } 17 | } 18 | # Remove the original message that we copied in 101-*.conf if no parse failure 19 | if [@meta][event_type] != "_parsefailure" { 20 | mutate { remove_field => "_original_message"} 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /9999-logstash-output.conf: -------------------------------------------------------------------------------- 1 | output { 2 | 3 | if [@meta][event_type] != "_parsefailure" { 4 | 5 | if [@meta][event_type] == "network" { 6 | elasticsearch { 7 | hosts => [ "127.0.0.1:9200" ] 8 | index => "logs-network-%{[@meta][log][type]}-%{+YYYY.MM.dd}" 9 | document_type => "%{[@meta][log][type]}" 10 | } 11 | } 12 | 13 | else if [@meta][log][type] == "windows-wef" { 14 | elasticsearch { 15 | hosts => [ "127.0.0.1:9200" ] 16 | index => "logs-endpoint-%{[@meta][log][type]}-%{+YYYY.MM.dd}" 17 | document_type => "%{[@meta][log][type]}" 18 | } 19 | } 20 | 21 | else { 22 | elasticsearch { 23 | hosts => [ "127.0.0.1:9200" ] 24 | index => "indexme-%{+YYYY.MM.dd}" 25 | } 26 | } 27 | } 28 | 29 | else { 30 | elasticsearch { 31 | hosts => [ "127.0.0.1:9200" ] 32 | index => "parse-failures-%{+YYYY.MM.dd}" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ARP Table to Event: -------------------------------------------------------------------------------- 1 | if([System.Diagnostics.EventLog]::SourceExists("ARP Status Script")){ 2 | $x = Get-NetIPInterface | where-object {$_.ConnectionState -eq "Connected" -and $_.InterfaceAlias -notlike "*VMware*" -and $_.InterfaceAlias -notlike "*Loopback*"} | get-netneighbor | select-object -property InterfaceAlias,LinkLayerAddress,IPAddress 3 | $x = [system.String]::Join("`r`n", $x) 4 | write-eventlog -logname "System" -source "ARP Status Script" -message $x -EventId 10001 5 | }Else{ 6 | $x = Get-NetIPInterface | where-object {$_.ConnectionState -eq "Connected" -and $_.InterfaceAlias -notlike "*VMware*" -and $_.InterfaceAlias -notlike "*Loopback*"} | get-netneighbor | select-object -property InterfaceAlias,LinkLayerAddress,IPAddress 7 | $x = [system.String]::Join("`r`n", $x) 8 | New-EventLog –LogName "System" –Source “ARP Status Script” 9 | write-eventlog -logname "System" -source "ARP Status Script" -message $x -EventId 10001 10 | } 11 | -------------------------------------------------------------------------------- /ETW Tracing Consumption Example: -------------------------------------------------------------------------------- 1 | https://github.com/acalarch/ETL-to-EVTX/blob/master/etl-to-evtx.ps1 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WinLogsZero2Hero talk links and scripts 2 | This repo is code used from our talks at Derbycon 7, BSides Detroit 2017, and Bloomcon 0x2. 3 | 4 | Slides for the talk are here: https://bit.ly/WinLogsZero2Hero 5 | 6 | The 3 live presentations are here: 7 | 8 | Bloomcon: https://youtu.be/H3t_kHQG1Js?t=1m44s 9 | 10 | Derbycon: https://www.youtube.com/watch?v=8AKxt-5RB6w 11 | 12 | BSides Detroit: https://www.youtube.com/watch?v=jiHP0nQoAfs 13 | 14 | 15 | 16 | This script has configurations/examples for: 17 | * Deployming Sysmon where it will check version and upgrade if new or install if non-existent or restart/start if stopped/disabled/not-running. 18 | 19 | * Cuckoo Sandbox Windows Event collections 20 | 21 | * Logstash enrichment examples for PowerShell 22 | 23 | * ETW (Event Tracing for Windows) implementation for WMI and consumption via WEF 24 | 25 | * DNS Debug Log consumption via WEF 26 | 27 | * Example of collecting ARP table continously via WEF 28 | -------------------------------------------------------------------------------- /WMI Events To Windows Events: -------------------------------------------------------------------------------- 1 | #GET LIST OF ALL WMI EVENTS Get-WmiObject -Namespace "root" -Query "SELECT * FROM meta_class WHERE __This ISA '__Event'" 2 | #http://www.powershellmagazine.com/2013/01/15/pstip-list-all-wmi-event-classes/ 3 | #Good Tutorial 4 | #https://learn-powershell.net/2013/08/14/powershell-and-events-permanent-wmi-event-subscriptions/ 5 | #More Namespaces 6 | #Get-WMIObject -class __Namespace -namespace root | Format-Table name 7 | 8 | $filter = ([wmiclass]"\\.\root\subscription:__EventFilter").CreateInstance(); 9 | $filter.QueryLanguage = "WQL"; 10 | $filter.Query = "SELECT * FROM __InstanceOperationEvent WHERE BLAH"; 11 | $filter.Name = "My Filter Name"; 12 | $filter.EventNamespace = 'root\StandardCimv2'; 13 | $result = $instanceFilter.Put(); 14 | $filterx = $result.Path; 15 | $consumer = ([wmiclass]"\\.\root\subscription:NTEventLogEventConsumer").CreateInstance(); 16 | $consumer.Name = 'ArpConsumer'; 17 | $consumer.Category = 1 18 | $consumer.EventType=4; 19 | $consumer.EventID = 7754; 20 | $consumer.SourceName = "TEST ARP"; 21 | $consumer.NumberOfInsertionStrings = 2; 22 | $consumer.InsertionStringTemplates = {"This is my message: %TargetInstance.MyProperty%"}; 23 | $result = $instanceConsumer.Put() 24 | $consumerx = $result.Path 25 | $binding = ([wmiclass]"\\.\root\subscription:__FilterToConsumerBinding").CreateInstance() 26 | $binding.Filter = $newFilter 27 | $binding.Consumer = $newConsumer 28 | $result = $instanceBinding.Put() 29 | $bindingx = $result.Path 30 | -------------------------------------------------------------------------------- /add-cuckoo-id-to-winlogs.txt: -------------------------------------------------------------------------------- 1 | # Add the following to The cuckoo manager in $CUCKOODIR/cuckoo/analyzer/windows/analyzer.py 2 | file_path_id_for_win_logs = "C:\\id.txt" 3 | cust_file = open(file_path_id_for_win_logs, 'w') 4 | cust_file.write(str(self.config.id)) 5 | cust_file.close 6 | # Then you will add the file path above the nxlog.conf 7 | -------------------------------------------------------------------------------- /dns-debug-log-gpo-steps.txt: -------------------------------------------------------------------------------- 1 | # Set DNS Debug Logging. 2 | # In order to set log immediately use "0x8000" vs windows default 8KB buffer. So, you would specify "0x8000f321" 3 | # Just import the scheduled task IN ORDER of loglevel, then logfile, then logsize! 4 | # Max size is 500000000 500MB but use 50000000 for 50MB 5 | 1) Create GPO called "audit-dns-via-debug" 6 | 2) Under "Scope" tab set "Security Filtering to "Authenticated Users" 7 | 3) Under "Details" tab set "GPO Status" to "User configuration settings disabled" 8 | 4) Create 9 | 4.1) Task named "set_dns_debug_log" 10 | # Then for each task set following: 11 | 4.a) When running the task, user the following user account:"NT AUTHORITY/SYSTEM" 12 | 4.b) Check "Run whether user is logged on or not" and "Run with highest privileges" 13 | 4.c) Check "Hidden" 14 | 4.d) Configure for:"Windows Vista or Windows Server 2008" 15 | 4.e) Any Triggers you want, that make sense 16 | 4.f) Any Conditions you want, that make sense 17 | 4.g) Any Settings you want, that make sense 18 | 4.h) Common: check "Remove this item when it is no longer applied" 19 | 4.i) Common: check "Item-level targeting" and use: 20 | Match value data 21 | Any 22 | HKEY_LOCAL_MACHINE 23 | SYSTEM\ControlSet001\services\DNS 24 | Start 25 | REG_DWORD 26 | 00000002 Hexadeciaml 27 | 5) For 4) need to set following under actions IN ORDER! IN ORDER! Don't ask how long took to troubleshoot why putting all the dnscmd commands in one line was not working 28 | 4.1) run:dnscmd paramter:/Config /LogLevel 0x8000f321 29 | 4.2) run:dnscmd paramter:/Config /LogFilePath "%WINDIR%\System32\dns\Dns.log" 30 | 4.3) run:dnscmd paramter:/Config /LogFileMaxSize 50000000 31 | #TODO:In scheduled task set to only run if connected to DOMAIN! using either site or start if only network in the scheduled task itself under Condition tab 32 | 33 | ######### Windows DNS Debug Logging 34 | # %WINDIR%\System32\dns\Dns.log" 35 | # Debug Logging settings: 36 | # Log packets for debugging 37 | # Packet direction: Outgoing TCP & UDP 38 | # Packet direction: Incoming TCP & UDP 39 | # Packet contents: Queries/Transfer Request & Response 40 | # Packet contents: Updates Request & Response 41 | # Message logging key (for packets - other items use a subset of these fields): 42 | # Field # Information Values 43 | # ------- ----------- ------ 44 | # 1 Date 45 | # 2 Time 46 | # 3 Thread ID 47 | # 4 Context 48 | # 5 Internal packet identifier 49 | # 6 UDP/TCP indicator 50 | # 7 Send/Receive indicator 51 | # 8 Remote IP 52 | # 9 Xid (hex) 53 | # 10 Query/Response R = Response 54 | # blank = Query 55 | # 11 Opcode Q = Standard Query 56 | # N = Notify 57 | # U = Update 58 | # ? = Unknown 59 | # 12 [ Flags (hex) 60 | # 13 Flags (char codes) A = Authoritative Answer 61 | # T = Truncated Response 62 | # D = Recursion Desired 63 | # R = Recursion Available 64 | # 14 ResponseCode ] 65 | # 15 Question Type 66 | # 16 Question Name 67 | -------------------------------------------------------------------------------- /enable-wmi-tracing-via-gpo.txt: -------------------------------------------------------------------------------- 1 | 1) Create GPO called "audit-wmi-tracing" 2 | 2) Under the "Scope" tab set "Security Filtering to "Authenticated Users" 3 | 3) Under the "Details" tab set "GPO Status" to "User configuration settings disabled" 4 | 4) In the "Settings" tab edit the configuration and browse to "Preferences > Control Panel Settings > Scheduled Tasks" and right click, select New, "Scheduled Task (Windows Vista and later)" 5 | 5) Under the "General" tab 6 | Action:Replace 7 | Name:set_wmi_trace 8 | When running the task, use the following user account:NT AUTHORITY/SYSTEM 9 | Run only when user is logged on:CHECK 10 | Run with highest privileges:CHECK 11 | Hidden:CHECK 12 | 6) Under the "Triggers" tab, select anything that you desire for your environment. 13 | #recommend using a daily trigger 14 | 7) Under the "Actions" tab create the following "New" actions: 15 | 7.a) 16 | Action:Start a program 17 | Program/script:%SystemRoot%\system32\wevtutil.exe 18 | Add arguments (optional):sl "Microsoft-Windows-WMI-Activity/Trace" /e:false /q:true 19 | 7.b) 20 | Action:Start a program 21 | Program/script:%SystemRoot%\system32\wevtutil.exe 22 | Add arguments (optional):sl "Microsoft-Windows-WMI-Activity/Trace" /rt:false 23 | 7.c) 24 | Action:Start a program 25 | Program/script:%SystemRoot%\system32\wevtutil.exe 26 | Add arguments (optional):sl "Microsoft-Windows-WMI-Activity/Trace" /ms:35000000 27 | 7.d) 28 | Action:Start a program 29 | Program/script:%SystemRoot%\system32\wevtutil.exe 30 | Add arguments (optional):sl "Microsoft-Windows-WMI-Activity/Trace" /e:true /q:true 31 | 8) Under the "Conditions" tab, select anything that you desire for your environment. 32 | #recommend just leaving the defaults 33 | 9) Under the "Settings" tab, select anything that you desire for your environment. 34 | #recommend to check the boxes for stop task if it runs longer than 1 hour and if task does not end than force it to close 35 | 10) Under the "Conditions" tab, select anything that you desire for your environment. 36 | Remove this item when it is no longer applied:CHECK 37 | -------------------------------------------------------------------------------- /install-and-or-upgrade-sysmon-and-config.ps1: -------------------------------------------------------------------------------- 1 | # Ensure to change the $ADDomainName to your domain name on line 3; 2 | # Ensure to complete the path to the directory where you will keep Sysmon.exe including the preceding "\"; Sysmon64.exe; and the config sysmonconfig.xml on line 7; 3 | # Only change these variables and no other variables :) 4 | $ADDomainName = "domain.local" 5 | $SysVolSysmonPath = "\\$ADDomainName\sysvol\$ADDomainName\" 6 | $LocalSysmonPath = "$Env:SystemRoot\Temp\" 7 | $SysVolSysmonConfig = "$SysVolSysmonPath\sysmonconfig.xml" 8 | $LocalSysmonConfig = "$LocalSysmonPath\sysmonconfig.xml" 9 | $LogFileForScript = "$LocalSysmonPath\sysmon-install-log.txt" 10 | 11 | # Set time that script started 12 | $ScriptRunTime = ([DateTime]::Now.AddHours(-$NumberOfHoursToQuery)) 13 | 14 | # If log file does not already exist create it, so the rest of the script can just use add-content 15 | $LogFileExists = Test-Path $LogFileForScript 16 | if ($LogFileExists -eq $false) 17 | { 18 | Set-Content $LogFileForScript "" -NoNewline 19 | } 20 | 21 | Function main{ 22 | 23 | # Determines if OS is 64 or 32-bit 24 | if([System.IntPtr]::Size -eq 4) {$Sysmon = "Sysmon.exe"} else {$Sysmon = "Sysmon64.exe"} 25 | $SysVolSysmonPE = "$SysVolSysmonPath\$Sysmon" 26 | 27 | 28 | # Finds sysmon.exe at $SysVolSysmonPath; Is the share available and is sysmon available on the share to update?# 29 | $SysVolSysmonAvailable = Test-Path "$SysVolSysmonPE" 30 | $SysVolSysmonConfigAvailabe = Test-Path "$SysVolSysmonConfig" 31 | 32 | # Set local variables for when we copy config and PE to device 33 | $LocalSysmonPE = "$LocalSysmonPath\$Sysmon" 34 | 35 | if(($SysVolSysmonAvailable -eq $true) -and ($SysVolSysmonConfigAvailabe -eq $true)) 36 | { 37 | 38 | $SysmonVersionAvailable=[System.Diagnostics.FileVersionInfo]::GetVersionInfo($SysVolSysmonPE).FileVersion 39 | 40 | # Finds sysmon.exe at C:\windows\sysmon.exe 41 | $InstalledSysmon = Test-Path $Env:windir\sysmon.exe 42 | # When you remove sysmon, it doesn't remove the EXE.. so do another check :) 43 | $InstalledSysmonAsAService = get-service -Name "sysmon" -ErrorAction SilentlyContinue 44 | # We want to log that for some reason sysmon.exe exists on the host but it is not a service :0 45 | # Usually this means that at one point it was installed 46 | if (-Not $InstalledSysmonAsAService) 47 | { 48 | Add-Content $LogFileForScript "$ScriptRunTime ---- Service was uninstalled at some point! May want to figure out why..." 49 | } 50 | 51 | if (($InstalledSysmon -eq $true) -and ($InstalledSysmonAsAService)) { 52 | 53 | # Get current sysmon version 54 | $SysmonVersion=[System.Diagnostics.FileVersionInfo]::GetVersionInfo("$Env:windir\sysmon.exe").FileVersion 55 | 56 | # Convert strings to integers for comparison 57 | [double]$intSysmonAvailable = [convert]::ToDouble($SysmonVersionAvailable) 58 | [double]$intSysmonVersion = [convert]::ToDouble($SysmonVersion) 59 | 60 | # If sysvol version is greater than current version update#> 61 | if($intSysmonAvailable -gt $intSysmonVersion) 62 | { 63 | # Copy sysmon locally, for install performance and incase network drops during install 64 | cmd /c "copy /V $SysVolSysmonPE $LocalSysmonPE" 65 | cmd /c "copy /V $SysVolSysmonConfig $LocalSysmonConfig" 66 | # Make sure copies where successful 67 | if((Test-Path $LocalSysmonPE) -and (Test-Path $LocalSysmonConfig)) 68 | { 69 | # #TODO:what to do?! Way older versions of Sysmon used to put the EXE/PE in %WINDIR%/System32/Sysmon.exe -- so need to just call system sysmon to do uninstall... 70 | cmd /c "$LocalSysmonPE -u" 71 | cmd /c "$LocalSysmonPE -accepteula -i $LocalSysmonConfig" 72 | Add-Content $LogFileForScript "$ScriptRunTime ---- Updated PE." 73 | } 74 | 75 | else { 76 | Add-Content $LogFileForScript "$ScriptRunTime ---- Failed to copy sysmon items." 77 | exit 78 | } 79 | } 80 | 81 | # If sysmon drv last write time is later than last write time of sysvol config file, update the config. 82 | 83 | # Obtain sysvol sysmonconfig.xml last write time 84 | $sysvolconfiglastwrite = (get-item $SysVolSysmonConfig).LastWriteTime 85 | 86 | # Obtain current configuration last write time, this is available in the registry 87 | $key = get-item "HKLM:\SYSTEM\CurrentControlSet\Services\SysmonDrv\Parameters" 88 | $localconfiglastwrite = ($key | Get-RegistryKeyTimestamp).LastWriteTime 89 | 90 | # Finally, compare both write times. If the lastwrite for the sysvol config is greater than the lastwrite for the registry.. update config 91 | if($sysvolconfiglastwrite -gt $localconfiglastwrite) 92 | { 93 | # Copy config locally, for install performance and incase network drops during install 94 | cmd /c "copy /V $SysVolSysmonConfig $LocalSysmonConfig" 95 | # Make sure network copy was successful 96 | if(Test-Path $LocalSysmonConfig) 97 | { 98 | cmd /c "$LocalSysmonPE -c $LocalSysmonConfig" 99 | Add-Content $LogFileForScript "$ScriptRunTime ---- Updated configuration." 100 | } 101 | else { 102 | Add-Content $LogFileForScript "$ScriptRunTime ---- Failed to copy sysmon items." 103 | exit 104 | } 105 | } 106 | } 107 | # Sysmon is not installed, so install with config :) 108 | else 109 | { 110 | # Copy sysmon locally, for install performance and incase network drops during install 111 | cmd /c "copy /V $SysVolSysmonPE $LocalSysmonPE" 112 | cmd /c "copy /V $SysVolSysmonConfig $LocalSysmonConfig" 113 | # Make sure copies where successful 114 | if((Test-Path $LocalSysmonPE) -and (Test-Path $LocalSysmonConfig)) 115 | { 116 | cmd /c "$LocalSysmonPE -accepteula -i $LocalSysmonConfig" 117 | Add-Content $LogFileForScript "$ScriptRunTime ---- First install." 118 | } 119 | else { 120 | Add-Content $LogFileForScript "$ScriptRunTime ---- Failed to copy sysmon items." 121 | exit 122 | } 123 | } 124 | } 125 | else{ 126 | Add-Content $LogFileForScript "$ScriptRunTime ---- Failed to find sysmon items in sysvol." 127 | } 128 | # Ensure sysmon services are running 129 | try{ 130 | $SysmonService = get-service -Name "sysmon" -ErrorAction STOP 131 | $SysmonDrvService = get-service -Name "sysmondrv" -ErrorAction STOP 132 | if($SysmonService.Status -ne "running"){Add-Content $LogFileForScript "$ScriptRunTime ---- Service was stopped, starting sysmon PE."; start-service -name "sysmon" -ErrorAction Stop} 133 | if($SysmonDrvService.Status -ne "running"){Add-Content $LogFileForScript "$ScriptRunTime ---- Driver was stopped, starting sysmon driver.";start-service -name "sysmondrv" -ErrorAction Stop} 134 | } 135 | catch{ 136 | Add-Content $LogFileForScript "$ScriptRunTime ---- Failed restarting and or getting status of sysmon services." 137 | exit 138 | } 139 | 140 | } 141 | 142 | Function Get-RegistryKeyTimestamp { 143 | <# 144 | .SYNOPSIS 145 | Retrieves the registry key timestamp from a local or remote system. 146 | 147 | .DESCRIPTION 148 | Retrieves the registry key timestamp from a local or remote system. 149 | 150 | .PARAMETER RegistryKey 151 | Registry key object that can be passed into function. 152 | 153 | .PARAMETER SubKey 154 | The subkey path to view timestamp. 155 | 156 | .PARAMETER RegistryHive 157 | The registry hive that you will connect to. 158 | 159 | Accepted Values: 160 | ClassesRoot 161 | CurrentUser 162 | LocalMachine 163 | Users 164 | PerformanceData 165 | CurrentConfig 166 | DynData 167 | 168 | .NOTES 169 | Name: Get-RegistryKeyTimestamp 170 | Author: Boe Prox 171 | Version History: 172 | 1.0 -- Boe Prox 17 Dec 2014 173 | -Initial Build 174 | 175 | .EXAMPLE 176 | $RegistryKey = Get-Item "HKLM:\System\CurrentControlSet\Control\Lsa" 177 | $RegistryKey | Get-RegistryKeyTimestamp | Format-List 178 | 179 | FullName : HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa 180 | Name : Lsa 181 | LastWriteTime : 12/16/2014 10:16:35 PM 182 | 183 | Description 184 | ----------- 185 | Displays the lastwritetime timestamp for the Lsa registry key. 186 | 187 | .EXAMPLE 188 | Get-RegistryKeyTimestamp -Computername Server1 -RegistryHive LocalMachine -SubKey 'System\CurrentControlSet\Control\Lsa' | 189 | Format-List 190 | 191 | FullName : HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa 192 | Name : Lsa 193 | LastWriteTime : 12/17/2014 6:46:08 AM 194 | 195 | Description 196 | ----------- 197 | Displays the lastwritetime timestamp for the Lsa registry key of the remote system. 198 | 199 | .INPUTS 200 | System.String 201 | Microsoft.Win32.RegistryKey 202 | 203 | .OUTPUTS 204 | Microsoft.Registry.Timestamp 205 | #> 206 | [OutputType('Microsoft.Registry.Timestamp')] 207 | [cmdletbinding( 208 | DefaultParameterSetName = 'ByValue' 209 | )] 210 | Param ( 211 | [parameter(ValueFromPipeline=$True, ParameterSetName='ByValue')] 212 | [Microsoft.Win32.RegistryKey]$RegistryKey, 213 | [parameter(ParameterSetName='ByPath')] 214 | [string]$SubKey, 215 | [parameter(ParameterSetName='ByPath')] 216 | [Microsoft.Win32.RegistryHive]$RegistryHive, 217 | [parameter(ParameterSetName='ByPath')] 218 | [string]$Computername 219 | ) 220 | Begin { 221 | #region Create Win32 API Object 222 | Try { 223 | [void][advapi32] 224 | } Catch { 225 | #region Module Builder 226 | $Domain = [AppDomain]::CurrentDomain 227 | $DynAssembly = New-Object System.Reflection.AssemblyName('RegAssembly') 228 | $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run) # Only run in memory 229 | $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('RegistryTimeStampModule', $False) 230 | #endregion Module Builder 231 | 232 | #region DllImport 233 | $TypeBuilder = $ModuleBuilder.DefineType('advapi32', 'Public, Class') 234 | 235 | #region RegQueryInfoKey Method 236 | $PInvokeMethod = $TypeBuilder.DefineMethod( 237 | 'RegQueryInfoKey', #Method Name 238 | [Reflection.MethodAttributes] 'PrivateScope, Public, Static, HideBySig, PinvokeImpl', #Method Attributes 239 | [IntPtr], #Method Return Type 240 | [Type[]] @( 241 | [Microsoft.Win32.SafeHandles.SafeRegistryHandle], #Registry Handle 242 | [System.Text.StringBuilder], #Class Name 243 | [UInt32 ].MakeByRefType(), #Class Length 244 | [UInt32], #Reserved 245 | [UInt32 ].MakeByRefType(), #Subkey Count 246 | [UInt32 ].MakeByRefType(), #Max Subkey Name Length 247 | [UInt32 ].MakeByRefType(), #Max Class Length 248 | [UInt32 ].MakeByRefType(), #Value Count 249 | [UInt32 ].MakeByRefType(), #Max Value Name Length 250 | [UInt32 ].MakeByRefType(), #Max Value Name Length 251 | [UInt32 ].MakeByRefType(), #Security Descriptor Size 252 | [long].MakeByRefType() #LastWriteTime 253 | ) #Method Parameters 254 | ) 255 | 256 | $DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String])) 257 | $FieldArray = [Reflection.FieldInfo[]] @( 258 | [Runtime.InteropServices.DllImportAttribute].GetField('EntryPoint'), 259 | [Runtime.InteropServices.DllImportAttribute].GetField('SetLastError') 260 | ) 261 | 262 | $FieldValueArray = [Object[]] @( 263 | 'RegQueryInfoKey', #CASE SENSITIVE!! 264 | $True 265 | ) 266 | 267 | $SetLastErrorCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder( 268 | $DllImportConstructor, 269 | @('advapi32.dll'), 270 | $FieldArray, 271 | $FieldValueArray 272 | ) 273 | 274 | $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute) 275 | #endregion RegQueryInfoKey Method 276 | 277 | [void]$TypeBuilder.CreateType() 278 | #endregion DllImport 279 | } 280 | #endregion Create Win32 API object 281 | } 282 | Process { 283 | #region Constant Variables 284 | $ClassLength = 255 285 | [long]$TimeStamp = $null 286 | #endregion Constant Variables 287 | 288 | #region Registry Key Data 289 | If ($PSCmdlet.ParameterSetName -eq 'ByPath') { 290 | #Get registry key data 291 | $RegistryKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($RegistryHive, $Computername).OpenSubKey($SubKey) 292 | If ($RegistryKey -isnot [Microsoft.Win32.RegistryKey]) { 293 | Throw "Cannot open or locate $SubKey on $Computername" 294 | } 295 | } 296 | 297 | $ClassName = New-Object System.Text.StringBuilder $RegistryKey.Name 298 | $RegistryHandle = $RegistryKey.Handle 299 | #endregion Registry Key Data 300 | 301 | #region Retrieve timestamp 302 | $Return = [advapi32]::RegQueryInfoKey( 303 | $RegistryHandle, 304 | $ClassName, 305 | [ref]$ClassLength, 306 | $Null, 307 | [ref]$Null, 308 | [ref]$Null, 309 | [ref]$Null, 310 | [ref]$Null, 311 | [ref]$Null, 312 | [ref]$Null, 313 | [ref]$Null, 314 | [ref]$TimeStamp 315 | ) 316 | Switch ($Return) { 317 | 0 { 318 | #Convert High/Low date to DateTime Object 319 | $LastWriteTime = [datetime]::FromFileTime($TimeStamp) 320 | 321 | #Return object 322 | $Object = [pscustomobject]@{ 323 | FullName = $RegistryKey.Name 324 | Name = $RegistryKey.Name -replace '.*\\(.*)','$1' 325 | LastWriteTime = $LastWriteTime 326 | } 327 | $Object.pstypenames.insert(0,'Microsoft.Registry.Timestamp') 328 | $Object 329 | } 330 | 122 { 331 | Throw "ERROR_INSUFFICIENT_BUFFER (0x7a)" 332 | } 333 | Default { 334 | Throw "Error ($return) occurred" 335 | } 336 | } 337 | #endregion Retrieve timestamp 338 | } 339 | } 340 | 341 | main 342 | -------------------------------------------------------------------------------- /nxlog.conf: -------------------------------------------------------------------------------- 1 | define ROOT C:\Program Files (x86)\nxlog 2 | Moduledir %ROOT%\modules 3 | CacheDir %ROOT%\data 4 | Pidfile %ROOT%\data\nxlog.pid 5 | SpoolDir %ROOT%\data 6 | LogFile %ROOT%\data\nxlog.log 7 | 8 | 9 | Module xm_json 10 | 11 | 12 | 13 | Module xm_fileop 14 | 15 | 16 | 17 | Module im_msvistalog 18 | Query \ 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 | Module om_udp 62 | Host $IP 63 | Port 8531 64 | Exec $submission_id = file_read("C:\id.txt"); 65 | Exec to_json(); $message = $raw_event; 66 | 67 | 68 | 69 | Path in => out 70 | 71 | --------------------------------------------------------------------------------