Introduction
Understanding user logon behavior is a powerful tool in red teaming and adversary simulations. By analyzing who logs into a system, when, from where, and how frequently, operators gain deep situational awareness that can inform stealthy movements, impersonation opportunities, and identify high-value targets. This post presents a PowerShell script built to extract and analyze Windows logon (4624) and logoff (4634) events, offering a practical way to track user activity across local and remote systems. Integrated with SpecterInsight, the script enhances operational intelligence by enabling rapid filtering, visualization, and export of logon data for further analysis.
This post walks through a red team–oriented PowerShell script designed to collect and analyze Windows user logon/logoff events. It starts by defining a flexible parameter set for local and remote execution, including options for filtering out noisy system accounts. The script leverages SpecterInsight’s high-performance event log parser to query and normalize Security Event Log data, converting raw fields into actionable insights—such as logon types and originating hosts. A sample analysis illustrates how identifying consistent RDP patterns or cached domain credentials can lead to stealthy lateral movement or credential harvesting. Finally, the post shows how these insights integrate with SpecterInsight’s dashboards for visual trend analysis, making it easier to track user behavior, filter on targets of interest, and drill into individual logon events.
Tools Leveraged:
Per User Logon Statistics
In red teaming and adversary simulations, situational awareness is critical. Knowing who logs in, from where, and how often gives insight into patterns of behavior, prime impersonation targets, and anomalies worth exploiting. This PowerShell script does exactly that: extracts and organizes Windows logon events (4624 – logon, and 4634 – logoff), helping red teamers gain a high-level understanding of user activity on a target system.
Red teams can use this script to:
- Profile privileged or frequently active accounts.
- Identify remote logons (e.g., LogonType 10 = RDP).
- Spot service accounts or interactive users.
- Plan lateral movement by knowing where users log in from (IP, workstation).
For example, if ADMIN01 logs in via RDP (LogonType 10) from a specific workstation every morning, a red teamer could mimic that behavior to stay stealthy.
Script Summary
This PowerShell script collects and summarizes Windows logon (4624
) and logoff (4634
) events from the Security Event Log, either locally or from a remote system. It supports both impersonation and credential-based authentication and allows filtering by event count and excluded usernames (e.g., system accounts like DWM-*
, UMFD-*
, or service accounts ending in $
).
The script:
- Queries logon-related events from the target system using SpecterInsight’s
Get-Events
command. - Extracts key fields such as
TargetUserName
,LogonType
,WorkstationName
, andIpAddress
. - Filters out noise by excluding accounts that match user-defined patterns.
- Aggregates logon activity by creating a summary table showing the count of each
LogonType
per user and IP/workstation combination. - Outputs a clean table, sorted by user and source, that helps red teamers identify interactive sessions, RDP usage, and persistent access patterns.
This enables quick identification of high-value targets, remote access behaviors, and anomalies that can inform lateral movement strategies or post-exploitation activities.
Define Parameters
For this script, there are a bunch of options we want to open up to the operator. We want to be able to support local or remote execution as well as limits and filters for querying event logs. To support remote authentication, we need to provide arguments for Target, Username, and Password… but we only want Username and Password fields to show up when NOT using impersonation. For limits, the Windows API provides options to limit queries based on a max event count. Another issue that I’ve identified is that there are a lot of user logons that you don’t necessarily care about (e.g. DWM-X and UMFD-X user accounts) that just really clutter the output.
The parameter block below defines two parameter sets so that the Username and Password parameters only show up when the operator wants to authenticate with those parameters. The default Target is set to localhost, but can be configured to any IP address or hostname reachable by the current system. The ExcludedSecurityFilter option provides a default list of user accounts that will be filtered out.
param(
[Parameter(ParameterSetName="Impersonate", Mandatory=$False, HelpMessage="The IP address or hostname of the system to query.")]
[Parameter(ParameterSetName="Username and Password", Mandatory=$False, HelpMessage="The IP address or hostname of the system to query.")]
[ValidateNotNullOrEmpty]
[string]$Target = 'localhost',
[Parameter(ParameterSetName="Username and Password", Mandatory=$True, HelpMessage="The local or domain username to authenticate with.")]
[ValidateNotNullOrEmpty]
[string]$Username,
[Parameter(ParameterSetName="Username and Password", Mandatory=$True, HelpMessage="The password for the specified user.")]
[ValidateNotNullOrEmpty]
[string]$Password,
[Parameter(ParameterSetName="Impersonate", Mandatory=$False, HelpMessage="The maximum number of events to retrieve.")]
[Parameter(ParameterSetName="Username and Password", Mandatory=$False, HelpMessage="The maximum number of events to retrieve.")]
[ValidateNotNullOrEmpty]
[int]$Max = [Int32]::MaxValue,
[Parameter(ParameterSetName="Impersonate", Mandatory=$False, HelpMessage="A set of filters to remove users from the output.")]
[Parameter(ParameterSetName="Username and Password", Mandatory=$False, HelpMessage="A set of filters to remove users from the output.")]
[string[]]$ExcludeUserFilter = @('SYSTEM', 'NETWORK SERVICE', 'LOCAL SERVICE', 'UMFD*', 'DWM*', '*$')
)
The parameter block above ultimately gets rendered into the GUI shown below in the Interactive Session window.

