{
	"id": "773a4d57-186f-4f94-aba9-988998807bdd",
	"created_at": "2026-04-06T01:30:30.216013Z",
	"updated_at": "2026-04-10T03:20:44.06973Z",
	"deleted_at": null,
	"sha1_hash": "97ae6a8584053abc5e574a08d5bd340bcd121e69",
	"title": "Ranbyus's DGA, Revisited - A second version of the Domain Generation Algorithm",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 80848,
	"plain_text": "Ranbyus's DGA, Revisited - A second version of the Domain\r\nGeneration Algorithm\r\nArchived: 2026-04-06 00:51:53 UTC\r\nEdit Dec. 8th, 2015: I found two additional samples. One of them uses a different tld ordering and an\r\nadditional operation on the hardcoded seed. I left the original text as is and put the changes in as edits.\r\nEdit Jan. 25, 2016: found another seed: 0x572473BB Edit Mar. 2, 2016: found another seed:\r\n0x17794CF1\r\nEdit Apr. 7, 2016: found another seed: 0x7CB7966E\r\nIn May I wrote about the Domain Generation Algorithm (DGA) of the banking trojan Ranbyus. This week I\r\nstumbled on some new Ranbyus samples that use a significant modification of the DGA. For simplicity’s sake I\r\ncall the DGA from the previous post the May DGA, and the DGA in this post the September DGA. However, I\r\ncan’t tell if the chronology is correct; the DGA in this post might just as well be an earlier or concurrent version of\r\nthe DGA reported in May.\r\nThe domains of the September version at first glance look like the ones from May. The second level domains\r\nconsist of the letters a-y; the top level domains are the same and they appear in the same order, i.e., .in → .me →\r\n.cc → .su → .tw → .net → .com → .pw. (Edit: this newer sample uses the same TLDs in a different order: .in →\r\n.net → .org → .com → .me → .su → .tw → .cc → .pw)\r\nFor example, these are the first few domains from this report:\r\nrftkbenepisfitgdj.in\r\nxiqmvbjmhmhvmgcmi.me\r\nwxdunehygeonndttn.cc\r\nsbghxfgtslfpppqiu.su\r\nupixinckripequtam.tw\r\noamxeavybfwlhqhob.net\r\njkkugptcygpwxkjkw.com\r\ncvorpvaacmkfacelm.pw\r\nvptafodmeuaxopjbs.in\r\neycagukbeduvmjnpx.me\r\nThe most striking difference to the May version is the increased length of the second level domains: the May\r\nversion has 14 letters, while the September version uses 17 letters. As it turns out, the September DGA uses a\r\nvastly different algorithm to generate the second level domains.\r\nThe DGA\r\nSeeding and Samples\r\nhttps://bin.re/blog/ranbyuss-dga-revisited/\r\nPage 1 of 11\n\nLike the May DGA, the new DGA is seeded with the current date. It also produces 40 fresh domains (almost)\r\nevery day. In addition to the new domains, the DGA will revisit the domains of up 30 days into the past.\r\nApart from the current date, the DGA is seeded with a hard-coded magic number, which allows for separate sets of\r\ndomains. So far, the DGArchive collected seven different seeds for the DGA from May. For the new variant, I\r\nfound seven seeds so far. Edit Dec. 8th, 2015: Some samples, e.g., b625b87a9dfdc345d226e913f9f95d77 and\r\nd8c247f95b2784419ffc14c8df8efc07, actually reverse the seed before applying it:\r\nThe following table lists the seed after negation so I could leave the reimplementation as is. The negated column\r\nshows the original seed before the NOT-operation.\r\nMD5 seed negated\r\neb35f453b87a2f430f53da4dafb2c968 0F0D5BFA no\r\nb82bfd9f649e08185a4100ab555ee9b9 F2C72B14 no\r\n72a367560582ccd51be6f2284d92c946 0F0D5BFA no\r\n293cb29f3009503bebb3f9a4d4362537 F2C72B14 no\r\nb7e7c7b77abbc89922806f4bf42fb30e AE8714BE no\r\nb625b87a9dfdc345d226e913f9f95d77 CE7F8514 yes (~31807AEB)\r\nad9f06a74114dfee3e52d63b6b97ce54 F2C72B14 no\r\n821c05d5c949a9b03ba21973ef9072a1 F2C72B14 no\r\nd8c247f95b2784419ffc14c8df8efc07 572473BB yes (~A8DB8C44)\r\n1d4edada362f6a289b156d94bff26f41 17794CF1 yes (~E886B30E)\r\nc6665471f52a0a7aba50edf8fc9cc886a C0E32524 yes (~3F1CDADB)\r\nd9393e7afcae648aa742ecaeefd36e07 7CB7966E yes (~83486991)\r\nThe way the current date influences the domains is different. The May DGA uses the year, month and day directly\r\nas variables to generate the letters of the second level domain. The September version condenses the date and the\r\nhard-coded magic number into a single 32bit value:\r\n𝑋0 = (year ⋅month ⋅ day) ⊕ seed\r\nConsequently, all dates that have the same product of year, month and day will generate the same domains. For\r\nexample, the domains from Januar 24 will be revisited six times the same year: February 12, March 8, April 6,\r\nJune 4, August 3, and December 12. From a sinkholing perspective, it makes sense to pick a domain from this set.\r\nhttps://bin.re/blog/ranbyuss-dga-revisited/\r\nPage 2 of 11\n\nPython Implementation\r\nThe DGA differs in the way the second level characters are picked. While the May version used a custom\r\nalgorithm to determine the characters, the September edition relies on a pseudo random number generator\r\n(PRNG). The PRNG is of the LCG (linear congruential generator) family with common multiplier and increment:\r\nYou also find this code, along with a reimplementation of the other Ranbyus version, on my Github).\r\n\"\"\"\r\n The DGA of Ranbyus as described here:\r\n https://bin.re/blog/ranbyuss-dga-revisited/\r\n Known Seeds are:\r\n - 0F0D5BFA\r\n - F2C72B14\r\n - AE8714BE\r\n - CE7F8514 (= ~ 31807AEB)\r\n - 572473BB (= ~ A8DB8C44)\r\n - 17794CF1 (= ~ E886B30E)\r\n - C0E32524 (= ~ 3F1CDADB)\r\n\"\"\"\r\nimport argparse\r\nfrom datetime import datetime\r\ndef to_little_array(val):\r\n a = 4*[0]\r\n for i in range(4):\r\n a[i] = (val \u0026 0xFF)\r\n val \u003e\u003e= 8\r\n return a\r\n \r\ndef pcg_random(r):\r\n alpha = 0x5851F42D4C957F2D\r\n inc = 0x14057B7EF767814F\r\n step1 = alpha*r + inc\r\n step2 = alpha*step1 + inc\r\n step3 = alpha*step2 + inc\r\n tmp = (step3 \u003e\u003e 24) \u0026 0xFFFFFF00 | (step3 \u0026 0xFFFFFFFF) \u003e\u003e 24\r\n a = (tmp ^ step2) \u0026 0x000FFFFF ^ step2\r\n b = (step2 \u003e\u003e 32)\r\n c = (step1 \u0026 0xFFF00000) | ((step3 \u003e\u003e 32) \u0026 0xFFFFFFFF) \u003e\u003e 12\r\n d = (step1 \u003e\u003e 32) \u0026 0xFFFFFFFF\r\n data = 32*[None]\r\nhttps://bin.re/blog/ranbyuss-dga-revisited/\r\nPage 3 of 11\n\ndata[0:4] = to_little_array(a)\r\n data[4:8] = to_little_array(b)\r\n data[8:12] = to_little_array(c)\r\n data[12:16] = to_little_array(d)\r\n return step3 \u0026 0xFFFFFFFFFFFFFFFF, data\r\ndef dga(year, month, day, seed):\r\n x = (day*month*year) ^ seed\r\n tld_index = day\r\n for _ in range(40):\r\n random = 32*[None]\r\n x, random[0:16] = pcg_random(x)\r\n x, random[16:32] = pcg_random(x)\r\n domain = \"\"\r\n for i in range(17):\r\n domain += chr(random[i] % 25 + ord('a'))\r\n if seed == 0xCE7F8514:\r\n tlds = [\"in\", \"net\", \"org\", \"com\", \"me\", \"su\", \"tw\", \"cc\", \"pw\"]\r\n else:\r\n tlds = [\"in\", \"me\", \"cc\", \"su\", \"tw\", \"net\", \"com\", \"pw\", \"org\"]\r\n domain += '.' + tlds[tld_index % (len(tlds) - 1)]\r\n tld_index += 1\r\n yield domain\r\nif __name__==\"__main__\":\r\n parser = argparse.ArgumentParser()\r\n parser.add_argument(\"-d\", \"--date\", help=\"date for which to generate domains\")\r\n parser.add_argument(\"-s\", \"--seed\", help=\"seed as hex string\", default=\"0F0D5BFA\")\r\n args = parser.parse_args()\r\n if args.date:\r\n d = datetime.strptime(args.date, \"%Y-%m-%d\")\r\n else:\r\n d = datetime.now()\r\n for domain in dga(d.year, d.month, d.day, int(args.seed, 16)):\r\n print(domain)\r\nPlease note that the above Python script only generates the 40 domains of the current day. Like the May version,\r\nRanbyus can also revisit older domains up to 30 days into the past. So to get the full set of domains for any given\r\nday, you need to run the script for 31 different days.\r\nProperties\r\nAlmost all characteristics of the Ranbyus September DGA are the same as for the May version. The only\r\ndifference is the increased length of the second level domains:\r\nhttps://bin.re/blog/ranbyuss-dga-revisited/\r\nPage 4 of 11\n\nproperty value\r\nseed magic number and current date\r\ngranularity 1 day, with a 31 day sliding window\r\ndomains per seed and day 40\r\ndomains per sliding window 1240\r\nsequence sequential\r\nwait time between domains 500 ms\r\ntop level domains .in, .me, .cc, .su, .tw, .net, .com, .pw\r\nsecond level characters lower case letters except ‘z’\r\nsecond level domain length 17 letters (May version: 14 letters)\r\nAppendix - Reversing the DGA\r\nSimilarities with May version\r\nThe new samples share most of the DGA code with the May version. The following graph views show the\r\ncallback loop from May (left) and September (right):\r\nhttps://bin.re/blog/ranbyuss-dga-revisited/\r\nPage 5 of 11\n\nThe basic structure of the DGA itself is also equal:\r\nhttps://bin.re/blog/ranbyuss-dga-revisited/\r\nPage 6 of 11\n\nMost other DGA-related functions stayed the same too, in particular:\r\nThe routine to determine the top level domain top_level_domain, i.e., the domains will have the same top\r\nlevel domains in the same order as the DGA from May.\r\nThe routines to determine and handle the current date.\r\nThe data structures to configure the DGA.\r\nDifferences to May version\r\nThe main difference between the two DGAs is the routine to generate the second level domains:\r\nhttps://bin.re/blog/ranbyuss-dga-revisited/\r\nPage 7 of 11\n\nhttps://bin.re/blog/ranbyuss-dga-revisited/\r\nPage 8 of 11\n\nThe May DGA (on the left) uses a custom algorithm inside the loop body to produce a pseudo random number.\r\nThe September version on the right first generates 32 bytes of random data using the pcg_random routine, and\r\nthen simply accesses this data inside the loop body. Both version take the resulting pseudo random number\r\nmodulo 25 to get letters from a to y.\r\nThe pseudo random number generator is based on 64bit numbers, which make the routine a little hard to read:\r\nhttps://bin.re/blog/ranbyuss-dga-revisited/\r\nPage 9 of 11\n\nhttps://bin.re/blog/ranbyuss-dga-revisited/\r\nPage 10 of 11\n\nAt the core of the above routine is the following linear congruential generator:\r\n𝑋𝑛 + 1 = (6364136223846793005 ⋅ 𝑋𝑛 + 1442695040888963407) mod 2\r\n64\r\nThe initial value 𝑋0 is set to the product of year, month, and day, XORed with the hardcoded seed:\r\nSource: https://bin.re/blog/ranbyuss-dga-revisited/\r\nhttps://bin.re/blog/ranbyuss-dga-revisited/\r\nPage 11 of 11",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://bin.re/blog/ranbyuss-dga-revisited/"
	],
	"report_names": [
		"ranbyuss-dga-revisited"
	],
	"threat_actors": [],
	"ts_created_at": 1775439030,
	"ts_updated_at": 1775791244,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/97ae6a8584053abc5e574a08d5bd340bcd121e69.pdf",
		"text": "https://archive.orkl.eu/97ae6a8584053abc5e574a08d5bd340bcd121e69.txt",
		"img": "https://archive.orkl.eu/97ae6a8584053abc5e574a08d5bd340bcd121e69.jpg"
	}
}