Hunting in ETW Providers and Security event logs with Azure Data Explorer

In this blog post, we are going to demonstrate how we could use Azure Data Explorer to hunt in data based on ETW Providers and Security event logs. Everything that will be showed during in this blog post, is just sample data. The goal of this blog post is to show practical examples on using ADX for data analysis.

Event Tracing for Windows

Event Tracing for Windows (ETW) is an efficient kernel-level tracing facility that lets you log kernel or application-defined events to a log file. ETW providers are displayed in the “Applications and Services Log” tree, and it allows us to view event logs for individual providers.

Event tracing is built upon an API that exposes the following ETW components:

  • Tracing session: responsible for collecting events from providers and for relaying them to log files and consumers.
  • Consumer: consumes the events from an event tracing session.
  • Provider: provides events to an event tracing session.
  • Controller: enables providers, starts and stops event tracing sessions, defines log files, etc.
Figure 1 from Core system event analysis on windows vista | Semantic Scholar
Source: https://www.semanticscholar.org/paper/Core-system-event-analysis-on-windows-vista-Park/36b574c212a5f16ff188b9731a3600f809164328

We can use the logman.exe utility in Windows to list all the available providers. In order to do so, we have to run following command:

logman query providers

Another example would be gathering additional information about a provider, which can be achieved by running the following command as an example:

logman query providers Microsoft-Windows-PowerShell

In this case, we were specifically looking at Microsoft-Windows-PowerShell. However, this can be replaced with any ETW provider you like to choose from.

Why collecting data from ETW Providers?

ETW Providers generates numerous of events on an operating system that can be useful to collect during an incident response. Once doing an investigation, we should get historical data from systems, that may have been affected during a breach.

Not every organization may have an EDR solution, and perhaps they do. It might not have all the right data telemetry to detect and respond to a certain attack for instance. It may fire an alert here and there, but perhaps it wasn’t be able to catch other attacker’s techniques, which may have been crucial during an investigation.

Collecting data from ETW Providers on (known) affected systems can be a great pivot point to gather more context on other systems, that may have been touched by an attacker for example.

An important thing to keep in mind is that in the end of the day, nothing is perfect. This means that an attacker can also evade ETW, which is something to keep in mind.

Collecting data of ETW Providers & others

In this example, we are going to collect various data on an (affected) system. This will be all the data that is generated by an ETW provider, but also the traditional Security & System event logs. Everything that has been collected is just sample data.

In order to collect all the data and transform it into a CSV format, we will be using the Export-EventLogs.ps1 script that can be found here.

Command

Run the following command as admin

.\Export-EventLogs.ps1

Result

At the sample result, we can see that it will collect various data. It is not necessary required to collect everything, because we should only collect data that we are interested in. However, as an example. I will collect everything.

Once everything has been collected, we can store the data somewhere at a storage account in Azure. This allows us to have a central place to access all the data we need to do an investigation.

Importing data to Azure Data Explorer

Once we have collected all the relevant data that we wanted. We can start importing it into Azure Data Explorer, so we can perform further analysis by running KQL queries.

At the left side of the image, we can see different tables. This is all the data sources that have been ingested.

Analyzing logs

In this section, we are going to analyse different data sources. Everything will be explained with detailed examples and some thought process will be shared for analysts.

We will hunt in sample data for well-known techniques like suspicious PowerShell activities, WMI persistence, and so on.

All the queries are meant as an example. No, they can’t be use as a detection. Yes, there is always room for improvement to tweak it here and there.

Windows Defender AV

Once a system has been affected during a breach. One of the most important logs to look at is, the traditional Windows Defender AV logs.

  1. A quick win can be querying a specific event ID that indicates, that a malicious file was detected by Windows Defender AV.

Query

WindowsDefender
| where isnotempty(['Computer Name'])
| where ['Event Code'] == 1116
| project Date, ['Computer Name'], ['Event Code'], Source, Description

Result

At the sample results, we can see a few alerts.

2. Windows Defender AV provides metadata as well for each alert that has been generated. In this example, we are parsing all the metadata into different columns.

Query

WindowsDefender
| where isnotempty(['Computer Name'])
| where ['Event Code'] == 1116
// FileName that was considered malicious
| extend FileName = tostring(split(Description, ":")[3])
| extend FileName = tostring(split(FileName, "&")[0])
// Path where the script was executed
| extend Path = tostring(split(Description, ":")[11])
// Detection Source
| extend DetectionSource = tostring(split(Description, ":")[9])
// Additional information that may be relevant
| extend AdditionalFields = tostring(split(Description, ":")[12])
| project Date, ['Computer Name'], ['Event Code'], FileName, Path, DetectionSource, AdditionalFields

