{
	"id": "d310eb81-ddea-4830-b8aa-04228ac1104a",
	"created_at": "2026-04-06T00:16:17.369098Z",
	"updated_at": "2026-04-10T13:12:49.657014Z",
	"deleted_at": null,
	"sha1_hash": "8fa16a3d00b2f0fc3672c673554696864d120101",
	"title": "The DGAs of Necurs",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 113711,
	"plain_text": "The DGAs of Necurs\r\nArchived: 2026-04-05 20:00:23 UTC\r\nNecurs is a malware that opens a backdoor on infected systems, see NECURS: The Malware That Breaks Your\r\nSecurity. A broad analysis of the malware can be found in the three part series The Curse of Necurs by Peter\r\nFerrie.\r\nThis post focuses exclusively on the network traffic of Necurs, in particular the used domains. Necurs features\r\nthree different sets of hostnames that serve different purposes. Although all three sets reek of domain generation\r\nalgorithm (DGA), only the first and last set are actually generated algorithmically. The following example traffic\r\nshows the different stages of the callback attempts with number 3, 4 and 6 marking the three malicious domains\r\nbatches:\r\n1.---- 13:09:39.635819000 facebook.com\r\n +- 13:09:39.638039000 0.pool.ntp.org\r\n2.--| 13:09:39.998526000 1.pool.ntp.org\r\n +- 13:09:40.020627000 2.pool.ntp.org\r\n +- 13:09:40.027471000 sxotmrxwhddr.com\r\n | 13:09:40.028494000 btysiiquuc.com\r\n3.--| 13:09:40.032591000 kfncxvayakmb.com\r\n +- 13:09:40.033539000 vmslcvvocseu.com\r\n +- 13:09:40.688199000 qcmbartuop.bit\r\n | 13:09:41.699491000 qcmbartuop.bit\r\n4.--| 13:09:42.716128000 qcmbartuop.bit\r\n | (... 16 times total)\r\n +- 13:16:32.364351000 qcmbartuop.bit\r\n5.---- 13:16:49.187559000 facebook.com\r\n +- 13:16:49.516477000 boymlujtgp.nu\r\n | 13:16:49.520651000 ybynentfsjvmsgtktcoog.im\r\n | 13:16:49.527701000 oiijxplrnmvgskxwaye.ru\r\n6.--| 13:16:49.529669000 imgirmyddbsniuh.pw\r\n | 13:16:49.834913000 ultrttvbvjaanrj.jp\r\n | (... 2048 total)\r\n +- 13:18:03.607552000 porgtemsbycy.ki\r\nThe following list gives a brief summary of the source and purpose of the domains. See the respective section for\r\nan in-depth analysis of the three DGA sets.\r\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 1 of 25\n\n1. Necurs starts by checking internet connectivity by resolving facebook.com or microsoft.com.\r\n2. The malware then contacts three NTP pools to get the accurate date and time.\r\n3. Next, four DGA domains are generated by the first dga with top level domain .com. The domains are\r\nunpredictable and can therefore not be sinkholed or used to identify Necurs samples when taken in\r\nisolation. The purpose of the domains is to detect simulated internet in lab environments\r\n4. If the lab detection test passes, Necurs will try sixteen domains from a hard-coded list of pseudo-DGA\r\ndomains, see second dga. Necurs will reuse domains if the list contains less than 16 domains. The analysed\r\nsample, for example, only has one hard-coded domain which will therefore be repeated 16 times. Necurs\r\nsleeps between 1 and 20 seconds after each failed connection attempt, which mounts up to about 5 minutes\r\nbefore the malware moves on to the next stage. The hard-coded domains are probably the main C\u0026C\r\ndomains.\r\n5. Another connectivity check to facebook.com or microsoft.com is made before Necurs resorts to the last\r\nDGA.\r\n6. The third dga finally checks up to 2048 different domains. One special feature is the large list of 43\r\ndifferent top level domains, some of which are quite exotic. The DGA is time-dependent — the domains\r\nchange every four days. The DGA is probably a backup in case the set of hard-coded domains no longer\r\nwork.\r\nThe First DGA\r\nConnectivity Checks\r\nNecurs makes a connectivity check to either facebook.com or microsoft.com before the first set of DGA domains\r\nare tested:\r\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 2 of 25\n\nThe routine random_int(0,1) returns 0 or 1 (the implementation of random_int is discussed below). The\r\nmalware aborts the callback attempt if it can’t resolve and contact the domain of Facebook or Microsoft as the\r\ncase may be.\r\nDGA Caller\r\nNecurs then launches four threads that attempt to resolve domains generated by the first DGA:\r\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 3 of 25\n\nThere is no delay between creating the threads, the four DNS queries happen at around the same time.\r\nThe Heart of the First DGA\r\nLet’s see how the domains are generated by dga1 :\r\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 4 of 25\n\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 5 of 25\n\nThis very simple algorithm first randomly determines the length of the second level domain to be between 10 and\r\n15 characters. It then builds the domain by picking uniformly at random from all lowercase letters and appending\r\nthe hard-coded top level domain .com. The DGA concludes with querying the resulting domain; the resolved IP, if\r\nthere is any, is appended to the array dga1_ips .\r\nPseudorandom Number Generator\r\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 6 of 25\n\nThe connectivity check, as well as dga1 , use random_int to generate random integers. The disassembly of this\r\nroutine is:\r\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 7 of 25\n\nThis is just a mapping of the return value of random_mwc to the desired range:\r\nfunction random_int(lower, upper)\r\n if lower \u003e upper then\r\n return 0\r\n else\r\n return lower + random_mwc % (upper - lower + 1)\r\nThe routine random_mwc is an almost standard implementation of a lag-3 multiply-with-carry pseudorandom\r\nnumber generator invented by George Marsaglia:\r\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 8 of 25\n\nThe only difference is the addition of an rdtsc summand, which will add the current time stamp counter to all\r\ngenerated numbers (not just the initial seed). The initial seed values and initial carry are:\r\n0040F048 rng3 dq 77465321\r\n0040F04C c dd 13579\r\n0040F050 rng2 dd 362436069\r\n0040F054 rng1 dd 123456789\r\nThe choice of these values is pretty common. Necurs never changed these values in its long existence according to\r\nthe aforementioned article “the curse of Necurs”. This is the PRNG random_mwc in pseudo-code is:\r\nb = 2^(32)\r\na = 916905990\r\n// Initial seeds and carry\r\nrng1 = 123456789\r\nrng2 = 362436069\r\nrng3 = 77465321\r\nc = 13579\r\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 9 of 25\n\nfunction random_mwc()\r\n t = a*rng1 + rdtsc() + c\r\n rng1 = rng2;\r\n rng2 = rng3;\r\n rng3 = t;\r\n c = (t // b) % b\r\n return v1;\r\nThe addition of rdtsc makes this random number generator, and therefore also the first domain generation\r\nalgorithm, virtually impossible to predict.\r\nComparison with Facebook’s or Microsoft’s IP\r\nAlthough the domains of dga1 are unusable as callback targets, they still serve a purpose: If one of the DGA IPs\r\nmatches the one of Facebook or Microsoft (resolved during connectivity checking), Necurs will abort the callback\r\nattempt:\r\nThe excessively long named routine ip_of_mic_or_fac_equal_to_dga1_ips compares the IP of Microsoft or\r\nFacebook to the IPs of dga1 stored in array dga1_ips :\r\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 10 of 25\n\nI suspect that Necurs tries to detect simulated internet services this way. Simulated Internet will typically return\r\nthe same IP for all requested domains. The 4 DGA domains, however, are unpredictable and therefore non-existent. And even if the victim’s ISP uses DNS hijacking, the returned IPs will not match Facebook’s or\r\nMicrosoft’s IP.\r\nSummary\r\nThe first DGA of Necurs generates four .com domains of 10 to 15 lower case letters. It tries to resolve all four\r\ndomains in parallel. The modified MWC-PRNG makes the domains unusable as callback targets; instead they are\r\nprobably used to detect simulated internet connections in a lab environment.\r\nThe Second DGA\r\nNecurs will continue without break with the second set of DGA domains in case the lab detection passed. The\r\nsecond set of DGA-like domains, for example qcmbartuop.bit, are not generated algorithmically but hard-coded.\r\nThe following loop fetches 16 of those hard-coded domains and tries to contact them:\r\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 11 of 25\n\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 12 of 25\n\nNecurs sleeps 1 to 20 seconds after each failed attempt, which sums up to a average total sleep time of about 5\r\nminutes for all 16 attempts.\r\nThe relevant snippet of get_hard-coded_domain is:\r\nThis returns the hard-coded domains one after another, starting over with the first domain if necessary.\r\nFrom what I could gather from other reports, the hard-coded domains seem to mostly use the special .bit pseudo\r\ntop level domain served by the namecoin project. The domains are probably Necurs` main C\u0026C targets.\r\nThe Third DGA\r\nIf the second set of domains also fails to produce a working C\u0026C server, Necurs tries one last set of domains.\r\nSequence Numbers\r\nThe third DGA starts by creating an array of 2048 sequence numbers 0 to 2047; these sequence numbers are then\r\nrandomly permutated:\r\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 13 of 25\n\nThe permutation routine randomizes the sequence numbers in place with the following algorithm:\r\nn = A.length\r\nfor i = 1 to n\r\n swap A[i] with A[Random(1,n)]\r\nAlthough this randomizes the sequence numbers, it does not do it uniformly. A better implementation would call\r\nRandom(i,n) instead of Random(1,n) , see for instance exercise 5.3-3 on page 129 of “Introduction to\r\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 14 of 25\n\nAlgorithms”, 3rd edition. Nevertheless, the sequence numbers are still random and unpredictable because of the\r\ncall to random_mwc which includes the current tick count see section above.\r\nDGA Caller\r\nAfter the sequence numbers have been randomized, Necurs starts up 16 threads, each with 128 of the sequence\r\nnumbers (referenced by ecx , indexed by edi ):\r\nEach thread will call the routine to generates the domains for all 128 sequence numbers it got assigned to (unless a\r\ncallback is successful beforehand):\r\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 15 of 25\n\nThe DGA\r\nAt the heart of the third DGA we find this long, but easy to understand algorithm:\r\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 16 of 25\n\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 17 of 25\n\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 18 of 25\n\nThe first call to the routine fetches a hard-coded magic number from a rather complicated linked list and saves it\r\nas magic_number . For my sample, the hard-coded seed was 9.\r\nNecurs then determines a length between 7 and 21 letters with multiple calls to pseudo_random , using the current\r\ndate, the sequence number and the magic number as seeds. This will lead to a new set of domains every four days:\r\n n = pseudo_random(date.year)\r\n n = pseudo_random(n + date.month + 43690)\r\n n = pseudo_random(n + (date.day\u003e\u003e2))\r\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 19 of 25\n\nn = pseudo_random(n + sequence_nr)\r\n n = pseudo_random(n + magic_nr)\r\n domain_length = mod64(n, 15) + 7\r\nNext, Necurs picks the characters of the second level domain from from ‘a’ to ‘y’ (‘z’ is unreachable like for\r\nRamnit):\r\n domain = \"\"\r\n for i in range(domain_length):\r\n n = pseudo_random(n+i)\r\n ch = mod64(n, 25) + ord('a')\r\n domain += chr(ch)\r\n n += 0xABBEDF\r\n n = pseudo_random(n)\r\nFinally, one of 43 top level domains is chosen to finish the domain name:\r\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 20 of 25\n\ntlds = ['tj','in','jp','tw','ac','cm','la','mn','so','sh','sc','nu','nf','mu',\r\n 'ms','mx','ki','im','cx','cc','tv','bz','me','eu','de','ru','co','su','pw',\r\n 'kz','sx','us','ug','ir','to','ga','com','net','org','biz','xxx','pro','bit']\r\n tld = tlds[mod64(n, 43)]\r\n domain += '.' + tld\r\n return domain\r\nAll picks are randomized with calls to pseudo_random which is:\r\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 21 of 25\n\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 22 of 25\n\nThis routine calculates:\r\n def pseudo_random(value):\r\n loops = (value \u0026 0x7F) + 21\r\n for index in range(loops):\r\n value += ((value*7) ^ (value \u003c\u003c 15)) + 8*index - (value \u003e\u003e 5)\r\n value \u0026= ((1 \u003c\u003c 64) - 1)\r\n return value\r\nSummary\r\nThe third DGA, including the random number generator, boils down to this routine:\r\nimport argparse\r\nfrom datetime import datetime\r\ndef generate_necurs_domain(sequence_nr, magic_nr, date):\r\n def pseudo_random(value):\r\n loops = (value \u0026 0x7F) + 21\r\n for index in range(loops):\r\n value += ((value*7) ^ (value \u003c\u003c 15)) + 8*index - (value \u003e\u003e 5)\r\n value \u0026= ((1 \u003c\u003c 64) - 1)\r\n return value\r\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 23 of 25\n\ndef mod64(nr1, nr2):\r\n return nr1 % nr2\r\n n = pseudo_random(date.year)\r\n n = pseudo_random(n + date.month + 43690)\r\n n = pseudo_random(n + (date.day\u003e\u003e2))\r\n n = pseudo_random(n + sequence_nr)\r\n n = pseudo_random(n + magic_nr)\r\n domain_length = mod64(n, 15) + 7\r\n domain = \"\"\r\n for i in range(domain_length):\r\n n = pseudo_random(n+i)\r\n ch = mod64(n, 25) + ord('a')\r\n domain += chr(ch)\r\n n += 0xABBEDF\r\n n = pseudo_random(n)\r\n tlds = ['tj','in','jp','tw','ac','cm','la','mn','so','sh','sc','nu','nf','mu',\r\n 'ms','mx','ki','im','cx','cc','tv','bz','me','eu','de','ru','co','su','pw',\r\n 'kz','sx','us','ug','ir','to','ga','com','net','org','biz','xxx','pro','bit']\r\n tld = tlds[mod64(n, 43)]\r\n domain += '.' + tld\r\n return domain\r\nif __name__==\"__main__\":\r\n parser = argparse.ArgumentParser()\r\n parser.add_argument(\"-d\", \"--date\", help=\"as YYYY-mm-dd\")\r\n args = parser.parse_args()\r\n date_str = args.date\r\n if date_str:\r\n date = datetime.strptime(date_str, \"%Y-%m-%d\")\r\n else:\r\n date = datetime.now()\r\n for sequence_nr in range(2048):\r\n print(generate_necurs_domain(sequence_nr, 9, date))\r\nThe characteristics of this DGA are:\r\nproperty value\r\nseed magic number and current date (changing domains every four days)\r\ndomains per seed 2048\r\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 24 of 25\n\nproperty value\r\nsequence randomized (unpredictable, albeit not uniformly random)\r\nwait time between domains none, 16 parallel threads\r\ntop level domain 43 different tld, picked randomly\r\nsecond level characters lower case letters except ‘z’\r\nsecond level domain length 7 to 21 letters\r\nThe DGA likely serves as a fallback in case the hard-coded domains fail.\r\nNote: I removed the Disqus integration in an effort to cut down on bloat. The following comments were retrieved\r\nwith the export functionality of Disqus. If you have comments, please reach out to me by Twitter or email.\r\nSource: https://bin.re/blog/the-dgas-of-necurs/\r\nhttps://bin.re/blog/the-dgas-of-necurs/\r\nPage 25 of 25\n\nswap A[i] Although this with A[Random(1,n)] randomizes the sequence numbers, it does not do it uniformly. A better implementation would call\nRandom(i,n) instead of Random(1,n) , see for instance exercise 5.3-3 on page 129 of “Introduction to\n   Page 14 of 25",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://bin.re/blog/the-dgas-of-necurs/"
	],
	"report_names": [
		"the-dgas-of-necurs"
	],
	"threat_actors": [
		{
			"id": "f8dddd06-da24-4184-9e24-4c22bdd1cbbf",
			"created_at": "2023-01-06T13:46:38.626906Z",
			"updated_at": "2026-04-10T02:00:03.043681Z",
			"deleted_at": null,
			"main_name": "Tick",
			"aliases": [
				"G0060",
				"Stalker Taurus",
				"PLA Unit 61419",
				"Swirl Typhoon",
				"Nian",
				"BRONZE BUTLER",
				"REDBALDKNIGHT",
				"STALKER PANDA"
			],
			"source_name": "MISPGALAXY:Tick",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		},
		{
			"id": "54e55585-1025-49d2-9de8-90fc7a631f45",
			"created_at": "2025-08-07T02:03:24.563488Z",
			"updated_at": "2026-04-10T02:00:03.715427Z",
			"deleted_at": null,
			"main_name": "BRONZE BUTLER",
			"aliases": [
				"CTG-2006 ",
				"Daserf",
				"Stalker Panda ",
				"Swirl Typhoon ",
				"Tick "
			],
			"source_name": "Secureworks:BRONZE BUTLER",
			"tools": [
				"ABK",
				"BBK",
				"Casper",
				"DGet",
				"Daserf",
				"Datper",
				"Ghostdown",
				"Gofarer",
				"MSGet",
				"Mimikatz",
				"Netboy",
				"RarStar",
				"Screen Capture Tool",
				"ShadowPad",
				"ShadowPy",
				"T-SMB",
				"down_new",
				"gsecdump"
			],
			"source_id": "Secureworks",
			"reports": null
		},
		{
			"id": "d4e7cd9a-2290-4f89-a645-85b9a46d004b",
			"created_at": "2022-10-25T16:07:23.419513Z",
			"updated_at": "2026-04-10T02:00:04.591062Z",
			"deleted_at": null,
			"main_name": "Bronze Butler",
			"aliases": [
				"Bronze Butler",
				"CTG-2006",
				"G0060",
				"Operation ENDTRADE",
				"RedBaldNight",
				"Stalker Panda",
				"Stalker Taurus",
				"Swirl Typhoon",
				"TEMP.Tick",
				"Tick"
			],
			"source_name": "ETDA:Bronze Butler",
			"tools": [
				"8.t Dropper",
				"8.t RTF exploit builder",
				"8t_dropper",
				"9002 RAT",
				"AngryRebel",
				"Blogspot",
				"Daserf",
				"Datper",
				"Elirks",
				"Farfli",
				"Gh0st RAT",
				"Ghost RAT",
				"HOMEUNIX",
				"HidraQ",
				"HomamDownloader",
				"Homux",
				"Hydraq",
				"Lilith",
				"Lilith RAT",
				"McRAT",
				"MdmBot",
				"Mimikatz",
				"Minzen",
				"Moudour",
				"Muirim",
				"Mydoor",
				"Nioupale",
				"PCRat",
				"POISONPLUG.SHADOW",
				"Roarur",
				"RoyalRoad",
				"ShadowPad Winnti",
				"ShadowWali",
				"ShadowWalker",
				"SymonLoader",
				"WCE",
				"Wali",
				"Windows Credential Editor",
				"Windows Credentials Editor",
				"XShellGhost",
				"XXMM",
				"gsecdump",
				"rarstar"
			],
			"source_id": "ETDA",
			"reports": null
		}
	],
	"ts_created_at": 1775434577,
	"ts_updated_at": 1775826769,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/8fa16a3d00b2f0fc3672c673554696864d120101.pdf",
		"text": "https://archive.orkl.eu/8fa16a3d00b2f0fc3672c673554696864d120101.txt",
		"img": "https://archive.orkl.eu/8fa16a3d00b2f0fc3672c673554696864d120101.jpg"
	}
}