As the team was nearing the deadline for pushing their project to production, one of the engineers, Alex, took charge of the deployment process. Alex had been exploring ways to expedite the deployment process and had recently discovered a package that promised to streamline the process significantly. Excited about the prospect of saving time, Alex decided to utilise this package for the deployment. As Alex began the deployment process using the package, he noticed that the CPU usage on the deployment server started to spike unusually high. Concerned about this anomaly, he decided to ask for help. A Triage image has been acquired for your examination. Your primary objective is to ascertain the means by which this malware infiltrated our organisation, successfully evading our existing security measures.
A deployment engineer installed a NuGet package whose name was one letter off a legitimate one. The package shipped an init.ps1 that NuGet runs on install, and that script switched off Defender real-time protection before the attacker’s payload landed. The payload was a Sliver implant, which beaconed to the attacker, ran whoami as its first enumeration step, and registered a scheduled task named to look like a Microsoft update job. A second binary was later dropped into ProgramData and renamed after it had run. Defender caught it anyway, because only real-time scanning had been disabled. The whole chain is recoverable from the PowerShell history, the MFT and USN journal, prefetch, the Defender logs and the scheduled task XML.
ConsoleHost_history.txt in the Administrator profile → read with cat.nuget\packages\publishignor\1.0.11-beta\ → .nuspec, tools\init.ps1, .nupkg.metadataMFTECmdPECmdAmcacheParserwinevt\logs → parse with EvtxECmd, including the Defender Operational logMPLog and MPDetection text logsWindows\System32\Tasks → raw XML, UTF-16Timestamp note: Q4 wants
YYYY-MM-DD HH:MMwith no seconds, while Q11, Q12, Q15 and Q19 want seconds. Zimmerman CSVs are already UTC.
Alex’s PowerShell console history is the first place to look for a “what did the user type” question. ConsoleHost_history.txt holds the nuget install line with the package name and the exact version pin.
cat ConsoleHost_history.txt
The package name from Q1 resolves to its nuget.org page. The local .nupkg.metadata and NuGet.Config confirm the default nuget.org source was used. The package has since been removed, but the page for the pinned version still shows its details (https://www.nuget.org/packages/PublishIgnor/1.0.11-beta).
cat NuGet.Config .nupkg.metadata
Gotcha: The format hint ends in a trailing slash and the answer is the package page, not the version-specific URL you see in your browser bar.
The local package files do not name a publisher, so this one comes from the web. The nuget.org page for the pinned version lists its owner (https://www.nuget.org/packages/PublishIgnor/1.0.11-beta).
The download lands in the NuGet cache as a .nupkg. Parse the $MFT and look at the created timestamp of publishignor.1.0.11-beta.nupkg: 18:41:54, so the answer truncates to the minute.
MFTECmd -f '$MFT' --csv work/mft
The .nuspec inside the cached package carries the <id> the attacker registered. It is the name of the real package (PublishIgnore) with the final letter dropped, which is how the upload got past the name already being taken.
cat publishignor.nuspec
Gotcha: The format mask is 12 characters ending in
r. Read the<id>from the nuspec rather than copying the legitimate package name.
A package ID one character away from a legitimate one, published to the same registry, is typosquatting. This answer was reasoned from the Q5 finding rather than read from an artifact.
NuGet runs tools\init.ps1 automatically when a package is installed into a project, so that is the file to read. It sits in the extracted package folder under the Administrator profile and holds the Defender tampering from Q8.
cat '.nuget/packages/publishignor/1.0.11-beta/tools/init.ps1'
The same init.ps1 from Q7 holds the command. The PowerShell and Defender events from EvtxECmd agree that real-time monitoring was switched off just before the payload arrived.
EvtxECmd -d winevt/logs --csv work/evtx
The file is uninstall.exe, created at 19:23:36 in the $J journal. Defender’s MPLog records the file’s hash in its cloud lookup (SDN query) lines, so search the support logs for the filename rather than hashing a sample.
grep -ri 'uninstall.exe' 'ProgramData/Microsoft/Windows Defender/Support/'
The Defender detection event (ID 1116) for the Q9 file names the threat as VirTool:Win32/Sliver.D!MTB, which gives the C2 framework.
grep -h ',1116,' work/evtx/*.csv
Prefetch records the first run of UNINSTALL.EXE. PECmd over the prefetch folder shows 19:23:36, the same second the $J journal logs its creation.
PECmd -d Windows/prefetch --csv work/pf
Only real-time monitoring was disabled, so Defender’s other protection kept working. The Defender Operational log has a detection event (ID 1116) at 19:33:32, about ten minutes after execution.
grep -h ',1116,' work/evtx/*.csv
Sort the prefetch output by last run time after the Q11 execution. The first new process is WHOAMI.EXE at 19:24:46, the classic first check of the current user and privileges.
PECmd -d Windows/prefetch --csv work/pf
The task definition is a UTF-16 XML file under Windows\System32\Tasks, so convert it before reading. The file name is the task name, styled after a Microsoft update job.
iconv -f UTF-16 -t UTF-8 Windows/System32/Tasks/MicrosoftSystemDailyUpdates
Hint: The format mask ends in
sand is 26 characters. The name is styled after a Windows update job.
The task file’s $MFT creation time and the StartBoundary in its XML both give 19:24:05.
MFTECmd -f '$MFT' --csv work/mft
Amcache and the USN journal show a second executable, file.exe, created in ProgramData well after the Sliver implant. Parse both and look for new executables in that folder.
AmcacheParser -f Amcache.hve --csv work/amcache
The $J journal has the FileCreate entry for file.exe in ProgramData at 19:30:04.
MFTECmd -f '$J' --csv work/j
Following the same file through $J, a rename entry at 19:33:48 turns file.exe into a name that reads like a legitimate updater, 16 seconds after Defender’s detection in Q12.
MFTECmd -f '$J' --csv work/j
Take the SHA1 of file.exe from Amcache and look it up on VirusTotal. The popular threat label reads impalastealer. The accepted family name is the short form. This was reasoned from the VirusTotal label rather than read from an artifact.
vt f58f3660...
Gotcha: Longer vendor labels (
Paketer,Trojan.MSIL.PAKETER,Impala Stealer) were not accepted. The answer is one word.
| Phase | Action |
|---|---|
| Initial Access | Typosquatted NuGet package PublishIgnor published by a1l4m |
| Initial Access | Alex installs it with nuget install and the package lands in the cache |
| Execution | NuGet runs tools\init.ps1 on install |
| Defense Evasion | Set-MpPreference disables Defender real-time monitoring |
| Command and Control | uninstall.exe, a Sliver implant, is dropped and run |
| Discovery | whoami.exe is the first enumeration command |
| Persistence | Scheduled task MicrosoftSystemDailyUpdates created |
| Defense Evasion | Second binary file.exe dropped in ProgramData |
| Defense Evasion | Defender detects it; the actor renames it to Updater.exe |
| Timestamp (UTC) | Event |
|---|---|
| 2024-03-19 18:41:54 | publishignor.1.0.11-beta.nupkg created in the NuGet cache |
| 2024-03-19 19:23:36 | uninstall.exe created and executed |
| 2024-03-19 19:24:05 | Scheduled task MicrosoftSystemDailyUpdates created |
| 2024-03-19 19:24:46 | whoami.exe runs |
| 2024-03-19 19:30:04 | file.exe dropped in ProgramData |
| 2024-03-19 19:33:32 | Defender detects the malicious file |
| 2024-03-19 19:33:48 | file.exe renamed to Updater.exe |
| Type | Value |
|---|---|
| NuGet package | PublishIgnor 1.0.11-beta |
| Publisher | a1l4m |
| URL | hxxps[://]www[.]nuget[.]org/packages/PublishIgnor/ |
| File | C:\Users\Administrator\.nuget\packages\publishignor\1.0.11-beta\tools\init.ps1 |
SHA1 (uninstall.exe, Sliver) | 57b7acf278968eaa53920603c62afd8b305f98bb |
SHA1 (file.exe / Updater.exe) | f58f3660... |
| Scheduled task | MicrosoftSystemDailyUpdates |
| File | Updater.exe |
| Technique | ID | Description |
|---|---|---|
| Supply Chain Compromise: Compromise Software Dependencies and Development Tools | T1195.002 | Typosquatted NuGet package pulled in by the deployment engineer |
| Command and Scripting Interpreter: PowerShell | T1059.001 | init.ps1 executes on package install |
| Impair Defenses: Disable or Modify Tools | T1562.001 | Set-MpPreference -DisableRealtimeMonitoring $true |
| Ingress Tool Transfer | T1105 | Sliver implant and the second binary brought onto the host |
| System Owner/User Discovery | T1033 | whoami.exe as the first post-C2 command |
| Scheduled Task/Job: Scheduled Task | T1053.005 | MicrosoftSystemDailyUpdates |
| Masquerading | T1036 | Update-themed task name and the Updater.exe rename |
Package install scripts are code execution. NuGet runs init.ps1 the moment a package is installed, with no prompt. Pin and mirror approved packages on an internal feed, and block installs straight from the public registry on deployment servers.
Typosquatted names are cheap to catch. PublishIgnor is one character from a real package. Alert on package installs whose ID is a small edit distance from a known package, and review the publisher before first use.
Disabling real-time protection is a loud event. The Set-MpPreference call is visible in PowerShell logging and Defender’s configuration-change events. Turn on script block logging, enable Defender tamper protection, and alert on any change to DisableRealtimeMonitoring.
Partial tampering leaves detections behind. The actor only turned off real-time monitoring, and Defender still flagged the second binary about ten minutes later. Treat any detection that follows a Defender settings change as a live intrusion, not routine malware cleanup.
Look-alike task names hide persistence. MicrosoftSystemDailyUpdates is styled to pass as a Windows job. Baseline the task list and flag Microsoft-themed names that don’t match known tasks.
A one-letter typo in a package name gave the attacker code execution on a deployment server, and a single PowerShell line took out real-time protection long enough to run a Sliver implant. The attack failed to stay hidden only because Defender’s remaining features still fired, and the case shows how little stands between a mistyped
nuget installand a foothold.