Result

At the sample result, we can see that additional fields have been added. This includes things, like the detection source, filename and the path of where a script was executed.

3. An attacker can create an exclusion in Windows Defender to exclude a folder from being scanned.

Query

WindowsDefender
| where ['Event Code'] == 5007
| extend NewValue = tostring(split(Description, ":")[2]) 
| extend ExcludedFolder = tostring(split(Description, ":")[3]) // Folder that was excluded from Windows Defender AV
| where NewValue has "HKLM\\SOFTWARE\\Microsoft\\Windows Defender\\Exclusions" // Identifies that an exclusion has been configured
| project Date, ['Computer Name'], ['Event Code'], NewValue, ExcludedFolder, Description

Result

At the sample result, we can see one result. Where the C:\Temp folder has been excluded.

4. It is very common for attackers to disable Windows Defender. The good thing is that we can track this in the event logs. Only thing we have to keep in mind is that, when there is a third-party AV solution installed. It may automatically disable Windows Defender AV.

Query

WindowsDefender
| where ['Event Code'] == 5007
| extend NewValue = tostring(split(Description, ":")[2]) 
| extend Disabled = tostring(split(Description, "=")[2]) // Once the value is set to "0x1", the service has been disabled.
| where NewValue has "HKLM\\SOFTWARE\\Microsoft\\Windows Defender\\IsServiceRunning" // Registry key of the Windows Defender Service
| project Date, ['Computer Name'], ['Event Code'], NewValue, Disabled
| sort by Date desc

Result

At the sample result, we can see that Windows Defender AV has generated logs to tell that the service has been stopped.

Windows Security Event Logs – PtH

We are going to look at the security event logs and see if we can track down potential lateral movement behavior of an attacker. One common technique is Pass the Hash, whereby an attacker captures a password hash of an account and then passes it through for authentication to access other systems on the network.

  1. This is an example of a KQL query, but it may get some additional improvements. Filtering on Logon Type 9 does not immediately indicate an PtH attack. However, it does tell that a user has started a new logon session under the same local identity, while using other credentials. Which is the same behavior, when executing a Pass the Hash attack.

Query

Security
| extend ParsedFields = parse_csv(data)
| extend Date = ParsedFields[0]
| extend Time = ParsedFields[1]
| extend Type = ParsedFields[2]
| extend Computer = ParsedFields[3]
| extend EventID = ParsedFields[4]
| extend Source = ParsedFields[5]
| extend Task = ParsedFields[6]
| extend Properties = ParsedFields[8]
| project-away data, ParsedFields
| where EventID == 4624
// Looking for LogonType = 9 (NewCredentials)
// The program starts a new logon session that has the same local identity,
// but uses other credentials for network connection.
// NOTE: Runas.exe generates LogonType 9 as well, so keep that in mind.
| extend LogonType = tostring(split(Properties, "Restricted Admin Mode")[0])
| extend LogonType = tostring(split(LogonType, ":")[7])
| where LogonType has "9"
// Looks to see which user tried to perform PtH behavior
| extend Actor = tostring(split(Properties, ":")[14])
| extend Actor = tostring(split(Actor, "Account Domain")[0])
// Discovers which account got affected during the potential PtH attack
| extend AffectedUser = tostring(split(Properties, ":")[18])
| extend AffectedUser = tostring(split(AffectedUser, "Network Account Domain")[0])
// Find the LogonID that belongs to this session
| extend LogonID = tostring(split(Properties, ":")[16])
| extend LogonID = tostring(split(LogonID, "Linked Logon ID")[0])
| project Date, Computer, EventID, Actor, AffectedUser, LogonType, LogonID

Result

We can see one result. As discussed earlier, when there is a Logon Type 9. It does not immediately indicate a PtH attack. What we do see in the results is, the LogonID column with a special value that have been initiated for this session.

This LogonID value may have a relationship with other Event ID’s that can be interesting to look at.

2. Now we are going to look at other Event IDs that have a relationship with event 4624 and 0x7A8772 as a value.

Query

