Building a Threat Hunt for SolarMarker TTPs

Manuel Arrieta
Maveris Labs
10 min readJan 26, 2024

--

Don’t get caught just staring at SolarMarker, let’s get to building some detections

This post will cover methodology for hunting SolarMarker malware (also known as Juypter/YellowCockatoo/Polazert) given its resurgence in the last quarter of 2023. The analytics presented are for Microsoft Defender for Endpoint (MDE) using KQL.

The foundation of an effective threat hunt begins with understanding your adversary; the better you understand the adversary, the better you can tailor your hunt analytics to produce meaningful results. Equally, you must be cognizant of the capabilities of your hunt tools as they may not yield the same level of granularity across filesystem artifacts, which may render hunting certain TTPs beyond your tool’s purview. Accordingly, we begin by aggregating recent TTPs of SolarMarker malware from the following sources:

A high-level summary of SolarMarker TTPs gathered from these sources is as follows:

  • The initial infection vector of SolarMarker relies on search engine optimization (SEO) poisoning to deliver large .NET executable files to unsuspecting users.
  • Upon execution, SolarMarker may launch a decoy installer or pdf file whilst launching PowerShell to decrypt and load additional payloads.
  • SolarMarker can deliver both backdoor and infostealer payloads in the form of obfuscated .NET DLLs.
  • SolarMarker achieves persistence through the startup folder and registers custom file extensions whose handler registry key contains PowerShell code to decrypt additional payloads.
  • SolarMarker encrypts all traffic to C2 servers using hard-coded keys.

With the general understanding of SolarMarker TTPs we have established thus far, we can begin to scope our hunt efforts and build analytics for SolarMarker. Each section will tackle a distinct SolarMarker TTP and recommend MDE analytics to identify the activity.

SolarMarker SEO File Download

SolarMarker file downloads consist of a large executable file (roughly 200–350 MB in size) with a naming convention that includes the victim’s initial search engine query as a hyphen-separated file name. Before we begin constructing our hunt analytic, we will supplement our understanding of the initial SolarMarker file download by leveraging VT Enterprise (requires licensing) using the following query:

(engines:polazert or engines:solarmarker) and tag:assembly and size:200mb+
VT query results for SolarMarker samples.

Inspecting some of the results further, we confirm file names match the expected nomenclature:

VT file details tab view.

By taking this additional step, we can be confident our hunt analytics align with the intel we are referencing and that we are targeting the latest SolarMarker TTPs. At this point, there are several approaches we can take to hunt for SolarMarker files resident on an endpoint: file size, file name, and file metadata. Although it may be tempting to build analytics to hunt for these file attributes individually, you run the risk of creating an analytic that is overly broad. For instance, if we constructed a query to search for file creation events where the file size is over 200MB, it is possible that our result set is in the hundreds if not thousands, especially in larger organizations. Likewise, applying stack counting techniques to unrefined data in these circumstances may still not yield a manageable result set. Therefore, when applicable, combine TTPs to enhance the fidelity of your analytics.

To that effect, we know the initial SolarMarker file download transpires via web browser process, so we will include a list of web browser processes that SolarMarker is known to target with its infostealer stage in our analytic. If you have baselined your environment and know what browsers are in use, adjust the analytic as needed. Finally, our proposed analytic looks like this:

Let webBrowser = dynamic (["chrome.exe", "msedge.exe", "firefox.exe", "brave.exe"]);
DeviceFileEvents
| where Timestamp > ago (30d)
| where InitiatingProcessFileName in~ (webBrowser)
| where isnotempty(FileName)
| where FileName endswith ".exe"
| extend filename_length = strlen(FileName),
hyphenCount = countof(FileName, "-")
| where (hyphenCount > 2) and FileSize between (200000000 .. 350000000)
| summarize count()by ActionType, InitiatingProcessFileName, FileName, FileSize, SHA1, SHA256, MD5, filename_length, hyphenCount
| where count_ <= 5
| order by FileName asc

Note: The results from this analytic may produce a variety of software installer file names. If the software is unapproved in your organization, create an inventory of the software identified as part of your overall hunt findings.

SolarMarker File Download and Execution

The immediate parent-child process tree of a successful SolarMarker file download and execution can be illustrated as follows:

Note: The process tree has been simplified as the SolarMarker executable will generate various PowerShell child processes and may spawn an additional web browser process to open a decoy pdf.

By modifying the previous analytic, we can detect a potential SolarMarker file download and its execution by searching for web browser processes spawning a large file containing a hyphen-separated name followed by a PowerShell grandchild process:

