Kansa Hands on Labs¶
- No installation
- Guided scenario
- One Windows VM
- Saved evidence and finding
Interactive Lab¶
Kansa Interactive Lab
Investigate an unusual process under C:\Users\Public on WIN11-LAB.
Explain what you found
You found chrome_update.exe in the Public folder. Choose the conclusion that fits its file location and how it was started. These are reasons to investigate, but they do not prove malware. ParentProcessId is the ID number of the process that started it. Look up that number in other records; it does not tell you the parent program's name by itself.
Choose what the collected details tell you, without guessing what they prove.
- Observation: what you saw
- The fictional row shows chrome_update.exe under C:\Users\Public, process ID 4216 and parent process ID 3180.
- Interpretation: what it may mean
- The file location and launch details need checking. The parent ID alone does not name the program that started it.
- What remains unknown
- This row matches your search. It does not establish whether the file is malware.
- What to do next
- Keep the original table and logs. Look up parent ID 3180 in the full process list and other records. Check the times because process IDs can be reused. Examine the file and its hash, a file fingerprint.
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
Full Lab¶
Kansa Full Lab
Case KANSA-01 / before and after
One VM. Two collections. A finding you can explain.
A triage note mentions chrome_update.exe in C:\Users\Public. Collect a process baseline, start a harmless stand in on the same Windows VM, collect again, and show what changed. The filename is a lead, not a malware verdict.
Tick a checkpoint only after saving its evidence. Progress is self reported; this page cannot check your VM.
Investigation brief¶
One Windows VM. Kansa collects through localhost; you compare two saved process snapshots.
Real case note: This lab gives you a training baseline. In an incident, you may only have a live result to check against other evidence.
Before you start¶
Need: a disposable Windows 10 or 11 VM, administrator access, Windows PowerShell 5.1, and Kansa at C:\Tools\Kansa. Start from a clean VM or snapshot.
Run: copy the PowerShell blocks in order in one administrator window. Check each Look for result before continuing, and keep the training window open for the second collection.
Need setup help?
Follow Install Kansa, then the local WinRM steps in Remoting and modules. Review kansa.ps1 and Modules\Process\Get-ProcsWMI.ps1 before running them. If you plan to restore the VM snapshot, arrange a way to copy the case folder outside the VM first.
Activity 1: Prepare the VM¶
1. Create a new case folder on the Windows VM
Open Windows PowerShell as administrator on the VM. Keep this window open through Activity 2. These checks confirm the tool and shell before creating a new case folder.
$kansaCase = 'C:\Cases\Kansa-Local-01'
if ($PSVersionTable.PSVersion.ToString() -notlike '5.1.*') { throw 'Open Windows PowerShell 5.1 for this lab.' }
Set-Location 'C:\Tools\Kansa' -ErrorAction Stop
if (-not (Test-Path -LiteralPath '.\kansa.ps1')) { throw 'Kansa runner not found.' }
if (-not (Test-Path -LiteralPath '.\Modules\Process\Get-ProcsWMI.ps1')) { throw 'Process module not found.' }
if (Test-Path -LiteralPath $kansaCase) { throw 'Case folder already exists. Choose a new folder.' }
New-Item -ItemType Directory -Path $kansaCase -ErrorAction Stop | Out-Null
@(
"Computer: $env:COMPUTERNAME"
"Local time: $(Get-Date -Format o)"
"Time zone: $((Get-TimeZone).Id)"
"PowerShell: $($PSVersionTable.PSVersion)"
'Target: localhost'
'Module: Modules\Process\Get-ProcsWMI.ps1'
) | Set-Content -LiteralPath "$kansaCase\case-context.txt"
if (Test-Path -LiteralPath '.\.git') {
git rev-parse HEAD | Set-Content -LiteralPath "$kansaCase\kansa-revision.txt"
}
Get-Content -LiteralPath "$kansaCase\case-context.txt"
Look for: case-context.txt with your VM's name and time. If Kansa came from a ZIP rather than Git, add the download URL, date, and archive name to that file. Check that C:\Users\Public\chrome_update.exe is absent; if it is already there, inspect it and use a fresh VM snapshot for this lab.
2. Check local PowerShell Remoting
Kansa reaches the same VM through WinRM. Run this test from the elevated PowerShell window:
Test-WSMan -ComputerName localhost
Invoke-Command -ComputerName localhost -Authentication Default -ErrorAction Stop -ScriptBlock {
[pscustomobject]@{
HostName = $env:COMPUTERNAME
Time = Get-Date -Format o
}
} | Export-Csv -LiteralPath "$kansaCase\localhost-check.csv" -NoTypeInformation
Import-Csv -LiteralPath "$kansaCase\localhost-check.csv" | Format-Table
Look for: a WinRM response and one row with this VM's name and time. If the test fails, follow the local setup in Remoting and modules: check the network profile, run Enable-PSRemoting -Force on this disposable VM when appropriate, then repeat the test. Record any network profile or WinRM change so you can reverse it later. No TrustedHosts entry is needed for localhost.
$kansaCase again and return to C:\Tools\Kansa.Next: capture the VM's running processes.
Continue to collectionActivity 2: Collect before and after¶
Before running Kansa, predict what the two saved process files will say about C:\Users\Public\chrome_update.exe. What would a matching row prove, and what would it leave unanswered?
1. Collect the baseline before creating the training file
Confirm the training file is absent, then run only the Get-ProcsWMI collector. -Target localhost confines this run to the current VM. A baseline is your saved starting state; TSV is a text table with tabs between its columns.
$kansaSeed = 'C:\Users\Public\chrome_update.exe'
if (Test-Path -LiteralPath $kansaSeed) { throw 'Training path already exists. Stop and inspect it.' }
Set-Location 'C:\Tools\Kansa'
$kansaExistingRuns = @(Get-ChildItem -Path . -Directory -Filter 'Output_*' |
Select-Object -ExpandProperty FullName)
.\kansa.ps1 -Target localhost -ModulePath '.\Modules\Process\Get-ProcsWMI.ps1' -Authentication Default -OutputFormat TSV -Transcribe -Verbose
$kansaNewRuns = @(Get-ChildItem -Path . -Directory -Filter 'Output_*' |
Where-Object { $_.FullName -notin $kansaExistingRuns })
if ($kansaNewRuns.Count -ne 1) { throw 'Expected one new Output_ folder. Inspect the Kansa run.' }
$kansaBaselineRun = $kansaNewRuns[0].FullName
$kansaBaselineRun
Kansa writes a timestamped Output_* folder. The commands above select the folder created by this run. Preserve the whole folder, including any transcript or Error.Log:
if (Test-Path -LiteralPath "$kansaCase\baseline") { throw 'Baseline already saved. Do not overwrite it.' }
Copy-Item -LiteralPath $kansaBaselineRun -Destination "$kansaCase\baseline" -Recurse -ErrorAction Stop
$kansaBaselineRun | Set-Content -LiteralPath "$kansaCase\baseline-run-path.txt"
Get-ChildItem -LiteralPath "$kansaCase\baseline" -Recurse -File | Select-Object FullName, Length
Look for: a process TSV, usually inside baseline\ProcsWMI. Open it in Notepad: the first line names the columns, and the following lines should contain processes. A file containing only column headings is an empty result. Check the transcript and Error.Log if present. Some processes may have blank paths or hashes; note those gaps. Fix an empty collection before starting the training process.
2. Start one harmless training process
This copies the VM's own cmd.exe under the training filename. /k keeps the command window open and title labels it. It downloads no program. Run it only on the disposable VM, then leave the labelled window open until the second collection finishes.
if (Test-Path -LiteralPath $kansaSeed) { throw 'Training path already exists. Stop and inspect it.' }
Copy-Item -LiteralPath "$env:SystemRoot\System32\cmd.exe" -Destination $kansaSeed -ErrorAction Stop
$kansaSeedProcess = Start-Process -FilePath $kansaSeed -ArgumentList '/k title KANSA-01 harmless training process' -PassThru
$kansaSeedProcess | Select-Object Id, ProcessName, StartTime |
Out-String | Set-Content -LiteralPath "$kansaCase\training-process.txt"
Get-FileHash -Algorithm SHA256 -LiteralPath $kansaSeed |
Export-Csv -LiteralPath "$kansaCase\training-file-sha256.csv" -NoTypeInformation
$kansaSeedProcess | Select-Object Id, ProcessName, StartTime
Look for: a labelled command window and a process ID (PID). The path is intentionally unusual here; this exercise does not make it malicious.
3. Collect again while the training window is open
Run the same Kansa command from the same VM. Record the new Output_* folder separately from the baseline.
Set-Location 'C:\Tools\Kansa'
$kansaExistingRuns = @(Get-ChildItem -Path . -Directory -Filter 'Output_*' |
Select-Object -ExpandProperty FullName)
.\kansa.ps1 -Target localhost -ModulePath '.\Modules\Process\Get-ProcsWMI.ps1' -Authentication Default -OutputFormat TSV -Transcribe -Verbose
$kansaNewRuns = @(Get-ChildItem -Path . -Directory -Filter 'Output_*' |
Where-Object { $_.FullName -notin $kansaExistingRuns })
if ($kansaNewRuns.Count -ne 1) { throw 'Expected one new Output_ folder. Inspect the Kansa run.' }
$kansaAfterRun = $kansaNewRuns[0].FullName
$kansaBaselineRun = Get-Content -LiteralPath "$kansaCase\baseline-run-path.txt"
if ($kansaAfterRun -eq $kansaBaselineRun) { throw 'Select the new run, not the baseline.' }
if (Test-Path -LiteralPath "$kansaCase\after") { throw 'Second run already saved. Do not overwrite it.' }
Copy-Item -LiteralPath $kansaAfterRun -Destination "$kansaCase\after" -Recurse -ErrorAction Stop
$kansaAfterRun | Set-Content -LiteralPath "$kansaCase\after-run-path.txt"
Get-ChildItem -LiteralPath "$kansaCase\after" -Recurse -File | Select-Object FullName, Length
Check that the second run has a nonempty process TSV. Read its transcript and Error.Log if present. A warning about an inaccessible process is different from an empty or failed collection; record the warning and continue if the expected rows were captured. If you must retry, retain the failed run in a separately named folder and record why.
Optional: hash the saved Kansa output
Keep the hash list outside the two folders it describes:
Get-ChildItem -LiteralPath "$kansaCase\baseline", "$kansaCase\after" -Recurse -File |
Get-FileHash -Algorithm SHA256 |
Export-Csv -LiteralPath "$kansaCase\kansa-output-sha256.csv" -NoTypeInformation
These SHA-256 hashes identify the saved files. They do not prove that every live process was captured. The collector's own Hash field is an MD5 of the process image, where available.
Next: find the changed row and follow its parent.
Investigate the saved resultsActivity 3: Investigate and report¶
The answer is now in the saved files. These are two live collections taken at different times; other ordinary processes may also change between them.
Returning in a new PowerShell window?
Open Windows PowerShell on the same VM and restore the two variables used below. Use your actual case path if you changed it. Then run Activity 3 from Step 1 to load the saved files; you do not need to collect again.
1. Open and check the two saved process files
Find the one TSV in each saved folder. Kansa may name the file with localhost or the VM name. Confirm both names are consistent with the saved localhost check. If either folder has zero or multiple TSVs, inspect the run before choosing a file.
$kansaBaselineFiles = @(Get-ChildItem -LiteralPath "$kansaCase\baseline" -Recurse -File -Filter '*.tsv')
$kansaAfterFiles = @(Get-ChildItem -LiteralPath "$kansaCase\after" -Recurse -File -Filter '*.tsv')
if ($kansaBaselineFiles.Count -ne 1 -or $kansaAfterFiles.Count -ne 1) {
throw 'Expected one process TSV in each saved run. Inspect the folders and logs.'
}
$kansaBaselineRows = @(Import-Csv -LiteralPath $kansaBaselineFiles[0].FullName -Delimiter ([char]9))
$kansaAfterRows = @(Import-Csv -LiteralPath $kansaAfterFiles[0].FullName -Delimiter ([char]9))
if ($kansaBaselineRows.Count -eq 0 -or 'ExecutablePath' -notin $kansaBaselineRows[0].PSObject.Properties.Name) {
throw 'Empty or unexpected baseline TSV. Check the file, delimiter, and log.'
}
if ($kansaAfterRows.Count -eq 0 -or 'ExecutablePath' -notin $kansaAfterRows[0].PSObject.Properties.Name) {
throw 'Empty or unexpected second TSV. Check the file, delimiter, and log.'
}
foreach ($kansaFile in @($kansaBaselineFiles[0], $kansaAfterFiles[0])) {
if ($kansaFile.Name -notlike 'localhost-*' -and
$kansaFile.Name -notlike "$($env:COMPUTERNAME)-*") {
throw 'A TSV filename does not match localhost or this VM. Check both runs.'
}
}
"Baseline: $($kansaBaselineRows.Count) process rows"
"After: $($kansaAfterRows.Count) process rows"
$kansaAfterRows | Select-Object -First 5 Name, ProcessId | Format-Table
Look for: a positive row count for each run and a preview of five processes. Your totals will vary. You have now loaded real output from your VM; the original TSVs remain in their saved folders.
2. Count the matching rows before and after
Filter on the full executable path, then save a compact comparison. Run this in the same PowerShell window as Step 1:
$kansaBaselineHits = @($kansaBaselineRows | Where-Object ExecutablePath -EQ $kansaSeed)
$kansaAfterHits = @($kansaAfterRows | Where-Object ExecutablePath -EQ $kansaSeed)
@(
[pscustomobject]@{Phase='Baseline'; Rows=$kansaBaselineRows.Count; Matches=$kansaBaselineHits.Count; Source=$kansaBaselineFiles[0].FullName}
[pscustomobject]@{Phase='After'; Rows=$kansaAfterRows.Count; Matches=$kansaAfterHits.Count; Source=$kansaAfterFiles[0].FullName}
) | Export-Csv -LiteralPath "$kansaCase\comparison.csv" -NoTypeInformation
Import-Csv -LiteralPath "$kansaCase\comparison.csv" | Format-Table
Expected result: zero matches in the baseline and one in the second run if the labelled window was open during collection. That shows Kansa saw the controlled process at collection time. Closing the window later does not change a saved TSV.
My result is different
| What you see | What to check |
|---|---|
| Zero matches in both runs | Was the training window open during the second collection? Search its PID from training-process.txt in the second TSV; a missing path may explain the failed path search. |
| A match in the baseline | Check the run times and source folders. You may have used an earlier lab run or started the process before taking the baseline. |
| More than one match after | Inspect every matching PID and command line. There may be more than one training window. |
| No process rows | Keep the logs and resolve the collection failure. This cannot support an absence finding. |
Keep the original runs and record the actual result. If you repeat collection, save it under a new name such as after-retry-1 and identify that source in your note.
3. Inspect the row and its parent
A matching path is the start of an investigation. Read its command line and parent process ID from the saved second run:
$kansaAfterHits |
Select-Object Name, ExecutablePath, ProcessId, ParentProcessId, CommandLine, CreationDate, Username, Hash |
Format-List
foreach ($kansaHit in $kansaAfterHits) {
"Parent candidate for child PID $($kansaHit.ProcessId):"
$kansaParentId = $kansaHit.ParentProcessId
$kansaAfterRows | Where-Object ProcessId -EQ $kansaParentId |
Select-Object Name, ProcessId, ExecutablePath, CommandLine, CreationDate | Format-List
}
Look for: the training title in CommandLine and a parent candidate such as the PowerShell process you used to launch it. Compare the two CreationDate values: a parent candidate created after the child cannot be its original parent. A missing parent row may mean the parent exited. Windows can reuse process IDs, so record the timing and any uncertainty.
The collector's Hash is MD5, so compare it only with another MD5. The SHA-256 you saved identifies the training file separately.
4. Write a short case handover
Use your actual counts, file paths, and collection times from the two runs. Paste this template into Notepad and save it as handover.txt inside your $kansaCase folder.
Question: Did the training process appear after the controlled change?
VM name and time zone:
Kansa source or revision, module, and exact command:
Baseline: collection time, TSV path, row count, matching rows.
After: collection time, TSV path, row count, matching rows.
Supporting row: process ID, path, command line, and hash if present.
Parent process: observed name and path, or not present in the saved snapshot.
Meaning: What does the change from baseline to second run show?
Limits: What can these two live snapshots not establish?
Errors or missing fields:
Next check:
Explain the zero carefully: no baseline match means the collector returned no matching row at that moment. It does not prove the file never existed or never ran. For a real alert, you could next check the file's provenance, event records, and parent process context.
Case closeout5. Verify evidence and clean up the VM
Check that comparison.csv, handover.txt, and both run folders are in the case folder. Close the labelled command window before removing the training file.
Then confirm the process ended and the file still matches its saved hash:
$kansaSeed = 'C:\Users\Public\chrome_update.exe'
if (Get-Process -Name chrome_update -ErrorAction SilentlyContinue) { throw 'Close the labelled training window first.' }
$kansaRecordedHash = (Import-Csv -LiteralPath "$kansaCase\training-file-sha256.csv").Hash
if ((Get-FileHash -Algorithm SHA256 -LiteralPath $kansaSeed).Hash -ne $kansaRecordedHash) {
throw 'Training file changed. Inspect it before cleanup.'
}
Remove-Item -LiteralPath $kansaSeed
Record the closeout: note the cleanup and any WinRM or network profile change you reversed in the handover. Before restoring a VM snapshot, copy the complete case folder outside the VM and open the copy.
Finish with a note someone else can verify from your files.
Review the evidence checklistFull Lab evidence checklist¶
- 1Context
case-context.txt,localhost-check.csv, and the Kansa source or revision (kansa-revision.txtif created) - 2Collections
baseline/andafter/with nonempty process TSVs and any run logs;training-process.txtandtraining-file-sha256.csv - 3Finding
comparison.csvandhandover.txt, with collection times, errors, limits, and a next check
Before snapshot rollback: keep failed or retried runs with an explanation, then copy and open the complete case folder outside the VM.