Security
| extend ParsedFields = parse_csv(data)
| extend Date = ParsedFields[0]
| extend Time = ParsedFields[1]
| extend Type = ParsedFields[2]
| extend Computer = ParsedFields[3]
| extend EventID = ParsedFields[4]
| extend Source = ParsedFields[5]
| extend Task = ParsedFields[6]
| extend Properties = ParsedFields[8]
| project-away data, ParsedFields
| where Properties has "0x7A82772"

Result

At the sample result, we can see different Event IDs that have a relationship with each other. We can see for example an event ID 4648 that has the same LogonID value as 4624.

3. Let’s proof that Event ID 4648 has the same LogonID value as 4624.

Query

Security
| extend ParsedFields = parse_csv(data)
| extend Date = ParsedFields[0]
| extend Time = ParsedFields[1]
| extend Type = ParsedFields[2]
| extend Computer = ParsedFields[3]
| extend EventID = ParsedFields[4]
| extend Source = ParsedFields[5]
| extend Task = ParsedFields[6]
| extend Properties = ParsedFields[8]
| project-away data, ParsedFields
| where Properties has "0x7A82772"
| where EventID == 4648
// The actor that potentially performed PtH
| extend Actor = tostring(split(Properties, ":")[3])
| extend Actor = tostring(split(Actor, "Account Domain")[0])
// Discovers which account got affected during the potential PtH attack
| extend AffectedUser = tostring(split(Properties, ":")[8])
| extend AffectedUser = tostring(split(AffectedUser, "Account Domain")[0])
// Target server a connection was made to
| extend TargetServer = tostring(split(Properties, ":")[12])
| extend TargetServer = tostring(split(TargetServer, "Additional Information")[0])
// LogonID that has the same value as 4624
| extend LogonID = tostring(split(Properties, ":")[5])
| extend LogonID = tostring(split(LogonID, "Logon GUID")[0])
| project Date, Time, Computer, EventID, Actor, AffectedUser, TargetServer, LogonID

Result

At the sample result, we can see that event ID 4648 has a relationship with 4624.

4. Correlating event ID 4624 with 4648.

Query

Security
| extend ParsedFields = parse_csv(data)
| extend Date = ParsedFields[0]
| extend Time = ParsedFields[1]
| extend Type = ParsedFields[2]
| extend Computer = ParsedFields[3]
| extend EventID = ParsedFields[4]
| extend Source = ParsedFields[5]
| extend Task = ParsedFields[6]
| extend Properties = ParsedFields[8]
| project-away data, ParsedFields
| where EventID == 4624
// Looking for LogonType = 9 (NewCredentials)
// The program starts a new logon session that has the same local identity,
// but uses other credentials for network connection.
// NOTE: Runas.exe generates LogonType 9 as well, so keep that in mind.
| extend LogonType = tostring(split(Properties, "Restricted Admin Mode")[0])
| extend LogonType = tostring(split(LogonType, ":")[7])
| where LogonType has "9"
// Looks to see which user tried to perform PtH behavior
| extend Actor = tostring(split(Properties, ":")[14])
| extend Actor = tostring(split(Actor, "Account Domain")[0])
// Discovers which account got affected during the potential PtH attack
| extend AffectedUser = tostring(split(Properties, ":")[18])
| extend AffectedUser = tostring(split(AffectedUser, "Network Account Domain")[0])
// Find the LogonID that belongs to this session
| extend LogonID = tostring(split(Properties, ":")[16])
| extend LogonID = tostring(split(LogonID, "Linked Logon ID")[0])
| join (Security
    | extend ParsedFields = parse_csv(data)
    | extend Properties = ParsedFields[8]
    | project-away data, ParsedFields
    | where Properties has "0x7A82772"
    // The actor that potentially performed PtH
| extend Actor = tostring(split(Properties, ":")[3])
| extend Actor = tostring(split(Actor, "Account Domain")[0])
// Discovers which account got affected during the potential PtH attack
| extend AffectedUser = tostring(split(Properties, ":")[8])
| extend AffectedUser = tostring(split(AffectedUser, "Account Domain")[0])
// Target server a connection was made to
| extend TargetServer = tostring(split(Properties, ":")[12])
| extend TargetServer = tostring(split(TargetServer, "Additional Information")[0])
// LogonID that has the same value as 4624
| extend LogonID = tostring(split(Properties, ":")[5])
| extend LogonID = tostring(split(LogonID, "Logon GUID")[0])
) on LogonID
| project Date, Time, EventID, Computer, Task, LogonID, Actor, AffectedUser, TargetServer, CorrelationId = LogonID1

Result