let webBrowser = dynamic (["chrome.exe", "msedge.exe", "firefox.exe", "brave.exe"]);
DeviceProcessEvents
| where Timestamp > ago (30d)
| where InitiatingProcessParentFileName in~ (webBrowser)
| where ActionType == "ProcessCreated"
| where FileName has "powershell" or FileName has "pwsh"
| extend hyphenCount = countof(InitiatingProcessFileName, "-")
| where isnotempty(InitiatingProcessFileSize)
| where (hyphenCount > 2) and InitiatingProcessFileSize between (200000000 .. 350000000)
| summarize count()by ActionType, InitiatingProcessParentFileName, InitiatingProcessFileName, FileName, FileSize, SHA1, SHA256, MD5, hyphenCount

Note: This analytic assumes the user opens the downloaded file from within the web browser window and not File Explorer.

SolarMarker Assembly Load

Next, we will build a hunt analytic for detecting SolarMarker executables which attempt to load additional payload stages in the form of .NET assemblies. It is important to note that what we are hunting here will not be found in the DeviceImageLoadEvents table, which is similar to what other tools may categorize as a DLL image load. Instead, this analytic aims to detect anomalous assembly names that have been loaded through reflection using MDE’s ClrUnbackedModuleLoaded ActionType.

Based on analysis of the most recent SolarMarker samples, we can confirm the initial SolarMarker file download is still leveraging an embedded encrypted payload which is decrypted via PowerShell and loaded into memory using a call to Reflection.Assembly::Load (used to load a byte array containing a .NET assembly). To demonstrate what this analytic will detect, we will analyze the most recent SolarMarker sample available at the time of writing:

https://www.virustotal.com/gui/file/b68a65e9f8cb6aff77c8d1973e60063de53ca052ee6c98919c96decf5ef705a8/details

This sample was analyzed in a lab environment and the first stage PowerShell script is extracted by setting a breakpoint prior to the “powerShell.BeginInvoke” call and saving the contents of the local variable @string to a designated file:

Sample loaded into dnSpy with breakpoint set.

If the sample is allowed to execute beyond this point, you can also gather the script contents through PowerShell transcript logs. The PowerShell script content is as follows:

We will modify the script so that it writes the decrypted payload to disk. As the payload consists of a byte array prior to being loaded by “Reflection.Assembly::Load,” we convert it back to base64 and write it to a file. CyberChef is then used with the “From Base64” recipe with “remove non-alphabet characters” selected:

Note: The entire script can also be extracted from Microsoft-Windows-PowerShell/Operational Event logs by searching for Event ID 4104. This sample generated Scriptblock text spanning 109 event log entries.

Consequently, we obtain the following .NET assembly:

The assembly was previously submitted to VT:

https://www.virustotal.com/gui/file/80a4243ec5fde0768e9d054ee478bdb6cf85e2a91b641877bb68a62dfceea8fa/details

Using Ildasm.exe (requires Visual Studio), we inspect the assembly manifest file to find the assembly’s name:

Ildasm.exe displaying the manifest file.

Referring back to the PowerShell transcript logs generated during dynamic analysis, you will notice the assembly name loaded by the initial SolarMarker executable highlighted in yellow. This matches the assembly name we observed in the assembly manifest:

PowerShell transcript logs displaying the obfuscated script and assembly name.

As you may know, .NET assemblies are required to have a manifest file which contains the name of the assembly as well as other dependencies provided to the common language runtime (CLR). This is precisely the data we are targeting with our analytic. Notice the length of the assembly’s name is 121 alphanumeric characters and at least the first 50 characters are letters:

“KABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZPOF89gHKDH2wH83GgTuBCKcKeMaOYhygFEOfjaTCm4HTF2RKfEYrN0I75FbLaREGVwLg”

Next, we validate the initial SolarMarker file we are analyzing loads the assembly we observed in the PowerShell transcript logs and assembly manifest:

Lastly, we leverage VT Enterprise to establish whether the observed naming convention is applicable to other SolarMarker assemblies:

Accordingly, our analytic will encompass detecting assembly names with similar naming conventions as the name length is an unusual characteristic that may provide high-fidelity results. This analytic may also be valuable when attempting to ascertain actions on objectives by identifying different .NET assemblies loaded during a possible SolarMarker infection:

DeviceEvents
| where Timestamp > ago (30d)
| where ActionType == "ClrUnbackedModuleLoaded"
| where InitiatingProcessFileName has "powershell" or InitiatingProcessFileName has "pwsh"
| extend split(AdditionalFields, ',')
| extend clr = tostring(AdditionalFields[0])
| where clr matches regex @"[a-zA-Z]{50,}"
| extend clr_length = strlen(clr)
| summarize count()by ActionType, InitiatingProcessParentFileName, InitiatingProcessFileName, FileName, FileSize, tostring(AdditionalFields), tostring(clr), clr_length, InitiatingProcessCommandLine, ProcessCommandLine

SolarMarker C2 Attempt

Shortly after execution, SolarMarker will attempt to establish an outbound network connection with its C2 IP address. In the sample we analyzed, our executable attempted a network connection via:

ProcDOT and Process Monitor output.

Using the profile we developed in our first analytic for SolarMarker, we can expect to identify possible SolarMarker executables generating outbound network connections via:

