{
	"id": "a6fb26f3-ecb8-4a6b-8c46-1f96527f06d5",
	"created_at": "2026-04-06T01:30:43.296389Z",
	"updated_at": "2026-04-10T03:21:15.589646Z",
	"deleted_at": null,
	"sha1_hash": "f3660afd7f2e729d630655f5673bc5299300cfa6",
	"title": "A Look into SUNBURST’s DGA",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 866168,
	"plain_text": "A Look into SUNBURST’s DGA\r\nBy asuna amawaka\r\nPublished: 2020-12-22 · Archived: 2026-04-06 00:57:21 UTC\r\n6 min read\r\nDec 20, 2020\r\nMany fellow researchers have written very good analysis on SUNBURST malware, so I shall not do a\r\nwalkthrough on reverse engineering it. But I’ve been intrigued by the domain generation algorithm. The RedDrip\r\nTeam wrote a nice decoder [1]. Folks at NETRESEC has an improved version [2]. However, there are still some\r\ngenerated DGA strings that cannot be decoded. I want to know what these are! *Edit: Just moments before I click\r\non Publish for this post, I saw that Kaspersky’s analysts wrote their code in C [3] with nice writeup [4] that also\r\nhandled decoding of all two types of DGA string. But hey, I got Python :)\r\nBefore I go on to explain what I did to decode these DGA strings, I would like to summarize the related functions\r\nand variable names so you won’t be lost.\r\nVictims of SUNBURST are uniquely identified with a GUID that is created within\r\nOrionImprovementBusinessLayer.GetOrCreateUserID(). This GUID is a 8-byte value made up of the victim\r\nmachine’s MAC address, MachineGUID value read from HKLM\\Software\\Microsoft\\Cryptography\\MachineGuid\r\nand the victim machine’s domain name. These three information are concatenated and MD5-hashed. The MD5\r\nvalue is then “cut” into 2 and XORed (where 1st byte is XORed with the 9th byte; 8th byte is XORed with 16th\r\nbyte), hence we end up with a 8-byte irreversible unique identifier.\r\nOrionImprovementBusinessLayer.Update() is the function that calls the respective functions to generate the DGA\r\nstrings and responsible for handling the stuff that happens after a DNS response is received.\r\nOrionImprovementBusinessLayer.CryptoHelper.GetStatus() returns the concatenation of “.appsync-api.\u003cone of\r\nfour below\u003e.avsvmcloud.com”.\r\neu-west-1\r\nus-west-2\r\nus-east-1\r\nus-east-2\r\nFour functions within OrionImprovementBusinessLayer.CryptoHelper are the ones that are called upon to generate\r\nthe DGA strings:\r\nGetNextStringEx(), GetNextString(), GetPreviousString() and GetCurrentString().\r\nhttps://medium.com/insomniacs/a-look-into-sunbursts-dga-ba4029193947\r\nPage 1 of 6\n\nWithin them, the functions CreateSecureString(), CreateString(), DecryptShort(), Base64Decode() and\r\nBase64Encode() are responsible for the encoding process.\r\n(At this point, I would like to comment on the names of the functions and variables — geez most of them are not\r\nin any way descriptive of the actual meaning of the task they perform or the value they hold. If anything, they are\r\nthere to mislead the analyst.)\r\nCreateSecureString() does nothing to create secure strings. What it really does is XOR-encode some data with a\r\nrandom byte as XOR key, prepend this byte to the data and then return a Base32-like encoded string of the data.\r\nThe Base32-like encoding is performed by Base64Encode(). Look at that nasty confusing name!\r\nThe other related function, Base64Decode() is sort of a substitution cipher where by default the characters’ are\r\nshifted by 4 to the right according to a custom alphabet. If a special character “0”, “.”, “-“ or “_” is encountered,\r\nthen a random shift value is selected. Either Base64Decode() and Base64Encode() is called within\r\nDecryptShort().\r\nCreateString() creates a 1 byte value that encodes the index of the DGA string. In the event that multiple DGA\r\nstrings are required to fully represent the victims’ domain, this index numbering (I call it the “chunk index”, and it\r\nranges from 0 to 35) will help the receiving end to piece back the domain. This is because the DGA string is\r\ncapped at 32 bytes (excluding the fixed “.appsync-api…avsvmcloud.com”), so encoded victim’s domain that is\r\nlonger than 16 bytes would have to be expressed across multiple DGA strings (the first 16 bytes in each of such\r\nDGA string is taken up by victim’s GUID and the chunk index). Having a chunk index of 35 means that this DGA\r\nstring is the last piece.\r\nTo explain this concept of “index numbering”, let’s look at the output of my decoder. After decoding the victim\r\nGUID and the index, I was able to link up two DGA strings (involving abit of manual searching) that makes up\r\none victim’s domain. Chunk Index of “0” means it is the first piece, and Chunk Index of “35” means it is the last.\r\nIf there had been another piece in the middle, it would be index “1”.\r\nPress enter or click to view image in full size\r\nhttps://medium.com/insomniacs/a-look-into-sunbursts-dga-ba4029193947\r\nPage 2 of 6\n\nWhat made decoding the victims’ domain possible is because the malware generated the DGA string via\r\nGetPreviousString() and GetCurrentString(). The victims’ domain is encoded and included in the DGA string\r\nthrough the variables dnStr and dnStrLower. The decoders out there would try to reverse the DGA string to decode\r\ndnStrLower to retrieve the victim’s domain.\r\nGet asuna amawaka’s stories in your inbox\r\nJoin Medium for free to get updates from this writer.\r\nRemember me for faster sign in\r\nThis is how the DGA string would look like. I call this “Type 1” DGA string.\r\nPress enter or click to view image in full size\r\nNotice that in GetNextString() and GetNextStringEx(), dnStr and dnStrLower are not used. Instead, another\r\nfunction UpdateBuffer() is called.\r\nPress enter or click to view image in full size\r\nhttps://medium.com/insomniacs/a-look-into-sunbursts-dga-ba4029193947\r\nPage 3 of 6\n\nAs such, a different kind of DGA string is generated. I call this “Type 2” DGA string.\r\nPress enter or click to view image in full size\r\nWithin UpdateBuffer(), a 3-bytes time value is calculated through GetStringHash(). The last two bytes of this time\r\nvalue is going to be used as the XOR key to encode the 8-bytes victim GUID. UpdateBuffer() returns a 11-bytes\r\nvalue made up of 8-bytes encoded GUID and 3-bytes time/XOR key. If data is provided to the function (data that\r\ndescribes the number of security tools’ processes present somehow), then UpdateBuffer() returns a 13-bytes value,\r\nwith the additional 2 bytes appended behind the time/XOR key.\r\nThe value is then encoded through CreateSecureString(), which applies the Base32-like encoding. The ending\r\nDGA value is a 20 or 23 bytes string. Interesting. I can use this as a condition to identify this form of DGA string.\r\nAlright, stop talking. Let’s decode!\r\nI made 2 assumptions to try to differentiate between the two types DGA strings.\r\n- If the decoded chunk index is 0, the length of the DGA string cannot be less than 32 bytes (which should not be\r\nhappening, because if data is “overflowing” into another DGA string, then the first should be filled up to the max\r\nlength). Else, it could be a Type 2 DGA string.\r\n- If chunk index is successfully decoded to 35, then it is a Type 1 DGA string.(what are the chances of getting this\r\nexact value using the 16th byte and the 1st byte? I think low enough for this assumption to work.)\r\nAlong with the expected length for Type 2 DGA strings, I’m able to come up with the following if-else checks:\r\nhttps://medium.com/insomniacs/a-look-into-sunbursts-dga-ba4029193947\r\nPage 4 of 6\n\nWith the decoded Victim GUIDs from Type 1 and Type 2 DGA strings, we can identify the related DNS queries,\r\nand how many different machines are infected within the same domain.\r\nLet’s see some examples. I worked with data from here:\r\nhttps://github.com/bambenek/research/blob/main/sunburst/uniq-hostnames.txt\r\nIf anyone has a list of all such DNS queries from within their network, I guess it would also be helpful to be able\r\nto decode Type 2 DGA strings to identify all the different affected machines.\r\nHere’s the link to the script on Github:\r\nhttps://github.com/asuna-amawaka/SUNBURST-Analysis\r\nIf anyone is keen to discuss (or point out where I can improve in the code), DM me on Twitter!\r\nReferences:\r\n[1] https://github.com/RedDrip7/SunBurst_DGA_Decode\r\n[2] https://www.netresec.com/?page=Blog\u0026month=2020-12\u0026post=Reassembling-Victim-Domain-Fragments-from-SUNBURST-DNS\r\nhttps://medium.com/insomniacs/a-look-into-sunbursts-dga-ba4029193947\r\nPage 5 of 6\n\n[3] https://github.com/2igosha/sunburst_dga\r\n[4] https://securelist.com/sunburst-connecting-the-dots-in-the-dns-requests/99862/\r\nSource: https://medium.com/insomniacs/a-look-into-sunbursts-dga-ba4029193947\r\nhttps://medium.com/insomniacs/a-look-into-sunbursts-dga-ba4029193947\r\nPage 6 of 6",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://medium.com/insomniacs/a-look-into-sunbursts-dga-ba4029193947"
	],
	"report_names": [
		"a-look-into-sunbursts-dga-ba4029193947"
	],
	"threat_actors": [],
	"ts_created_at": 1775439043,
	"ts_updated_at": 1775791275,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/f3660afd7f2e729d630655f5673bc5299300cfa6.pdf",
		"text": "https://archive.orkl.eu/f3660afd7f2e729d630655f5673bc5299300cfa6.txt",
		"img": "https://archive.orkl.eu/f3660afd7f2e729d630655f5673bc5299300cfa6.jpg"
	}
}