Windows Fundamentals · beginner · ~9 min
**What you will learn** - Open and navigate the Windows **Event Log** (Security, System, Application) using **Event Viewer** and **Get-WinEvent**. - Recognise the high-value **event IDs** defenders watch — 4624, 4625, 4672, 4688, 7045, 1102 — and say what each one means. - Read the **logon type** field to tell a keyboard logon from a network or RDP logon. - Build a simple detection idea (for example, a burst of failed logons) and describe how to reconstruct an incident timeline from events. - Explain, in defensive terms, why log integrity matters and why clearing logs is itself a monitored, high-signal event.
Security objective: the asset you are protecting is the audit trail — the record of who authenticated, what ran, and what services were installed on a Windows host. The threat is an attacker who logs on with stolen credentials, runs tools, installs persistence, and then tries to hide. What you will learn to detect is that activity as it appears in the event logs, and what you will learn to preserve is the integrity of the log itself.
Windows continuously records activity in the Event Log. It is split into separate logs, the most important of which are:
You read these logs two ways:
eventvwr.msc) — the built-in graphical browser.This lesson builds directly on Windows users, groups, and SIDs (the prereq). Every logon event ties back to an account and its SID; every privileged logon (4672) means a security principal that belongs to a powerful group like Administrators was used. If you cannot read a SID or reason about group membership, the logs will not make sense. Think of this as the Windows counterpart to reading /var/log/auth.log on Linux — same idea, different field names.
In authorized professional work, event logs are where defence actually happens.
Whether you end up on blue team or red team, fluency in Windows logging is one of the most transferable skills in the field.
Definition. The Event Log is a structured, append-oriented store of events. Each event has a source, an Event ID, a level (Information/Warning/Error/Audit Success/Audit Failure), a timestamp, and named data fields.
How it works. Different components write to different channels (logs):
| Channel | What it records | Defender use |
|---|---|---|
| Security | Logons, privilege use, audit policy, log clears | Primary source for intrusion detection |
| System | Services, drivers, OS components | Persistence and stability signals |
| Application | Events from installed programs | App errors, some security tools |
When / when not. Use Security for authentication questions; use System for "was a service installed?"; use Application for program-specific detail. Not every interesting action is logged by default — for example, process creation (4688) and command lines must be enabled via audit policy first.
Pitfall. Assuming an event's absence proves nothing happened. If auditing for that category was never enabled, the event simply was never written.
| Event ID | Log | Meaning | Why it matters |
|---|---|---|---|
| 4624 | Security | Successful logon | Establishes who was on the host and how |
| 4625 | Security | Failed logon | Bursts suggest brute-force / password spraying |
| 4672 | Security | Special privileges assigned at logon | Effectively an administrator logon |
| 4688 | Security | A new process was created | Shows what ran (command line if enabled) |
| 7045 | System | A service was installed | Classic persistence / lateral-movement signal |
| 1102 | Security | The Security log was cleared | High-signal anti-forensics indicator |
Pitfall. Treating a single 4625 as an attack. People mistype passwords constantly. It is the pattern — many failures across many accounts from one source in a short window — that signals spraying.
Definition. A field inside 4624/4625 that says how the session was created.
| Type | Name | Typical cause |
|---|---|---|
| 2 | Interactive | Someone at the physical keyboard / local console |
| 3 | Network | SMB file share, remote authentication |
| 10 | RemoteInteractive | Remote Desktop (RDP) |
How it works. A type 10 logon at 03:00 to a server that normally only sees type 2 is worth a second look. Analysts triage almost every logon question by first checking the type.
Pitfall. Confusing type 3 (network) with type 10 (RDP). Both are "remote," but type 3 is usually service-to-service, while type 10 is an interactive desktop session — very different risk.
Definition. The trustworthiness of the record. Clearing the Security log writes event 1102 before the log is emptied, so the clear itself cannot be hidden locally.
Why it matters defensively. Forwarding logs to a central collector (WEF/SIEM) means that even if a host's local log is cleared, the evidence already left the box. The gap on the host plus a 1102 is itself strong evidence of tampering.
THREAT MODEL: Windows host authentication & audit trail
Assets Trust boundary Entry points
------------------------------ ------------------------ ----------------------
[ User accounts / SIDs ] || Network perimeter || <- RDP (logon type 10)
[ Local admin privileges ] || || <- SMB (logon type 3)
[ Services (persistence) ] || Host OS boundary || <- Console (logon type 2)
[ The Event Log itself ] <==== || (audit subsystem) ||
------------------------
| |
v v
Local Security log --forward--> Central SIEM / WEF collector
(can be cleared: 1102) (off-host copy: tamper-evident)
Detections: 4625 bursts (spray), 4672 (admin logon), 7045 (new service),
1102 (log cleared), off-hours logon type 10.
Knowledge check
The core defensive command is Get-WinEvent in PowerShell. It reads a channel and lets you filter server-side, which is far faster than exporting everything.
# Read the 20 most recent failed-logon events from the Security log.
# -FilterHashtable filters at the source (fast, precise).
Get-WinEvent -FilterHashtable @{
LogName = 'Security'; # which channel
Id = 4625 # failed logon
} -MaxEvents 20 |
Select-Object TimeCreated, Id, @{
Name = 'Account'
Expression = { $_.Properties[5].Value } # target account name
}
Annotated pieces:
-FilterHashtable @{ LogName=...; Id=... } — the efficient way to filter; add StartTime/EndTime keys to bound a window.$_.Properties[N].Value — event fields are positional; index 5 is the target user name for 4625 on modern Windows. Always confirm the index for your event and OS build before trusting it.eventvwr.msc, then browse Windows Logs -> Security.Run these only on a host you own or are authorized to test (see the lab checklist in Practice tasks).
Windows records activity in the Event Log. You can browse it with Event Viewer (eventvwr) or query it with PowerShell (Get-WinEvent).
The logon type recorded in a 4624 event tells you how the user connected:
Defenders triage events by these types.
The shape below is INSECURE detection -> SECURE detection -> VERIFY. The "insecure" part is a weak detection rule, not an exploit — a common beginner mistake that misses real attacks and floods analysts with noise.
A naive rule that alerts on any single failed logon. It is defensive code, but it is a bad detection: too noisy to be useful, and it misses the pattern that actually matters.
# WEAK DETECTION - do not rely on this.
# Fires on EVERY 4625, including honest typos. Analysts will mute it,
# and a slow, spread-out password spray still slips through.
Get-WinEvent -FilterHashtable @{ LogName='Security'; Id=4625 } -MaxEvents 50 |
ForEach-Object { Write-Warning "Failed logon at $($_.TimeCreated)" }
Count failed logons per source account within a time window and only alert past a threshold. This catches brute-force and spraying while tolerating normal typos.
# BETTER DETECTION: threshold + time window + grouping.
$WindowMinutes = 15
$Threshold = 10 # tune to your baseline
$since = (Get-Date).AddMinutes(-$WindowMinutes)
$events = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = 4625
StartTime = $since
} -ErrorAction SilentlyContinue
$events |
Group-Object { $_.Properties[5].Value } | # group by target account
Where-Object { $_.Count -ge $Threshold } |
ForEach-Object {
[pscustomobject]@{
Account = $_.Name
Failures = $_.Count
Window = "$WindowMinutes min"
FirstSeen = ($_.Group.TimeCreated | Sort-Object | Select-Object -First 1)
Alert = 'Possible brute-force / password spray'
}
}
On a local, isolated lab VM you own, generate controlled failures against a throwaway account, then confirm the rule stays quiet below threshold and fires above it.
# LAB ONLY. Run on a VM you own. Uses a disposable local test account.
# This triggers real 4625 events with WRONG passwords on purpose.
$acct = 'labtestuser'
$wrong = ConvertTo-SecureString 'definitely-wrong-pw' -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential($acct, $wrong)
# Produce a handful of failures (below threshold) -> rule should stay SILENT.
1..5 | ForEach-Object {
Start-Process cmd.exe -Credential $cred -ErrorAction SilentlyContinue
}
# Then produce enough to cross the threshold -> rule should ALERT.
1..12 | ForEach-Object {
Start-Process cmd.exe -Credential $cred -ErrorAction SilentlyContinue
}
# Re-run the SECURE detection above and confirm exactly one alert for $acct.
Expected behaviour. With 5 failures the SECURE rule prints nothing (below threshold). With 12 failures it prints one row naming labtestuser, the failure count, and the window. The WEAK rule, by contrast, would have shouted 17 times. This proves the fix accepts a real attack pattern and rejects ordinary noise.
Walkthrough of the SECURE detection.
$WindowMinutes = 15 / $Threshold = 10 — the two tuning knobs. Together they define "too many failures, too fast." You set these from your own baseline of normal failed logons.$since = (Get-Date).AddMinutes(-$WindowMinutes) — computes the start of the look-back window (15 minutes ago).Get-WinEvent -FilterHashtable @{ ... StartTime=$since } — pulls only 4625 events from the Security log inside the window. Filtering at the source keeps it fast even on busy hosts. -ErrorAction SilentlyContinue avoids a hard error when the window contains zero matching events.Group-Object { $_.Properties[5].Value } — buckets the events by target account name (field index 5 for 4625). After this, each group is one account with a .Count of failures.Where-Object { $_.Count -ge $Threshold } — keeps only accounts at or above the threshold. This single line is what turns "noisy per-event" into "meaningful per-pattern."[pscustomobject] block — formats each surviving group into a tidy alert row with the account, failure count, window, first-seen time, and a verdict string.Trace with the lab data.
| Stage | 5-failure run | 12-failure run |
|---|---|---|
| Events pulled by Get-WinEvent | 5 | 12 |
| After Group-Object (labtestuser) | Count = 5 | Count = 12 |
| After Where-Object (>= 10) | dropped | kept |
| Output | (nothing) | 1 alert row |
The value that decides everything is .Count versus $Threshold: 5 is below 10 so the group is discarded; 12 is above so it becomes an alert.
Mistake 1 — Alerting on single events.
Mistake 2 — Assuming 4688 always has command lines.
Mistake 3 — Reading only the local log during an incident.
Mistake 4 — Confusing logon types.
"Get-WinEvent : No events were found" — Your filter matched nothing. Widen the window (StartTime), remove the Id key temporarily to confirm the channel has data, and check you are querying the right LogName.
"Access is denied" reading the Security log — The Security channel requires elevation. Open PowerShell as Administrator (on a host you are authorized to use).
Wrong field appears for $_.Properties[N] — Field indexes differ between event IDs and OS builds. Debug by piping one event to Format-List * or inspecting $_.Properties and $_.Message to find the correct index before hard-coding it.
4688 events missing — Process-creation auditing is not enabled. Confirm audit policy in your lab, regenerate an event, and re-query.
Detection never fires in the lab — Confirm your test actions actually produced 4625s (query them directly), check the clock/timezone on StartTime, and verify $Threshold is reachable given how many failures you generated.
Questions to ask when it fails: Is auditing enabled for this category? Am I elevated? Is my time window correct and in the right timezone? Am I reading the right channel and field index? Is there an off-host copy to compare against?
Security & safety — detection, logging, and privacy
What to log when you build detections or record an investigation:
What to NEVER log:
Events that signal abuse: 4625 bursts (brute-force/spray), a 4625 burst followed by 4624 + 4672 for the same account (successful compromise to admin), 7045 for an unexpected service (persistence), 1102 (log cleared), and off-hours type 10 logons.
How false positives arise: vulnerability scanners and monitoring agents generate authentication noise; a user returning from holiday mistypes an expired password repeatedly; service accounts fail after a password rotation. Tune thresholds against a real baseline and maintain an allow-list of known-benign sources so genuine attacks stand out.
Integrity reminders: decoding or reading a log entry is not the same as proving it is authentic — trust off-host forwarded copies over a single local log. Passing an automated log-hygiene scan does not prove a host is secure, and no logging setup is ever "completely secure"; it reduces blind spots, it does not eliminate them.
Authorized real-world use case. A SOC receives an alert that a finance server saw many failed logons overnight. An analyst queries the Security log, finds a burst of 4625 across several accounts from one workstation (password spraying), then a single 4624 and 4672 for one account (the spray succeeded and gained admin), followed by 7045 in the System log (a service installed for persistence). From these events alone they reconstruct the timeline, scope the compromise, and hand a clear remediation list to IT. The off-host SIEM copy lets them trust the record even though the local log was later found cleared (1102).
Best-practice habits.
| Practice | Beginner | Advanced |
|---|---|---|
| Validation | Confirm auditing is enabled before trusting event absence | Baseline normal 4625/4624 volumes per host to tune thresholds |
| Least privilege | Only elevate when the Security log requires it | Restrict who can read/clear logs; monitor those grants |
| Secure defaults | Enable process-creation (4688) auditing in the lab | Deploy audit policy via Group Policy fleet-wide |
| Logging | Record timestamp, source, account, result | Forward all Security/System events to a tamper-evident SIEM |
| Error handling | Handle empty result sets gracefully | Alert when log forwarding stops (a blind-spot signal) |
For testers: document the events your authorized actions generate (7045, 4624) and share them so the blue team can validate detections — that hand-off is a deliverable, not an afterthought.
All tasks are lab-only. Use a Windows VM you own or a purpose-built training range. Never run these against a system you are not authorized to test.
Authorization checklist (complete before starting):
Beginner 1 — Tour the channels.
eventvwr.msc) and locate the Security, System, and Application logs.Beginner 2 — Query with PowerShell.
Get-WinEvent to pull the 10 most recent 4624 events.-FilterHashtable @{ LogName='Security'; Id=4624 } -MaxEvents 10.Intermediate 1 — Build the threshold detection.
$Threshold to your baseline.Intermediate 2 — Enable and read 4688.
Challenge — Reconstruct an incident timeline.
Cleanup / reset for every task: delete any disposable test account and dummy service you created, clear your working variables, and revert the VM to its pre-lab snapshot so no test artefacts persist.
eventvwr.msc) or Get-WinEvent (filter server-side with -FilterHashtable).