Sunburst malware — Uncozying the code

Scutum
6 min readDec 21, 2020

Hi all, I hope you all are doing well in this uncertain times of Covid as well as the recent developments we saw in infosec community. As you probably know, an espionage campaign was discovered by FireEye after their set of Red Team tools were accessed. It was a supply chain attack where attackers were able to insert their malicious code in SolarWinds Orion software update back in March 2020 and it was digitally signed by SolarWinds. You can find the actual findings here:

In this blog, I will go through some research findings FireEye mentioned in the above blog and try to find the relevant code in the sample of Sunburst malware used in this attack.

As mentioned in the report, the malicious code resides in the DLL named “SolarWinds.Orion.Core.BusinessLayer.dll”.

SolarWinds.Orion.Core.BusinessLayer.dll
MD5: b91ce2fa41029f6955bff20079468448

I was able to grab a copy of this malicious DLL. So let’s put on my malware analyst’s hat and begin with some recon steps. As usual, I opened the file in DLL in PEStudio to look at its attributes. The DLL is indeed signed by SolarWinds and the compilation time is in March 2020:

PEStudio view of malicious DLL

The code looks like its programmed in Microsoft .NET. During one of the malware courses I did, I learned about DnSpy debugger. You can also download it from here:

Loading the malicious DLL in DnSpy shows tons of code from the original genuine DLL. There are lot of functions, classes and objects. On a first glance, it looks like part of a normal software development package. FireEye mentioned that the backdoor code resides in the location:

SolarWinds.Orion.Core.BusinessLayer.BackgroundInventory.InventoryManager.RefreshInternal

Take a look at the screenshot below which shows the code defined there:

RefreshInternal() starting a new Thread

The method RefreshInternal() is starting a new Thread by loading the code in the method named Initialize(). This thread runs in background and probably one of the ways to hide the activity. Let’s jump directly to Initialize() method to see what it is doing!

Code inside Intialize()

Woho! Looks like there are many IF conditions in the Initialize() method. Our goal here will be to see how this malicious backdoor reveals the actual commands for exploitation, CnC and other stages of attack. Therefore, we will go through each and every important IF condition to see how it can help us to understand the sample.

The first IF condition is getting the name of the current process running and hashing it. Once it computes its hash, it is being compared with a constant value:

Process name hash to be compared with constant hash: 17291806236368054941UL

The function GetHash() implements a version of Fowler–Noll–Vo hash function. FireEye already reversed it and the condition is basically checking the process name to “solarwinds.businesslayerhost”.

Constants used in the GetHash() function (Fowler–Noll–Vo hash function)

If this condition is TRUE, then only the next code executes. The next instruction is very interesting. This is the code which makes the sample inactive for about 12 to 14 days after the system runs the malicious DLL for the first time.

Logic to remain inactive for 12 to 14 days

So basically what this code is doing is first it gets the lastWriteTime of the file containing the code. The windows method Assembly.GetExecutingAssembly() does the job. The code first grabs the file write time using this, then generates a random number between 288 and 336 (i.e. basically 12 to 14 days, 288/24 hours = 12 days and 336/24 hours = 14 days). It adds a random number between this range and appended it to lastWriteTime and then compares it to current date and time. If the result is more than 0, it means the file was written 12 to 14 days before today. Therefore, this allows the malicious code to remain inactive for days.

You can find the details here windows methods here:

https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getexecutingassembly?view=net-5.0
https://docs.microsoft.com/en-us/dotnet/api/system.random.next?view=net-5.0
https://docs.microsoft.com/en-us/dotnet/api/system.datetime.compareto?view=net-5.0

Moving forward with the code, the other IF conditions looks like it doing something related to configuration, setting appId and all. The next interesting and perhaps the jewel of this whole backdoor lies in the function Update(). its the second last line of code for Initialize(). Let’s dissect it as well:

Update() method

Scanning through the code in Update() function, the sample checks connectivity to the domain api.solarwinds.com and few other operations. I came across another very interesting IF statement which seems to be checking list of processes running on a system. It is performing this step using the function TrackProcesses().

TrackProcesses() checks output of three other functions:

  • SearchAssemblies() — This function checks a list of hashes defined in array assemblyTimeStamps. FireEye listed the original values of these hashes on their Github page. Basically the sample is scanning for malware analysis and reversing tools such as x64-dbg, apimonitor-x64, pe-sieve64 and many more.
assemblyTimeStamps list of hashes
  • SearchServices() I am finding difficulty to analyze what this is checking. However, this certainly seems to be related to below function.
  • SearchConfigurations()- This function runs a command to get all information from class Win32_SystemDriver. Once it gets holds of it, it specifically tries to access a property called as PathName.

Command it runs: Select * From Win32_SystemDriver

Property: PathName

This probably means it is running the same hash function on all the processes listed in Win32_SystemDriver function and checks them against a hardcoded list of hashes in array named configTimeStamps. These values seems to be related to system monitoring softwares like lragentmf.sys(LogRhythm System Monitor), hexisfsmonitor.sys, groundling32.sys (Dell SecureWorks Red Cloak) and few more.

In conclusion, TrackProcesses() is the method which implements all anti-reversing, anti-monitoring and protection services for the malware.

Once this checks are passed and if these services and processes are not running, the sample continues to execute. I scanned the code further and found one line of code where it defines a string hostName. It checks a condition and based on its output it executes either of these methods:

  • GetCurrentString()
  • GetPreviousString()

Both these methods ultimately executed another method called as GetStatus(). This is the most interesting part of the code since now we will be able to see the actual network IOCs as mentioned in the FireEye report.

Domains strings is called

domain2 : appsync-api

domain3: eu-west-1, eu-west-2, eu-west-1, eu-east-2

domain1: avsvmcloud(.)com

The domain1(avsvmcloud(.)com) is the command and control server for this Sunburst malware and it was seized by Microsoft recently. You can find more details here:

Up to this point, it seems like we were able to locate the complete path of the sample execution right from checking the hash of process name and up to contacting the command and control server. Now, I can further continue my analysis and see what it does next i.e. downloading additional payloads, changes to the victim host, etc.

I hope you find the above information useful to understand the code behind the behavior of this malware sample as described in FireEye report. Kudos to all the researchers who were able to publish this in such a short amount of time. And lastly, thank you for taking the time to go through this report. As always, please let me know your suggestions and comments!

Thank you!

--

--