Threat Research Blog

December 22, 2020

Sunburst Backdoor, Part III: DGA & Security Software

In the previous parts of our blog (part I and part II), we have described the most important parts of the Sunburst backdoor functionality and its Domain Generation Algorithm (DGA).

This time, let's have a deeper look into the passive DNS requests reported by Open-Source Context and Zetalytics.

The valid DNS requests generated by the malware fall into 2 groups:

  • DNS requests that encode a local domain name
  • DNS requests that encode data

The first type of DNS requests allows splitting long domain names into separate requests. These requests are generated by the malware's functions GetPreviousString() and GetCurrentString().

In general, the format of a DNS request that encodes a domain name may look like:

USER_ID.NUM.COMPUTER_DOMAIN[.]appsync-api.us-west-2[.]avsvmcloud[.]com

where:

  • USER_ID is an 8-byte user ID that uniquely identifies a compromised host, encoded as a 15-character string
  • NUM is a number of a domain name - either 0 or 1, encoded as a character
  • COMPUTER_DOMAIN is an encoded local computer domain


Let's try decoding the following 3 DNS requests:

  • olc62cocacn7u2q22v02eu.appsync-api.us-west-2.avsvmcloud.com
  • r1qshoj05ji05ac6eoip02jovt6i2v0c.appsync-api.us-west-2.avsvmcloud.com
  • lt5ai41qh5d53qoti3mkmc0.appsync-api.us-west-2.avsvmcloud.com

String #1

Let's start from the 1st string in the list:

olc62cocacn7u2q22v02eu.appsync-api.us-west-2.avsvmcloud.com.

In this string, the first 15-character string is an encoded USER_ID: "olc62cocacn7u2q".

Once it is base-64 decoded, as explained in the previous post, it becomes a 9-byte byte array:

86 7f 2f be f9 fb a3 ae c4

The first byte in this byte array is a XOR key: 0x86. Once applied to the 8 bytes that follow it, we get the 8-byte user ID - let's take a note and write it down, we will need it later:

f9 a9 38 7f 7d 25 28 42

Next, let's take the NUM part of the encoded domain: it's a character "2" located at the position #15 (starting from 0) of the encrypted domain.

In order to decode the NUM number, we have to take the first character of the encrypted domain, take the reminder of its division by 36, and subtract the NUM's position in the string "0123456789abcdefghijklmnopqrstuvwxyz":

num = domain[0] % 36 - "0123456789abcdefghijklmnopqrstuvwxyz".IndexOf(domain.Substring(15, 1));

The result is 1. That means the decrypted domain will be the 2nd part of a full domain name. The first part must have its NUM decoded as 0.

The COMPUTER_DOMAIN part of the encrypted domain is "2v02eu". Once decoded, using the previously explained method, the decoded computer domain name becomes "on.ca".

String #2

Let's decode the second passive DNS request from our list:

r1qshoj05ji05ac6eoip02jovt6i2v0c.appsync-api.us-west-2.avsvmcloud.com

Just as before, the decoded 8-byte user ID becomes:

f9 a9 38 7f 7d 25 28 42

The NUM part of the encoded domain, located at the position #15 (starting from 0), is a character "6".

Let's decode it, by taking the first character ("r" = 114), take the reminder of its division by 36 (114 % 36 = 6), and subtracting the position of the character "6" in the "0123456789abcdefghijklmnopqrstuvwxyz", which is 6. The result is 0. That means the decrypted domain will be the 1st part of the full domain name.

The COMPUTER_DOMAIN part of the encrypted domain is "eoip02jovt6i2v0c". Once decoded, it becomes "city.kingston."

Next, we need to match 2 decrypted domains by the user ID, which is f9 a9 38 7f 7d 25 28 42 in both cases, and concatenate the first and the second parts of the domain. The result will be "city.kingston.on.ca".

String #3

Here comes the most interesting part. Lets try to decrypt the string #3 from our list of passive DNS requests:

lt5ai41qh5d53qoti3mkmc0.appsync-api.us-west-2.avsvmcloud.com

The decoded user ID is not relevant, as the decoded NUM part is a number -29.

It's neither 0 nor 1, so what kind of domain name that is? If we ignore the NUM part and decode the domain name, using the old method, we will get "thx8xb", which does not look like a valid domain name.

Cases like that are not the noise, and are not some artificially encrypted artifacts that showed up among the DNS requests.

This is a different type of DNS requests. Instead of encoding local domain names, these types of requests contain data. They are generated by the malware's function GetNextStringEx().

The encryption method is different as well. Let's decrypt this request.

First, we can decode the encrypted domain, using the same base-64 method, as before.

The string will be decoded into 14 bytes:

7c a5 4d 64 9b 21 c1 74 a6 59 e4 5c 7c 7f

