Skip to main content
See Security Labs

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

Live-response triage with PowerShellProcess and network connection mapping (Get-Process, Get-NetTCPConnection)Registry persistence inspection (HKLM/HKCU Run keys)Persistence eradication (Remove-ItemProperty, Remove-Item, Stop-Process)Baseline diffing with Compare-Object

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

PowerShellGet-ProcessGet-NetTCPConnectionCompare-ObjectRegistry triage

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-Process

2Inspect 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, Id

4Find 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 match

5Enumerate 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-NetTCPConnection

6Project 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, OwningProcess

7Confirm 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 1672

8Kill 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-Process

9Browse 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.exe

12List 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 baseline

13Snapshot 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.txt
Select-Object -ExpandProperty Nameflatten objects to a list of name strings
Out-Filewrite the pipeline to a text file

14Snapshot 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.txt

15Sanity-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 10

16Load 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.txt

17Diff 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 $servicesnow
Compare-Objectdiff two object sets
SideIndicator <=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 $schedulednow

Key findings

calcache.exe (PID 1672) running out of C:\Users\Sec504\AppData\Local\Temp
Outbound TCP from the host to 23.11.32.159:80 owned by PID 1672 / 484
HKCU\Software\Microsoft\Windows\CurrentVersion\Run\Calcache pointed at the Temp binary
Baseline diff revealed a rogue 'Dynamics' service
Baseline diff revealed a rogue 'Microsoft eDynamics' scheduled task

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.

Evidence gallery

Lab 1.1 - PowerShell Live Investigation | Luis Javier Lozoya