{
	"id": "3ca8abf7-1a7e-41d9-86c9-5b3b0503e19e",
	"created_at": "2026-04-06T00:22:04.199192Z",
	"updated_at": "2026-04-10T03:20:53.098297Z",
	"deleted_at": null,
	"sha1_hash": "407c6dbfff71805b196ed1b747dc649a95398ffa",
	"title": "Necurs – hybrid spam botnet",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 519372,
	"plain_text": "Necurs – hybrid spam botnet\r\nArchived: 2026-04-02 11:02:30 UTC\r\nNecurs is one of the biggest botnets in the world – according to MalwareTech there are a couple\r\nmillions of infected computers, several hundred thousand of which are online at any given time.\r\nCompromised computers send spam email to large number of recipients – usually the messages are\r\ncreated to look like a request to check invoice details or to confirm purchase. The attachments\r\ncontain packed scripts which install malware when ran. Currently, the dropped ransomware is\r\nLocky, which encrypts the hard disk and then asks for money (often in Bitcoin) in order to retrieve\r\nthe original files. Necurs is an example of hybrid network in terms of Command and Control\r\narchitecture – a mixture of centralized model (which allows to quickly control the botnet), with\r\npeer-to-peer (P2P) model, making it next to impossible to take over the whole botnet by shutting\r\ndown just a single server. For those reasons, the huge success of Necurs is no surprise.\r\nBehaviour\r\nThe malware attempts to connect to the C2 server, whose IP address is retrieved in a number of\r\ndifferent ways. First, a couple of domains or raw IP addresses are embedded in the program\r\nresources (in encrypted form – more about this in the technical analysis section). If the connection\r\nfails, Necurs runs domain generation algorithm, crafting up to 2048 pseudorandom names,\r\ngeneration of which depends on current date and seed hardcoded in encrypted resources, and tries\r\nthem all in a couple of threads. If any of them resolves and responds using the correct protocol, it is\r\nsaved as a server address. Otherwise, if all these methods fail, C2 domain is retrieved from the P2P\r\nnetwork – the initial list of about 2000 peers (in form of IP+port pairs) is hardcoded in the binary.\r\nDuring analysis, Necurs used the last method, since none of the DGA domains was responding. It is\r\nhowever possible, that in the future the botnet’s author will start to register these domains – a new\r\nlist of potential addresses is generated every 4 days.\r\nhttps://www.cert.pl/en/news/single/necurs-hybrid-spam-botnet/\r\nPage 1 of 16\n\nAfter establishing a successful connection to the C2, Necurs downloads (using custom protocol over\r\nHTTP) a list of information – from now on, I will call them “resources”. Every resource is\r\nidentified by a constant 64-bit number. It is quite likely a hash of some sensible name used in source\r\ncode, but after compilation we obviously cannot access it. Nonetheless, analyzing how these\r\nresources are used in the code, we could map some of the IDs to useful names.\r\nExamples of information sent by the C2 include: new P2P neighborhood (ca. 2000 IP:port pairs),\r\nnew C2 domain list, sleep command (usually about twenty minutes or so), or request to download\r\nand run DLL module. Every request we receive contains the sleep request – it is probably a way to\r\nreduce the server’s load.\r\nThe analyzed binary did not contain what we really sought – mail sending routine. As it turns out,\r\nthat functionality is in one of the dropped DLLs. Unfortunately, it was written in C++, which\r\nincreased code size (because the author used C++ templates) and therefore, slowed down reverse\r\nengineering. For that reason, we mostly used behavioral analysis – debugging malware and\r\nobserving sent data (before the encryption of course).\r\nAs we could see, the payload is formatted as JSON. Unfortunately all of the keys were obfuscated\r\nand it was impossible to discover their meaning just by the name – for example “dg3XGB9”\r\ncorresponds to the current Unix time. There are a couple of message formats, but not all of them are\r\nreally interesting. The most important was the request for mail to be sent and of course the server’s\r\nresponse. The text Necurs sends is not just literal email – a simple script language is used to\r\nrandomize them:\r\n%%var boundary = b1_{{lowercase(rndhex(32,32))}}\r\n%%var company = {{[subj]}}\r\n%%var aname = {{lowercase(rndhex(10,12))}}\r\n%%var f_sname = {{[eng_Surnames]}}\r\nhttps://www.cert.pl/en/news/single/necurs-hybrid-spam-botnet/\r\nPage 2 of 16\n\n%%var f_name = {{[eng_Names]}}\r\n%%var f_sname2 = {{[eng_Surnames]}}\r\n%%var f_name2 = {{[eng_Names]}}\r\n%%var fromname = {{f_name}} {{f_sname}}\r\n%%var fromdomain = {{spf_host([domains_neutral])}}\r\n%%var fromaddr = {{f_sname}}.{{rndnum(2,5)}}@{{fromdomain}}\r\nTo: \"{{to_name}}\" \u003c{{to_addr}}\u003e\r\nSubject: bank transactions\r\nDate: {{date}}\r\nFrom: \"{{fromname}}\" \u003c{{fromaddr}}\u003e\r\nMessage-ID: \u003c{{lowercase(rndhex(32,32))}}@{{to_host}}\u003e\r\nX-Priority: 3\r\nMIME-Version: 1.0\r\nContent-Type: multipart/related;\r\ntype=\"text/html\";\r\nboundary=\"{{boundary}}\"\r\n--{{boundary}}\r\nContent-Type: text/plain; charset=\"utf-8\"\r\nContent-Transfer-Encoding: 8bit\r\nGood morning {{to_name}}.\r\nAttached is the bank transactions made from the company during last month.\r\nPlease file these transactions into financial record.\r\nYours truly,\r\n{{fromname}}\r\n--{{boundary}}\r\nhttps://www.cert.pl/en/news/single/necurs-hybrid-spam-botnet/\r\nPage 3 of 16\n\nContent-Type: {{rnd('application/x-compressed','application/x-zip-compressed','application/zip')}}; name=\"{{aname}}.zip\"\r\nContent-Transfer-Encoding: base64\r\nContent-Disposition: attachment; filename=\"{{aname}}.zip\"\r\n{{rnd([file.doc],[file1.doc],[file2.doc])}}\r\n--{{boundary}}--\r\nWe can see, that the script supports local variables (declared by %%var directive), predefined\r\nfunctions, such as randnum, but there are also references to external data – e.g. [file.doc] – these\r\nvariables are downloaded in a separate request. We checked the attachments, and despite the name\r\n(file.doc), these are ZIP archives containing a single JS file. When executed, they download and run\r\nZepto, a rather new variant of Locky ransomware.\r\nTechnical analysis\r\nNecurs uses a couple of anti-analysis techniques. For example, every C2 connection is attempted\r\nrandomly: either to the address given in function argument, or to the address being a hash of the\r\nargument. Virtualization is detected using instructions such as “vmcpuid”, or “in al”. Some\r\nmalware analysis environments can also fail on checking whether Facebook and random domain\r\nresolve to the same IP address. Many texts and binary resources are encrypted – communication\r\nwith peers and C2 as well.\r\nResources\r\nConstants in the binary are hidden in a separate section – the file contains two named “.reloc”\r\nsections, the second of which contains resources. First four bytes of that section are interpreted as a\r\ndecryption key, and the resources themselves start at offset 0x18. Every byte is xored with key,\r\nwhich changes according to LCG algorithm: K’=K*0x19661f+0x3c6ef387. After decryption, the\r\ndata is a list of concatenated structures of the following format:\r\nstruct resource{\r\nuint32_t size; // Shifted left by 8.\r\nuint64_t id;\r\nuint8_t data[];\r\n};\r\nLast field size is size\u003e\u003e8. Every resource has its unique identifier – examples of resources are\r\ninitial peer or C2 communication keys or initial peer neighborhood list.\r\nhttps://www.cert.pl/en/news/single/necurs-hybrid-spam-botnet/\r\nPage 4 of 16\n\nP2P communication\r\nP2P communication is unfortunately much more complicated. All information exchange happens\r\nover UDP protocol. The outermost layer of communication is:\r\nstruct outer_layer{\r\nuint32_t key;\r\nuint32_t checksum;\r\nuint8_t data[];\r\n};\r\nWrapped data are encrypted using the key calculated as a sum of the key field and the first 32 bits of\r\nthe public key contained in file resources. The homemade encryption algorithm is equivalent to the\r\nfollowing Python code:\r\ndef xorEncryptUDP(msg, key):\r\nres=key\r\nbuff=\"\"\r\nfor c in msg:\r\nc=chr( ord(c)^res\u00260xff)\r\nbuff+=c\r\ntmp=ror4(res, 7)\r\nres+=(8*(res+4*ord(c)))^tmp\r\nres\u0026=0xFFFFffff\r\nreturn buff, res\r\nChecksum sent as second field in the structure is simply a final value of key after encryption. The\r\ndecrypted data have the following form:\r\nstruct stage2{\r\nuint32_t size_flag;\r\nuint8_t data[];\r\n};\r\nhttps://www.cert.pl/en/news/single/necurs-hybrid-spam-botnet/\r\nPage 5 of 16\n\nSize of data is size_flag\u003e\u003e4, and the type of the message is determined by four least significant bits\r\nof that field. For example, first message (“greeting”) has these bits all zeroed.\r\nThe next stage depends on the message type. For the first message, the structure is:\r\nstruct greeting{\r\nuint32_t time; // Milliseconds since 1900-01-01\r\nuint64_t last_received; // ID of last received data\r\nuint8_t flags; // E.g. compression flag\r\n};\r\nShould the peer respond, the message has the following form:\r\nstruct response{\r\nuint32_t versionL;\r\nuint8_t versionH;\r\nuint8_t size[3]; // Little Endian\r\nresourceList resources;\r\nuint8_t signature[];\r\n};\r\nThe whole message is signed using key from file resources. The most important field of this\r\nstructure is resources – list of resources in the same format as described in “Resources” section.\r\nInterestingly, peers don’t send new neighborhood list – these are sent by the C2 itself. The most\r\nlikely reason for this measure is avoiding P2P poisoning, since it is known that peer list received\r\nfrom the main server is authorized and correct.\r\nC2 communication\r\nC2 protocol is vaguely similar to the P2P one, but encryption routines and structures it uses are a bit\r\ndifferent – also, the underlying protocol is HTTP (POST payload) instead of raw UDP sockets. The\r\nfirst stage is exactly the same (outer_layer structure), with different constants in encryption\r\nalgorithm:\r\ndef xorEnc(data, key):\r\nres=key\r\nhttps://www.cert.pl/en/news/single/necurs-hybrid-spam-botnet/\r\nPage 6 of 16\n\nbuf=\"\"\r\nfor c in data:\r\nc=ord(c)^res\u00260xff\r\ntmp=ror4(res, 13)\r\nres+=(2*(res+4*c))^tmp\r\nres\u0026=0xFFFFffff\r\nbuf+=chr(c)\r\nreturn buf, res\r\nDecrypted data is of the following structure:\r\nstruct cc_structure{\r\nuint64_t random_data;\r\nuint64_t botID;\r\nuint64_t millis_since_1900;\r\nuint8_t command; // 0 - get command, 1 - download file, 2 - ping.\r\nuint8_t flags; // 1 - RSA sign, 2 - compress, 4 - timePrecision\r\nuint8_t payload[];\r\n};\r\nThe first field contains randomly generated 8 bytes, probably to increase entropy of sent data and to\r\nmake it harder to see patterns like common initial bytes across messages.\r\nContents of the payload field (perhaps compressed, depending on the second bit of flags) depends\r\non message type (command field). If it is file download request (command=1), the payload is simply\r\nthe SHA-1 hash of the requested file. On the other hand, if the whole message is a periodic\r\ncommand request (command=0), the payload structure is much more complex – again, a kind of list\r\nof resources, but with different structure. Every resource has the following general form (can be\r\nthought of as header):\r\nstruct cc_resource{\r\nuint8_t type;\r\nuint64_t id;\r\nhttps://www.cert.pl/en/news/single/necurs-hybrid-spam-botnet/\r\nPage 7 of 16\n\nuint8_t data[];\r\n};\r\nDepending on resource type, data has different format:\r\nstruct cc_resource_type_0{\r\nuint32_t size;\r\nuint8_t data[]; // length=size\r\n};\r\nstruct cc_resource_type_1{\r\nuint32_t data;\r\n};\r\nstruct cc_resource_type_2{\r\nuint64_t data;\r\n};\r\nstruct cc_resource_type_3{\r\nuint64_t data;\r\n};\r\nstruct cc_resource_type_4{\r\nuint16_t size;\r\nuint8_t data[]; // length=size+1\r\n};\r\nstruct cc_resource_type_5{\r\nuint8_t data[20];\r\n};\r\nType 4 is usually used to send text data, which is probably the reason of the resource size being\r\nincreased by one (for null terminator). Client sends a list of such resources to the C2. We were able\r\nto identify the meaning of some of them:\r\nDGA seed\r\nhttps://www.cert.pl/en/news/single/necurs-hybrid-spam-botnet/\r\nPage 8 of 16\n\nnumber of seconds since malware start\r\nUnix timestamp of malware start\r\nOS version and its default language\r\ncomputer’s IP (local if behind NAT)\r\nUDP port used to listen for P2P connections\r\ncustom hash of current peer list\r\nThe server response uses a very similar format. The payload also depends on request type – if it was\r\n1 (download file request), server responds with that file’s contents (usually compressed, depending\r\non flags). For command request, the server response is the list of resources in the same format as\r\nabove. Some of these resources can be interpreted as commands to be executed, for example “sleep\r\nN milliseconds” or “log off the user” (although I did not see the latter used in the wild).\r\nSample (parsed) resource list received from C2:\r\nOut of a large number of possible resources, the most important are the new peer list (only if its\r\nhash differs from current), or announcement of a new DLL being available to download. The latter\r\nhttps://www.cert.pl/en/news/single/necurs-hybrid-spam-botnet/\r\nPage 9 of 16\n\nresource has its own structure for communication purposes (a real matrioshka!), also made of a list\r\nof concatenated sub-resources of the following form:\r\nstruct subresource{\r\nuint32_t size;\r\nuint8_t unknown[18];\r\nuint8_t sha1[20];\r\nchar cmdline[]; // length=size-42\r\n};\r\nThe command can be interpreted as a request for running DLL identified by its SHA-1 with\r\ncommand line parameters stated in cmdline field – in practice, the argument is a newline-separated\r\nlist of C2 addresses (with HTTP path) to be connected to.\r\nSpam C2 communication\r\nThe last protocol I will describe in this post, is the communication of the downloaded DLL module,\r\nwhose responsibility is to send spam emails. The information is wrapped in the following structure\r\n(sent as POST data over HTTP):\r\nstruct spam_wrap{\r\nuint8_t data[];\r\nuint32_t crc32;\r\nuint32_t key; // 4th bit of key is compression flag.\r\n};\r\nThe encryption algorithm:\r\ndef encrypt(msg, key):\r\nkey=rol4(key, 0x11)\r\nres=\"\"\r\nfor c in msg:\r\ntmp=ror4(key, 0xB)\r\nkey+=((0x359038a9*key)^tmp)\u00260xFFFFffff\r\nhttps://www.cert.pl/en/news/single/necurs-hybrid-spam-botnet/\r\nPage 10 of 16\n\nres+=chr( (ord(c)+key) \u0026 0xFF )\r\nreturn res\r\nAfter decryption, there are no more steps – we receive raw data as a JSON string (unless the\r\ncompression flag was set, in which case the data needs to be unpacked – as we found out, a\r\nQuickLZ library was used in the malware for this purpose). Unfortunately, keys are obfuscated, so\r\nwe had to guess their meaning. Sample JSON (pretty-printed and edited to fit on screen):\r\nhttps://www.cert.pl/en/news/single/necurs-hybrid-spam-botnet/\r\nPage 11 of 16\n\nFinally, one of the fields in the received dictionary contains a script used to generate randomized\r\nemails (like on the top of the post), and as another field – list of parameters passed to this script (e.g.\r\neng_Names). We can make a separate request to download value of these arguments – as a response,\r\nwe will receive, for example, list of English names to be substituted, or a few base64-encoded files\r\nto be used as an attachment.\r\nExample communication\r\nhttps://www.cert.pl/en/news/single/necurs-hybrid-spam-botnet/\r\nPage 12 of 16\n\nI’m aware understanding all those structures and ways they are used is quite hard, so I have created\r\na simplified graph showing the data flow. Example communication could look like this:\r\nhttps://www.cert.pl/en/news/single/necurs-hybrid-spam-botnet/\r\nPage 13 of 16\n\nhttps://www.cert.pl/en/news/single/necurs-hybrid-spam-botnet/\r\nPage 14 of 16\n\nSample hashes:\r\nfe929245ee022e3410b22456be10c4f1 - original file (packed)\r\n35be639c5618272f70a0bbfbc25d4465 - dropped DLL module\r\nYARA rules:\r\nrule necurs\r\n{\r\nmeta:\r\nauthor=\"akrasuski1\"\r\nstrings:\r\n$pushID0 = {68 0A CA 9B 2E}\r\n$pushID1 = {68 18 DD F0 3E}\r\n$pushID2 = {68 31 BF D7 B2}\r\n$pushID3 = {68 60 48 6A E1}\r\n$pushID4 = {68 84 9A 75 C3}\r\n$pushID5 = {68 9B 54 CC D8}\r\n$pushID6 = {68 EE A0 8A 0A}\r\n$pushID7 = {68 D7 91 35 54}\r\n$pushID8 = {68 44 FC 9D EA}\r\n$pushID9 = {68 A4 51 C4 74}\r\n$dga = {1B D9 01 7D 08 11 5D 0C FF 45 FC 39 75 FC}\r\n$string_drivers = \"%s\\\\drivers\\\\%s.sys\"\r\n$string_findme = \"findme\"\r\n$string_stupid = \"some stupid error\" wide\r\n$string_bcdedit = \"bcdedit.exe -set TESTSIGNING ON\"\r\ncondition:\r\n(2 of ($string*) and $dga)\r\nhttps://www.cert.pl/en/news/single/necurs-hybrid-spam-botnet/\r\nPage 15 of 16\n\nor\r\n($dga and 7 of ($pushID*))\r\nor\r\n(2 of ($string*) and 7 of ($pushID*))\r\n}\r\nSource: https://www.cert.pl/en/news/single/necurs-hybrid-spam-botnet/\r\nhttps://www.cert.pl/en/news/single/necurs-hybrid-spam-botnet/\r\nPage 16 of 16",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia",
		"ETDA"
	],
	"references": [
		"https://www.cert.pl/en/news/single/necurs-hybrid-spam-botnet/"
	],
	"report_names": [
		"necurs-hybrid-spam-botnet"
	],
	"threat_actors": [],
	"ts_created_at": 1775434924,
	"ts_updated_at": 1775791253,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/407c6dbfff71805b196ed1b747dc649a95398ffa.pdf",
		"text": "https://archive.orkl.eu/407c6dbfff71805b196ed1b747dc649a95398ffa.txt",
		"img": "https://archive.orkl.eu/407c6dbfff71805b196ed1b747dc649a95398ffa.jpg"
	}
}