Let's decode these bytes, starting from the 2nd byte, and using the first byte as a XOR key. We will get:

7c d9 31 18 e7 5d bd 08 da 25 98 20 00 03

In this array, the bytes marked in yellow are an 8-byte User ID, encoded with a XOR key that is selected from 2 bytes marked in red.

Let's decode User ID:

for (int i = 0; i < 8; i++)
{
    bytes[i + 1] ^= bytes[11 - i % 2];
}

The decoded byte array becomes:

7c f9 a9 38 7f 7d 25 28 42 25 98 20 00 03

The User ID part in marked in yellow. Does it look familiar? Indeed, it's the same User ID we've seen before, when we decoded "city.kingston.on.ca".

The next 3 bytes marked in red are: 25 98 20.

  • 2
  • 0x59820

The first number 2 stands for the size of data that follows - this data is 00 03 (selected in green).

The number 0x59820, or 366,624 in decimal, is a timestamp. It's a number of 4-second periods of time since 1 January 2010.

To obtain the real time stamp, we need to multiple it by 15 to get minutes, then add those minutes to 1 January 2010:

var date = (new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc)).AddMinutes(timestamp * 15);

For the number 0x59820, the time stamp becomes 16 July 2020 12:00:00 AM - that's the day when the DNS request was made.

The remaining 2 bytes, 00 03, encrypt the state of 8 security products, to indicate whether each one of them is running or whether it is stopped.

The 8 security products are:

  • Windows Live OneCare / Windows Defender
  • Windows Defender Advanced Threat Protection
  • Microsoft Defender for Identity
  • Carbon Black
  • CrowdStrike
  • FireEye
  • ESET
  • F-Secure

2 states for 8 products require 2 * 8 = 16 bits = 2 bytes.

The 2 bytes 00 03 in binary form are:

00 00 00 00 00 00 00 11

Here, the least-significant bits 11 identify that the first product in the list, Windows Live OneCare / Windows Defender, is reported as 'running' (1) and as 'stopped' (1).

Now we know that apart from the local domain, the trojanised SolarWinds software running on the same compromised host on "city.kingston.on.ca" domain has also reported the status of the Windows Defender software.

What Does it Mean?

As explained in the first part of our description, the malware is capable of stopping the services of security products, be manipulating registry service keys under Administrator account.

It's likely that the attackers are using DNS queries as a C2 channel to first understand what security products are present. Next, the same channel is used to instruct the malware to stop/deactivate these services, before the 2nd stage payload, TearDrop Backdoor,  is deployed.

Armed with this knowledge, let's decode other passive DNS requests, printing the cases when the compromised host reports a running security software.

NOTES:

  • As a private case, if the data size field is 0 or 1, the timestamp field is not followed with any data. Such type of DNS request is generated by the malware's function GetNextString(). It is called 'a ping' in the listing below.
  • If the first part of the domain name is missing, the recovered domain name is pre-pended with '*'.
  • The malware takes the time difference in minutes, then divides it by 30 and then converts the result from double type to int type; as a result of such conversion, the time stamps are truncated to the earliest half hour.
2D82B037C060515C SFBALLET

                Data:
                Windows Live OneCare / Windows Defender [running] 11/07/2020 12:00:00 AM

                Pings:
                12/07/2020 12:30:00 AM
70DEE5C062CFEE53 ccscurriculum.c

                Data:
                ESET [running] 17/04/2020 4:00:00 PM

                Pings:
                20/04/2020 5:00:00 PM

AB902A323B541775 mountsinai.hospital

                Pings:
                4/07/2020 12:30:00 AM

9ACC3A3067DC7FD5 *ripta.com

                Data:
                ESET [running] 12/09/2020 6:30:00 AM

                Pings:
                13/09/2020 7:30:00 AM
                14/09/2020 9:00:00 AM

CB34C4EBCB12AF88 DPCITY.I7a

                Data:
                ESET [running] 26/06/2020 5:00:00 PM

                Pings:
                27/06/2020 6:30:00 PM
                28/06/2020 7:30:00 PM
                29/06/2020 8:30:00 PM
                29/06/2020 8:30:00 PM

E5FAFE265E86088E *scroot.com

                Data:
                CrowdStrike [running] 25/07/2020 2:00:00 PM

                Pings:
                26/07/2020 2:30:00 PM
                26/07/2020 2:30:00 PM
                27/07/2020 3:00:00 PM
                27/07/2020 3:00:00 PM

426030B2ED480DED *kcpl.com

                Data:
                Windows Live OneCare / Windows Defender [running] 8/07/2020 12:00:00 AM
                Carbon Black [running] 8/07/2020 12:00:00 AM

Full list of decoded pDNS requests can be found here.
An example of a working implementation is available at this repo.