DeviceNetworkEvents
| where Timestamp > ago (30d)
| where isnotempty(InitiatingProcessFileSize)
| extend hyphenCount = countof(InitiatingProcessFileName, "-")
| where (hyphenCount > 2)
| where InitiatingProcessFileSize between (200000000 .. 350000000)
| summarize count()by ActionType, InitiatingProcessParentFileName, InitiatingProcessFileName, RemoteIP, RemotePort, RemoteUrl, RemoteIPType, hyphenCount, InitiatingProcessFileSize

SolarMarker Startup Folder Persistence

SolarMarker establishes persistence on an endpoint by creating shortcut (.lnk) files in the startup folder path via PowerShell which correspond to custom file extensions registered during infection. Therefore, this analytic will be straightforward. We will be hunting for PowerShell processes which generate file creation events whose extension ends in .lnk and where the file path references the startup folder:

DeviceFileEvents
| where Timestamp > ago (30d)
| where InitiatingProcessFileName has "powershell" or InitiatingProcessFileName has "pwsh"
| where FolderPath has @"start menu\programs\startup" and FileName endswith ".lnk"
| summarize count()by InitiatingProcessParentFileName, InitiatingProcessFileName, FileName, InitiatingProcessCommandLine, SHA1, SHA256, MD5

Note: You could potentially branch off this hunt by including other LOLBins which exhibit similar behavior.

SolarMarker Registry Persistence

As a product of startup folder persistence, SolarMarker creates a corresponding registry key under HKEY_CURRENT_USER\Software\Classes\*\shell\open\command to handle the custom file extension. The registry key data contains PowerShell code that is executed when the custom file extension is called:

https://www.esentire.com/blog/solarmarker-to-jupyter-and-back

By combining the registry key path with the specific PowerShell command line references to AesCryptoServiceProvider and Reflection.Assembly, we can build an analytic to hunt for these artifacts:

DeviceRegistryEvents
| where Timestamp > ago (30d)
| where RegistryKey has @"shell\open\command"
| where isnotempty(RegistryValueData)
| where RegistryValueData has_any ("powershell", "pwsh") and RegistryValueData has_any ("command", "AesCryptoServiceProvider", "frombase64string", "reflection.assembly")
| summarize count()by ActionType, InitiatingProcessFileName, RegistryKey, RegistryValueData, RegistryValueName, RegistryValueType

Note: There are plenty of opportunities to expand this analytic to hunt for other registry keys containing suspicious commands.

SolarMarker Staged Browser Data

SolarMarker’s infostealer payload creates directories under the %TEMP% folder based on web browsers installed on the infected machine. This activity will typically result in several thousand file modifications and can be detected by folder path or number of file modifications. The following image details the folder string associated with a particular browser:

https://www.esentire.com/blog/solarmarker-to-jupyter-and-back

Our analytic will focus on file events where a file is created containing a specific browser string or where the folder path references the browser string:

DeviceFileEvents
| where Timestamp > ago (30d)
| where FileName has_any("chrprf", "edgprf", "ffxprf", "brvprf") or FolderPath has_any("chrprf", “edgprf", “ffxprf”, "brvprf")
| summarize count()by ActionType, InitiatingProcessParentFileName, InitiatingProcessFileName, FileName, InitiatingProcessFolderPath, FolderPath, SHA1, SHA256, MD5

In conclusion, by leveraging several intel sources and performing our own analysis, we created analytics to hunt for SolarMarker TTPs spanning initial access to command and control. To better visualize where we can expect to detect SolarMarker, we map our analytics to MITRE:

References

https://learn.microsoft.com/en-us/dotnet/standard/assembly/

https://learn.microsoft.com/en-us/dotnet/standard/clr

https://learn.microsoft.com/en-us/dotnet/api/system.reflection.assembly.load?view=net-8.0

https://support.virustotal.com/hc/en-us/articles/360001385897-File-search-modifiers

https://www.virustotal.com/gui/file/80a4243ec5fde0768e9d054ee478bdb6cf85e2a91b641877bb68a62dfceea8fa/details

https://www.virustotal.com/gui/file/b68a65e9f8cb6aff77c8d1973e60063de53ca052ee6c98919c96decf5ef705a8/details

https://attack.mitre.org/matrices/enterprise/

https://unit42.paloaltonetworks.com/solarmarker-malware/

https://www.esentire.com/blog/solarmarker-to-jupyter-and-back

https://squiblydoo.blog/2023/11/07/october-2023-solarmarker/

https://redcanary.com/blog/intelligence-insights-december-2023/

Maveris is an IT and cybersecurity company committed to helping organizations create secure digital solutions to accelerate their mission. We are Veteran-owned and proud to serve customers across the Federal Government and private sector. Maveris Labs is a space for employees and customers to ask and explore answers to their burning “what if…” questions and to expand the limits of what is possible in IT and cybersecurity. To learn more, go to maveris.com

--

--