Query Events
The next step is to load the high-performance Windows Event Log parser that ships with SpecterInsight and leverage the Get-Events cmdlet to pull events from the Security event log with the properly configured query syntax. The loading of the EventLog module is pretty straight forward. The following line will check to make sure the EventLog module is loaded. If it is not, then the Specter implant will pull it down from the C2 server and reflectively load it.
load EventLog;
Now we need to retrieve events. The Windows Events we are about are about for profiling user logons is 4624 and 4634. T
EventId | Name | Description |
---|---|---|
4624 | Successful Logon | Indicates that an account successfully logged on to a system. – Which users regularly log in. – What logon types are common (interactive, network, RDP). – What times users log in (to blend in or avoid detection). |
4634 | Logoff | Indicates that an account successfully logged on to a system. |
To pull this information with SpecterInsight, the command below can be utilized to query the Security event log with a filter for events where System.EventId is 4624 or 4634.
$events = Get-Events -ComputerName $Target -Username $Username -Password $Password -Logname Security -Query "*[System[(EventID=4624 or EventID=4634)]]" -Max $Max;
Data Normalization
Next, we need to restructure the events so that they can be displayed and output in a more digestible format. Additionally, there are some fields located in different areas of the events that come back from Windows. Lastly, the values for each field are extracted as strings, which means we need to convert some of them. For example, will need to parse the CreationDate value into a DateTime object in order to property sort and filter on that value.
$events = $events | Select @{ Name='CreationDate'; Expression={[DateTime]$_.System.TimeCreated.SystemTime; }},
@{ Name='SubjectUserName'; Expression={$_.EventData.SubjectUserName; }},
@{ Name='SubjectDomainName'; Expression={$_.EventData.SubjectDomainName; }},
@{ Name='TargetDomainName'; Expression={$_.EventData.TargetDomainName; }},
@{ Name='TargetUserName'; Expression={$_.EventData.TargetUserName; }},
@{ Name='LogonType'; Expression={$_.EventData.LogonType; }},
@{ Name='WorkstationName'; Expression={$_.EventData.WorkstationName; }},
@{ Name='IpAddress'; Expression={$_.EventData.IpAddress; }},
@{ Name='ProcessId'; Expression={$_.EventData.ProcessId; }},
@{ Name='ProcessName'; Expression={$_.EventData.ProcessName; }}
Event Analysis
The following lines of code perform summary analysis of the LogonTypes for each user by creating a count of each LogonType. This can be used to see where and how users are logging in.
$table = New-Object hashtable
foreach($event in $events) {
$key = $event.TargetDomainName + $event.TargetUserName + $event.IpAddress;
$lookup = $table[$key];
if($lookup -eq $null) {
$lookup = New-Object psobject -Property @{
SubjectDomainName = $event.SubjectDomainName;
SubjectUserName = $event.SubjectUserName;
WorkstationName = $event.WorkstationName;
IpAddress = $event.IpAddress;
TargetDomainName = $event.TargetDomainName;
TargetUserName = $event.TargetUserName;
'0' = 0;
'1' = 0;
'2' = 0;
'3' = 0;
'4' = 0;
'5' = 0;
'6' = 0;
'7' = 0;
'8' = 0;
'9' = 0;
'10' = 0;
'11' = 0;
'12' = 0;
Key = $key;
}
$table.Add($key, $lookup);
}
$lookup.($event.LogonType) += 1;
}
Sort and Output
The last step is to sort by the domain and username and order the fields. The following line of code does all of those things.
$table.Values | sort Key | select WorkstationName,IpAddress,TargetDomainName,TargetUserName,'0','1','2','3','4','5','6','7','8','9','10','11','12';
.Finally, we run the script which produces the output similar to what’s shown below. The “Workstation” field represents the system from which the logon request originated. This can be the localhost or a remote host. When this script is run against a workstation, any workstation name that doesn’t come from the loopback IpAddress is a remote connection.
WorkstationName IpAddress TargetDomainName TargetUserName 0 1 2 3 4 5 6 7 8 9 10 11 12
--------------- --------- ---------------- -------------- - - - - - - - - - - -- -- --
DESKTOP-835KS5V defaultuser0 0 0 2 0 0 0 0 0 0 0 0 0 0
WIN-F739KPIIAPP - DESKTOP-835KS5V defaultuser0 0 0 2 0 0 0 0 0 0 0 0 0 0
WIN-F739KPIIAPP 127.0.0.1 DESKTOP-835KS5V defaultuser0 0 0 2 0 0 0 0 0 0 0 0 0 0
DESKTOP-835KS5V helpdesk 0 0 6 0 0 0 0 0 0 0 0 0 0
WIN-F739KPIIAPP - DESKTOP-835KS5V helpdesk 0 0 2 0 0 0 0 0 0 0 0 0 0
WIN-F739KPIIAPP 127.0.0.1 DESKTOP-835KS5V helpdesk 0 0 10 0 0 0 0 0 0 0 0 0 0
WKST-001 - LAB Administrator 0 0 0 0 0 0 0 2 0 0 0 0 0
WKST-001 127.0.0.1 LAB administrator 0 0 2 0 0 0 0 0 0 0 0 0 0
DEV 192.168.1.101 LAB Administrator 0 0 0 16 0 0 0 2 0 0 0 0 1
WKST-001 helpdesk 0 0 2 0 0 0 0 0 0 0 0 0 0
WKST-001 127.0.0.1 WKST-001 helpdesk 0 0 6 0 0 0 0 0 0 0 0 0 0
In the output above, you can quickly see that the Administrator is remotely logging in with Type 3, 7, and 12 logons. Well, what does that mean?
Using the table below as a key to decipher the output above, we can surmise that the Administrator remotely connects via RDP to the current workstation, WKST-001 from a system called DEV with IP address 192.168.1.101. They do a bunch of Network logons, which aren’t very helpful to us because we can’t use those user tokens for lateral movement. The type 7 and 12 are more interesting. The Type 7 means that the user was logged in, the RDP session locked, and then the user unlocked. That means this user logs in using RDP and then, sometimes, leaves that session open for a really long time. Logon type 12 means that this Administrator logged in via RDP, but the workstation couldn’t communicate with the domain, so the system used their cached credentials for authentication.
Logon Type | Name | Description |
---|---|---|
1 | Unused | Reserved / legacy; not used in practice in modern Windows systems. Sometimes incorrectly referenced in older docs. |
2 | Interactive | A user logged on directly at the console (keyboard/monitor), such as logging in at the physical machine or unlocking the workstation. |
3 | Network | A logon over the network (e.g., accessing shared folders or connecting over SMB). No interactive desktop is presented. |
4 | Batch | Used by scheduled tasks (Task Scheduler) when a task runs under a user account. |
5 | Service | A service started by the Service Control Manager logs on using a service account. |
6 | Proxy | Indicates a logon by a proxy service. This was used by the Windows Subsystem for Terminal Services or older systems where a process might act on behalf of another user. This type is very rarely seen. |
7 | Unlock | A user unlocked the workstation. This is tied to the original logon session (typically type 2). |
8 | NetworkCleartext | A user logged on over the network using cleartext credentials (e.g., Basic authentication in IIS). |
9 | NewCredentials | Logon with new credentials, often using RunAs /netonly. Allows using alternate credentials for outbound connections. |
10 | RemoteInteractive | A user logged on via Remote Desktop (RDP) or Terminal Services. |
11 | CachedInteractive | A user logged on with cached domain credentials, typically when the machine is offline or cannot reach a domain controller. |
12 | RemoteCachedInteractive | A remote desktop logon using cached credentials. |
What does that mean for us?
Well, we have a couple of avenues we could go. This Administrator spends a lot of time on this box doing… something. What are they doing? Why is this box important? Is there a local service that is important to the business? Is critical information stored on this system? Is this a hop point to administer the network?
At a minimum, the domain administrator’s credential’s are cached, so we could dump those potentially if we can get SYSTEM.
It might also be worth looking in the event logs for plaintext creds this Administrator might have used when administering systems. When that user logs in, we might also be able to process inject into explorer and run a keylogger to try and capture credentials. We might even be able to spoof a credential popup to trick the use into providing plaintext credentials.
Full Script
Here is the full script for this technique.
param(
[Parameter(ParameterSetName="Impersonate", Mandatory=$False, HelpMessage="The IP address or hostname of the system to query.")]
[Parameter(ParameterSetName="Username and Password", Mandatory=$False, HelpMessage="The IP address or hostname of the system to query.")]
[ValidateNotNullOrEmpty]
[string]$Target = 'localhost',
[Parameter(ParameterSetName="Username and Password", Mandatory=$True, HelpMessage="The local or domain username to authenticate with.")]
[ValidateNotNullOrEmpty]
[string]$Username,
[Parameter(ParameterSetName="Username and Password", Mandatory=$True, HelpMessage="The password for the specified user.")]
[ValidateNotNullOrEmpty]
[string]$Password,
[Parameter(ParameterSetName="Impersonate", Mandatory=$False, HelpMessage="The maximum number of events to retrieve.")]
[Parameter(ParameterSetName="Username and Password", Mandatory=$False, HelpMessage="The maximum number of events to retrieve.")]
[ValidateNotNullOrEmpty]
[int]$Max = [Int32]::MaxValue,
[Parameter(ParameterSetName="Impersonate", Mandatory=$False, HelpMessage="A set of filters to remove users from the output.")]
[Parameter(ParameterSetName="Username and Password", Mandatory=$False, HelpMessage="A set of filters to remove users from the output.")]
[string[]]$ExcludeUserFilter = @('SYSTEM', 'NETWORK SERVICE', 'LOCAL SERVICE', 'UMFD*', 'DWM*', '*$')
)
load EventLog;
$events = Get-Events -ComputerName $Target -Username $Username -Password $Password -Logname Security -Query "*[System[(EventID=4624 or EventID=4634)]]" -Max $Max;
$events = $events | Select @{ Name='CreationDate'; Expression={$_.System.TimeCreated.SystemTime; }},
@{ Name='SubjectUserName'; Expression={$_.EventData.SubjectUserName; }},
@{ Name='SubjectDomainName'; Expression={$_.EventData.SubjectDomainName; }},
@{ Name='TargetDomainName'; Expression={$_.EventData.TargetDomainName; }},
@{ Name='TargetUserName'; Expression={$_.EventData.TargetUserName; }},
@{ Name='LogonType'; Expression={$_.EventData.LogonType; }},
@{ Name='WorkstationName'; Expression={$_.EventData.WorkstationName; }},
@{ Name='IpAddress'; Expression={$_.EventData.IpAddress; }},
@{ Name='ProcessId'; Expression={$_.EventData.ProcessId; }},
@{ Name='ProcessName'; Expression={$_.EventData.ProcessName; }}
# Filter out excluded usernames
$events = $events | Where {
foreach($filter in $ExcludeUserFilter) {
if($_.TargetUserName -ilike $filter) {
return $false;
}
}
return $true;
}
$table = New-Object hashtable
foreach($event in $events) {
$key = $event.TargetDomainName + $event.TargetUserName + $event.IpAddress;
$lookup = $table[$key];
if($lookup -eq $null) {
$lookup = New-Object psobject -Property @{
SubjectDomainName = $event.SubjectDomainName;
SubjectUserName = $event.SubjectUserName;
WorkstationName = $event.WorkstationName;
IpAddress = $event.IpAddress;
TargetDomainName = $event.TargetDomainName;
TargetUserName = $event.TargetUserName;
'0' = 0;
'1' = 0;
'2' = 0;
'3' = 0;
'4' = 0;
'5' = 0;
'6' = 0;
'7' = 0;
'8' = 0;
'9' = 0;
'10' = 0;
'11' = 0;
'12' = 0;
Key = $key;
}
$table.Add($key, $lookup);
}
$lookup.($event.LogonType) += 1;
}
$table.Values | sort Key | select WorkstationName,IpAddress,TargetDomainName,TargetUserName,'0','1','2','3','4','5','6','7','8','9','10','11','12';
All User Logon Events
If the network isn’t too big, it may actually be easier to collect all logon events from a system. SpecterInsight will automatically normalize and export those to kibana for further analysis. We need a script that will quickly pull the relevant fields and ship those back to the C2 server.
Script Summary
This PowerShell script retrieves and filters Windows logon (4624) and logoff (4634) events from the Security Event Log, either locally or from a remote system. It supports both impersonation and credential-based access, and provides operators with flexible parameters to control the output.
Specifically, the script:
- Authenticates locally or remotely using either the current session (impersonation) or supplied credentials.
- Pulls a maximum number of security events, configurable via the
$Max
parameter. - Filters out noisy accounts such as system, service, or background users using customizable patterns (e.g.,
DWM*
, `*$’, etc.). - Extracts relevant fields like usernames, domains, IPs, logon types, and timestamps.
- Sorts the output chronologically, based on user preference (
NewestFirst
orOldestFirst
). - Returns a clean, time-ordered list of parsed logon/logoff events for further analysis.
This script is ideal for timeline reconstruction and monitoring user activity, especially when trying to track remote access behavior or identify potential lateral movement opportunities.
Define Parameters
We can pretty much use the exact same parameter block from the last script.
param(
[Parameter(ParameterSetName="Impersonate", Mandatory=$False, HelpMessage="The IP address or hostname of the system to query.")]
[Parameter(ParameterSetName="Username and Password", Mandatory=$False, HelpMessage="The IP address or hostname of the system to query.")]
[ValidateNotNullOrEmpty]
[string]$Target = 'localhost',
[Parameter(ParameterSetName="Username and Password", Mandatory=$True, HelpMessage="The local or domain username to authenticate with.")]
[ValidateNotNullOrEmpty]
[string]$Username,
[Parameter(ParameterSetName="Username and Password", Mandatory=$True, HelpMessage="The password for the specified user.")]
[ValidateNotNullOrEmpty]
[string]$Password,
[Parameter(ParameterSetName="Impersonate", Mandatory=$False, HelpMessage="The maximum number of events to retrieve.")]
[Parameter(ParameterSetName="Username and Password", Mandatory=$False, HelpMessage="The maximum number of events to retrieve.")]
[ValidateNotNullOrEmpty]
[int]$Max = [Int32]::MaxValue,
[Parameter(ParameterSetName="Impersonate", Mandatory=$False, HelpMessage="The way in which to order the events.")]
[Parameter(ParameterSetName="Username and Password", Mandatory=$False, HelpMessage="The way in which to order the events.")]
[ValidateSet('NewestFirst', 'OldestFirst')]
[string]$Sort = 'NewestFirst',
[Parameter(ParameterSetName="Impersonate", Mandatory=$False, HelpMessage="A set of filters to remove users from the output.")]
[Parameter(ParameterSetName="Username and Password", Mandatory=$False, HelpMessage="A set of filters to remove users from the output.")]
[string[]]$ExcludeUserFilter = @('SYSTEM', 'NETWORK SERVICE', 'LOCAL SERVICE', 'UMFD*', 'DWM*', '*$')
)
That generates the following UI window when loaded into the Command Builder during an interactive session.

Extracting User Logon Events
We can re-use a lot of the same components from the previous script and just omit the code that summarized the logon information. One key difference is that we actually need to type case the SystemTime to a DateTime object so that the Timestamp will be property converted when it gets shipped back to SpecterInsight, otherwise you may get issues with time zone mismatch.
load EventLog;
$descending = $Sort -eq 'NewestFirst';
$events = Get-Events -ComputerName $Target -Username $Username -Password $Password -Logname Security -Query "*[System[(EventID=4624 or EventID=4634)]]" -Max $Max;
$events = $events | Select @{ Name='CreationDate'; Expression={[DateTime]$_.System.TimeCreated.SystemTime; }},
@{ Name='SubjectUserName'; Expression={$_.EventData.SubjectUserName; }},
@{ Name='SubjectDomainName'; Expression={$_.EventData.SubjectDomainName; }},
@{ Name='TargetDomainName'; Expression={$_.EventData.TargetDomainName; }},
@{ Name='TargetUserName'; Expression={$_.EventData.TargetUserName; }},
@{ Name='LogonType'; Expression={$_.EventData.LogonType; }},
@{ Name='WorkstationName'; Expression={$_.EventData.WorkstationName; }},
@{ Name='IpAddress'; Expression={$_.EventData.IpAddress; }},
@{ Name='ProcessId'; Expression={$_.EventData.ProcessId; }},
@{ Name='ProcessName'; Expression={$_.EventData.ProcessName; }}
$events = $events | Where {
foreach($filter in $ExcludeUserFilter) {
if($_.TargetUserName -ilike $filter) {
return $false;
}
}
return $true;
}
$events | Sort CreationDate -Descending:$descending;
Example Output
Here is an example of the output you might get from this script.
CreationDate SubjectUserName SubjectDomainName TargetDomainName TargetUserName LogonType WorkstationName IpAddress ProcessId ProcessName
------------ --------------- ----------------- ---------------- -------------- --------- --------------- --------- --------- -----------
5/10/2025 9:02:23 PM - - LAB Administrator 3 DEV 192.168.1.101 0x0 -
5/10/2025 8:28:38 PM WKST-001$ LAB LAB Administrator 7 WKST-001 - 0x2a0 C:\Windows\System32\lsass.exe
5/10/2025 8:28:38 PM WKST-001$ LAB LAB Administrator 7 WKST-001 192.168.1.101 0x480 C:\Windows\System32\svchost.exe
5/10/2025 8:28:36 PM - - LAB Administrator 3 DEV 192.168.1.101 0x0 -
5/10/2025 8:28:36 PM - - LAB Administrator 3 DEV 192.168.1.101 0x0 -
1/2/2024 9:58:25 AM WKST-001$ LAB LAB administrator 2 WKST-001 127.0.0.1 0x2ec C:\Windows\System32\svchost.exe
6/13/2022 1:47:10 PM WKST-001$ LAB LAB administrator 2 WKST-001 127.0.0.1 0x1fc C:\Windows\System32\svchost.exe
6/13/2022 1:39:50 PM WKST-001$ LOCALGROUP WKST-001 helpdesk 2 WKST-001 127.0.0.1 0x16c C:\Windows\System32\svchost.exe
6/13/2022 1:39:50 PM WKST-001$ LOCALGROUP WKST-001 helpdesk 2 WKST-001 127.0.0.1 0x16c C:\Windows\System32\svchost.exe
6/13/2022 1:26:30 PM WKST-001 helpdesk 2
6/13/2022 1:26:30 PM WKST-001 helpdesk 2
6/13/2022 1:26:30 PM WKST-001$ LAB WKST-001 helpdesk 2 WKST-001 127.0.0.1 0x174 C:\Windows\System32\svchost.exe
6/13/2022 1:26:30 PM WKST-001$ LAB WKST-001 helpdesk 2 WKST-001 127.0.0.1 0x174 C:\Windows\System32\svchost.exe
6/13/2022 11:02:14 AM WKST-001$ LAB WKST-001 helpdesk 2 WKST-001 127.0.0.1 0x174 C:\Windows\System32\svchost.exe
6/13/2022 11:02:14 AM WKST-001$ LAB WKST-001 helpdesk 2 WKST-001 127.0.0.1 0x174 C:\Windows\System32\svchost.exe
3/11/2022 12:21:59 AM DESKTOP-835KS5V helpdesk 2
3/11/2022 12:21:59 AM DESKTOP-835KS5V helpdesk 2
3/11/2022 12:21:59 AM DESKTOP-835KS5V$ WORKGROUP DESKTOP-835KS5V helpdesk 2 DESKTOP-835KS5V 127.0.0.1 0x1b8 C:\Windows\System32\svchost.exe
3/11/2022 12:21:59 AM DESKTOP-835KS5V$ WORKGROUP DESKTOP-835KS5V helpdesk 2 DESKTOP-835KS5V 127.0.0.1 0x1b8 C:\Windows\System32\svchost.exe
3/11/2022 12:17:13 AM DESKTOP-835KS5V$ WORKGROUP DESKTOP-835KS5V helpdesk 2 DESKTOP-835KS5V 127.0.0.1 0x1b8 C:\Windows\System32\svchost.exe
3/11/2022 12:17:13 AM DESKTOP-835KS5V$ WORKGROUP DESKTOP-835KS5V helpdesk 2 DESKTOP-835KS5V 127.0.0.1 0x1b8 C:\Windows\System32\svchost.exe
3/11/2022 12:11:44 AM DESKTOP-835KS5V helpdesk 2
3/11/2022 12:11:44 AM DESKTOP-835KS5V helpdesk 2
3/11/2022 12:11:44 AM DESKTOP-835KS5V$ WORKGROUP DESKTOP-835KS5V helpdesk 2 DESKTOP-835KS5V 127.0.0.1 0x478 C:\Windows\System32\svchost.exe
3/11/2022 12:11:44 AM DESKTOP-835KS5V$ WORKGROUP DESKTOP-835KS5V helpdesk 2 DESKTOP-835KS5V 127.0.0.1 0x478 C:\Windows\System32\svchost.exe
3/11/2022 12:07:37 AM DESKTOP-835KS5V$ WORKGROUP DESKTOP-835KS5V helpdesk 2 DESKTOP-835KS5V 127.0.0.1 0x478 C:\Windows\System32\svchost.exe
3/11/2022 12:07:37 AM DESKTOP-835KS5V$ WORKGROUP DESKTOP-835KS5V helpdesk 2 DESKTOP-835KS5V 127.0.0.1 0x478 C:\Windows\System32\svchost.exe
3/10/2022 11:25:49 PM WIN-F739KPIIAPP$ WORKGROUP DESKTOP-835KS5V helpdesk 2 WIN-F739KPIIAPP 127.0.0.1 0x3fc C:\Windows\System32\svchost.exe
3/10/2022 11:25:49 PM WIN-F739KPIIAPP$ WORKGROUP DESKTOP-835KS5V helpdesk 2 WIN-F739KPIIAPP 127.0.0.1 0x3fc C:\Windows\System32\svchost.exe
3/10/2022 11:25:48 PM DESKTOP-835KS5V helpdesk 2
3/10/2022 11:25:48 PM DESKTOP-835KS5V helpdesk 2
3/10/2022 11:25:04 PM defaultuser0 DESKTOP-835KS5V DESKTOP-835KS5V helpdesk 2 WIN-F739KPIIAPP - 0x1078 C:\Windows\System32\CloudExperienceHostBroker.exe
3/10/2022 11:25:04 PM defaultuser0 DESKTOP-835KS5V DESKTOP-835KS5V helpdesk 2 WIN-F739KPIIAPP - 0x1078 C:\Windows\System32\CloudExperienceHostBroker.exe
3/10/2022 11:21:52 PM WIN-F739KPIIAPP$ WORKGROUP DESKTOP-835KS5V defaultuser0 2 WIN-F739KPIIAPP 127.0.0.1 0x3fc C:\Windows\System32\svchost.exe
3/10/2022 11:21:52 PM WIN-F739KPIIAPP$ WORKGROUP DESKTOP-835KS5V defaultuser0 2 WIN-F739KPIIAPP 127.0.0.1 0x3fc C:\Windows\System32\svchost.exe
3/10/2022 11:21:43 PM DESKTOP-835KS5V defaultuser0 2
3/10/2022 11:21:43 PM DESKTOP-835KS5V defaultuser0 2
3/10/2022 11:21:38 PM WIN-F739KPIIAPP$ WORKGROUP DESKTOP-835KS5V defaultuser0 2 WIN-F739KPIIAPP - 0x448 C:\Windows\System32\oobe\msoobe.exe
3/10/2022 11:21:38 PM WIN-F739KPIIAPP$ WORKGROUP DESKTOP-835KS5V defaultuser0 2 WIN-F739KPIIAPP - 0x448 C:\Windows\System32\oobe\msoobe.exe
Full Script
Here is the full script.
param(
[Parameter(ParameterSetName="Impersonate", Mandatory=$False, HelpMessage="The IP address or hostname of the system to query.")]
[Parameter(ParameterSetName="Username and Password", Mandatory=$False, HelpMessage="The IP address or hostname of the system to query.")]
[ValidateNotNullOrEmpty]
[string]$Target = 'localhost',
[Parameter(ParameterSetName="Username and Password", Mandatory=$True, HelpMessage="The local or domain username to authenticate with.")]
[ValidateNotNullOrEmpty]
[string]$Username,
[Parameter(ParameterSetName="Username and Password", Mandatory=$True, HelpMessage="The password for the specified user.")]
[ValidateNotNullOrEmpty]
[string]$Password,
[Parameter(ParameterSetName="Impersonate", Mandatory=$False, HelpMessage="The maximum number of events to retrieve.")]
[Parameter(ParameterSetName="Username and Password", Mandatory=$False, HelpMessage="The maximum number of events to retrieve.")]
[ValidateNotNullOrEmpty]
[int]$Max = [Int32]::MaxValue,
[Parameter(ParameterSetName="Impersonate", Mandatory=$False, HelpMessage="The way in which to order the events.")]
[Parameter(ParameterSetName="Username and Password", Mandatory=$False, HelpMessage="The way in which to order the events.")]
[ValidateSet('NewestFirst', 'OldestFirst')]
[string]$Sort = 'NewestFirst',
[Parameter(ParameterSetName="Impersonate", Mandatory=$False, HelpMessage="A set of filters to remove users from the output.")]
[Parameter(ParameterSetName="Username and Password", Mandatory=$False, HelpMessage="A set of filters to remove users from the output.")]
[string[]]$ExcludeUserFilter = @('SYSTEM', 'NETWORK SERVICE', 'LOCAL SERVICE', 'UMFD*', 'DWM*', '*$')
)
load EventLog;
$descending = $Sort -eq 'NewestFirst';
$events = Get-Events -ComputerName $Target -Username $Username -Password $Password -Logname Security -Query "*[System[(EventID=4624 or EventID=4634)]]" -Max $Max;
$events = $events | Select @{ Name='CreationDate'; Expression={[DateTime]$_.System.TimeCreated.SystemTime; }},
@{ Name='SubjectUserName'; Expression={$_.EventData.SubjectUserName; }},
@{ Name='SubjectDomainName'; Expression={$_.EventData.SubjectDomainName; }},
@{ Name='TargetDomainName'; Expression={$_.EventData.TargetDomainName; }},
@{ Name='TargetUserName'; Expression={$_.EventData.TargetUserName; }},
@{ Name='LogonType'; Expression={$_.EventData.LogonType; }},
@{ Name='WorkstationName'; Expression={$_.EventData.WorkstationName; }},
@{ Name='IpAddress'; Expression={$_.EventData.IpAddress; }},
@{ Name='ProcessId'; Expression={$_.EventData.ProcessId; }},
@{ Name='ProcessName'; Expression={$_.EventData.ProcessName; }}
$events = $events | Where {
foreach($filter in $ExcludeUserFilter) {
if($_.TargetUserName -ilike $filter) {
return $false;
}
}
return $true;
}
$events | Sort CreationDate -Descending:$descending;
Analysis in Kibana
SpecterInsight now ships with a User Logon’s dashboard that can be used to profile user logons within the target environment. There are a bunch of different visualizations built into this dashboard. The timeline can show trends about when this user logs onto a system. The Workstations table shows the name of the system where a logon originated. If you’re looking at one system, then this field will show the local hostname (which may change over time), but it may also show the hostname of the remote system that logged in (e.g. Remote Desktop or PSExec).

We can quickly apply filters to the dashboard to focus in on individual users such as the “Administrator” and we see that this user is logging in remotely from the DEV system. We know that because the logon type 7 is an unlock command that you would typically see in RDP sessions that are unlocked.

Lastly, once you’ve filtered down the events you’re interested in at the top using the summary visualizations, you can drill down into the individual events matching your filters at the bottom.

Conclusion
Tracking and analyzing user logon activity is not just about collecting data—it’s about turning that data into actionable intelligence. This PowerShell script, combined with SpecterInsight’s powerful parsing and visualization capabilities, enables red teamers to identify behavioral patterns, pinpoint high-value accounts, and uncover stealthy avenues for lateral movement. Whether you’re profiling domain administrators, detecting cached credentials, or mapping remote access paths, this technique provides a streamlined and effective approach to understanding how users interact with systems in the target environment. By operationalizing event log data, red teams can move with greater precision, stealth, and strategic insight.