SUNBURST's DGA Has a Subtle Split-Personality Quirk
When QiAnXin's RedDrip Team published their analysis of the SUNBURST malware's Domain Generation Algorithm (DGA) in mid-December, they laid out how the malware crafts its DNS queries. The subdomains are assembled from an encoded GUID, a single byte, and an encoded version of the compromised hostname. Cloudflare's follow-up work on the same query logs has now highlighted a peculiar detail in that scheme: long hostnames don't travel in one piece. They're broken across multiple DNS queries, and the fragments are stitched back together using an XOR-based relationship in the message headers.
Two Encoding Paths, One Telltale Prefix
The RedDrip team's reverse engineering showed that the hostname portion of the DGA string is generated by a custom base32 variant. The actual function names inside the compromised DLL, Base64Decode and Base64Encode, are decoys meant to resemble legitimate .NET library routines. The encoding scheme itself picks a path based on the characters in the hostname:
- If every character is in the domain-safe set
"0123456789abcdefghijklmnopqrstuvwxyz-_.", the malware uses the function masquerading asBase64Decode. - If the hostname contains any other character, the lookalike
Base64Encodefunction is used instead, and the literal characters'00'are prepended to the output.
That prefix makes decoding straightforward: checking whether the encoded hostname starts with '00' immediately tells you which routine produced it.
A Blind Spot in the Decoder
Cloudflare noticed that RedDrip's published Python decoding script includes a filter on line 138 that discards any encoded hostname lacking a '0' character. The logic is understandable—'0' is the encoded representation of a dot, dash, underscore, or the digit zero—so fully-qualified names with their necessary dots are expected to contain one. But that assumption breaks down for short domain fragments.
Take a hostname like www2.example.com. When it's too long to fit into a single query's payload, the malware splits it. The first message might carry www2.example.c, and a second query would transmit just om. That trailing fragment has no dot, so its encoding has no '0', and RedDrip's script silently ignores it. Among the published query logs, this explains the otherwise puzzling decoded strings such as a lone 'm' or 'l'.
Finding the Matching Fragments
The key to reassembly lies in the first 15 bytes of the DGA string, which RedDrip had identified as an encoded GUID. Cloudflare refers to this section as the header. By examining a known split—two distinct queries that both decoded to www2.example.c and two more that decoded to om—they found a deterministic relationship:
_Base32Decode(message1) XOR KEY = Base32Decode(message2)_
The KEY here is a single repeating character, applied byte-by-byte to the decoded header of the first message to yield the decoded header of the second. The exact key value isn't required to pair messages. Since XOR is symmetric, you can simply compare two candidate headers directly:
Base32Decode(message1) XOR Base32Decode(message2) = KEY
If the result of that operation is a repeating character, the two messages belong to the same fragmented hostname. In the working example, the XOR result resolves to the repeating binary sequence '01101101', which is the ASCII value 0x6D, or the letter 'm'.
Key Position Identified
Shortly after Cloudflare's initial findings, Erik Hjelmvik published a post clarifying where the XOR key lives within the message structure. Building on that code, Cloudflare now offers a Python implementation that converts the first 16 bytes of a DGA query header directly into the decoded GUID string. With that GUID in hand, pairing messages becomes a simple lookup: fragments carrying the same decoded GUID are parts of the same original hostname, and the full domain can be reconstructed in order.
Cloudflare's initial hypothesis was that the key might be derivable from the header or padding bytes alone. With the key's location now identified, that relationship is no longer a mystery, and the full decoding path—from raw query to reassembled, decodable hostname—is now public.



