SEC504: Hacker Tools, Techniques, and Incident Handling

Experience SANS training through course previews.
Learn MoreLet us help.
Contact usConnect, learn, and share with other cybersecurity professionals
Engage, challenge, and network with fellow CISOs in this exclusive community of security leaders
Become a member for instant access to our free resources.
Sign UpMission-focused cybersecurity training for government, defense, and education
Explore industry-specific programming and customized training solutions
Sponsor a SANS event or research paper
We're here to help.
Contact UsIn part 3 of Working with the Event Log we look at using a third-party function to make accessing event log data much easier.
In part 1, we looked at PowerShell get winevent to work with the event log: [code]Get-WinEvent[/code]. In part 2 we looked at 10 practical examples of using [code]Get-WinEvent[/code] to perform threat hunting using event log data, using [code]-FilterHashTable[/code], the PowerShell pipeline, and [code]-FilterXPath[/code].
In this article we'll look at using a third-party script to make the data in the [code]Get-WinEvent[/code][code]Message[/code] property much more easily accessible.
Paul Masek (SANS Advisor Board member and threat hunting fan) reached out after my part 1 article to share a third-party script that makes working with the [code]Get-WinEvent[/code][code]Message[/code] property data much easier: Convert-EventLogRecords.
[code]Get-WinEvent[/code] is a powerful cmdlet, and once you get the hang of building filter hash tables (see part 2 of this series) you can quickly interrogate event logs for a lot of data. However, the data in the event log [code]Message[/code] property is not nearly as easily accessible.
For example, let's look at event ID 4624 in the Security event log, An account was successfully logged on:
PS C:\Windows\System32> Get-WinEvent -FilterHashtable @{ LogName="Security"; ID=4624 } -MaxEvents 1
ProviderName: Microsoft-Windows-Security-Auditing
TimeCreated Id LevelDisplayName Message
----------- -- ---------------- -------
7/14/2022 10:58:00 AM 4624 Information An account was successfully logged ...
PS C:\Windows\System32> Get-WinEvent -FilterHashtable @{ LogName="Security"; ID=4624 } -MaxEvents 1 | Select-Object -Property Message | Format-List
Message : An account was successfully logged on.
Subject:
Security ID: S-1-5-18
Account Name: SEC504STUDENT$
Account Domain: SEC504
Logon ID: 0x3E7
Logon Information:
Logon Type: 5
Restricted Admin Mode: -
Virtual Account: No
Elevated Token: Yes
Impersonation Level: Impersonation
New Logon:
Security ID: S-1-5-18
Account Name: SYSTEM
Account Domain: NT AUTHORITY
Logon ID: 0x3E7
Linked Logon ID: 0x0
Network Account Name: -
Network Account Domain: -
Logon GUID: {00000000-0000-0000-0000-000000000000}
Process Information:
Process ID: 0x290
Process Name: C:\Windows\System32\services.exe
Network Information:
Workstation Name: -
Source Network Address: -
Source Port: -
Detailed Authentication Information:
Logon Process: Advapi
Authentication Package: Negotiate
Transited Services: -
Package Name (NTLM only): -
Key Length: 0
...
When we access the [code]Message[/code] property in the pipeline, it appears as if it were unstructured data: just a big string blob with indentations and newlines. However, this data is actually an XML structure with lots of elements that are individually accessible, just not as [code]Get-WinEvent[/code] properties.
Let's dig into the [code]Message[/code] property for the event ID 4624 event, declaring a variable [code]$logonEvent[/code]:
PS C:\Windows\System32> $logonEvent = Get-WinEvent -FilterHashtable @{ LogName="Security"; ID=4624 } -MaxEvents 1
PS C:\Windows\System32>
Next, let's convert the [code]$logonEvent[/code] variable into XML format:
PS C:\Windows\System32> $xml = $logonEvent.ToXml()
PS C:\Windows\System32>
Here we declare a new variable for ease-of-access called [code]$xml[/code]. The value stored in [code]$xml[/code] is the result of calling the [code]ToXml()[/code] method on the [code]$logonEvent[/code] variable for the 4624 event ID.
With the [code]$xml[/code] variable declared, we can start to interrogate the data and look at the available properties. You can start just by entering the variable name:
PS C:\Windows\System32> $xml
Event
-----
Event
This output indicates there is a member object called [code]Event[/code]. We can access this object using dot notation:
PS C:\Windows\System32> $xml.Event
xmlns System EventData
----- ------ ---------
http://schemas.microsoft.com/win/2004/08/events/event System EventData
This reveals that [code]$xml.Event[/code] has three member objects: [code]xmlns[/code], [code]System[/code], and [code]EventData[/code]. We can continue to interrogate the data by using dot notation to access the [code]EventData[/code] object:
PS C:\Windows\System32> $xml.Event.EventData
Data
----
{SubjectUserSid, SubjectUserName, SubjectDomainName, SubjectLogonId...}
Which reveals a [code]Data[/code] object:
PS C:\Windows\System32> $xml.Event.EventData.Data
Name #text
---- -----
SubjectUserSid S-1-5-18
SubjectUserName SEC504STUDENT$
SubjectDomainName SEC504
SubjectLogonId 0x3e7
TargetUserSid S-1-5-18
TargetUserName SYSTEM
TargetDomainName NT AUTHORITY
TargetLogonId 0x3e7
LogonType 5
LogonProcessName Advapi
AuthenticationPackageName Negotiate
WorkstationName -
LogonGuid {00000000-0000-0000-0000-000000000000}
TransmittedServices -
LmPackageName -
KeyLength 0
ProcessId 0x290
ProcessName C:\Windows\System32\services.exe
IpAddress -
IpPort -
ImpersonationLevel %%1833
RestrictedAdminMode -
TargetOutboundUserName -
TargetOutboundDomainName -
VirtualAccount %%1843
TargetLinkedLogonId 0x0
ElevatedToken %%1842
Accessing [code]$xml.Event.EventData.Data[/code] displays lots of new parameters accessible by name instead of just one large string. This is the data that can be accessed using XmlPath notation (e.g., [code]*[EventData[Data[@Name='ProcessName'][/code]). But, as Paul pointed out to me, there is an easier way.
[code]Convert-EventLogRecord[/code] is a PowerShell function written by @JeffHicks, available as part of his PSScriptTools project. Using [code]Convert-EventLogRecord[/code] allows us to easily use [code]Get-WinEvent[/code], taking the individual XML data elements in the [code]Message[/code] property and make them individually accessible on the pipeline.
To use this function, first make a location where you can store PowerShell scripts. I'll make a directory called [code]C:\Tools[/code]. Then, change to the directory and download the [code]Convert-EventLogRecord.ps1[/code] script, as shown here:
PS C:\Windows\System32> New-Item -ItemType Directory -Path C:\Tools -ErrorAction SilentlyContinue
PS C:\Windows\System32> Set-Location C:\Tools
PS C:\Tools> Invoke-WebRequest -Uri https://raw.githubusercontent.com/jdhitsolutions/PSScriptTools/master/functions/Convert-EventLogRecord.ps1 -OutFile Convert-EventLogRecord.ps1
PS C:\Tools>
Next, import the script for use. To use the script on Windows 10, you'll need to change the PowerShell [code]ExecutionPolicy[/code]. On Twitter advice, change the policy to [code]RemoteSigned[/code] to block browser-downloaded PowerShell scripts, but not local scripts like those downloaded with [code]Invoke-WebRequest[/code] (again, I think PowerShell execution policies are silly; more on that another time):
PS C:\Tools> Set-ExecutionPolicy RemoteSigned -Force
PS C:\Tools>
Next, import the [code]Convert-EventLogRecord.ps1[/code] into the PowerShell session namespace using [code]Import-Module[/code]:
PS C:\Tools> Import-Module .\Convert-EventLogRecord.ps1
PS C:\Tools>
Perfect! Now you have access to [code]Convert-EventLogRecord[/code] as a function in PowerShell. Let's take a look at the properties available for the 4624 event with and without it. First, here are the properties available using [code]Get-WinEvent[/code] with a filter for event ID 4624:
PS C:\Tools> Get-WinEvent -FilterHashtable @{ LogName="Security"; ID=4624 } -MaxEvents 1 | Get-Member -MemberType Property
TypeName: System.Diagnostics.Eventing.Reader.EventLogRecord
Name MemberType Definition
---- ---------- ----------
ActivityId Property System.Nullable[guid] ActivityId {get;}
Bookmark Property System.Diagnostics.Eventing.Reader.EventBookmark Bookmark {get;}
ContainerLog Property string ContainerLog {get;}
Id Property int Id {get;}
Keywords Property System.Nullable[long] Keywords {get;}
KeywordsDisplayNames Property System.Collections.Generic.IEnumerable[string] KeywordsDisplayNames {get;}
Level Property System.Nullable[byte] Level {get;}
LevelDisplayName Property string LevelDisplayName {get;}
LogName Property string LogName {get;}
MachineName Property string MachineName {get;}
MatchedQueryIds Property System.Collections.Generic.IEnumerable[int] MatchedQueryIds {get;}
Opcode Property System.Nullable[int16] Opcode {get;}
OpcodeDisplayName Property string OpcodeDisplayName {get;}
ProcessId Property System.Nullable[int] ProcessId {get;}
Properties Property System.Collections.Generic.IList[System.Diagnostics.Eventing.Reader.EventProperty] Prope...
ProviderId Property System.Nullable[guid] ProviderId {get;}
ProviderName Property string ProviderName {get;}
Qualifiers Property System.Nullable[int] Qualifiers {get;}
RecordId Property System.Nullable[long] RecordId {get;}
RelatedActivityId Property System.Nullable[guid] RelatedActivityId {get;}
Task Property System.Nullable[int] Task {get;}
TaskDisplayName Property string TaskDisplayName {get;}
ThreadId Property System.Nullable[int] ThreadId {get;}
TimeCreated Property System.Nullable[datetime] TimeCreated {get;}
UserId Property System.Security.Principal.SecurityIdentifier UserId {get;}
Version Property System.Nullable[byte] Version {get;}
Here is the same event, this time using [code]Convert-EventLogRecord[/code]:
PS C:\Tools> Get-WinEvent -FilterHashtable @{ LogName="Security"; ID=4624 } -MaxEvents 1 | Convert-EventLogRecord |Get-Member
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
AuthenticationPackageName NoteProperty string AuthenticationPackageName=Negotiate
Computername NoteProperty string Computername=Sec504Student
ElevatedToken NoteProperty string ElevatedToken=%%1842
ID NoteProperty int ID=4624
ImpersonationLevel NoteProperty string ImpersonationLevel=%%1833
IpAddress NoteProperty string IpAddress=-
IpPort NoteProperty string IpPort=-
KeyLength NoteProperty string KeyLength=0
Keywords NoteProperty ReadOnlyCollection[string] Keywords=System.Collections.ObjectModel.ReadOnlyCollec...
LmPackageName NoteProperty string LmPackageName=-
LogName NoteProperty string LogName=Security
LogonGuid NoteProperty string LogonGuid={00000000-0000-0000-0000-000000000000}
LogonProcessName NoteProperty string LogonProcessName=Advapi
LogonType NoteProperty string LogonType=5
Message NoteProperty string Message=An account was successfully logged on....
ProcessId NoteProperty string ProcessId=0x290
ProcessName NoteProperty string ProcessName=C:\Windows\System32\services.exe
RecordType NoteProperty string RecordType=Information
RestrictedAdminMode NoteProperty string RestrictedAdminMode=-
Source NoteProperty string Source=Microsoft-Windows-Security-Auditing
SubjectDomainName NoteProperty string SubjectDomainName=SEC504
SubjectLogonId NoteProperty string SubjectLogonId=0x3e7
SubjectUserName NoteProperty string SubjectUserName=SEC504STUDENT$
SubjectUserSid NoteProperty string SubjectUserSid=S-1-5-18
TargetDomainName NoteProperty string TargetDomainName=NT AUTHORITY
TargetLinkedLogonId NoteProperty string TargetLinkedLogonId=0x0
TargetLogonId NoteProperty string TargetLogonId=0x3e7
TargetOutboundDomainName NoteProperty string TargetOutboundDomainName=-
TargetOutboundUserName NoteProperty string TargetOutboundUserName=-
TargetUserName NoteProperty string TargetUserName=SYSTEM
TargetUserSid NoteProperty string TargetUserSid=S-1-5-18
TimeCreated NoteProperty datetime TimeCreated=7/14/2022 3:31:27 PM
TransmittedServices NoteProperty string TransmittedServices=-
VirtualAccount NoteProperty string VirtualAccount=%%1843
WorkstationName NoteProperty string WorkstationName=-
When we add [code]Convert-EventLogRecord[/code] to the pipeline, it takes the elements in the [code]Message[/code] parameter and makes them accessible as properties. This allows us to retrieve specific information elements such as [code]TargetUserName[/code]:
PS C:\Tools> Get-WinEvent -FilterHashtable @{ LogName="Security"; ID=4624 } -MaxEvents 1 | Convert-EventLogRecord | Select-Object -Property TargetUserName
TargetUserName
--------------
SYSTEM
Since these added [code]Message[/code] data elements are properties, we can reference them in the pipeline using [code]Where-Object[/code] too:
PS C:\Tools> Get-WinEvent -FilterHashtable @{ LogName="Security"; ID=4624 } | Convert-EventLogRecord | Where-Object -Property TargetUserName -NE 'SYSTEM' | Select-Object TargetUsername, TimeCreated, LogonType
TargetUserName TimeCreated LogonType
-------------- ----------- ---------
assetmgr 7/13/2022 11:10:23 AM 3
assetmgr 7/13/2022 11:09:42 AM 3
Sec504 7/13/2022 12:52:54 AM 3
Sec504 7/13/2022 12:12:18 AM 2
Sec504 7/13/2022 12:12:18 AM 2
ANONYMOUS L... 7/13/2022 12:11:47 AM 3
LOCAL SERVICE 7/13/2022 12:11:43 AM 5
DWM-1 7/13/2022 12:11:43 AM 2
DWM-1 7/13/2022 12:11:43 AM 2
NETWORK SER... 7/13/2022 12:11:43 AM 5
UMFD-1 7/13/2022 12:11:43 AM 2
UMFD-0 7/13/2022 12:11:43 AM 2
Sec504 7/13/2022 12:10:19 AM 2
Sec504 7/13/2022 12:10:19 AM 2
...
Let's break down this command step-by-step:
[code]Convert-EventLogRecord[/code] makes accessing the data in the [code]Message[/code] property a lot easier, but it can be detrimental to performance if used with lots of event data. Each event returned in the pipeline has the XML data extracted and added as custom properties to the object. This isn't a lot of overhead, but if you send all events to [code]Convert-EventLogRecord[/code] for filtering then you may be in for some waiting.
To avoid unnecessary performance detriment, remember this rule:
For example, let's say you want to see the Security event logs with event ID 4799 (A security-enabled local group membership was enumerated.) where the process name enumerating the group is not [code]svchost.exe[/code]. You could use [code]Convert-EventLogRecord[/code] to query both the event ID and the process name in the pipeline:
PS C:\Tools> Measure-Command { Get-WinEvent -LogName 'Security' | Convert-EventLogRecord | Where-Object -Property Id -EQ 4799 | Where-Object -Property CallerProcessName -NotLike '*esentutl*' | Select-Object -Property TimeCreated, SubjectAccountName, CallerProcessName }
Days : 0
Hours : 0
Minutes : 7
Seconds : 56
Milliseconds : 708
Ticks : 4767084570
TotalDays : 0.00551745899305556
TotalHours : 0.132419015833333
TotalMinutes : 7.94514095
TotalSeconds : 476.708457
TotalMilliseconds : 476708.457
This took NNN seconds. I took a nap while it was running and came back. This example violates the rule: filter hash table first. We can rewrite this query using a filter hash table for the event ID, then use [code]Convert-EventLogRecord[/code] and [code]Where-Object[/code] for the properties we can't access in the filter hash table:
PS C:\Tools> Measure-Command { Get-WinEvent -FilterHashtable @{LogName='Security'; ID=4799} | Convert-EventLogRecord | Where-Object -Property CallerProcessName -NotLike '*esentutl*' | Select-Object -Property TimeCreated, SubjectAccountName, CallerProcessName }
Days : 0
Hours : 0
Minutes : 0
Seconds : 18
Milliseconds : 373
Ticks : 183738383
TotalDays : 0.000212660165509259
TotalHours : 0.00510384397222222
TotalMinutes : 0.306230638333333
TotalSeconds : 18.3738383
TotalMilliseconds : 18373.8383
Only 18 seconds on my system, a huge improvement. Remember:
In this article we looked we looked at some of the challenges in accessing the data elements embedded within the event log Message property, and how the [code]Convert-EVentLogRecord[/code] function makes accessing that data much easier, giving you a lot more flexibility in building a pipeline to query the event log.
We have one last article in this short series: optimizing the Windows event log configuration. Stay tuned!
-Joshua Wright
Return to Getting Started With PowerShell
Joshua Wright is the author of SANS SEC504: Hacker Tools, Techniques, and Incident Handling, a faculty fellow for the SANS Institute, and a senior technical director at Counter Hack.
As Senior Technical Director at Counter Hack and SANS Faculty Fellow, Joshua has advanced cybersecurity through ethical penetration testing, uncovering critical vulnerabilities across Fortune 500 companies and national infrastructure providers.
Read more about Joshua Wright