Operation Blackout 2025: Phantom Check
Date: 28/10/2025
Difficulty: Very Easy
Category: DFIR
Description: Phantom Check is a Sherlock challenge showcasing some of the common virtualization detection techniques used by attackers. Players will gain the ability to create detection rules by identifying specific WMI queries, comparing processes for virtual machine detection, and analyzing registry keys or file paths associated with virtual environments.
1. Executive Summary
This report details the forensic investigation into a [System/Service, e.g., Confluence Server] compromise via a [Attack Type, e.g., Brute Force Attack] against its SSH service. The investigation utilised the provided Unix auth.log and wtmp artifacts. Analysis confirmed a successful breach, post-exploitation activities including privilege escalation and persistence via a new user account, and the subsequent download of an external script. The threat actor’s initial access was gained using the root account.
2. Artefacts
| Artefact | Description |
|---|---|
Microsoft-Windows-Powershell.evtx | A Windows event log file that stores PowerShell events. |
Windows-Powershell-Operational.evtx | A Windows event log file that stores a more broad range of events. |
3. Questions
3.1. Which WMI class did the attacker use to retrieve model and manufacturer information for virtualization detection?
After importing the saved logs, we can easily use the ‘Find’ function to search for keywords. In this case, we know that the powershell script would use the Get-WmiObject cmdlet to get an instance of a WMI (Windows Management Instrumentation) class.
WMI Classes
The WMI system classes are a collection of predefined classes based on the Common Information Model (CIM). Unlike classes supplied by providers, the system classes are not declared in a Managed Object Format (MOF) file. WMI creates a set of these classes whenever a new WMI namespace is created.
After moving through a few of the returned results we see:
CommandInvocation(Get-WmiObject): "Get-WmiObject"
ParameterBinding(Get-WmiObject): name="Class"; value="Win32_ComputerSystem"
CommandInvocation(Select-Object): "Select-Object"
ParameterBinding(Select-Object): name="ExpandProperty"; value="Model"
ParameterBinding(Select-Object): name="InputObject"; value="\\DESKTOP-M3AKJSD\root\cimv2:Win32_ComputerSystem.Name="DESKTOP-M3AKJSD""
The Win32_ComputerSystem WMI class represents a computer system running Windows.
This class returns key properties of a device running Windows and is used for retrieving general, high-level information about a machine. The key to using Win32_ComputerSystem for virtualisation detection lies in examining properties that hypervisors must expose, this often reveals the virualisation vendors name.
For example, when the ‘model’ or ‘manufacturer’ values are returned from this class, the manufacturer might be set to a value such as ‘VMWare’ or ‘Oracle’ and the model might include words such as ‘Virtual’.
3.2. Which WMI query did the attacker execute to retrieve the current temperature value of the machine?
While searching through events within the Windows-Powershell-Operational.evtx file for Get-WmiObject we likely saw another event with the below details:
CommandInvocation(Get-WmiObject): "Get-WmiObject"
ParameterBinding(Get-WmiObject): name="Query"; value="SELECT * FROM MSAcpi_ThermalZoneTemperature"
ParameterBinding(Get-WmiObject): name="ErrorAction"; value="SilentlyContinue"
NonTerminatingError(Get-WmiObject): "Invalid class "MSAcpi_ThermalZoneTemperature""
The MSAcpi_ThermalZoneTemperature WMI class is designed to retrieve temperature readings from thermal sensors on the physical machine. The key property it exposes is CurrentTemperature. When this query is made on a virtual machine an error is returned. The result of this query is another technique for identifying virtualisation.
3.3. The attacker loaded a PowerShell script to detect virtualization. What is the function name of the script?
We can use the ‘Filter Current Log’ feature to search the Windows-Powershell-Operational.evtx logs for a specific event. In this case we will filter the logs to only show IDs that match 4104.
4104 Script Block Logging
This is the most crucial for script execution. When Script Block Logging is enabled (available in PowerShell 5.0 and later), this event ID records the content of all script blocks, functions, and scripts that are processed, whether interactive or through a script file.
Looking through the returned results, the ‘Verbose’ level events stand out, so we start there. Running through these events the below function can be found:
function Check-VM
{
<#
.SYNOPSIS
Nishang script which detects whether it is in a known virtual machine.
.DESCRIPTION
This script uses known parameters or 'fingerprints' of Hyper-V, VMWare, Virtual PC, Virtual Box,
Xen and QEMU for detecting the environment.
.EXAMPLE
PS > Check-VM
.LINK
http://www.labofapenetrationtester.com/2013/01/quick-post-check-if-your-payload-is.html
https://github.com/samratashok/nishang
.NOTES
The script draws heavily from checkvm.rb post module from msf.
https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/checkvm.rb
#>
[CmdletBinding()] Param()
$ErrorActionPreference = "SilentlyContinue"
#Hyper-V
$hyperv = Get-ChildItem HKLM:\SOFTWARE\Microsoft
if (($hyperv -match "Hyper-V") -or ($hyperv -match "VirtualMachine"))
{
$hypervm = $true
}
if (!$hypervm)
{
$hyperv = Get-ItemProperty hklm:\HARDWARE\DESCRIPTION\System -Name SystemBiosVersion
if ($hyperv -match "vrtual")
{
$hypervm = $true
}
}
if (!$hypervm)
{
$hyperv = Get-ChildItem HKLM:\HARDWARE\ACPI\FADT
if ($hyperv -match "vrtual")
{
$hypervm = $true
}
}
if (!$hypervm)
{
$hyperv = Get-ChildItem HKLM:\HARDWARE\ACPI\RSDT
if ($hyperv -match "vrtual")
{
$hypervm = $true
}
}
if (!$hypervm)
{
$hyperv = Get-ChildItem HKLM:\SYSTEM\ControlSet001\Services
if (($hyperv -match "vmicheartbeat") -or ($hyperv -match "vmicvss") -or ($hyperv -match "vmicshutdown") -or ($hyperv -match "vmiexchange"))
{
$hypervm = $true
}
}
if ($hypervm)
{
"This is a Hyper-V machine."
}
#VMWARE
$vmware = Get-ChildItem HKLM:\SYSTEM\ControlSet001\Services
if (($vmware -match "vmdebug") -or ($vmware -match "vmmouse") -or ($vmware -match "VMTools") -or ($vmware -match "VMMEMCTL"))
{
$vmwarevm = $true
}
if (!$vmwarevm)
{
$vmware = Get-ItemProperty hklm:\HARDWARE\DESCRIPTION\System\BIOS -Name SystemManufacturer
if ($vmware -match "vmware")
{
$vmwarevm = $true
}
}
if (!$vmwarevm)
{
$vmware = Get-Childitem hklm:\hardware\devicemap\scsi -recurse | gp -Name identifier
if ($vmware -match "vmware")
{
$vmwarevm = $true
}
}
if (!$vmwarevm)
{
$vmware = Get-Process
if (($vmware -eq "vmwareuser.exe") -or ($vmware -match "vmwaretray.exe"))
{
$vmwarevm = $true
}
}
if ($vmwarevm)
{
"This is a VMWare machine."
}
#Virtual PC
$vpc = Get-Process
if (($vpc -eq "vmusrvc.exe") -or ($vpc -match "vmsrvc.exe"))
{
$vpcvm = $true
}
if (!$vpcvm)
{
$vpc = Get-Process
if (($vpc -eq "vmwareuser.exe") -or ($vpc -match "vmwaretray.exe"))
{
$vpcvm = $true
}
}
if (!$vpcvm)
{
$vpc = Get-ChildItem HKLM:\SYSTEM\ControlSet001\Services
if (($vpc -match "vpc-s3") -or ($vpc -match "vpcuhub") -or ($vpc -match "msvmmouf"))
{
$vpcvm = $true
}
}
if ($vpcvm)
{
"This is a Virtual PC."
}
#Virtual Box
$vb = Get-Process
if (($vb -eq "vboxservice.exe") -or ($vb -match "vboxtray.exe"))
{
$vbvm = $true
}
if (!$vbvm)
{
$vb = Get-ChildItem HKLM:\HARDWARE\ACPI\FADT
if ($vb -match "vbox_")
{
$vbvm = $true
}
}
if (!$vbvm)
{
$vb = Get-ChildItem HKLM:\HARDWARE\ACPI\RSDT
if ($vb -match "vbox_")
{
$vbvm = $true
}
}
if (!$vbvm)
{
$vb = Get-Childitem hklm:\hardware\devicemap\scsi -recurse | gp -Name identifier
if ($vb -match "vbox")
{
$vbvm = $true
}
}
if (!$vbvm)
{
$vb = Get-ItemProperty hklm:\HARDWARE\DESCRIPTION\System -Name SystemBiosVersion
if ($vb -match "vbox")
{
$vbvm = $true
}
}
if (!$vbvm)
{
$vb = Get-ChildItem HKLM:\SYSTEM\ControlSet001\Services
if (($vb -match "VBoxMouse") -or ($vb -match "VBoxGuest") -or ($vb -match "VBoxService") -or ($vb -match "VBoxSF"))
{
$vbvm = $true
}
}
if ($vbvm)
{
"This is a Virtual Box."
}
#Xen
$xen = Get-Process
if ($xen -eq "xenservice.exe")
{
$xenvm = $true
}
if (!$xenvm)
{
$xen = Get-ChildItem HKLM:\HARDWARE\ACPI\FADT
if ($xen -match "xen")
{
$xenvm = $true
}
}
if (!$xenvm)
{
$xen = Get-ChildItem HKLM:\HARDWARE\ACPI\DSDT
if ($xen -match "xen")
{
$xenvm = $true
}
}
if (!$xenvm)
{
$xen = Get-ChildItem HKLM:\HARDWARE\ACPI\RSDT
if ($xen -match "xen")
{
$xenvm = $true
}
}
if (!$xenvm)
{
$xen = Get-ChildItem HKLM:\SYSTEM\ControlSet001\Services
if (($xen -match "xenevtchn") -or ($xen -match "xennet") -or ($xen -match "xennet6") -or ($xen -match "xensvc") -or ($xen -match "xenvdb"))
{
$xenvm = $true
}
}
if ($xenvm)
{
"This is a Xen Machine."
}
#QEMU
$qemu = Get-Childitem hklm:\hardware\devicemap\scsi -recurse | gp -Name identifier
if ($qemu -match "qemu")
{
$qemuvm = $true
}
if (!$qemuvm)
{
$qemu = Get-ItemProperty hklm:HARDWARE\DESCRIPTION\System\CentralProcessor\0 -Name ProcessorNameString
if ($qemu -match "qemu")
{
$qemuvm = $true
}
}
if ($qemuvm)
{
"This is a Qemu machine."
}
}
This is clearly the function used to detect virtualisation.
3.4. Which registry key did the above script query to retrieve service details for virtualization detection?
We can see from the code that HKLM is being used to query the required registry keys. HKLM stands for ‘HKEY_LOCAL_MACHINE’ and represents a Registry Hive that contains configuration settings specific to the local computer.
By searching for this string within the found script we can find 20 different lines of code using the Get-ItemProperty and Get-ChildItem commands to query registry keys. looking through this list the below stands out the most:
$hyperv = Get-ChildItem HKLM:\SYSTEM\ControlSet001\Services
if (($hyperv -match "vmicheartbeat") -or ($hyperv -match "vmicvss") -or ($hyperv -match "vmicshutdown") -or ($hyperv -match "vmiexchange"))
{
$hypervm = $true
}
HKLM:\SYSTEM\ControlSet001\Services
This registry path can be used for virtualisation detection by serving as the primary location where an operating system registers device drivers and services, including those installed by virtualisation software.
$hyperv -match "vmicheartbeat"
The script uses the -match comparrison operator to hunt for any services that match: ‘vmicheartbeat’, ‘vmicvss’, ‘vmicshutdown’ or ‘vmiexchange’. If a match is found the IF statement will pass as true.
3.5. The VM detection script can also identify VirtualBox. Which processes is it comparing to determine if the system is running VirtualBox?
In this scenario the threat actor, very usefully, labelled each section of code by the virtualisation vendor. After searching for ‘Box’ we can quickly find the section of code we need to analyse. This section starts with the below code:
$vb = Get-Process
if (($vb -eq "vboxservice.exe") -or ($vb -match "vboxtray.exe"))
{
$vbvm = $true
}
The IF statement within this code first checks if any process names exactly equals (-eq) vboxservice.exe, this process is a core component of the VirtualBox Guest Additions. The second check uses the -match operator to see if any process name in the collection contains a match for vboxtray.exe, which is the user-facing tray application for Guest Additions.
3.6. The VM detection script prints any detection with the prefix ‘This is a’. Which two virtualization platforms did the script detect?
This information is easily found by returning to Event Viewer and using the Find function to search for “This is a " within the Windows-Powershell-Operational.evtx logs.
This immediatly returns the below event log:
CommandInvocation(Out-Default): "Out-Default"
ParameterBinding(Out-Default): name="InputObject"; value="This is a Hyper-V machine."
ParameterBinding(Out-Default): name="InputObject"; value="This is a VMWare machine."
Bonus
By running a search function against the same Get-WmiObject cmdlet we can find the latest instance of a WMI class being called that we suspect is malicious. The first result that is returned:
Pipeline execution details for command line: Get-WmiObject -Query "SELECT * FROM MSAcpi_ThermalZoneTemperature" -ErrorAction SilentlyContinue.
Context Information:
DetailSequence=1
DetailTotal=1
SequenceNumber=53
UserId=DESKTOP-M3AKJSD\User
HostName=ConsoleHost
HostVersion=5.1.26100.2161
HostId=0fad0cf8-6cb6-4657-86f7-655ec22eed9f
HostApplication=C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe
EngineVersion=5.1.26100.2161
RunspaceId=2aeeba59-d0f6-4ce7-b41c-e07625b3beec
PipelineId=21
ScriptName=
CommandLine=Get-WmiObject -Query "SELECT * FROM MSAcpi_ThermalZoneTemperature" -ErrorAction SilentlyContinue
Details:
CommandInvocation(Get-WmiObject): "Get-WmiObject"
ParameterBinding(Get-WmiObject): name="Query"; value="SELECT * FROM MSAcpi_ThermalZoneTemperature"
ParameterBinding(Get-WmiObject): name="ErrorAction"; value="SilentlyContinue"
NonTerminatingError(Get-WmiObject): "Invalid class "MSAcpi_ThermalZoneTemperature""
From the ‘Context Information’ we can see that this command was ran with the below RunspaceId:
2aeeba59-d0f6-4ce7-b41c-e07625b3beec
RunspaceId
The RunspaceId is a Globally Unique Identifier (GUID) that PowerShell assigns to a specific runspace - the environment - where commands are being processed.
We can use this identifier to find the original script execition. This is because when the script was executed PowerShell assigned it this RunspaceId, all events then originating from this script will be assigned the same ID.
We can also use this technique to find all of the PowerShell commands ran from this script.