At the sample result, we have correlated both event IDs with each other. We can see that the LogonID have the same value, so in this example. An attacker compromised the account ”Edwards” and managed to grab the NT hash of ”Colby” to access the NYK-DC01 server.

Remote Desktop Protocol (RDP) – Lateral Movement

Attackers may use RDP to move laterally across other systems. When doing forensics to discover if an RDP session was initiated on the affected system. We can start with looking at Microsoft-Windows-TerminalServices-ClientActiveXCore.

Query

RDP
| where isnotempty( ['Computer Name'])
| where ['Event Code'] == 1102
| project Date, ['Computer Name'], Source, Username, Description

Result

At the sample result, we can see that a RDP session was setup to connect to 10.0.0.4

Later in this blog post, we will cover a more in-depth example on how you can track (potential) lateral movement attempts based on all the Kerberos service tickets a user has requested.

BITS Jobs

An adversary can use BITS Jobs to download and install payloads on a compromised system. Great thing is that all the BITS Jobs activities are by default logged on a Windows.

  1. We can start with looking at Microsoft-Windows-Bits-Client and filter on Event ID 59. This Event ID is generated when something has been downloaded from the web through BITS Jobs.

Query

BITS
| where isnotempty( ['Computer Name'])
| where ['Event Code'] == 59
| extend URL = tostring(split(Description, "the")[2])
| project Date, ['Computer Name'], ['Event Code'], URL

Result

At the sample result, we can see legitimate domains as well. The reason that we include is to help you be aware of such cases.

The following domains should be excluded from your query, because these are legit:

Domain
microsoft.com
live.com
sfx.ms
gvt1.com
googleapis.com

Query

In this query, we have excluded all the mentioned domains.

BITS
| where isnotempty( ['Computer Name'])
| where ['Event Code'] == 59
| extend URL = tostring(split(Description, "the")[2])
| where URL !has "microsoft.com"
| where URL !has "live.com"
| where URL !has "sfx.ms"
| where URL !has "gvt1.com"
| where URL !has "googleapis.com"
| project Date, ['Computer Name'], ['Event Code'], URL, Description

Result

At the sample result, we can see two results. Where someone has downloaded something from Github.

Windows Remote Management – Lateral Movement

Adversaries may use valid Accounts to interact with remote systems using Windows Remote Management (WinRM). When doing forensics to determine whether an adversary did use Windows Remote Management to move laterally. We can start looking at Microsoft-Windows-Windows Remote Management

  1. We can query event ID 6 in Microsoft-Windows-Windows Remote Management. This event ID is generated once a Windows Remote Management session was established with a remote host.

Query

WinRM
| extend ParsedFields = parse_csv(data)
| extend Date = ParsedFields[0]
| extend Time = ParsedFields[1]
| extend Type = ParsedFields[2]
| extend Computer = ParsedFields[3]
| extend EventID = ParsedFields[4]
| extend Source = ParsedFields[5]
| extend Task = ParsedFields[6]
| extend Properties = ParsedFields[8]
| project-away data, ParsedFields
| where EventID == 6
| extend TargetHost = tostring(split(Properties, ":")[1])
| project Date, Time, Computer, Source, Properties, TargetHost

Result

At the sample result, we can see that one WinRM session was established.

Suspicious PowerShell activities

Adversaries can use PowerShell to perform a number of actions, including discovery of information and execution of code. In this example, we are going to look for some typical behaviors that adversaries have been using in the past, and still do of course.

All the PowerShell activities can be found both in Windows PowerShell & Microsoft-Windows-PowerShell/Operational. However, when looking at it from a forensics perspective. I’ll prefer to look at the Windows PowerShell logs.

  1. The first example is looking at any suspicious PowerShell downloads.

Query

