P09: Windows Event Log Analyzer
P09: Windows Event Log Analyzer
A comprehensive guide to building a security event monitoring and analysis tool with PowerShell
The Core Question You’re Answering
“How can I systematically detect security threats and anomalies by analyzing Windows Event Logs, transforming raw log data into actionable security intelligence?”
This project addresses a fundamental challenge in security operations: Windows generates thousands of events daily, but only a small fraction indicate actual security concerns. Your analyzer must efficiently sift through this noise to identify patterns like brute force attacks, privilege escalation, and unauthorized access attempts.
The deeper questions you’ll answer:
- How do you query millions of events without exhausting system resources?
- How do you correlate isolated events into meaningful attack narratives?
- How do you distinguish normal administrative activity from malicious behavior?
- How do you build detection rules that minimize false positives while catching real threats?
Concepts You Must Understand First
Before diving into implementation, ensure you understand these foundational concepts:
1. Windows Event Log Fundamentals
- Event Channels: Security, System, Application, and custom logs
- Event Structure: Provider, EventID, Level, TimeCreated, EventData
- Audit Policies: What events Windows actually records (and what it doesn’t by default)
2. Security Event Categories
- Account Logon Events: 4624 (success), 4625 (failure), 4648 (explicit credentials)
- Account Management: 4720 (created), 4726 (deleted), 4740 (locked)
- Privilege Use: 4672 (special privileges), 4673 (privilege service)
- Process Events: 4688 (process creation), 4689 (process termination)
3. XPath Query Language
- Basic path expressions and predicates
- Time-based filtering with
timediff() - Combining multiple conditions with
and/or
4. Attack Pattern Recognition
- Brute Force: High volume of failed logins from single source
- Password Spraying: Single password tried against multiple accounts
- Privilege Escalation: Normal user gaining admin rights
- Lateral Movement: Access spreading across systems
5. PowerShell Performance Patterns
- Pipeline processing vs. collecting results
Get-WinEventvs.Get-EventLog(and why you should use the former)- FilterHashtable vs. FilterXPath (tradeoffs)
Project Overview
What you’ll build: A tool that queries Windows Event Logs, filters for security-relevant events (failed logins, service crashes, privilege escalation), and generates alerts or reports.
| Attribute | Value |
|---|---|
| Difficulty | Advanced |
| Time Estimate | 2 weeks |
| Programming Language | PowerShell |
| Knowledge Area | Security Operations, Forensics |
| Prerequisites | Intermediate PowerShell, Windows security concepts |
Learning Objectives
After completing this project, you will be able to:
- Query event logs efficiently - Use Get-WinEvent with optimized filters
- Build XPath queries - Filter events at the source for performance
- Understand security events - Interpret Windows security event IDs
- Correlate events - Link related events across time and logs
- Generate actionable alerts - Threshold-based anomaly detection
- Create security reports - Formatted output for security teams
Real World Outcome
When this project is complete, you will have a comprehensive security event monitoring system that transforms raw Windows Event Logs into actionable security intelligence. Here is exactly what you will see and experience:
What You’re Building: The Complete Picture
+============================================================================+
|| WINDOWS EVENT LOG ANALYZER - COMPLETE SYSTEM ||
+============================================================================+
| |
| +---------------------------+ +---------------------------+ |
| | PowerShell Module | | Scheduled Task | |
| | SecurityEventAnalyzer | | (runs every hour) | |
| +---------------------------+ +---------------------------+ |
| | | |
| v v |
| +----------------------------------------------------------------+ |
| | CORE ANALYSIS ENGINE | |
| | | |
| | [Query Layer] --> [Parse Layer] --> [Correlate] --> [Report] | |
| | | |
| +----------------------------------------------------------------+ |
| | |
| +---------------------+----------------------+ |
| | | | |
| v v v |
| +------------------+ +-------------------+ +---------------------+ |
| | Terminal Output | | HTML Report | | Email Alert | |
| | (live analysis) | | (daily summary) | | (critical findings) | |
| +------------------+ +-------------------+ +---------------------+ |
| |
+==============================================================================+
Terminal Experience: Live Analysis
When you run the analyzer from PowerShell, here’s exactly what you’ll see:
PS C:\Scripts> Import-Module .\SecurityEventAnalyzer.psm1
PS C:\Scripts> Analyze-SecurityEvents -Hours 24 -Verbose
VERBOSE: Starting security event analysis for last 24 hours
VERBOSE: Querying Security log with optimized filter...
VERBOSE: Retrieved 892 security events in 2.3 seconds
VERBOSE: Querying System log for service events...
VERBOSE: Retrieved 45 system events in 0.8 seconds
VERBOSE: Parsing event data...
VERBOSE: Running correlation engine...
================================================================================
SECURITY EVENT ANALYSIS REPORT
Generated: 2025-12-26 14:32:45
Analysis Period: Last 24 hours
================================================================================
CRITICAL FINDINGS (3)
--------------------
[CRITICAL] BRUTE FORCE ATTACK DETECTED
Time Range: 2025-12-26 02:14:00 - 02:14:30
Source IP: 192.168.1.105
Target: jsmith@CORP
Attempts: 47 failed logins in 30 seconds
Status Codes: 0xC000006D (bad password)
Action: Block IP immediately, investigate account
[CRITICAL] PRIVILEGE ESCALATION
Time: 2025-12-26 10:23:15
User: mwilson@CORP
Privileges: SeDebugPrivilege, SeTakeOwnershipPrivilege
Context: User not in Administrators group
Action: Investigate user activity, check for compromise
[CRITICAL] ACCOUNT LOCKED - POSSIBLE ATTACK
Time: 2025-12-26 02:14:35
Account: jsmith@CORP
Lock Reason: Too many failed attempts
Source: 192.168.1.105
Action: Verify with user, check if legitimate
WARNING FINDINGS (8)
-------------------
[WARNING] Multiple failed logins - below threshold
User: admin@CORP
Count: 7 failures in 10 minutes
Source: 10.0.0.50 (internal)
Note: May be misconfigured service
[WARNING] Service stopped unexpectedly
Service: Windows Defender (WinDefend)
Time: 2025-12-26 08:45:22
Occurrences: 3 times in 24 hours
Action: Check service health, may indicate tampering
[WARNING] Explicit credential use detected
User: svc_backup
Time: 2025-12-26 11:30:00
Target: \\FILESERVER01\backup$
Note: Verify if scheduled task or manual
... (5 more warnings)
SUMMARY STATISTICS
------------------
Total Events Analyzed: 12,345
Security Events: 892
System Events: 45
Application Events: 123
By Severity:
Critical: 3
Warning: 8
Informational: 926
Authentication:
Successful Logins: 4,523
Failed Logins: 167
Account Lockouts: 2
Top Failed Login Sources:
192.168.1.105 47 attempts
10.0.0.50 7 attempts
192.168.1.200 5 attempts
================================================================================
Report saved to: C:\Reports\SecurityAnalysis_2025-12-26.html
Analysis completed in 8.4 seconds
================================================================================
PowerShell Commands You’ll Have
#------------------------------------------------------------------------------
# COMMAND 1: Get-SecurityEvents - Query and parse security events
#------------------------------------------------------------------------------
PS> Get-SecurityEvents -EventId 4625 -Hours 1
TimeCreated EventId EventType Account SourceIP LogonType
----------- ------- --------- ------- -------- ---------
12/26/2025 14:25:33 4625 FailedLogin jsmith 192.168.1.105 3
12/26/2025 14:25:34 4625 FailedLogin jsmith 192.168.1.105 3
12/26/2025 14:25:35 4625 FailedLogin jsmith 192.168.1.105 3
...
# With verbose output showing query performance
PS> Get-SecurityEvents -EventId 4625 -Hours 24 -Verbose
VERBOSE: Building FilterHashtable query
VERBOSE: Query: @{LogName='Security'; ID=4625; StartTime='12/25/2025 14:30:00'}
VERBOSE: Executing query...
VERBOSE: Retrieved 167 events in 1.2 seconds
VERBOSE: Parsing EventData XML...
VERBOSE: Parsed 167 events in 0.3 seconds
#------------------------------------------------------------------------------
# COMMAND 2: Find-BruteForceAttempts - Detect brute force patterns
#------------------------------------------------------------------------------
PS> Find-BruteForceAttempts -Hours 24 -Threshold 10 -WindowMinutes 5
Type : BruteForce
Severity : Critical
SourceIP : 192.168.1.105
TargetAccounts : jsmith, admin
StartTime : 12/26/2025 02:14:00
EndTime : 12/26/2025 02:14:30
AttemptCount : 47
Description : 47 failed logins from 192.168.1.105 in 5 minutes
Action : Block IP, investigate targeted accounts
#------------------------------------------------------------------------------
# COMMAND 3: Find-PrivilegeEscalation - Detect privilege abuse
#------------------------------------------------------------------------------
PS> Find-PrivilegeEscalation -Hours 24
Type : PrivilegeEscalation
Severity : Critical
TimeCreated : 12/26/2025 10:23:15
Account : mwilson
Domain : CORP
PrivilegesGranted : SeDebugPrivilege, SeTakeOwnershipPrivilege
Description : Normal user mwilson received sensitive privileges
Action : Investigate account activity, verify if authorized
#------------------------------------------------------------------------------
# COMMAND 4: Get-ServiceAnomalies - Monitor service health
#------------------------------------------------------------------------------
PS> Get-ServiceAnomalies -Hours 24
ServiceName : WinDefend
DisplayName : Windows Defender
EventType : Unexpected Stop
Occurrences : 3
LastOccurrence : 12/26/2025 08:45:22
Severity : Warning
Pattern : Multiple stops may indicate tampering
#------------------------------------------------------------------------------
# COMMAND 5: New-SecurityReport - Generate comprehensive report
#------------------------------------------------------------------------------
PS> New-SecurityReport -Date (Get-Date).AddDays(-1) -OutputPath "C:\Reports"
Generating security report for 2025-12-25...
Querying Security log... done (2.1s)
Querying System log... done (0.4s)
Running brute force detection... done (0.8s)
Running privilege escalation detection... done (0.5s)
Analyzing service anomalies... done (0.2s)
Generating HTML report... done
Report generated successfully!
Path: C:\Reports\SecurityReport_2025-12-25.html
Size: 45 KB
Findings: 3 critical, 8 warnings
PS> Invoke-Item "C:\Reports\SecurityReport_2025-12-25.html"
HTML Report Output
When you generate a report, it creates a professional HTML file:
+============================================================================+
|| SECURITY REPORT - 2025-12-25 ||
+============================================================================+
| |
| +----------------------+ +----------------------+ +------------------+ |
| | 12,345 | | 4,523 | | 167 | |
| | Total Events | | Successful Logins | | Failed Logins | |
| +----------------------+ +----------------------+ +------------------+ |
| |
| CRITICAL FINDINGS |
| ================ |
| |
| +------------------------------------------------------------------------+|
| | BRUTE FORCE ATTACK DETECTED ||
| |------------------------------------------------------------------------|
| | Source IP: 192.168.1.105 ||
| | Target: jsmith@CORP ||
| | Attempts: 47 in 30 seconds ||
| | Time: 02:14:00 - 02:14:30 ||
| | Action: Block IP, investigate account ||
| +------------------------------------------------------------------------+|
| |
| TOP FAILED LOGIN SOURCES |
| ======================== |
| | Source IP | Attempts | Target Accounts | First Seen | Last | |
| |-----------------|----------|-------------------|------------|--------| |
| | 192.168.1.105 | 47 | jsmith | 02:14:00 | 02:14:30| |
| | 10.0.0.50 | 7 | admin, svc_app | 08:00:00 | 08:10:00| |
| |
| Generated by SecurityEventAnalyzer v1.0 at 2025-12-26 08:00:00 |
+============================================================================+
Email Alert Format
When critical findings are detected:
From: security-alerts@company.com
To: soc-team@company.com
Subject: [CRITICAL] 3 Security Findings Detected - WORKSTATION01
================================================================================
SECURITY ALERT - IMMEDIATE ACTION REQUIRED
================================================================================
Server: WORKSTATION01.corp.local
Time: December 26, 2025 14:32:45
CRITICAL FINDINGS (3)
---------------------
1. BRUTE FORCE ATTACK
Source: 192.168.1.105
Target: jsmith@CORP
47 attempts in 30 seconds
ACTION: Block IP, check account status
2. PRIVILEGE ESCALATION
User: mwilson@CORP
Privileges: SeDebugPrivilege, SeTakeOwnershipPrivilege
ACTION: Investigate immediately
Full report: C:\Reports\SecurityReport_2025-12-26.html
================================================================================
File Structure You’ll Create
C:\Scripts\SecurityEventAnalyzer\
|
+-- SecurityEventAnalyzer.psm1 # Main module with all functions
+-- SecurityEventAnalyzer.psd1 # Module manifest
|
+-- Private\ # Internal helper functions
| +-- ConvertTo-SecurityEvent.ps1
| +-- Build-XPathQuery.ps1
|
+-- Public\ # Exported commands
| +-- Get-SecurityEvents.ps1
| +-- Find-BruteForceAttempts.ps1
| +-- Find-PrivilegeEscalation.ps1
| +-- New-SecurityReport.ps1
|
+-- Config\
| +-- EventDefinitions.json # Event ID metadata
| +-- Thresholds.json # Detection thresholds
Deep Dive: Theoretical Foundation
Windows Event Log Architecture
Windows Event Logs are stored as .evtx files and managed by the Event Log service. Understanding this architecture is essential for efficient querying and security analysis.
Book Reference: Windows Security Internals by James Forshaw, Part 2: “Auditing and Logging” - provides deep coverage of how Windows generates and stores security events.
The Event Log Service Architecture
+============================================================================+
|| WINDOWS EVENT LOG ARCHITECTURE ||
+============================================================================+
| |
| +------------------------+ +------------------------+ |
| | APPLICATIONS | | WINDOWS KERNEL | |
| | (Your apps, services)| | (LSA, SAM, etc.) | |
| +----------+-------------+ +----------+-------------+ |
| | | |
| | ETW (Event Tracing | Security Events |
| | for Windows) | (4624, 4625, etc.) |
| | | |
| v v |
| +------------------------------------------------------------------+ |
| | EVENT LOG SERVICE | |
| | (wevtsvc.dll / svchost.exe) | |
| | | |
| | +-------------------+ +-------------------+ +----------------+ | |
| | | Event Buffer | | Event Buffer | | Event Buffer | | |
| | | (Application) | | (Security) | | (System) | | |
| | +-------------------+ +-------------------+ +----------------+ | |
| | | |
| | +----------------------------------------------------------+ | |
| | | EVENT CHANNEL DISPATCHER | | |
| | | - Receives events from producers | | |
| | | - Routes to appropriate channel | | |
| | | - Manages subscriptions | | |
| | +----------------------------------------------------------+ | |
| +------------------------------------------------------------------+ |
| | | |
| v v |
| +------------------------------------------------------------------+ |
| | .EVTX FILE STORAGE | |
| | %SystemRoot%\System32\winevt\Logs\ | |
| | | |
| | +-------------------+ +-------------------+ +----------------+ | |
| | | Application.evtx | | Security.evtx | | System.evtx | | |
| | | (configurable | | (default: 20MB) | | (configurable | | |
| | | max size) | | (circular/auto- | | max size) | | |
| | | | | backup) | | | | |
| | +-------------------+ +-------------------+ +----------------+ | |
| +------------------------------------------------------------------+ |
| |
+============================================================================+
Log Types and Their Contents
| Log | Location | Contents | Security Relevance |
|---|---|---|---|
| Security | Security.evtx |
Authentication, authorization, policy changes | Critical - primary security log |
| System | System.evtx |
OS and driver events, service state changes | High - service tampering detection |
| Application | Application.evtx |
Application errors, warnings, information | Medium - application-level anomalies |
| PowerShell | PowerShell-Operational.evtx |
Script execution, module loading | Critical - command & control detection |
| Sysmon | Sysmon-Operational.evtx |
Process creation, network, file changes | Critical - if installed, best visibility |
Event Structure Deep Dive
WINDOWS EVENT STRUCTURE
+====================================================================+
|| EVENT XML SCHEMA ||
+====================================================================+
| |
| <Event xmlns="..."> |
| | |
| +-- <System> [METADATA - same structure for ALL events] |
| | | |
| | +-- <Provider Name="..." Guid="..."/> |
| | | ^-- Who generated the event |
| | | |
| | +-- <EventID>4625</EventID> |
| | | ^-- THE KEY! Identifies what happened |
| | | |
| | +-- <Level>0</Level> |
| | | ^-- 0=Info, 1=Critical, 2=Error, 3=Warning |
| | | |
| | +-- <TimeCreated SystemTime="2025-12-26T10:23:45.123Z"/> |
| | | ^-- UTC timestamp with millisecond precision |
| | | |
| | +-- <Computer>WORKSTATION01.corp.local</Computer> |
| | ^-- FQDN of machine that generated event |
| | |
| +-- <EventData> [PAYLOAD - varies by EventID] |
| | |
| +-- <Data Name="TargetUserName">jsmith</Data> |
| +-- <Data Name="TargetDomainName">CORP</Data> |
| +-- <Data Name="Status">0xC000006D</Data> |
| +-- <Data Name="IpAddress">192.168.1.105</Data> |
| | |
| ^-- Different fields for each EventID! |
| |
+=====================================================================+
The Security Event Generation Pipeline
HOW SECURITY EVENTS ARE GENERATED
+====================================================================+
| |
| USER ACTION KERNEL PROCESSING EVENT |
| =========== ================= ===== |
| |
| [User types password] |
| | |
| v |
| +-------------+ |
| | WinLogon.exe| |
| +------+------+ |
| | |
| | LsaLogonUser() |
| v |
| +-------------+ +-------------+ +------------------+ |
| | LSASS.exe |---->| SAM / AD |---->| Validate Creds | |
| | (Local | | (Account | | (Check password, | |
| | Security | | Database) | | policy, lockout)| |
| | Authority) | +-------------+ +--------+---------+ |
| +------+------+ | |
| | | |
| | <---- AUDIT DECISION POINT ---- | |
| | (Is auditing enabled?) | |
| v v |
| +--------------------------------------------------+ |
| | SECURITY EVENT GENERATED | |
| | | |
| | Success? --> EventID 4624 (Logon Success) | |
| | Failure? --> EventID 4625 (Logon Failure) | |
| +------------------------+-------------------------+ |
| | |
| v |
| +-------------+ |
| | Security. | |
| | evtx | |
| +-------------+ |
| |
+=====================================================================+
KEY INSIGHT: Events are only generated if AUDIT POLICY is enabled!
Check your audit policy:
> auditpol /get /category:*
Critical Security Event IDs
Authentication events (Security log):
| Event ID | Description | Severity |
|---|---|---|
| 4624 | Successful logon | Info |
| 4625 | Failed logon | Warning |
| 4634 | Logoff | Info |
| 4648 | Explicit credential logon | Warning |
| 4672 | Special privileges assigned | Info/Warning |
| 4720 | User account created | Info |
| 4726 | User account deleted | Warning |
| 4740 | Account locked out | Critical |
Logon types:
| Type | Description | Risk Level |
|---|---|---|
| 2 | Interactive (console) | Low |
| 3 | Network (file share) | Medium |
| 4 | Batch (scheduled task) | Low |
| 5 | Service | Low |
| 7 | Unlock | Low |
| 8 | NetworkCleartext | High |
| 10 | RemoteInteractive (RDP) | Medium |
| 11 | CachedInteractive | Low |
Process and service events:
| Event ID | Log | Description |
|---|---|---|
| 7034 | System | Service crashed |
| 7035 | System | Service start/stop |
| 7036 | System | Service state change |
| 4688 | Security | Process created (if auditing enabled) |
| 4689 | Security | Process terminated |
XPath Filtering: Performance at Scale
Book Reference: PowerShell in Depth by Don Jones, Jeffrey Hicks, and Richard Siddaway, Chapter 21: “Working with Events” - covers efficient event querying techniques.
The Performance Problem Visualized
NAIVE QUERY (Where-Object)
+====================================================================+
| |
| DISK MEMORY YOUR SCRIPT |
| ==== ====== =========== |
| |
| +---------------+ +------------------+ +-----------------+ |
| | Security.evtx | | ALL 1,000,000 | | Where-Object | |
| | (1 million |--->| events loaded |---->| { $_.Id -eq | |
| | events) | | into memory | | 4625 } | |
| +---------------+ +------------------+ +-----------------+ |
| | | |
| v v |
| Memory: 2GB+ Result: 50 events |
| Time: 15 minutes (0.005% matched) |
+=====================================================================+
OPTIMIZED QUERY (XPath/FilterHashtable)
+====================================================================+
| |
| DISK KERNEL YOUR SCRIPT |
| ==== ====== =========== |
| |
| +---------------+ +------------------+ +-----------------+ |
| | Security.evtx | | Windows Event | | Receive only | |
| | (1 million |--->| Log Service |---->| 50 matching | |
| | events) | | (native filter) | | events | |
| +---------------+ +------------------+ +-----------------+ |
| | | |
| Index-based lookup | v |
| (EventID indexed) | Memory: 5MB |
| v Time: 2 seconds |
| Only reads matching records! |
+=====================================================================+
The slow approach (NEVER use in production):
# SLOW: Gets ALL events, then filters in PowerShell
Get-WinEvent -LogName Security |
Where-Object { $_.Id -eq 4625 -and $_.TimeCreated -gt (Get-Date).AddHours(-1) }
# May read millions of events from disk!
The optimized approach using XPath:
# FAST: Filters at the source, only returns matching events
$query = "*[System[(EventID=4625) and TimeCreated[timediff(@SystemTime) <= 3600000]]]"
Get-WinEvent -LogName Security -FilterXPath $query
# Only reads relevant events
XPath syntax basics:
*[System[
(EventID=4625) <!-- Event ID filter -->
and
(EventID=4624) <!-- Multiple IDs: or -->
and
TimeCreated[@SystemTime >= '2025-12-26T00:00:00.000Z'] <!-- After date -->
and
TimeCreated[timediff(@SystemTime) <= 86400000] <!-- Last 24h in ms -->
]]
and
*[EventData[
Data[@Name='TargetUserName']='jsmith' <!-- Specific field value -->
]]
FilterHashtable alternative (easier but less flexible):
$filter = @{
LogName = 'Security'
ID = 4625
StartTime = (Get-Date).AddHours(-1)
}
Get-WinEvent -FilterHashtable $filter
FilterHashtable vs FilterXPath Comparison
| Feature | FilterHashtable | FilterXPath |
|---|---|---|
| Ease of use | Easy - PowerShell syntax | Hard - XPath syntax |
| Performance | Good | Excellent |
| Time filtering | StartTime/EndTime only | Any time expression |
| EventData filtering | Data=@{…} (limited) | Full XPath predicates |
| Debugging | Clear error messages | Cryptic errors |
Event Correlation: Connecting the Dots
Book Reference: Learn PowerShell in a Month of Lunches by Travis Plunk and Don Jones, Chapter 22: “Background Jobs and Parallel Processing” - helpful for building parallel event analysis.
The Correlation Challenge
THE SIGNAL VS NOISE PROBLEM
+====================================================================+
| |
| RAW EVENTS (1 day) AFTER CORRELATION ACTIONABLE |
| ================== ================= ========== |
| |
| +----------------+ +----------------+ +------------+ |
| | 4624: 5,000 | | Brute Force | | CRITICAL | |
| | 4625: 200 | ---> | Attempts: 3 | ---> | ALERT! | |
| | 4672: 1,500 | +----------------+ +------------+ |
| | 4634: 4,800 | | Priv Escalation| |
| | 7034: 15 | | Detected: 1 | +------------+ |
| | 7036: 500 | +----------------+ | Investigate| |
| +----------------+ | Service Crash | ---> | 2 services | |
| | | Cluster: 5 | +------------+ |
| 12,015 events 9 findings 3 priorities |
+=====================================================================+
Correlation Strategies
CORRELATION TECHNIQUES
+====================================================================+
| |
| 1. TEMPORAL CORRELATION (events close in time) |
| ============================================== |
| |
| TIMELINE: |
| 10:00:00 |--[4625]--|--[4625]--|--[4625]--|-...-|--[4625]--| |
| | fail1 | fail2 | fail3 | | fail50 | |
| |<----------- 30 seconds ------------>| |
| |
| DETECTION: Count events within sliding time window |
| THRESHOLD: >10 failed logins in 5 minutes from same source |
| |
+--------------------------------------------------------------------+
| |
| 2. SEQUENTIAL CORRELATION (events in specific order) |
| ==================================================== |
| |
| ATTACK CHAIN: |
| [4624 Login] ---> [4672 Privs] ---> [4688 Process] ---> [4634] |
| | | | | |
| v v v v |
| User logs in Gets debug priv Runs mimikatz.exe Logs off |
| |
| DETECTION: Look for event sequence within time window |
| |
+--------------------------------------------------------------------+
| |
| 3. STATISTICAL CORRELATION (deviation from baseline) |
| ==================================================== |
| |
| NORMAL WEEK: [===] [===] [===] [===] [===] (avg: 50/day) |
| THIS WEEK: [===] [===] [==========] [===] (spike: 200) |
| ^ |
| ANOMALY DETECTED! |
+=====================================================================+
Attack Pattern Examples
Example: Brute Force Attack Detection
RAW EVENTS:
===========
Time EventID User IP Status
10:00:01 4625 jsmith 192.168.1.105 0xC000006D (bad password)
10:00:02 4625 jsmith 192.168.1.105 0xC000006D
10:00:03 4625 jsmith 192.168.1.105 0xC000006D
...
10:00:30 4625 jsmith 192.168.1.105 0xC000006D
CORRELATION LOGIC:
==================
Group by: SourceIP
Count: 50 events
Window: 30 seconds
FINDING:
========
Type: BRUTE_FORCE
Severity: CRITICAL
Source: 192.168.1.105
Target: jsmith@CORP
Attempts: 50 in 30 seconds
Action: Block IP, notify SOC
Example: Privilege Escalation Detection
RAW EVENTS:
===========
Time EventID Detail
10:00:00 4624 jsmith logs in (LogonType 3, network)
10:00:01 4672 jsmith assigned SeDebugPrivilege
10:00:05 4688 jsmith runs mimikatz.exe (suspicious!)
FINDING:
========
Type: PRIVILEGE_ESCALATION
Severity: CRITICAL
User: jsmith
Evidence: Normal user obtained debug privilege, ran known attack tool
Action: Disable account, forensic investigation
Questions to Guide Your Design
Before writing code, answer these questions:
Query Strategy Questions
- What’s your expected log volume? (hundreds, thousands, or millions of events per day)
- Should you query all events once or make multiple targeted queries?
- How will you handle machines with limited audit policies?
Detection Logic Questions
- What constitutes a “brute force” attempt? (10 failures in 5 minutes? 50 in 1 minute?)
- How do you distinguish password spraying from brute force?
- What privileges indicate potential escalation?
Performance Questions
- What’s your target analysis time for 24 hours of logs?
- How much memory can your analyzer use?
- Can you parallelize analysis across multiple logs?
Output Questions
- Who is the target audience for your reports? (SOC analyst, manager, automated system)
- What severity thresholds trigger email alerts?
Thinking Exercise
Work through this scenario on paper before implementing:
Scenario: The Mystery Attack
You’re given these events from a 1-hour window:
Time EventID Details
09:00:00 4624 user: svc_backup, type: 5 (service), IP: -
09:15:00 4625 user: admin, type: 10 (RDP), IP: 10.0.0.50
09:15:02 4625 user: admin, type: 10 (RDP), IP: 10.0.0.50
09:15:04 4625 user: admin, type: 10 (RDP), IP: 10.0.0.50
09:15:10 4624 user: admin, type: 10 (RDP), IP: 10.0.0.50
09:16:00 4672 user: admin, privs: SeDebugPrivilege
09:17:00 4688 user: admin, process: cmd.exe
09:17:30 4688 user: admin, process: net.exe user hacker P@ssw0rd /add
09:18:00 4720 user: hacker created by admin
09:45:00 4625 user: guest, type: 3 (network), IP: 192.168.1.200
09:46:00 4625 user: test, type: 3 (network), IP: 192.168.1.200
09:55:00 7034 service: WinDefend crashed
Questions:
- How many distinct “attack scenarios” are represented?
- What type of attack is happening at 09:15?
- What’s suspicious about the 09:17:30 event?
- Is the 09:45-09:46 activity related to the earlier attack?
- What findings would your analyzer produce?
The Interview Questions They’ll Ask
Technical Questions
- “What’s the difference between Get-WinEvent and Get-EventLog?”
- Get-WinEvent is newer, supports .evtx format, uses FilterHashtable/FilterXPath
- Get-WinEvent is significantly faster for large logs
- “How would you query a million events efficiently?”
- Use FilterHashtable or FilterXPath to filter at the source
- Never use Where-Object on all events
- “Explain how you’d detect a brute force attack.”
- Query 4625 events, group by source IP, count within time window, flag if exceeds threshold
- “What’s XPath and why use it for event logs?”
- XML query language native to Windows Event Log, filters at kernel level
Design Questions
- “How would you scale this to 100 servers?”
- Windows Event Forwarding, parallel queries, database backend
- “How do you minimize false positives?”
- Baseline normal behavior, whitelist service accounts, context-aware thresholds
Project Specification
Functional Requirements
| ID | Requirement | Priority |
|---|---|---|
| F1 | Query specific event IDs efficiently | Must Have |
| F2 | Filter by time range | Must Have |
| F3 | Parse event data fields | Must Have |
| F4 | Detect failed login patterns | Must Have |
| F5 | Detect privilege escalation | Must Have |
| F6 | Group events by source/user/IP | Should Have |
| F7 | Generate summary reports | Should Have |
| F8 | Email alerts for critical findings | Should Have |
| F9 | Export to various formats (CSV, HTML) | Should Have |
| F10 | Cross-log correlation | Nice to Have |
| F11 | Remote server log analysis | Nice to Have |
Non-Functional Requirements
| ID | Requirement |
|---|---|
| NF1 | Analyze 24 hours of logs in < 60 seconds |
| NF2 | Memory usage < 500MB for large analyses |
| NF3 | Works with default Windows audit policies |
| NF4 | Graceful handling of access denied |
Solution Architecture
Data Flow
[Configure Analysis Parameters]
|
v
Build XPath/FilterHashtable Query
|
v
Get-WinEvent with Optimized Filter
|
+-- Security Log: 4624, 4625, 4672, etc.
+-- System Log: 7034, 7035, 7036
+-- Application Log: Errors
|
v
Parse Event Data into Structured Objects
|
v
Apply Correlation Rules
|
+-- Group by User/IP/Time
+-- Calculate frequencies
+-- Detect anomalies
|
v
Generate Findings
|
+-- Brute force attempts
+-- Privilege escalations
+-- Service failures
|
v
Output (Report / Alert / Export)
Key Data Structures
# Parsed security event
$SecurityEvent = [PSCustomObject]@{
TimeCreated = [DateTime]
EventId = [int]
EventType = [string] # "FailedLogin", "SuccessLogin", etc.
Severity = [string] # "Info", "Warning", "Critical"
Account = [string]
Domain = [string]
SourceIP = [string]
LogonType = [int]
Status = [string]
Details = [hashtable]
}
# Correlation finding
$Finding = [PSCustomObject]@{
Type = "BruteForce"
Severity = "Critical"
TimeRange = @{ Start = [DateTime]; End = [DateTime] }
AffectedAccount = "jsmith"
SourceIP = "192.168.1.105"
EventCount = 47
Description = "47 failed login attempts in 30 seconds"
RecommendedAction = "Block IP, investigate account"
RelatedEvents = @()
}
Implementation Guide
Phase 1: Basic Event Querying (2-3 hours)
Goal: Query and display security events.
Steps:
- Use Get-WinEvent with FilterHashtable
- Parse common security event IDs
- Extract relevant fields from EventData
- Display in readable format
Phase 2: Event Parsing (2-3 hours)
Goal: Transform raw events into structured objects.
Steps:
- Create parsing functions for each event type
- Handle XML EventData extraction
- Normalize field names across events
- Add severity classification
Phase 3: Failed Login Analysis (2-3 hours)
Goal: Detect and report authentication failures.
Steps:
- Query 4625 events
- Group by account and source IP
- Calculate attempt rates
- Flag potential brute force
Phase 4: Correlation Engine (3-4 hours)
Goal: Link related events into findings.
Steps:
- Build time-window correlation
- Detect privilege escalation patterns
- Track service failures
- Generate finding objects
Phase 5: Reporting (2-3 hours)
Goal: Generate actionable reports.
Steps:
- Summary statistics
- Critical findings section
- HTML report generation
- Email delivery option
Phase 6: Performance Optimization (2-3 hours)
Goal: Handle large log volumes.
Steps:
- Optimize XPath queries
- Stream processing for memory efficiency
- Parallel analysis for multiple servers
- Caching for repeated analyses
Hints in Layers
Use these hints progressively - try to solve problems yourself before looking at the next layer.
Layer 1: Conceptual Hints
Hint: How to structure your query layer
Think of queries in three tiers:
- Fast path: FilterHashtable for simple ID + time queries
- Flexible path: XPath for complex conditions
- Fallback path: Where-Object only for post-processing
Hint: How to parse EventData efficiently
EventData is XML inside the event. Use $event.ToXml() to get it, then parse with [xml] cast. Store field mappings in a hashtable keyed by EventID.
Hint: How to implement sliding window detection
Sort events by time, then iterate with two pointers. For each event, find all events within the window. Use Group-Object first to reduce the search space.
Layer 2: Code Structure Hints
Hint: Module structure
SecurityEventAnalyzer/
SecurityEventAnalyzer.psm1
Public/
Get-SecurityEvents.ps1
Find-BruteForceAttempts.ps1
Private/
ConvertTo-SecurityEvent.ps1
Hint: Event type mapping
$EventTypeMap = @{
4624 = @{ Type = 'SuccessLogin'; Severity = 'Info' }
4625 = @{ Type = 'FailedLogin'; Severity = 'Warning' }
4672 = @{ Type = 'SpecialPrivilege'; Severity = 'Warning' }
4740 = @{ Type = 'AccountLocked'; Severity = 'Critical' }
}
Code Hints
Hint 1: Efficient Event Querying
function Get-SecurityEvents {
[CmdletBinding()]
param(
[Parameter()]
[int[]]$EventId,
[Parameter()]
[DateTime]$StartTime = (Get-Date).AddHours(-24),
[Parameter()]
[DateTime]$EndTime = (Get-Date),
[Parameter()]
[int]$MaxEvents = 10000
)
$filter = @{
LogName = 'Security'
StartTime = $StartTime
EndTime = $EndTime
}
if ($EventId) {
$filter['ID'] = $EventId
}
Write-Verbose "Querying Security log from $StartTime to $EndTime"
try {
Get-WinEvent -FilterHashtable $filter -MaxEvents $MaxEvents -ErrorAction Stop
}
catch [Exception] {
if ($_.Exception.Message -like "*No events were found*") {
Write-Verbose "No matching events found"
return @()
}
throw
}
}
# Usage:
$failedLogins = Get-SecurityEvents -EventId 4625 -StartTime (Get-Date).AddHours(-1)
Hint 2: Event Data Parsing
function ConvertTo-SecurityEvent {
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[System.Diagnostics.Eventing.Reader.EventLogRecord]$Event
)
process {
$xml = [xml]$Event.ToXml()
$eventData = @{}
$xml.Event.EventData.Data | ForEach-Object {
$eventData[$_.Name] = $_.'#text'
}
$eventType = switch ($Event.Id) {
4624 { 'SuccessLogin' }
4625 { 'FailedLogin' }
4634 { 'Logoff' }
4648 { 'ExplicitCredential' }
4672 { 'SpecialPrivileges' }
4720 { 'AccountCreated' }
4726 { 'AccountDeleted' }
4740 { 'AccountLocked' }
default { 'Unknown' }
}
$severity = switch ($Event.Id) {
4625 { 'Warning' }
4740 { 'Critical' }
4672 { 'Warning' }
4726 { 'Warning' }
default { 'Info' }
}
[PSCustomObject]@{
TimeCreated = $Event.TimeCreated
EventId = $Event.Id
EventType = $eventType
Severity = $severity
Account = $eventData['TargetUserName']
Domain = $eventData['TargetDomainName']
SourceIP = $eventData['IpAddress']
LogonType = [int]$eventData['LogonType']
Status = $eventData['Status']
SubStatus = $eventData['SubStatus']
Workstation = $eventData['WorkstationName']
ProcessName = $eventData['ProcessName']
Details = $eventData
}
}
}
# Usage:
$events = Get-SecurityEvents -EventId 4625 | ConvertTo-SecurityEvent
Hint 3: Brute Force Detection
function Find-BruteForceAttempts {
[CmdletBinding()]
param(
[Parameter()]
[int]$Hours = 24,
[Parameter()]
[int]$ThresholdCount = 10,
[Parameter()]
[int]$TimeWindowMinutes = 5
)
$failedLogins = Get-SecurityEvents -EventId 4625 -StartTime (Get-Date).AddHours(-$Hours) |
ConvertTo-SecurityEvent
Write-Verbose "Found $($failedLogins.Count) failed login events"
$byIP = $failedLogins | Where-Object { $_.SourceIP } | Group-Object SourceIP
$findings = foreach ($group in $byIP) {
$ip = $group.Name
$events = $group.Group | Sort-Object TimeCreated
for ($i = 0; $i -lt $events.Count; $i++) {
$windowStart = $events[$i].TimeCreated
$windowEnd = $windowStart.AddMinutes($TimeWindowMinutes)
$windowEvents = $events | Where-Object {
$_.TimeCreated -ge $windowStart -and $_.TimeCreated -le $windowEnd
}
if ($windowEvents.Count -ge $ThresholdCount) {
$accounts = $windowEvents | Select-Object -ExpandProperty Account -Unique
[PSCustomObject]@{
Type = 'BruteForce'
Severity = 'Critical'
SourceIP = $ip
StartTime = $windowStart
EndTime = ($windowEvents | Select-Object -Last 1).TimeCreated
AttemptCount = $windowEvents.Count
TargetAccounts = $accounts -join ', '
Description = "$($windowEvents.Count) failed logins from $ip in $TimeWindowMinutes minutes"
RecommendedAction = "Block IP $ip, investigate targeted accounts"
}
$i = $events.IndexOf(($windowEvents | Select-Object -Last 1))
}
}
}
return $findings | Sort-Object AttemptCount -Descending
}
Hint 4: Privilege Escalation Detection
function Find-PrivilegeEscalation {
[CmdletBinding()]
param(
[int]$Hours = 24
)
$startTime = (Get-Date).AddHours(-$Hours)
$privEvents = Get-SecurityEvents -EventId 4672 -StartTime $startTime |
ConvertTo-SecurityEvent
$sensitivePrivs = @(
'SeDebugPrivilege',
'SeTakeOwnershipPrivilege',
'SeLoadDriverPrivilege',
'SeBackupPrivilege',
'SeRestorePrivilege',
'SeImpersonatePrivilege'
)
$logins = Get-SecurityEvents -EventId 4624 -StartTime $startTime |
ConvertTo-SecurityEvent |
Group-Object Account
$normalUsers = $logins | Where-Object {
$_.Name -notmatch '^(SYSTEM|LOCAL SERVICE|NETWORK SERVICE|\$)' -and
$_.Name -notmatch 'admin'
} | Select-Object -ExpandProperty Name
$findings = foreach ($event in $privEvents) {
if ($event.Account -in $normalUsers) {
$privileges = $event.Details['PrivilegeList'] -split '\s*,\s*'
$sensitiveGranted = $privileges | Where-Object { $_ -in $sensitivePrivs }
if ($sensitiveGranted) {
[PSCustomObject]@{
Type = 'PrivilegeEscalation'
Severity = 'Critical'
TimeCreated = $event.TimeCreated
Account = $event.Account
Domain = $event.Domain
PrivilegesGranted = $sensitiveGranted -join ', '
Description = "Normal user $($event.Account) received sensitive privileges"
RecommendedAction = "Investigate account activity, verify if authorized"
}
}
}
}
return $findings
}
Hint 5: Report Generation
function New-SecurityReport {
[CmdletBinding()]
param(
[DateTime]$Date = (Get-Date).Date.AddDays(-1),
[string]$OutputPath = ".",
[string]$EmailTo
)
$reportDate = $Date.ToString("yyyy-MM-dd")
$startTime = $Date
$endTime = $Date.AddDays(1)
Write-Verbose "Generating security report for $reportDate"
$bruteForce = Find-BruteForceAttempts -Hours 24
$escalations = Find-PrivilegeEscalation -Hours 24
$allEvents = Get-SecurityEvents -StartTime $startTime -EndTime $endTime
$failedLogins = ($allEvents | Where-Object Id -eq 4625).Count
$successLogins = ($allEvents | Where-Object Id -eq 4624).Count
$css = @"
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h1 { color: #333; border-bottom: 2px solid #c00; }
table { border-collapse: collapse; width: 100%; margin: 20px 0; }
th { background: #333; color: white; padding: 10px; text-align: left; }
td { border: 1px solid #ddd; padding: 8px; }
.critical { background: #f8d7da; color: #721c24; font-weight: bold; }
.stat-box { display: inline-block; padding: 20px; margin: 10px; background: #f5f5f5; }
.stat-number { font-size: 2em; font-weight: bold; }
</style>
"@
$html = @"
<!DOCTYPE html>
<html>
<head><title>Security Report - $reportDate</title>$css</head>
<body>
<h1>Security Report - $reportDate</h1>
<div class="stat-box"><div class="stat-number">$($allEvents.Count)</div><div>Total Events</div></div>
<div class="stat-box"><div class="stat-number">$successLogins</div><div>Successful Logins</div></div>
<div class="stat-box"><div class="stat-number">$failedLogins</div><div>Failed Logins</div></div>
<h2>Critical Findings</h2>
"@
if ($bruteForce) {
$html += "<h3>Brute Force Attacks ($($bruteForce.Count))</h3>"
$html += $bruteForce | Select-Object SourceIP, AttemptCount, TargetAccounts | ConvertTo-Html -Fragment
}
if ($escalations) {
$html += "<h3>Privilege Escalations ($($escalations.Count))</h3>"
$html += $escalations | Select-Object TimeCreated, Account, PrivilegesGranted | ConvertTo-Html -Fragment
}
$html += "<hr><p><em>Generated: $(Get-Date)</em></p></body></html>"
$reportPath = Join-Path $OutputPath "SecurityReport_$reportDate.html"
$html | Out-File $reportPath -Encoding UTF8
Write-Output "Report saved to: $reportPath"
return [PSCustomObject]@{
ReportPath = $reportPath
TotalEvents = $allEvents.Count
CriticalFindings = ($bruteForce.Count + $escalations.Count)
}
}
Testing Strategy
Unit Tests
| Test | Steps | Expected |
|---|---|---|
| Query by ID | Get events with specific ID | Only matching IDs returned |
| Parse 4625 | Parse failed login event | All fields extracted |
| Time filter | Query with StartTime | Only events in range |
| Empty results | Query for non-existent events | Empty array, no error |
Integration Tests
| Test | Steps | Expected |
|---|---|---|
| Full analysis | Run 24-hour analysis | Report generated |
| Brute force detect | Generate 20 failed logins | Finding created |
| HTML report | Generate report | Valid HTML opens in browser |
Test Scenario Creation
# Simulate brute force - run on test machine
1..50 | ForEach-Object {
$cred = New-Object PSCredential "testuser", (ConvertTo-SecureString "wrongpassword$_" -AsPlainText -Force)
try { Start-Process notepad -Credential $cred -ErrorAction Stop }
catch { } # Expected to fail
}
# This creates 50 4625 events in Security log
Common Pitfalls and Debugging Tips
Pitfall 1: Access Denied to Security Log
Symptoms: Error “Access is denied”
Solutions:
# Check if running as admin
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
Write-Warning "Run PowerShell as Administrator to access Security log"
}
# Add user to Event Log Readers (run as admin)
Add-LocalGroupMember -Group "Event Log Readers" -Member "DOMAIN\username"
Pitfall 2: Slow Queries on Large Logs
Symptoms: Queries take minutes instead of seconds
Debugging:
# Measure query time
Measure-Command {
$events = Get-WinEvent -LogName Security -FilterHashtable @{ID=4625} -MaxEvents 100
}
# Compare with naive approach - this should be 10-100x slower
Measure-Command {
$events = Get-WinEvent -LogName Security -MaxEvents 1000 | Where-Object { $_.Id -eq 4625 }
}
Pitfall 3: Missing EventData Fields
Symptoms: Account, SourceIP, or other fields are null
Solutions:
# Always check if field exists
$account = if ($eventData.ContainsKey('TargetUserName')) {
$eventData['TargetUserName']
} else {
$eventData['SubjectUserName'] # Fallback
}
# Debug by inspecting raw XML
$event = Get-WinEvent -LogName Security -MaxEvents 1 -FilterHashtable @{ID=4625}
$event.ToXml() | Out-File debug.xml
Pitfall 4: Audit Policy Not Enabled
Symptoms: Expected events don’t exist in log
Debugging:
# Check current audit policy
auditpol /get /category:*
# Check if specific events are being generated
Get-WinEvent -LogName Security -MaxEvents 100 | Group-Object Id | Sort-Object Count -Descending
# Enable more auditing (requires admin)
auditpol /set /subcategory:"Logon" /success:enable /failure:enable
Pitfall 5: Memory Exhaustion
Symptoms: PowerShell crashes or becomes unresponsive
Solutions:
# Process in batches
$batchSize = 1000
$skip = 0
do {
$events = Get-WinEvent -LogName Security -MaxEvents $batchSize -FilterHashtable @{StartTime=(Get-Date).AddHours(-24)} |
Select-Object -Skip $skip -First $batchSize
# Process this batch
$events | ConvertTo-SecurityEvent | ForEach-Object { }
$skip += $batchSize
} while ($events.Count -eq $batchSize)
# Force garbage collection
[GC]::Collect()
Extensions and Challenges
Easy Extensions (1-2 hours each)
- Configuration File - Move thresholds to JSON config
- CSV Export - Export findings for SIEM import
- Console Colors - Color-coded severity output
Medium Extensions (3-5 hours each)
- Password Spraying Detection - One-password-many-accounts pattern
- Service Account Baseline - Detect new service account activity
- Scheduled Analysis - Run as Windows scheduled task
Advanced Extensions (8+ hours each)
- Multi-Server Analysis - Query logs from multiple servers
- Machine Learning Anomaly Detection - Statistical baseline
- Real-Time Monitoring - Subscribe to event log changes
Books That Will Help
| Topic | Book | Chapter/Section | Why This Helps |
|---|---|---|---|
| Windows Security Events | Windows Security Internals by James Forshaw | Part 2: “Security Auditing” | Deep understanding of how Windows generates security events |
| PowerShell Event Logs | PowerShell in Depth by Don Jones et al. | Chapter 21: “Working with Events” | Covers Get-WinEvent, FilterHashtable, XPath queries |
| PowerShell Fundamentals | Learn PowerShell in a Month of Lunches by Travis Plunk | Chapters 20-22 | Foundation for structuring your analyzer as a module |
| Security Operations | The Practice of Network Security Monitoring by Richard Bejtlich | Part II | Security monitoring mindset: baselines, anomalies, correlation |
| Threat Detection | Crafting the InfoSec Playbook by Jeff Bollinger | Chapter 5: “Developing Indicators” | How to develop detection rules and manage false positives |
| Attack Patterns | The Hacker Playbook 3 by Peter Kim | Various attack chapters | Understand attacks to detect them |
Self-Assessment Checklist
Conceptual Understanding
- I can explain what an .evtx file contains and how Windows generates security events
- I understand the difference between FilterHashtable and XPath and when to use each
- I can describe 5+ critical security event IDs and what they indicate
- I understand logon types and their security implications
- I can explain temporal correlation and how it detects brute force attacks
- I understand why Where-Object is slow for event log filtering
Implementation Quality
- Queries complete in reasonable time (<60 seconds for 24h of logs)
- All event data fields are properly extracted
- Brute force detection works correctly with configurable thresholds
- Privilege escalation detection identifies sensitive privileges
- HTML report is valid and readable
- No false positives for service accounts
Code Quality
- Module structure is clean with Public/Private separation
- Error handling is comprehensive
- Verbose output aids debugging
- Functions are documented with help comments
Final Verification Questions
- What’s the difference between event ID 4624 and 4625?
- Why is logon type 8 (NetworkCleartext) high risk?
- What does a Status of 0xC000006D mean in a 4625 event?
- How would you detect password spraying vs brute force?
- What privilege indicates potential credential dumping?
Resources
Official Documentation
Security References
Start by understanding the Windows Event Log architecture, then build your query layer, add detection logic, and finally implement reporting. By the end you’ll have a powerful security analysis tool.