Lab 1.1 - PowerShell Live Investigation
Used PowerShell as a live-response tool against a compromised Sec504 lab host: pivoted from Get-Process to Get-NetTCPConnection to map calcache.exe (PID 1672) running from %TEMP% and beaconing to 23.11.32.159:80, stopped the process, removed its HKCU Run-key persistence and the binary itself, then ran Compare-Object against baseline service/scheduled-task snapshots to surface a rogue 'Dynamics' service and 'Microsoft eDynamics' scheduled task.
Commands
1. Stage 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
2. Inspect 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 *
3. Narrow 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
4. Find 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*"
5. Enumerate 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
6. Project 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
7. Confirm 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
8. Kill 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
9. Browse 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:
10. Hunt 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"
11. Eradicate 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
12. List 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
13. Snapshot 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
14. Snapshot 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
15. Sanity-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
16. Load 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
17. Diff 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
18. Diff 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
Security Controls
- 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