Most Windows detection programs are anchored on a small set of well-known event IDs: 4624, 4625, maybe 4688 if process creation auditing is turned on. The events that actually describe an intrusion (the new service, the scheduled task, the explicit credential, the share enumeration) live elsewhere on the same host, often on channels that are not enabled by default. We have written before about why a 4625-only mindset leaves most of the attack chain in the dark; this post is the catalog that picks up where that argument ended.
What follows are the Windows event IDs we keep coming back to in real investigations, organized by where they fit in the attack chain, with NXLog Agent configurations to collect and enrich them. It is not exhaustive (no such list could be), but every ID below has earned its place by repeatedly showing up in post-incident timelines.
Before the IDs: know your channels
The Security channel gets most of the attention, and most of this post lives there. But three other channels carry events that are often more decisive than anything Microsoft writes to Security:
-
Microsoft-Windows-Sysmon/Operational: process trees, network connections, image loads, registry changes. Requires the Sysmon driver and a tuned configuration. -
Microsoft-Windows-PowerShell/Operational: script block logging (4104), the single best source for catching obfuscated post-exploitation tooling. -
Microsoft-Windows-WMI-Activity/Operational: WMI consumer registration, a long-favored persistence trick that leaves no Security-channel trace.
A detection strategy that ignores these channels will miss techniques that have been mainstream for a decade. Plan to collect from all four.
Execution: what ran, and who launched it
| Event ID | Channel | What it tells you |
|---|---|---|
4688 |
Security |
A process was created. With command-line auditing enabled, includes the full command line and the parent process (the backbone of execution-chain reconstruction). |
4689 |
Security |
A process exited. Useful for pairing with 4688 to bound process lifetime. |
1 |
Sysmon/Operational |
Process create, with hashes, original file name, signing info, and the parent. Richer than 4688 and harder for an attacker to defeat. |
4104 |
PowerShell/Operational |
Script block executed. Captures the deobfuscated script content, invaluable against |
|
Note
|
Event 4688 only includes the command line when the Include command line in process creation events policy is enabled (Computer Configuration > Administrative Templates > System > Audit Process Creation). Without it, 4688 is a fraction as useful. Verify the policy is on before relying on this ID.
|
Persistence: what the attacker left behind
| Event ID | Channel | What it tells you |
|---|---|---|
7045 |
System |
A new service was installed. Classic for both legitimate software and tools like PsExec, Cobalt Strike service-mode beacons, and most ransomware loaders. |
4697 |
Security |
A service was installed by the Security Subsystem. The Security-channel counterpart to 7045; both are worth collecting because they cover slightly different paths. |
4698 |
Security |
A scheduled task was created. The XML payload includes the action, trigger, and principal; read it. |
4702 |
Security |
A scheduled task was updated. Often more interesting than 4698, because attackers reuse legitimate task names. |
4720 |
Security |
A user account was created. Pair with 4732/4728 (group membership change) to spot account-creation-then-promotion sequences. |
5861 |
WMI-Activity/Operational |
A permanent WMI event consumer was registered, a persistence technique that survives reboot and leaves no on-disk artifact in the usual places. |
Privilege escalation: who became powerful
| Event ID | Channel | What it tells you |
|---|---|---|
4672 |
Security |
Special privileges assigned to a new logon. Fires on every administrative logon, so it is noisy. But a 4672 for an account that should never be administrative is one of the cleanest privilege-escalation signals you will get. |
4673 |
Security |
A privileged service was called. Very noisy; useful only when filtered to specific privileges (e.g. |
4674 |
Security |
An operation was attempted on a privileged object. Same caveat as 4673. |
4732 |
Security |
A member was added to a security-enabled local group. Watch |
4728 |
Security |
A member was added to a security-enabled global group. Domain-side equivalent. |
|
Tip
|
4673 and 4674 are firehoses by default. Either filter them at the source by privilege name, or leave them off and rely on 4672 plus group-change events for escalation detection. |
Lateral movement: hopping between hosts
| Event ID | Channel | What it tells you |
|---|---|---|
4624 (type 3) |
Security |
Network logon. Source IP and source workstation are in the event. A burst of type-3 logons from one host to many is the textbook lateral-movement shape. |
4624 (type 10) |
Security |
RemoteInteractive (RDP) logon. Type 10 from a non-jump-host source is worth alerting on directly. |
4648 |
Security |
Logon with explicit credentials ( |
5140 |
Security |
A network share was accessed. Enable via Audit File Share. |
5145 |
Security |
A network share object was checked for access. Verbose, but the only way to see which files were touched during a SMB session. |
3 |
Sysmon/Operational |
Network connection from a process. Pairs with 4624 type 3 to attribute the inbound logon to a specific outbound caller on the source host. |
Defense evasion: covering tracks
| Event ID | Channel | What it tells you |
|---|---|---|
1102 |
Security |
The audit log was cleared. There is essentially no benign reason to see this in production. Alert on it. |
104 |
System |
Event log was cleared (any channel other than Security). Same logic. |
4719 |
Security |
System audit policy was changed. Attackers disable auditing before noisy actions; this is the event that exposes it. |
4907 |
Security |
Auditing settings on an object were changed. Lower urgency, but worth keeping for forensic timelines. |
Mapping the catalog to MITRE ATT&CK
The IDs above line up against the techniques most commonly seen in Windows intrusions:
| ATT&CK technique | Primary IDs | Channel |
|---|---|---|
T1059: Command and scripting interpreter |
4688, 4104, Sysmon 1 |
Security, PowerShell, Sysmon |
T1543.003: Windows service persistence |
7045, 4697 |
System, Security |
T1053.005: Scheduled task |
4698, 4702 |
Security |
T1546.003: WMI event subscription |
5861 |
WMI-Activity |
T1136: Account creation |
4720, 4732, 4728 |
Security |
T1078: Valid accounts |
4624 (10), 4648, 4672 |
Security |
T1021: Remote services |
4624 (3), 5140, 5145, Sysmon 3 |
Security, Sysmon |
T1070.001: Indicator removal, clear logs |
1102, 104, 4719 |
Security, System |
This is the table that should hang next to your detection backlog. Every row is a coverage question: do we collect it, do we parse it, do we alert on it?
Collecting the catalog with NXLog Agent
The configuration below uses im_msvistalog to subscribe to all four relevant channels in a single agent, with channel-specific QueryXML to keep volume manageable.
It assumes Sysmon is installed with at least the SwiftOnSecurity baseline configuration and that PowerShell script block logging is enabled via Group Policy.
<Input windows_security>
Module im_msvistalog
SavePos TRUE
ReadFromLast TRUE
<QueryXML>
<QueryList>
<Query Id="0">
<!-- Execution -->
<Select Path="Security">
*[System[(EventID=4688 or EventID=4689)]]
</Select>
<!-- Persistence -->
<Select Path="Security">
*[System[(EventID=4697 or EventID=4698 or EventID=4702
or EventID=4720 or EventID=4732 or EventID=4728)]]
</Select>
<!-- Privilege escalation -->
<Select Path="Security">
*[System[(EventID=4672)]]
</Select>
<!-- Lateral movement -->
<Select Path="Security">
*[System[(EventID=4624 or EventID=4648
or EventID=5140 or EventID=5145)]]
</Select>
<!-- Defense evasion -->
<Select Path="Security">
*[System[(EventID=1102 or EventID=4719 or EventID=4907)]]
</Select>
</Query>
<Query Id="1">
<!-- Service install (System channel) -->
<Select Path="System">
*[System[(EventID=7045 or EventID=104)]]
</Select>
</Query>
<Query Id="2">
<!-- Sysmon process and network -->
<Select Path="Microsoft-Windows-Sysmon/Operational">
*[System[(EventID=1 or EventID=3)]]
</Select>
</Query>
<Query Id="3">
<!-- PowerShell script block -->
<Select Path="Microsoft-Windows-PowerShell/Operational">
*[System[(EventID=4104)]]
</Select>
</Query>
<Query Id="4">
<!-- WMI persistence -->
<Select Path="Microsoft-Windows-WMI-Activity/Operational">
*[System[(EventID=5861)]]
</Select>
</Query>
</QueryList>
</QueryXML>
</Input>
A targeted query like this typically reduces forwarded volume by an order of magnitude compared to <Select Path="Security">*</Select>, while preserving every event that maps to the ATT&CK table above.
Enriching events with ATT&CK technique IDs
Tagging at the agent saves the SIEM a lookup table and makes routing trivial.
The Exec block below adds an attck_technique field that detection rules can pivot on directly:
<Extension json>
Module xm_json
</Extension>
<Input windows_security>
Module im_msvistalog
SavePos TRUE
ReadFromLast TRUE
<QueryXML>
<!-- as above -->
</QueryXML>
<Exec>
if ($EventID == 4688 or $EventID == 4104 or
($Channel == 'Microsoft-Windows-Sysmon/Operational' and $EventID == 1))
$attck_technique = 'T1059';
else if ($EventID == 7045 or $EventID == 4697) $attck_technique = 'T1543.003';
else if ($EventID == 4698 or $EventID == 4702) $attck_technique = 'T1053.005';
else if ($EventID == 5861) $attck_technique = 'T1546.003';
else if ($EventID == 4720 or $EventID == 4732
or $EventID == 4728) $attck_technique = 'T1136';
else if ($EventID == 4648 or $EventID == 4672) $attck_technique = 'T1078';
else if ($EventID == 5140 or $EventID == 5145
or $EventID == 4624) $attck_technique = 'T1021';
else if ($EventID == 1102 or $EventID == 104
or $EventID == 4719) $attck_technique = 'T1070.001';
</Exec>
</Input>
<Output siem>
Module om_tcp
Host siem.internal:6514
Exec to_json();
</Output>
<Route detection>
Path windows_security => siem
</Route>
The agent now emits structured JSON, one event per record, with the source channel, the original event fields, and an ATT&CK tag. From the SIEM side, a single field selector gives you a per-technique view of the entire estate.
A note on logon-type filtering
4624 fires on every successful logon, including the local services that authenticate dozens of times a minute.
If full collection is not on the table, a small Exec filter trims it to the logon types that matter for lateral movement:
<Exec>
if ($EventID == 4624 and not ($LogonType in (3, 10))) drop();
</Exec>
Type 2 (interactive at the console) and type 5 (service) account for the bulk of the noise; dropping them at the agent keeps the SIEM bill down without losing the network and RDP logons that detection actually depends on.
Where this leaves you
The 4625-only mindset is comfortable because it produces a chart that goes up and down. The catalog above produces something more useful: a coverage map. For each row in the ATT&CK table, you either collect the event, parse it, and alert on it, or you accept that you will not see that technique when it happens. NXLog Agent is the piece that makes the first three columns cheap; the rest is detection engineering.