PowerShell
| extend ParsedFields = parse_csv(data)
| extend Date = ParsedFields[0]
| extend Time = ParsedFields[1]
| extend Type = ParsedFields[2]
| extend Computer = ParsedFields[3]
| extend EventID = ParsedFields[4]
| extend Source = ParsedFields[5]
| extend Task = ParsedFields[6]
| extend Properties = ParsedFields[8]
| project-away data, ParsedFields
| extend ProcessCommandLine = tostring(split(Properties, "=")[7])
| extend ProcessCommandLine = tostring(split(ProcessCommandLine, "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\")[1])
| extend ProcessCommandLine = tostring(split(ProcessCommandLine, "EngineVersion")[0])
| where ProcessCommandLine has "Net.WebClient"
   or ProcessCommandLine has "DownloadFile"
   or ProcessCommandLine has "Invoke-WebRequest"
   or ProcessCommandLine has "Invoke-Shellcode"
   or ProcessCommandLine has "http"
   or ProcessCommandLine has "IEX"
   or ProcessCommandLine has "Start-BitsTransfer"
| project Date, Time, Computer, Source, ProcessCommandLine

Result

At the sample result, we can see a couple of PowerShell downloads.

2. Encoded PowerShell commands that were ran on a system

Query

PowerShell
| extend ParsedFields = parse_csv(data)
| extend Date = ParsedFields[0]
| extend Time = ParsedFields[1]
| extend Type = ParsedFields[2]
| extend Computer = ParsedFields[3]
| extend EventID = ParsedFields[4]
| extend Source = ParsedFields[5]
| extend Task = ParsedFields[6]
| extend Properties = ParsedFields[8]
| project-away data, ParsedFields
| extend ProcessCommandLine = tostring(split(Properties, "=")[7])
| extend ProcessCommandLine = tostring(split(ProcessCommandLine, "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\")[1])
| extend ProcessCommandLine = tostring(split(ProcessCommandLine, "EngineVersion")[0])
| where ProcessCommandLine matches regex @'(\s+-((?i)encod?e?d?c?o?m?m?a?n?d?|e|en|enc|ec)\s).*([A-Za-z0-9+/]{50,}[=]{0,2})'
| project Date, Time, Computer, Source, ProcessCommandLine

Result

At the sample result, we can see that an encoded PowerShell command was ran on a system.

Windows Management Instrumentation (WMI) – Persistence

Adversaries can use the ActiveScriptEventConsumer and CommandLineEventConsumer classes when responding to their events. Both event consumers offer a tremendous amount of flexibility for an adversary to execute any payload they want without needing to drop a single malicious executable to disk.

Great thing with Windows 10 & Windows Server 2016 and higher is, that we can capture WMI persistence pretty easily by looking at event ID 5861 in Microsoft-Windows-WMI-Activity/Operational.

Query

WMI
| extend ParsedFields = parse_csv(data)
| extend Date = ParsedFields[0]
| extend Time = ParsedFields[1]
| extend Type = ParsedFields[2]
| extend Computer = ParsedFields[3]
| extend EventID = ParsedFields[4]
| extend Source = ParsedFields[5]
| extend Task = ParsedFields[6]
| extend Properties = ParsedFields[8]
| project-away data, ParsedFields
| where EventID == 5861
| extend EventConsumer = tostring(split(Properties, ";")[9])
| extend EventConsumer = tostring(split(EventConsumer, "=")[1])
| project Date, Time, Computer, EventID, Source, EventConsumer, Properties

Result

At the sample result, we can see one result.

Hunting in Kerberos Service Ticket Requests

One of the common question, you may have been asked during an investigation is, that once an account got breached. What systems (may) have been touched by an adversary? This is a very important question, because adversaries have their objectives as well, so in order to achieve that. It is very likely, that they need to move laterally across systems to reach their goal, which is exfiltrating data.

Since I don’t want to talk about Kerberos and it’s so complex. I will explain it super short, so you will have a high-level understanding, when we are diving into the hunting aspects. This will make more sense after you have read the theory.

Once a user has received a TGT from the KDC. It is able to request Service Tickets to access a specific resource on the network. First the user needs to present a TGT to the KDC to obtain a Service Ticket (ST) for the resource it wants to access.

Service Tickets are encrypted with the NT(LM) hash of the associated service account. The NT(LM) hash is both known to the Domain Controller and the service, so the service is able decrypt the service ticket, validates the PAC, and determine what access should be granted to the service. This can be both a user account that is configured as a service account or just a regular computer account itself.

NOTE: This is not a silver bullet method, but it might give you some additional context.

  1. In this scenario, we have an account called ”Edwards” that got breached. What kind of systems may have been touched during the breach?

We will start with a simple query to look for event ID 4769 on the Domain Controller.

Query

Kerberos
| extend ParsedFields = parse_csv(data)
| extend Date = ParsedFields[0]
| extend Time = ParsedFields[1]
| extend Type = ParsedFields[2]
| extend Computer = ParsedFields[3]
| extend EventID = ParsedFields[4]
| extend Source = ParsedFields[5]
| extend Task = ParsedFields[6]
| extend Properties = ParsedFields[8]
| project-away data, ParsedFields
| where EventID == 4769 // EventID that indicates a ST was requested

Result

At the sample result, we can see multiple 4769 without any context.

2. Now we are going to parse all the interesting fields into different columns.

Query

Kerberos
| extend ParsedFields = parse_csv(data)
| extend Date = ParsedFields[0]
| extend Time = ParsedFields[1]
| extend Type = ParsedFields[2]
| extend Computer = ParsedFields[3]
| extend EventID = ParsedFields[4]
| extend Source = ParsedFields[5]
| extend Task = ParsedFields[6]
| extend Properties = ParsedFields[8]
| project-away data, ParsedFields
| where EventID == 4769 // EventID that indicates a ST was requested
// Parsing the AccountName property to see who requested a ST
| extend AccountName = tostring(split(Properties, ":")[2]) 
| extend AccountName = tostring(split(AccountName, "Account Domain")[0])
// Finding all the ST request the account 'Edwards' has made
| where AccountName has 'Edwards'
// Finding all the ST that were requested for a specific resource
| extend Service = tostring(split(Properties, ":")[6]) 
| extend Service = tostring(split(Service, "Service ID")[0]) 
// Excluding all the Domain Controllers and the KRBTGT principal from the results
| where Service !has 'NYK-DC01$'
| where Service !has 'krbtgt'
// Summarizing the ST requests the targeted account has made to a resource
| summarize OperationCount = count() by AccountName, Service, tostring(Date)
| sort by Date desc

Result

At the sample result, we can see that a Service Ticket was requested for the NYK-SQL & NYK-Server2012 service. This becomes interesting, because an adversary may have accessed a network share on one of these servers or perhaps it has initiated an RDP session. Who knows? The only thing we do know is, that the adversary attempted to interact somehow with the mentioned servers.

What we now perhaps could do is see if Edwards had admin rights on one of these servers for example. If it did had admin rights on it, we could use it as a pivot point that and start thinking if these servers may have been affected as well during the breach. This requires validation to be sure, because perhaps a ST was requested, but the user didn’t access to it. This will generate an 4679 as well, so keep that in mind.

What if I want to play with Azure Data Explorer, but I don’t have an ADX cluster?

Good news is that Microsoft provides a free ADX cluster with sample data. It is meant for people who want to learn more about KQL, etc.

First, we have to visit the following link: Azure Data Explorer

Second thing, we have to do is load some sample data. I have uploaded all the sample data that I’ve used during this blog post, to my GitHub repository, which can be found here. In order to load data into ADX, we can use the externaldata() operator.

Query

let PowerShell = (externaldata(payload_url: string) [@"https://raw.githubusercontent.com/DebugPrivilege/Sample-data/main/NYK-CLIENT_evt_WindowsPowerShell.csv"]
with (format="txt"))
| project payload_url;
PowerShell
| extend ParsedFields = parse_csv(payload_url)
| extend Date = ParsedFields[0]
| extend Time = ParsedFields[1]
| extend Type = ParsedFields[2]
| extend Computer = ParsedFields[3]
| extend EventID = ParsedFields[4]
| extend Source = ParsedFields[5]
| extend Task = ParsedFields[6]
| extend Properties = ParsedFields[8]
| project-away payload_url, ParsedFields
| extend ProcessCommandLine = tostring(split(Properties, "=")[7])
| extend ProcessCommandLine = tostring(split(ProcessCommandLine, "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\")[1])
| extend ProcessCommandLine = tostring(split(ProcessCommandLine, "EngineVersion")[0])
| where ProcessCommandLine has "Net.WebClient"
   or ProcessCommandLine has "DownloadFile"
   or ProcessCommandLine has "Invoke-WebRequest"
   or ProcessCommandLine has "Invoke-Shellcode"
   or ProcessCommandLine has "http"
   or ProcessCommandLine has "IEX"
   or ProcessCommandLine has "Start-BitsTransfer"
| project Date, Time, Computer, Source, ProcessCommandLine

Result

At the sample result, we can see that all the data has been loaded into Azure Data Explorer.

Summary

We have ran a PowerShell script that exports various of data on a system. All of the exported data has a CSV format, which we later on. Ingested it in Azure Data Explorer to analyze it with KQL.

This blog post contains examples of well-known adversarial techniques that have been documented, and the goal of this blog post was to show examples on, how we could hunt in various of datasets to look for TTPs.

I do agree that the KQL queries may look complex, but it’s actually not. Once you get use to the schema of how the events are stored. It is relative easy to query it and parse all the different fields into columns.

Reference

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s