◁ BACK TO HOME nullsector.cc // post // sunburst

SOLARWINDS BREACH

— SUNBURST BACKDOOR ANALYSIS

In this post i want to talk about the SolarWinds breach occurred in 2020 giving the emphasis to the backdoor inside SolarWinds.Orion.Core.BusinessLayer.dll. This is not about threat intelligence but more to understand from a technical perspective how an APT is able to evade detection for such a long time and maintain persistence. At the end i will share some links if you are interested and want to explore more.

Sunburst is a trojanized version of a signed plugin named SolarWinds.Orion.Core.BusinessLayer.dll. It was inserted into the software build of SolarWinds Orion products using another malware named SUNSPOT (more here). The backdoor was then distributed through software updates and installers to all of the customers.

The backdoor is a 32 bit signed dll written in .NET.

FIG.01FILE — DIGITAL SIGNATURE
File properties showing the SolarWinds digital signature on the trojanized DLL
▚ FIG.01 THE DLL SIGNED BY SOLARWINDS

The fact that is signed by SolarWinds private key makes this malware extremely more evasive and difficult to detect. Sunburst code begins at OrionImprovementBusinessLayer.Initialize().

FIG.02DECOMPILED — INITIALIZE()
Decompiled OrionImprovementBusinessLayer.Initialize method, the Sunburst entry point
▚ FIG.02 SUNBURST ENTRY POINT — INITIALIZE()

Here the hash of the string solarwinds.businesslayerhost.exe is being calculated and compared to the current process hash. This ensures that the program is running in the right context. After that the method makes a time check ensuring that sunburst is inside the system for at least 2 weeks, making the malware even more evasive under the radars of sandboxes trying to run it.

This method is called by a legitimate method in the program called RefreshInternal()

FIG.03DECOMPILED — REFRESHINTERNAL()
Decompiled RefreshInternal method calling the backdoor routine
▚ FIG.03 LEGITIMATE CALLER — REFRESHINTERNAL()

So basically whenever the dll will execute this normal routine the backdoor will be executed too in the background by another thread.

This is the hashing algorithm that will be always present from now on

FIG.04DECOMPILED — HASHING ALGORITHM
Decompiled hashing algorithm with its constants
▚ FIG.04 THE HASHING ALGORITHM USED FROM NOW ON

Doing a research online about the constants in the algorithm we can see its using FNV hash function. All the reverse-engineered hashes are available here.

The program then continues calling OrionImprovementBusinessLayer.GetOrCreateUserID

FIG.05DECOMPILED — GETORCREATEUSERID
Decompiled OrionImprovementBusinessLayer.GetOrCreateUserID method
▚ FIG.05 UNIQUE MACHINE ID GENERATION

This method has the purpose of creating an Unique ID of the machine. The UID is composed of:

All of that is then passed to a MD5 hashing function. This function will be crucial for the DGA implemented inside the malware.

At the end of the method another method OrionImprovementBusinessLayer.Update is called, this method contains all the logic of sunburst.

ENVIRONMENT CHECK // 00

At execution Sunburst goes through an extensive list of known security process, drivers and services to ensure its not being executed inside a machine dedicated for analysis. The method responsible for this is OrionImprovementBusinessLayer.TrackProcess

FIG.06DECOMPILED — TRACKPROCESS
Decompiled OrionImprovementBusinessLayer.TrackProcess method
▚ FIG.06 SECURITY PROCESSES, DRIVERS AND SERVICES CHECK

OrionImprovementBusinessLayer.ProcessTracker.SearchAssemblies compares all the running processes to a hardcoded list of hashes of security related processes

FIG.07DECOMPILED — SEARCHASSEMBLIES
Decompiled ProcessTracker.SearchAssemblies comparing running processes to a hash list
▚ FIG.07 RUNNING PROCESSES VS HASH LIST

Then Sunburst before initializing the connection with the C2 checks if the host api.solarwinds.com resolves to an expected IP address

FIG.08DECOMPILED — DNS CHECK
Decompiled check resolving api.solarwinds.com to an expected IP address
▚ FIG.08 RESOLVING API.SOLARWINDS.COM
FIG.09DECOMPILED — ENCODED HOST STRING
Decompiled encoded string holding the api.solarwinds.com hostname
▚ FIG.09 THE ENCODED API.SOLARWINDS.COM STRING

The above encoded string is passed as the parameter hostName and decoded gave: api.solarwinds.com

DOMAIN GENERATION ALGORITHM // 01

A Domain Algorithm Generator is the capability for a malware to dynamically generate domain hostname on the fly. DGA has a lot of benefits, the major one is probably the capacity to evade domain blacklisting

Talking about sunburst we can say that is not a real DGA because the top level domain always remains the same (avsvmcloud.com), this has a big downside, it's possible to make detection rule by simply doing something like: *.avsvmcloud.com

What stands out with sunburst is how the domain prefixes were generated in an unique way.

This is an example of URL generated by sunburst and used for C2 communications:

04jrge684mgk4eq8m8adfg7.appsync-api.us-east-2.avsvmcloud.com

You can find a list here

Starting with appsync-api.us-east-2.avsvmcloud.com

OrionImprovementBusinessLayer.GetStatus is responsible for concatenating the strings in the domain

FIG.10DECOMPILED — GETSTATUS
Decompiled OrionImprovementBusinessLayer.GetStatus concatenating the domain strings
▚ FIG.10 CONCATENATING THE C2 DOMAIN PARTS

domain1, domain2 and domain3 are hardcoded and defined as follows

FIG.11DECOMPILED — DOMAIN PARTS
Decompiled hardcoded domain1, domain2 and domain3 values
▚ FIG.11 HARDCODED DGA DOMAIN PARTS

