{
	"id": "af9cd28e-09b1-4631-8110-b935e087d06f",
	"created_at": "2026-04-06T00:09:16.639623Z",
	"updated_at": "2026-04-10T03:22:39.370684Z",
	"deleted_at": null,
	"sha1_hash": "fc5c0bf21871a74e6cae2dbc7759783d42130f84",
	"title": "The story of a ransomware builder: from Thanos to Spook and beyond (Part 1)",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 597173,
	"plain_text": "The story of a ransomware builder: from Thanos to Spook and\r\nbeyond (Part 1)\r\nBy CERT-SEKOIA\r\nPublished: 2022-02-17 · Archived: 2026-04-02 11:58:32 UTC\r\nIntroduction\r\nDuring an onsite incident response analysis, CERT-Sekoia was contacted in order to respond to a Spook\r\nransomware attack.\r\nAfter gathering the evidence, we identified that malicious actors used a legitimate VPN account to initiate the first\r\nconnection. The account was also a Domain Administrator in the company Active Directory. All lateral\r\nmovements used this account via RDP or SMB. We suspect that this valid account was acquired earlier in 2021,\r\nand potentially from an initial Access Broker, because:\r\nLocal computers’ files and artifacts show the presence of 2 executables containing Mimikatz and\r\nnetscanner with a last modification date back in July 2021;\r\nFrom the few residual logs available on the VPN appliance, shortly before the VPN session was\r\nestablished, the user was added to the VPN allowed group. We identified a web administration interface\r\nexposed to the internet;\r\nThe version of the software appliance was several months late in security updates, thus exposed to critical\r\nCVEs;\r\nThe duration of the attack (time elapsed between the 1st VPN connexion and the end of encryption routine)\r\nwas less than an hour;\r\nRDP and SMB failed authentications show errors with incorrect domains, these domains referring to\r\nknown Spook’s victims, followed by other typo errors. It could indicate an operator making copy / paste\r\nactions.\r\nFinally, three binaries were executed remotely via PSExec, which led to the encryption of all of the companies’\r\nfiles. It occurred only on the few systems up at the time of the operation (4 AM on a Sunday morning). All 3\r\nbinaries had a prefix “Worker-”, but none of them were retrieved. We suppose that each of them has a specific\r\npurpose with the last one responsible for covering threat actor’s actions. A ransom note was dropped on the\r\nvictim’s desktops detailing the payment and contact details to pay the ransom. The indicated website was the\r\nSpook leak website, hosted on a tor onion address.\r\nWhile attempting to recover the client’s files, it appeared that Volume Shadow Copies (VSC) had not been deleted\r\non the main file sharing server. Incident responders thus tested a VSC recovery on a sample of files, which\r\nhappened successfully.\r\nhttps://www.sekoia.io/en/the-story-of-a-ransomware-builder-from-thanos-to-spook-and-beyond-part-1/\r\nPage 1 of 11\n\nSource: CERT-SEKOIA\r\nUnderstanding a Spook sample\r\nFollowing the previous incident response, we chose to focus on Spook ransomware. As per many other\r\nransomware, Spook was conceived using the Thanos builder. The Thanos builder was first advertised on the XSS\r\nforum in February 2020 by the actor Nosophoros. It was sold using a subscription format, which explained its\r\nintegration in other ransomware considered as variants, such as Spook. The most common sample found through\r\ndifferent resources has the SHA-256\r\nhash: 8dad29bd09870ab9cacfdea9e7ab100d217ff128aea64fa4cac752362459991c.\r\nThe executable is an obfuscated .NET binary. ExeInfo PE indicates the obfuscator commercial tool\r\nSmartAssembly in version 6.5.2–612.X.\r\nSource: CERT-SEKOIA\r\nhttps://www.sekoia.io/en/the-story-of-a-ransomware-builder-from-thanos-to-spook-and-beyond-part-1/\r\nPage 2 of 11\n\nWith this information, we fire de4dot from the archived repository to get a readable version of our sample.\r\nUnfortunately, the tool failed to deobfuscate function names, class names, and the resource section that stores all\r\nthe strings even though it detects an obfuscator. An article from Fortinet dating back to July 2020 mentioned the\r\nsame behavior, but did not provide the process to get a readable sample.\r\nSource: CERT-SEKOIA\r\nAfter a few Google searches, we found this very interesting article from Jason Reaves, detailing how Haron (a\r\nformer ransomware also built with Thanos) obfuscation works based on SmartAssembly. He has not provided the\r\nSmartAssembly version within the article, but from its sample it seems it’s 7.5.1.4370. By opening our sample\r\nin DNSpy, we get v8.0.2.4779.\r\nSource: CERT-SEKOIA\r\nhttps://www.sekoia.io/en/the-story-of-a-ransomware-builder-from-thanos-to-spook-and-beyond-part-1/\r\nPage 3 of 11\n\nEven if the versions are different, we tried to follow his process. We retrieve the same type of constructor to\r\nretrieve a string. Each different class declares a first attribute of type GetString. It leads us to the\r\nmodule SmartAssembly.Delegates in our .NET browser.\r\nSource: CERT-SEKOIA\r\nThe second point is the usage of Strings.CreateGetStringDelegate (from SmartAssembly.HouseofCards module)\r\nbased on the class Strings (from SmartAssembly.StringsEncoding) with the parameter typeof(NameOfTheClass).\r\nSource: CERT-SEKOIA\r\nThis function retrieves the attribute GetType from the class parameter (of type GetString), and defines an\r\nunnamed DynamicMethod belonging to the class parameter’s module, taking an integer as input and returning a\r\nstring. Then, it retrieves the first method of the Strings class which returns a string value: the Get method like the\r\nscreenshot below shows.\r\nhttps://www.sekoia.io/en/the-story-of-a-ransomware-builder-from-thanos-to-spook-and-beyond-part-1/\r\nPage 4 of 11\n\nSource: CERT-SEKOIA\r\nLet’s take a look at the Strings class now, where all the obfuscation mechanics happens. This class stores basic\r\ninformation regarding where strings are located associated with different constants:\r\n   • An offset value, hardcoded with 75 as a class attribute;\r\n   • An initial XOR value with the integer parameter sets to 107396847 (in hexa 0x666BEEF).\r\nSource: CERT-SEKOIA\r\nNotice, a cache feature to avoid decrypting a string several times, controlled by a class attribute cacheStrings sets\r\nat the initialization of the class with a default value to True.\r\nSource: CERT-SEKOIA\r\nWe learn that strings are located inside a manifest resource named {56258a19–7489–468b-86ee-e7899203d67c} and uncompressed with a custom Unzip command. This function is defined in the Zip module.\r\nhttps://www.sekoia.io/en/the-story-of-a-ransomware-builder-from-thanos-to-spook-and-beyond-part-1/\r\nPage 5 of 11\n\nSource: CERT-SEKOIA\r\nThis decompression function starts by creating a custom ZipStream attribute which is a child class\r\nfrom MemoryStream with two additional methods:\r\n   • ReadShort which returns the result of the parent class method ReadByte, which reads and casts a byte to Int32.\r\nIf the parent’s method return -1 (end of stream) the function returns 0;\r\n     • ReadInt, same function but for a word (16 bytes) by using the ReadShort method.\r\nThe next step checks a header inside the resource file against the value ‘{z}\\x00’ (the code shows 8223355, which\r\nis the unsigned 32-bit value). From the resource we get ‘{z}\\x03’.\r\nSource: CERT-SEKOIA\r\nTo get the value where the next switch statement is based on, a 24 right bit-shift is performed on the initial\r\nunsigned 32-bit integer read (do not forget that we are in little endian) leading to value 0x03. The switch has only\r\ntwo cases:\r\n   • 1 = uncompress the content\r\n   • 3 = decrypts with the symmetric algorithm AES and hardcoded values for key and initial vector\r\nIn this case the stream is decrypted. In the screenshot below, array2 represents the hardcoded key and array3 the\r\nhardcoded initial vector for the symmetric AES encryption algorithm. Then, the new stream is passed again to\r\nthe Unzip function.\r\nhttps://www.sekoia.io/en/the-story-of-a-ransomware-builder-from-thanos-to-spook-and-beyond-part-1/\r\nPage 6 of 11\n\nSource: CERT-SEKOIA\r\nUsing the same process as described above we managed to decrypt the first stream. The newly decrypted one has\r\nthe following header ‘{z}\\x01’: the switch case will go in the first option where data is simply uncompressed.\r\nSource: CERT-SEKOIA\r\nFinally, we get a chain of strings prefixed with a one byte value. Based on the article of Jason Reaves, we thought\r\nthat the integer prefix was the length of the string. We tried to use his proof of concept: it works for a small part of\r\nthe data before raising an Exception regarding value to decode in UTF-8.\r\nBy looking in the GetFromResource method from the custom Strings class, it looks like different operations are\r\nrun before returning the final string. The first byte of the string is actually related to its length but does not\r\nrepresent the value itself for a specific case: if the value after the logical AND with 0x80 is not 0. In this case, the\r\nstring length is evaluated through a long lambda function. We can split it in 3 parts:\r\n The condition is again an AND operation with 0x40 value\r\n if true, then\r\n(num \u0026 0x1F) \u003c\u003c 24) + (bytes[index++] \u003c\u003c 16) + (bytes[index++] \u003c\u003c 8) + bytes[index++]\r\nhttps://www.sekoia.io/en/the-story-of-a-ransomware-builder-from-thanos-to-spook-and-beyond-part-1/\r\nPage 7 of 11\n\nif false\r\n((num \u0026 0x3F) \u003c\u003c 8) + bytes[index++]\r\nThe 0x80 (10000000in binary) and 0x40 (01000000 in binary) values are linked to the UTF-8 stream of a string as\r\nexplained in this stack-overflow thread.\r\nSource: CERT-SEKOIA\r\nCompiling all this knowledge in a small Python script, we were able to decode the entire resource. Some of these\r\nstrings show additional base64 and reversed base64 encoding schemas: we adjust the code to get a full plaintext.\r\nSource: CERT-SEKOIA\r\nBut one question remains, how to browse the original code and get the decoded string from the resource that the\r\nfunction we are currently analyzing uses (associating an integer to its readable value)?\r\nhttps://www.sekoia.io/en/the-story-of-a-ransomware-builder-from-thanos-to-spook-and-beyond-part-1/\r\nPage 8 of 11\n\nSource: CERT-SEKOIA\r\nHow to recover FTP method STOR from integer value 107364636 ?\r\nSource: CERT-SEKOIA\r\nSplitting into parts the python code and passing the integer observed to a decode function does not work. We\r\nmissed something.\r\nBack to the delegate function in module HouseofCards and class Strings, we notice that 4 calls to Opcodes are\r\nused:\r\nOpCodes.Ldarg_0 which pushes the argument at index 0 representing the integer used by the Get method\r\nin the Strings class;\r\nOpCodes.Ldc_I4 which pushes a 32-bit integer on the stack, in this case the MetadataToken of the input\r\nclass (the AND operation with 0xFFFFFF = 16777215 is meaningless);\r\nOpCodes.Sub which subtracts the last value to the previous value pushed on the stack;\r\nOpCodes.Call which simply call the function Get of the Strings class with the result of the previous\r\nsubtraction.\r\nhttps://www.sekoia.io/en/the-story-of-a-ransomware-builder-from-thanos-to-spook-and-beyond-part-1/\r\nPage 9 of 11\n\nSource: CERT-SEKOIA\r\nThe one-string decoder requires adding a subtraction with the value of the MetadaToken of the function, which is\r\nprinted as RID in DNSpy.\r\nWe have released the source code on the CERT GitHub repository, but Jiří Vinopal created an easier way to deal\r\nwith SmartAssembly v8+ on his late 2021 tweet. By using the latest version (2015) of Simple Assembly\r\nExplorer and its integrated deobfuscator using the profile Name, String and Flow and checking the box Delegate\r\nCall, associated with the famous old one de4dot, you get the sample deobfuscated and more readable. You just\r\nhave to spend some time on base64 strings.\r\nOur script is able to decode:\r\nThe entire resource content previously extracted from the malware;\r\nAn unique integer associated to its RID function to a plaintext string;\r\nSeveral strings associated to a rid.\r\nAll commands require the resource file (extracted from the sample), the key and initial vector hardcoded inside the\r\nmalware (Module SmartAssembly.Zip — Method Unzip). The tool has been tested on different samples of Spook\r\nransomware, but also on other ransomwares known for using the Thanos builder (Hackbit, Haron,\r\nRecoveryGroup) or even on most recent threat actors like Midas. This one has the same process, they just change\r\nthe hardcoded value for the XOR and offset in the Get method of the custom Strings class. We adjust our code to\r\nbe more customizable regarding this modification. The last version of SmartAssembly tested (Midas ransomware)\r\nwas v8.0.3.4821.\r\nConclusion\r\nTo conclude, starting from an incident response involving an opportunist threat actor, we succeeded in providing\r\nfresh intelligence on how obfuscation is implemented by the Thanos builder. Considering that malicious actors can\r\nacquire this builder for a few dollars, we will see in the second part, written by SEKOIA.IO’s Threat and\r\nDetection Team (TDR), how the above-mentioned intelligence can be extended to the entire ransomware\r\necosystem.\r\nYou can also read our article on the log4shell vulnerability\r\nSources\r\nhttps://www.sekoia.io/en/the-story-of-a-ransomware-builder-from-thanos-to-spook-and-beyond-part-1/\r\nPage 10 of 11\n\nhttps://medium.com/walmartglobaltech/decoding-smartassembly-strings-a-haron-ransomware-case-study-9d0c5af7080b\r\nhttps://stackoverflow.com/questions/3911536/utf-8-unicode-whats-with-0xc0-and-0x80\r\nhttps://www.fortinet.com/blog/threat-research/analysis-of-net-thanos-ransomware-supporting-safeboot-with-networking-mode\r\nhttps://www.recordedfuture.com/thanos-ransomware-builder/\r\nhttps://github.com/SekoiaLab/CERT-Services/tree/main/202202-thanos_story_of_a_ransomware\r\nRead our article on:\r\nChat with our team!\r\nWould you like to know more about our solutions?\r\nDo you want to discover our XDR and CTI products?\r\nDo you have a cybersecurity project in your organization?\r\nMake an appointment and meet us!\r\nDiscover our:\r\nCTI platform\r\nXDR platform\r\nSOC platform\r\nTools for SOC analyst\r\nSIEM solution\r\nCTI DFIR Ransomware\r\nShare this post:\r\nSource: https://www.sekoia.io/en/the-story-of-a-ransomware-builder-from-thanos-to-spook-and-beyond-part-1/\r\nhttps://www.sekoia.io/en/the-story-of-a-ransomware-builder-from-thanos-to-spook-and-beyond-part-1/\r\nPage 11 of 11\n\nThe condition if true, is again an then AND operation with 0x40 value \n(num \u0026 0x1F) \u003c\u003c 24) + (bytes[index++] \u003c\u003c 16) + (bytes[index++] \u003c\u003c 8) + bytes[index++]\n   Page 7 of 11",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://www.sekoia.io/en/the-story-of-a-ransomware-builder-from-thanos-to-spook-and-beyond-part-1/"
	],
	"report_names": [
		"the-story-of-a-ransomware-builder-from-thanos-to-spook-and-beyond-part-1"
	],
	"threat_actors": [
		{
			"id": "08c8f238-1df5-4e75-b4d8-276ebead502d",
			"created_at": "2023-01-06T13:46:39.344081Z",
			"updated_at": "2026-04-10T02:00:03.294222Z",
			"deleted_at": null,
			"main_name": "Copy-Paste",
			"aliases": [],
			"source_name": "MISPGALAXY:Copy-Paste",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		}
	],
	"ts_created_at": 1775434156,
	"ts_updated_at": 1775791359,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/fc5c0bf21871a74e6cae2dbc7759783d42130f84.pdf",
		"text": "https://archive.orkl.eu/fc5c0bf21871a74e6cae2dbc7759783d42130f84.txt",
		"img": "https://archive.orkl.eu/fc5c0bf21871a74e6cae2dbc7759783d42130f84.jpg"
	}
}