Author: Quentin Rhoads-Herrera, Director of Professional Services
When it comes to security incidents involving malware, most of us rely on the information provided by the investigating firm to understand what the malware does, why it does it, and how to find it in our own environment. However, if you are interested in more in-depth details like us, you also want to know how to find that data for yourself.
With that in mind, Charles Dardaman, a Senior Adversarial Engineer of TEAMARES, and I have outlined the basic steps you can use to reverse engineer malware by showing you how it can be done on currently the biggest malware topic in the industry, the SolarWinds backdoor.
How It’s Done:
When looking at any form of malware, you first want to investigate its starting point, including what first landed on disk, memory, or in someone’s inbox that led to the rest of the compromise. In this case, that was the SolarWinds Orion DLL backdoor named SolarWinds.Orion.Core.BusinessLayer.DLL (b91ce2fa41029f6955bff20079468448), also called the SUNBURST Backdoor by many. For our sake, we used the excellent writeup provided by FireEye.
The first task a reverse engineer needs to do is to determine the type of binary or file they are working with. Luckily for us, the SUNBURST DLL is a .NET library so we can leverage several different tools to reverse out the contents of the .NET assembly to as close to the original source code as possible. The tool we used in this walkthrough is dotPeek from JetBrains.
When you initially load the SUNBURST DLL into the dotPeek application, it displays some basic information along with metadata, references, and classes as seen in the below screenshot.
With this now loaded, we can do a few different things; we can either export it to a Visual Studio project or just directly navigate the decompiled code through the dotPeek application.
If you wish to export for the simplicity of using grep or other command-line tools, either right-click the imported DLL or highlight it on the assembly explorer toolbar and select the icon with Visual Studio on it. This will export the whole DLL into a Visual Studio project folder and convert these different classes into .cs files.
Moving on to the backdoor discovered by FireEye and others working on the SolarWinds investigation. FireEye stated that the malware resided in the class SolarWinds.Orion.Core.BusinessLayer.OrionImprovementBusinessLayer, which you should be able to find that class if you extend the SolarWinds.Orion.Core.BusinessLayer section in dotPeek.
Double-clicking the backdoor class will cause dotPeek to decompile the source as closely as possible to its original value. Keep in mind that it will not be a one-for-one translation, but you will still have enough data to continue your investigation.
If you are like us, you will want to comment in-line on any code that looks malicious, or suspicious, but dotPeek does not give you that ability, unfortunately. You can however copy all of the decompiled code and save it to a new file with the .cs extension.
With the decompiled code, it’s easy to begin to get a handle on what it is doing. To start with, we can see a large number of obfuscated strings that appear to be base64 encoded.
In order to know the value of these strings, you can jump down to the Unzip method shown below to look at the try-catch block.
As you can see, the Unzip function is converting from base64, decompressing (by deflating the data), and then encoding it into UTF-8. With this knowledge in hand, we can either write a quick script to deobfuscate this data or just throw it into CyberChef here.
The tool easily decodes the strings and you can now have a better idea of what the malicious code is attempting to do. By deobfuscating all the encoded strings inside the malware, you can already see the points called out by FireEye, including the domain that the malware phones home to.
With this extra data, you can now investigate the core functionality of the malware. In this case, we navigated to the Initialize() function which is used to start the malware by doing a number of checks and tricks in order to bypass sandboxes while not running on every machine.
To start, the malware will verify that its process name is “solarwinds.businesslayerhost” by hashing the lowercase name of the process and comparing it with the hardcoded hash. It will then only execute if the file write time was roughly two weeks prior. It does this because sandboxes have a significantly shorter runtime and the malicious code will not run inside of the sandbox. This can give people a false sense of security if they never dig any further.
The malware will then create a named pipe in order to ensure that it doesn’t run multiple instances.
After which, it will read the configuration file by the same name in the folder. As long as the number is either a 4 or 5 and not a 3, it will continue to run.
To see how that works, you can jump down to the ReadReportStatus() method and take note of which report status is being returned and how the Initiate() function is looking for the return value of Truncate or 3 in order to fail.
The malware then gets its domain name.
And creates a UserID based on the machine’s network interface, the domain name, and the GUID.
Now that we know the checks the malware will do before it runs, you can look for some other interesting functionalities. One thing that we like to look for is what kinds of jobs or commands the malware might be able to act upon.
In this sample, we can easily see what the malware is capable of by looking at the JobEngine.
From here, it’s easy to find the corresponding function and understand exactly how it works, such as jumping to ReadRegistryValue as shown below.
Understanding the malware’s capabilities is obviously very important, but we also want to discern how it communicates with the attacker’s Command and Control (C2). For this, you can look at the update function that is the main loop inside of the malware. This logic shows the malware creating a cryptoHelper object which it then uses to generate the DNS domains it reaches out to.
The UpdateNotification() below is called to validate that the malware has an Internet connection by checking if it can reach api.solarwinds.com.
While checking the Internet connection, it will call the TrackProcesses() function which contains a long list of AV/EDR tools to avoid. You can then dig further into it by brute-forcing the hashes that it compares with.
With these blocklists passed, the malware will reach out to the partially randomized domain and check-in with the C2 by starting a thread to handle the communication and job requests that the C2 responds with.
You can then follow this to the http.Initialize function that builds the JSON payload and acts on any job that might be received from the C2 server. Inside this function, we noticed a CreateUploadRequest that creates the actual JSON request sent to the server. Partway down the code where we commented, the json that it uses for the request is visible.
Even further down the http.Initalize function, you can see where the malware parses the response from the C2 server and acts on any jobs passed down to it.
While there is a lot more functionality to this malware, we hope this basic reverse engineering overview and examples can help you get started reversing the binaries. These techniques are not unique to this sample and can be used with other malware.
About the Author:
Quentin Rhodes-Herrera, Cyber One’s director of professional services, leads the offensive and defensive teams known as TEAMARES. He is an experienced security professional with expertise in security analysis, physical security, risk assessment, and penetration testing. Quentin’s diverse background is built from a variety of staff and leadership positions in IT, with specific experience in threat and vulnerability management, penetration testing, network operations, process improvement, standards development, and interoperability testing. Follow him on Twitter at @paragonsec.
References and links: