SEC504 - Hacker Tools, Techniques, and Incident Handling
Lab 1.1 - PowerShell Live Investigation
Solo, Lab
Focus: Incident Response
Level: SEC504
Date: May 2026
Artifacts: Sanitized PowerShell console screenshots from a compromised Windows lab host (Sec504 workstation)
TL;DR
- •Found calcache.exe (PID 1672) running from %TEMP% via Where-Object -Property Path -Like *temp*
- •Mapped its outbound connection to 23.11.32.159:80 with Get-NetTCPConnection
- •Removed the HKCU Run-key persistence, killed the process, deleted the binary, then used Compare-Object to confirm the rogue 'Dynamics' service and 'Microsoft eDynamics' scheduled task
Skills demonstrated
Note: Course-provided PCAPs and lab instructions are not shared. Only my own captures and sanitized notes are published.
Why this matters
When a Windows host is suspected of compromise, the first 30 minutes matter. PowerShell gives a responder one console that covers process triage, network state, registry inspection, and baseline diffing without installing a single third-party tool. Every cmdlet in this lab is something an attacker can also see and use, which is exactly why blue-team fluency in it is non-negotiable.
Context
This lab demonstrates the SEC504 live-investigation workflow on a Windows host: use PowerShell to enumerate processes, network connections, and registry persistence; identify a suspicious binary (calcache.exe) running from %TEMP% with a Run-key entry; eradicate the persistence; and then compare current services, scheduled tasks, and local users against a saved baseline to surface attacker artifacts.
Tools used
Steps taken
1Stage the lab and baseline processes
Ran the SEC504-provided setup script to prime the host with a simulated compromise, then dropped straight into Get-Process. The baseline output is the responder's first situational-awareness pass: every running process, with handles, working set, CPU seconds, and PID, in one screen.
$ ./live-investigation-setup.ps1
$ Get-Process2Inspect a known-good process for shape
Pulled the lsass process to anchor what a legitimate Windows process looks like: signed by Microsoft Corporation, path C:\WINDOWS\system32\lsass.exe, FileVersion 10.0.19041.1586. Knowing the canonical shape of trusted processes is what lets you spot the outlier on the next pass.
$ Get-Process lsass | Select-Object -Property *Select-Object -Property *dump every property the process object exposes (Path, FileVersion, Company, ProductVersion, ...)3Narrow to Path, Name, Id
Trimmed the projection to the three properties that matter for triage at scale: where it runs from, what it is called, and its PID. This is the projection used in every subsequent Where-Object filter.
$ Get-Process lsass | Select-Object -Property Path, Name, Id4Find processes running out of TEMP
Filtered the projection by path with -Like "*temp*". One hit: calcache.exe, PID 1672, running from C:\Users\Sec504\AppData\Local\Temp\calcache.exe. Anything executing from a user-writable Temp directory deserves the next five minutes of your attention.
$ Get-Process | Select-Object -Property Path, Name, Id | Where-Object -Property Path -Like "*temp*"Where-Objectfilter pipeline objects by a predicate-Property Paththe property to test-Like "*temp*"case-insensitive wildcard match5Enumerate active TCP connections
Get-NetTCPConnection lists every listening, bound, and established TCP socket on the box. The raw output is busy: many Listen rows from system services, plus the established sessions that actually matter for incident response.
$ Get-NetTCPConnection6Project the columns that map to OwningProcess
Trimmed Get-NetTCPConnection to LocalAddress, LocalPort, State, OwningProcess. The Established rows are the lead: 192.168.182.132:1593 talking to 23.11.32.159:80 with OwningProcess 484, and 192.168.182.132:4444 listening under PID 1672 (calcache.exe). PID 1672 has a bound listener and an outbound to the same external IP, which is the canonical shape of a reverse-shell beacon.
$ Get-NetTCPConnection | Select-Object -Property LocalAddress, LocalPort, State, OwningProcess7Confirm PID 1672 maps to calcache.exe
Joined the two leads by filtering Get-Process on Id -eq 1672. Confirmed: PID 1672 is calcache.exe, sitting in %TEMP%, with a network footprint visible above. That is enough to act.
$ Get-Process | Select-Object -Property Path, Name, Id | Where-Object -Property Id -eq 16728Kill the malicious process
Piped the same filter into Stop-Process to terminate calcache.exe. Pipelining the kill onto the filter is safer than typing the PID by hand, since the predicate guarantees you are stopping the right process even if PIDs have rolled.
$ Get-Process | Select-Object -Property Path, Name, Id | Where-Object -Property Id -eq 1672 | Stop-Process9Browse the HKCU registry hive
Get-ChildItem HKCU: lists the top-level keys under HKEY_CURRENT_USER (AppEvents, Console, Environment, Software, ...). PowerShell drives the registry like a filesystem, which is what makes registry triage scriptable.
$ Get-ChildItem HKCU:10Hunt for Run-key persistence
Read HKLM and HKCU CurrentVersion\Run with Get-ItemProperty. HKLM looked clean (SecurityHealth, VMware User Process). HKCU contained the smoking gun: a Calcache value pointing to C:\Users\Sec504\AppData\Local\Temp\calcache.exe. Auto-run from a user-writable path is one of the most common persistence techniques and one of the easiest to spot once you know where to look.
$ Get-ItemProperty "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run"
$ Get-ItemProperty "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce"
$ Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run"
$ Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\RunOnce"11Eradicate the persistence and the binary
Removed the Calcache value from HKCU Run, then deleted calcache.exe from %TEMP%. Removing the registry entry first matters: if you delete the binary first the entry still fires at next logon and the user gets a 'file not found' shell pop that tips off the attacker.
$ Remove-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name "Calcache"
$ Remove-Item $env:temp\calcache.exe12List the saved baseline
The lab ships with a clean-state baseline under .\baseline: services.txt, scheduledtasks.txt, localusers.txt. These are the 'before' snapshots that Compare-Object will diff against the current host.
$ Get-ChildItem baseline13Snapshot services to a file
Captured the current service inventory with Get-Service | Select-Object -ExpandProperty Name | Out-File services.txt. -ExpandProperty unwraps the Name property into a flat string list, which is the shape Compare-Object expects.
$ Get-Service | Select-Object -ExpandProperty Name | Out-File services.txtSelect-Object -ExpandProperty Nameflatten objects to a list of name stringsOut-Filewrite the pipeline to a text file14Snapshot scheduled tasks and local users
Did the same flatten-and-write pass for Get-ScheduledTask (TaskName) and Get-LocalUser (Name). Three files now describe the current host: services.txt, scheduledtasks.txt, localusers.txt.
$ Get-ScheduledTask | Select-Object -ExpandProperty TaskName | Out-File scheduledtasks.txt
$ Get-LocalUser | Select-Object -ExpandProperty Name | Out-File localusers.txt15Sanity-check the snapshot
Get-Content -First 10 prints the first ten lines of services.txt. Visual sanity check before the diff, so you know the file actually captured service names and not, for example, a Format-Table header.
$ Get-Content .\services.txt -First 1016Load baseline and current snapshots into variables
Stored the current and baseline service lists in $servicesnow and $servicebaseline. Variables make the next Compare-Object call readable.
$ $servicesnow = Get-Content .\services.txt
$ $servicebaseline = Get-Content .\baseline\services.txt17Diff services against baseline
Compare-Object surfaced one row: Dynamics, SideIndicator =>. The => arrow means 'present on the right side (current) but not on the left (baseline)'. A new service named Dynamics installed since the baseline was taken is exactly the kind of low-frequency, high-confidence signal a responder lives for.
$ Compare-Object $servicebaseline $servicesnowCompare-Objectdiff two object setsSideIndicator <=only in reference (baseline)SideIndicator =>only in difference (current)18Diff scheduled tasks against baseline
Same pattern for Get-ScheduledTask: $schedulednow vs $Scheduledbaseline. Compare-Object returned 'Microsoft eDynamics' with SideIndicator =>. A rogue task named to look like a Microsoft component, paired with a rogue Dynamics service, is the persistence pattern operators use when they expect a defender to scan the names list and skim past anything that starts with 'Microsoft'.
$ $schedulednow = Get-Content .\scheduledtasks.txt
$ $Scheduledbaseline = Get-Content .\baseline\scheduledtasks.txt
$ Compare-Object $Scheduledbaseline $schedulednowKey findings
Outcome / Lessons learned
Walked the full triage-to-eradication loop on a compromised Windows host with nothing but built-in PowerShell. Found calcache.exe running from %TEMP%, tied it to a beacon on 23.11.32.159:80, killed the process, removed its HKCU Run-key persistence and the binary, and confirmed two additional adversary artifacts (the Dynamics service and the Microsoft eDynamics scheduled task) by diffing against a baseline.
Bake the baseline-and-diff pattern into a recurring job: snapshot services, scheduled tasks, local users, and Run-key contents on every endpoint nightly and store the artifacts centrally. Forward calcache.exe and its IOC set (filename, hash, parent path under %TEMP%, outbound to 23.11.32.159) into EDR allow/block lists and the SIEM watchlist. For containment, pair the PowerShell triage with Disable-NetAdapter or a network-isolation policy so the host can be quarantined without losing visibility for the responder.
Security controls relevant
- Endpoint baselining (services, scheduled tasks, Run keys)
- Application control / WDAC denying execution from user-writable Temp directories
- PowerShell script-block logging and transcripts (Event IDs 4104, 4105)
- EDR with auto-isolation on outbound-beacon detection
- Privileged-account separation so a user-context Run key cannot escalate to LocalSystem
What I took away from this
Where-Object -Property Path -Like "*temp*" is the single most underrated triage filter on Windows. The vast majority of commodity malware lives in %TEMP%, %APPDATA%\Local\Temp, or %APPDATA%\Roaming because those paths are user-writable without elevation. If you run that one filter every time you sit down at a suspect host, you will catch a meaningful fraction of attacks in under thirty seconds.
The order of eradication matters more than people think. Remove the persistence registry value first, then kill the process, then delete the binary. If you reverse that order the Run-key still fires at next logon and creates a noisy 'file not found' shell that tips off the operator. The whole point of incident response is to keep the attacker from learning that you have learned about them.
Compare-Object against a saved baseline is the closest thing to a free EDR you have on a fresh Windows install. It will not catch fileless or in-memory threats, but for the persistence-via-service and persistence-via-scheduled-task patterns that still dominate commodity intrusions, a nightly snapshot plus a morning diff is the highest-signal cheap detection you can run.