Eric Zimmerman Tools Hands on Labs¶
Choose one browser simulation for focused practice, then complete the Full Lab to collect real Windows traces and explain a finding in a disposable training VM.
- No installation
- Guided parser
- No installation
- Guided viewer
- Isolated VM
- Three guided activities
PECmd Interactive Lab¶
JumpList Explorer Interactive Lab¶
Full Lab¶
Eric Zimmerman Tools Full Lab
Case SUSA-EZ-060 / follow the file
Open a file. Find its traces. Explain what they show.
The support team asks whether Windows records link Notepad to a handover file. Create harmless activity in a training VM, then investigate it with PECmd and JumpList Explorer. Leave a small case folder another analyst can check.
Tick each checkpoint after saving its evidence. Progress is self reported; this page cannot check your VM.
Investigation brief¶
Find an execution related trace with PECmd and a file reference with JumpList Explorer. Compare their paths and times. Record missing results too.
Real case note: This is a small live triage exercise. In an incident, collect existing artefacts under your evidence plan, usually from an acquired image or approved collection. Create training activity only in this disposable VM. Live files can change while being copied; this lab's hashes check the collected copies, not a complete disk acquisition.
Before you start¶
Need: a disposable Windows 10 or 11 VM, a clean VM snapshot, Windows PowerShell 5.1, PECmd and JumpList Explorer installed, and space for a case folder. Keep storage outside the VM available for the finished case.
Follow: use one administrator PowerShell window inside the VM, elevated with the same account that will open Notepad. Run the blocks in order. Expand one step at a time and check its Look for result. The browser examples above are fictional; their downloads are not native Prefetch or Jump List files.
Which tool paths and versions should I use?
Find the actual PECmd.exe and JumpListExplorer.exe from your installed release. In File Explorer, use Copy as path and replace the two example paths inside the single quotes in the first block. Remove any double quotes added by Copy as path before pasting. Keep the whole release folder; do not move just its executable. Follow the installation guide for the required .NET runtime. Record your installed versions, rather than trying to match the browser simulation's version numbers.
For a course supplied evidence package, follow its case sheet and source manifest. Do not run the file creation steps on an incident computer or assume this exercise's target exists in another case.
Activity 1: Create and collect¶
1. Set up the case and open the training file
Edit only the two tool paths before running this block. It stops if either lab folder already exists. For another attempt, choose a fresh case number and folders; keep previous evidence.
$ezPECmd = 'C:\Tools\EZ\PECmd.exe' # Replace with your installed path
$ezJumpList = 'C:\Tools\EZ\JumpListExplorer.exe' # Replace with your installed path
$ezCase = 'C:\Cases\SUSA-EZ-060'
$ezTraining = Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'SUSA-EZ-060'
$ezTarget = Join-Path $ezTraining 'handover.txt'
foreach ($ezTool in @($ezPECmd, $ezJumpList)) {
if (-not (Test-Path -LiteralPath $ezTool -PathType Leaf)) { throw "Check the tool path: $ezTool" }
}
if ((Test-Path -LiteralPath $ezCase) -or (Test-Path -LiteralPath $ezTraining)) { throw 'Lab folder exists. Use a fresh case and training folder.' }
New-Item -ItemType Directory -Path $ezCase, $ezTraining, "$ezCase\sources", "$ezCase\working", "$ezCase\parsed", "$ezCase\notes", "$ezCase\exports" -ErrorAction Stop | Out-Null
'Training handover: release window 17:00 UTC.' | Set-Content -LiteralPath $ezTarget -Encoding UTF8
@(
'Case SUSA-EZ-060; disposable VM; question: what links Notepad to handover.txt?'
"Computer: $env:COMPUTERNAME; account: $env:USERDOMAIN\$env:USERNAME"
"Windows: $([Environment]::OSVersion.VersionString); time zone: $((Get-TimeZone).Id)"
"Setup UTC: $((Get-Date).ToUniversalTime().ToString('o')); target: $ezTarget"
"PECmd: $ezPECmd; JumpList Explorer: $ezJumpList"
) | Set-Content -LiteralPath "$ezCase\notes\case-context.txt"
Get-FileHash -Algorithm SHA256 -LiteralPath $ezPECmd, $ezJumpList |
Export-Csv -LiteralPath "$ezCase\notes\tool-sha256.csv" -NoTypeInformation
$ezTarget
Now open Notepad from Start. Choose File > Open, select All files if needed, and open the full path printed above. Read the training line, then close Notepad. Save the approximate start and finish times in notes\case-context.txt, including the VM's time zone. Do not replace those times with an artefact timestamp later.
Open JumpList Explorer and record its version from Help > About or its version display. Run & $ezPECmd -h in the same PowerShell window and record the version it reports. Close the viewer for now.
Look for: your file contains release window 17:00 UTC; the case notes identify the host, account, tool versions and time window. Keep this PowerShell window open.
2. Collect Prefetch and this user's Jump Lists
Prefetch files can hold execution related records for a program. Jump Lists can hold an application's recent file references. You need both to compare their different roles. Wait briefly after closing Notepad, then run this block once. It records skipped, empty and inaccessible sources rather than inventing results.
if (Test-Path -LiteralPath "$ezCase\notes\collection.txt") { throw 'Collection already recorded. Keep it; use a fresh case for another attempt.' }
$ezGroups = [ordered]@{
Prefetch = Join-Path $env:WINDIR 'Prefetch'
AutomaticDestinations = Join-Path $env:APPDATA 'Microsoft\Windows\Recent\AutomaticDestinations'
CustomDestinations = Join-Path $env:APPDATA 'Microsoft\Windows\Recent\CustomDestinations'
}
"Collection start UTC: $((Get-Date).ToUniversalTime().ToString('o'))" | Set-Content -LiteralPath "$ezCase\notes\collection.txt"
foreach ($ezName in $ezGroups.Keys) {
$ezFrom = $ezGroups[$ezName]
$ezTo = Join-Path "$ezCase\sources" $ezName
New-Item -ItemType Directory -Path $ezTo -ErrorAction Stop | Out-Null
"${ezName}: $ezFrom -> $ezTo" | Add-Content -LiteralPath "$ezCase\notes\collection.txt"
try {
$ezFiles = @(Get-ChildItem -LiteralPath $ezFrom -File -ErrorAction Stop)
"Files found: $($ezFiles.Count)" | Add-Content -LiteralPath "$ezCase\notes\collection.txt"
foreach ($ezFile in $ezFiles) {
try { Copy-Item -LiteralPath $ezFile.FullName -Destination $ezTo -ErrorAction Stop }
catch { "Copy failed: $($ezFile.FullName); $_" | Add-Content -LiteralPath "$ezCase\notes\collection.txt" }
}
} catch { "Source unavailable: $ezFrom; $_" | Add-Content -LiteralPath "$ezCase\notes\collection.txt" }
}
"Collection finish UTC: $((Get-Date).ToUniversalTime().ToString('o'))" | Add-Content -LiteralPath "$ezCase\notes\collection.txt"
Get-Content -LiteralPath "$ezCase\notes\collection.txt"
Check sources in File Explorer and read collection.txt. Keep the exact original paths: the Jump Lists belong to the account recorded in the case notes. An access error means the collection failed for that path; it does not mean the artefact was absent.
Next, make working copies and compare their SHA-256 hashes. A hash is a file fingerprint: matching hashes show matching bytes.
if (Test-Path -LiteralPath "$ezCase\notes\copy-checks.csv") { throw 'Copy check already exists. Do not overwrite it.' }
if (@(Get-ChildItem -LiteralPath "$ezCase\working" -Force).Count -gt 0) { throw 'Working folder contains an earlier attempt. Use a fresh case.' }
foreach ($ezFolder in Get-ChildItem -LiteralPath "$ezCase\sources" -Directory) {
Copy-Item -LiteralPath $ezFolder.FullName -Destination "$ezCase\working" -Recurse -ErrorAction Stop
}
$ezChecks = @(foreach ($ezFile in Get-ChildItem -LiteralPath "$ezCase\sources" -File -Recurse) {
$ezRelative = $ezFile.FullName.Substring("$ezCase\sources\".Length)
$ezSourceHash = (Get-FileHash -LiteralPath $ezFile.FullName -Algorithm SHA256 -ErrorAction Stop).Hash
$ezWorkingHash = (Get-FileHash -LiteralPath (Join-Path "$ezCase\working" $ezRelative) -Algorithm SHA256 -ErrorAction Stop).Hash
[pscustomobject]@{File=$ezRelative; Bytes=$ezFile.Length; SourceSHA256=$ezSourceHash; WorkingSHA256=$ezWorkingHash; Match=($ezSourceHash -eq $ezWorkingHash)}
})
if ($ezChecks.Count -eq 0) { throw 'No files collected. Keep collection.txt and resolve the collection problem before continuing.' }
$ezChecks | Export-Csv -LiteralPath "$ezCase\notes\copy-checks.csv" -NoTypeInformation
if ($ezChecks.Match -contains $false) { throw 'Copy mismatch. Stop and check copy-checks.csv.' }
$ezChecks | Group-Object Match | Select-Object Name, Count
Look for: a saved copy-checks.csv with Match=True for every collected file. Examine working only; keep sources unchanged. If a copy step fails, keep the partial attempt and use a new case folder after fixing the problem.
No Notepad Prefetch or Jump List?
Windows settings, app versions and retention affect what is saved. Windows 11's packaged Notepad may behave differently from older Notepad. If you have no matching result, check the recorded user and collection paths first. An empty folder is different from a copy error.
In the training VM only, check Settings > Personalization > Start for the option to show recently opened items in Jump Lists. Record its state and any change. To try again, use a fresh case folder and record a new action window. Do not enable Prefetch or clear recent items on an incident machine just to create a result. A valid collection with no matching records can still lead to a useful, limited finding.
$ezCase and $ezPECmd to the paths in your notes.Next: find what each tool can tell you about the action.
Continue to the toolsActivity 2: Follow the traces¶
1. Run PECmd on the Prefetch working copy
In the same PowerShell window, run the command below. -d names the input folder; --csv names a separate output folder; --csvf names the CSV. This is PowerShell syntax; the browser lab above uses Command Prompt syntax.
if (-not (Get-ChildItem -LiteralPath "$ezCase\working\Prefetch" -Filter '*.pf' -File)) { throw 'No collected Prefetch files. Record this gap; do not run the parser on browser downloads.' }
if (Test-Path -LiteralPath "$ezCase\parsed\Prefetch") { throw 'Parser output exists. Use a new output folder for another run.' }
"`"$ezPECmd`" -d `"$ezCase\working\Prefetch`" --csv `"$ezCase\parsed\Prefetch`" --csvf prefetch.csv" |
Set-Content -LiteralPath "$ezCase\notes\pecmd-command.txt"
& $ezPECmd -d "$ezCase\working\Prefetch" --csv "$ezCase\parsed\Prefetch" --csvf prefetch.csv 2>&1 |
Tee-Object -FilePath "$ezCase\notes\pecmd-console.txt"
Open parsed\Prefetch\prefetch.csv in Timeline Explorer if installed, or a CSV viewer. Search for NOTEPAD in the executable name column. Keep the complete parser output, including extra CSVs, and the console log. A parser that finishes without a CSV or reports failed files needs investigation.
Save a selected row or screenshot as exports\prefetch-observation with an appropriate file extension. In notes\observations.txt, write the source .pf filename, executable name, run count, available run times and their time basis. Compare the available run times with your action window. Do not change Windows settings to force a matching time.
Look for: a Notepad execution related record, or a clear note saying which collected files you searched and why no result was available. A run count is not a count of times this particular document was opened. Prefetch alone does not identify who used the program or prove they read handover.txt.
Which timestamp should I compare?
Use the record's run time fields, not the CSV file's modified time. Check your tool's labels and documented time basis; record whether each time is UTC or local. PECmd can retain several run times, but this is not a complete execution history. If several Notepad rows exist, keep their source filenames and compare them rather than picking the first row.
2. Find the file reference in JumpList Explorer
- Open your installed JumpList Explorer. Use File to load files from
working\AutomaticDestinationsandworking\CustomDestinations. Choose the load directory option if your version has one; otherwise load the collected.automaticDestinations-msand.customDestinations-msfiles individually. Follow the viewer guide. - Search or filter the target path field for handover.txt. If nothing matches, clear the filter and check the available entries. Inspect both source groups before reporting no match.
- Select a matching entry. Check the full path against the target path in
case-context.txt. A matching filename in another folder is a different target. - Record the source Jump List filename, displayed application/AppID, entry ID and available path and time fields. AppID is the application's identifier; a friendly name is a mapping, so record an unknown mapping as unknown.
- Save selected rows using your version's export option, or save a screenshot and a small transcription in
notes\observations.txt. Keep the source filename, entry and field labels with the result inexports.
Look for: an application linked reference to the full target path, or a documented no match result with loaded sources, filters and errors. The result may be absent on your VM. Do not substitute the fictional browser entry.
The entry shows several dates. Which one matters?
Keep the label beside each date. DestList timestamps and the embedded shortcut's target file timestamps describe different things. A target file modified time is not automatically the time the user opened it. Automatic and Custom Jump Lists may offer different fields; do not fill missing fields with guessed values. A file reference does not prove its contents were read, changed or sent elsewhere.
Next: combine your observations into a short finding.
Continue to the findingActivity 3: Save your finding¶
1. Compare two observations and write the handover
Create this small table in notes\handover.txt. Use your actual records; a missing result belongs in the table too.
| Source | What to write | What it can tell you |
|---|---|---|
| Notepad Prefetch | Source filename, executable, run count, run time and time basis; or gap | Execution related activity for the program |
| Jump List | Source filename, application/AppID, entry, full target path and labelled times; or gap | An application linked reference to the file |
Compare the paths and the time window before drawing a conclusion. These are different artefact types, but their times need not match exactly. Your controlled action note is a training reference, not a replacement for a missing source record.
Add three short sentences below the table:
- Finding: state what your collected records support. For example, if both match: “The collected Prefetch records support Notepad execution near the training window. The user's Jump List contains a reference to the handover's full path.”
- Limit: state what you cannot show. These records alone do not prove who read the file, what they read, or whether they copied it elsewhere. With a gap, explain that the retained evidence is insufficient for that part of the question.
- Next check: name one useful source for the remaining question, such as an available process creation log for account context or an approved file access audit. Check whether it was enabled and retained; do not assume it exists.
Look for: another analyst can find every cited record in your saved sources and outputs. An honest no match finding is better than claiming the action did not happen.
2. Check the copies and keep the finished case
Close the tools and CSV viewer. Recheck the collected sources and working copies against the saved fingerprints. This block stops on a changed file before making the final package manifest.
foreach ($ezRow in Import-Csv -LiteralPath "$ezCase\notes\copy-checks.csv") {
foreach ($ezArea in @('sources', 'working')) {
$ezCheckPath = Join-Path "$ezCase\$ezArea" $ezRow.File
$ezNow = (Get-FileHash -LiteralPath $ezCheckPath -Algorithm SHA256 -ErrorAction Stop).Hash
if ($ezNow -ne $ezRow.SourceSHA256) { throw "File changed: $ezCheckPath. Keep and explain the mismatch." }
}
}
if (Test-Path -LiteralPath "$ezCase\package-sha256.csv") { throw 'Package manifest exists. Keep it; record a new manifest under a new name if the case changes.' }
Get-ChildItem -LiteralPath $ezCase -File -Recurse | Where-Object { $_.FullName -ne "$ezCase\package-sha256.csv" } | ForEach-Object {
[pscustomobject]@{File=$_.FullName.Substring("$ezCase\".Length); SHA256=(Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256 -ErrorAction Stop).Hash}
} | Export-Csv -LiteralPath "$ezCase\package-sha256.csv" -NoTypeInformation
Copy the entire case folder to approved storage outside the VM, such as a shared training folder that will survive rollback. Check the copied files against package-sha256.csv; the help below gives you a command for this. Keep the verification result alongside the final case so you do not change files already listed in the manifest. Then roll back the disposable VM to its clean snapshot. Keep failures and notes as well as successful results.
How do I check the saved case copy?
Before rollback, use the same PowerShell window in the VM. Replace the example below with the copied case folder's path as seen from the VM. The check compares the manifest itself, then every listed file. It does not change those files.
$ezSavedCase = 'X:\SUSA-EZ-060' # Replace with the copied case folder's path
$ezManifestHash = (Get-FileHash -LiteralPath "$ezCase\package-sha256.csv" -Algorithm SHA256 -ErrorAction Stop).Hash
if ((Get-FileHash -LiteralPath "$ezSavedCase\package-sha256.csv" -Algorithm SHA256 -ErrorAction Stop).Hash -ne $ezManifestHash) { throw 'Copied manifest differs. Keep and inspect this copy.' }
foreach ($ezRow in Import-Csv -LiteralPath "$ezSavedCase\package-sha256.csv") {
$ezSavedHash = (Get-FileHash -LiteralPath (Join-Path $ezSavedCase $ezRow.File) -Algorithm SHA256 -ErrorAction Stop).Hash
if ($ezSavedHash -ne $ezRow.SHA256) { throw "Copied file differs: $($ezRow.File). Keep and inspect this copy." }
}
"Verified manifest and listed files at $ezSavedCase; UTC: $((Get-Date).ToUniversalTime().ToString('o'))"
Save the success message as a screenshot alongside the copied case. On a missing file or mismatch, keep the error and fix the copy before rollback. If the case itself changed after the manifest was made, record why and make a new manifest under a new name; keep the earlier one.
Look for: the outside VM copy opens, its hashes match, and your handover can be checked without the training VM. Do not clear incident artefacts as a cleanup step.
Optional challenge: catch a filename mixup
After preserving the core case, create a second harmless handover.txt in a different folder in the training VM. Open it in Notepad. Collect into a new case folder, then see whether JumpList Explorer distinguishes the two full paths. Explain why a filename only search can mislead you. Preserve both cases.
For later practice, try other command line parsers or ShellBags Explorer with suitable training evidence. Amcache and ShimCache can add inventory or compatibility context; neither alone proves program execution. They are not required for this case.
Finish with a case another analyst can check from your saved files.
Review your evidenceFull Lab evidence checklist¶
- 1CollectionCase context, tool hashes, collection log, original paths, collected sources and checked working copies
- 2ExaminationPECmd command, console and complete output; Jump List sources and selected entries; labelled observations or gaps
- 3FindingShort handover, hash checks, package manifest and a verified copy outside the VM
Parser flags follow the PECmd documentation; use the author's tool catalogue for releases and runtimes. The recent items setting is described in Microsoft's taskbar guide. Browser ticks record your progress; saved source records support your finding.