domain3 can vary between the four different strings

The backdoor has also the capability to generate a pseudo-random URI

Here a little snippet not the full method

FIG.12DECOMPILED — URI GENERATION
Decompiled snippet of the pseudo-random URI generation
▚ FIG.12 PSEUDO-RANDOM URI BUILDER — SNIPPET

As you can see all the URIs are hardcoded inside the code. Other generated URIs are:

A new thread will be spawned and will run httpHelper.Initialize method used to communicate with the C2

FIG.13DECOMPILED — HTTPHELPER.INITIALIZE
Decompiled httpHelper.Initialize method spawning the C2 communication thread
▚ FIG.13 C2 COMMUNICATION THREAD

After this sunburst composes a JSON payload with information about the infected host and sends it to the C2 server. The payload contains data such as UID and session ID

FIG.14DECOMPILED — JSON PAYLOAD
Decompiled code composing the JSON payload sent to the C2
▚ FIG.14 HOST INFO JSON SENT TO THE C2

Another way used by Sunburst to blend itself inside the traffic is to modify the User-Agent of the requests. Depending on situation and the use case the user agent could have taken two different values:

This is especially useful to mimic legitimate traffic, Microsoft-CryptoAPI was used at early stage of the attack inside the DNS queries. Starting the HTTP communications the backdoor switches user-agent and starts using SolarWindsOrionImprovementClient masquerading as OIP (Orion Improvement Program) protocol.

The anatomy of the operations was basically to use DNS at the early stage for listening and information gathering, once all the checks were done the communications switched to HTTP and the backdoor started executing commands

FIG.15DECOMPILED — USER-AGENT
Decompiled User-Agent values used by the backdoor
▚ FIG.15 USER-AGENT VALUES USED BY SUNBURST

Sunburst capabilities as a backdoor are very basic, inside the program are defined as jobs

FIG.16DECOMPILED — JOBS
Decompiled list of jobs defined inside the backdoor
▚ FIG.16 THE BACKDOOR JOBS DEFINED IN THE DLL

Everything a normal backdoor would do like modify files, kill/run processes, upload stuff...

DNS HOSTNAME ENCODING // 02

Inside 04jrge684mgk4eq8m8adfg7.appsync-api.us-east-2.avsvmcloud.com we have seen how sunburst concatenates .appsync-api and .us-east-2 subdomains but not how the random 04jrge684mgk4eq8m8adfg7 is generated

This is how the random string subdomain can be divided

One of the first methods encountered was OrionImprovementBusinessLayer.GetOrCreateUserID, as we have seen it basically gets a UID using current system configurations (domain name, reg key and MAC address of first available network interface). The UID returned from this function is encoded using CreateSecureString method through OrionImprovementBusinessLayer.CryptoHelper.Base64Encode

FIG.17DECOMPILED — CREATESECURESTRING
Decompiled CreateSecureString method encoding the UID
▚ FIG.17 ENCODING THE UID — CREATESECURESTRING
FIG.18DECOMPILED — BASE64ENCODE
Decompiled CryptoHelper.Base64Encode method
▚ FIG.18 BASE64 ENCODE HELPER

The encoded string: K8gwSs1MyzfOMy0tSTfMskixNCksKkvKzTYoTswxN0sGAA= resolves to ph2eifo3n5utg1j8d94qrvbmk0sal76c

This value is then concatenated with a character generated by OrionImprovementBusinessLayer.CryptoHelper.CreateString.

FIG.19DECOMPILED — CREATESTRING
Decompiled CryptoHelper.CreateString method generating a character
▚ FIG.19 GENERATING THE CURRENT CHARACTER

Other methods responsible for the random subdomain generation are GetPreviousString, GetNextString, GetNextStringEx

The last part of the generated subdomain is the encoded domain name of the machine defined in OrionImprovementBusinessLayer.CryptoHelper.dnStrLower

FIG.20DECOMPILED — DNSTRLOWER
Decompiled CryptoHelper.dnStrLower handling the machine domain name
▚ FIG.20 MACHINE DOMAIN NAME — DNSTR

The value of OrionImprovementBusinessLayer.CryptoHelper.dnStr is passed in the constructor of CryptoHelper class using OrionImprovementBusinessLayer.CryptoHelper.DecryptShort method

FIG.21DECOMPILED — CRYPTOHELPER CONSTRUCTOR
Decompiled CryptoHelper class constructor
▚ FIG.21 CRYPTOHELPER CLASS CONSTRUCTOR

CryptoHelper class is then initialized as this

FIG.22DECOMPILED — INITIALIZATION
Decompiled initialization of the CryptoHelper class with UserID and domain4
▚ FIG.22 CRYPTOHELPER INITIALIZED — UID + DOMAIN4

UserID is the previously generated ID and domain4 is the domain of the compromised machine

DecryptShort method first checks if all the characters of the domain are part of this string: 0123456789abcdefghijklmnopqrstuvwxyz-_. If it is the case the domain will be encoded using OrionImprovementBusinessLayer.CryptoHelper.Base64Decode. Otherwise it will be prepended with "00" and encoded using OrionImprovementBusinessLayer.CryptoHelper.base64Encode

FIG.23DECOMPILED — DECRYPTSHORT
Decompiled CryptoHelper.DecryptShort method checking the domain characters
▚ FIG.23 DOMAIN CHARACTER CHECK — DECRYPTSHORT
FIG.24DECOMPILED — BASE64DECODE
Decompiled CryptoHelper.Base64Decode method
▚ FIG.24 BASE64 DECODE HELPER

CONCLUSION // 03

That's all, you can find a python script for decoding the domain names generated with the DGA algorithm here

Links i used for researching

CONTACT