{
	"id": "bb078060-ca3f-4507-b910-1c56e2e33147",
	"created_at": "2026-04-06T01:30:19.575507Z",
	"updated_at": "2026-04-10T03:21:25.198448Z",
	"deleted_at": null,
	"sha1_hash": "b18fb49c70ea7b907ed033af2b733209ec2142db",
	"title": "Snakes on a Domain: An Analysis of a Python Malware Loader",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 9283689,
	"plain_text": "Snakes on a Domain: An Analysis of a Python Malware Loader\r\nBy Matthew Brennan\r\nPublished: 2021-08-17 · Archived: 2026-04-06 00:56:31 UTC\r\nHackers and snakes—oh my! What do they have in common? Both are shady characters that can hide in plain\r\nsight, just waiting for the right moment to strike.\r\nBut how do you know if you have any unwanted pests nearby? Often, you just need to go looking for them—and\r\nthat’s exactly what we did. Along the way, we found a very shady Python (and coincidentally, a friendly RAT) just\r\nwaiting to strike. \r\nJoin us on our journey as we show just how important it is to keep your yard—both the real one with green grass\r\nand the virtual one with bytes and binaries—clean and tidy. Otherwise, you never know what kind of shady\r\ncreatures may be lurking in the shadows.\r\nWhat Happened?\r\nWe recently investigated a suspicious link file persisting in a user’s startup folder. The file was named\r\n“sysmon.lnk” and looked a bit fishy. After some quick initial investigation, we found that the link was executing a\r\nmalicious Python script that was used to inject a remote access Trojan (RAT) onto the system.\r\nAlong the way, we encountered a total of six consecutive payloads and some new offensive tooling which we\r\nfound pretty interesting. Towards the end, we also experimented with some custom scripts for de-obfuscating data\r\nand extracting configuration from the final RAT, resulting in some juicy indicators of compromise (IOCs) with 0\r\ndetections on VirusTotal (as of June 2021).\r\nLet's Dive In\r\nBefore we go too much further, here’s a visual representation of the malware we encountered. \r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 1 of 25\n\nWe stumbled upon a suspicious file (sysmon.lnk) that appeared to reside in a user’s startup directory. The nature of\r\nthe startup directory is to hold files that automatically run when a user logs into the computer. Since it looks just\r\nlike a normal folder, all you need to do is copy and paste a file into the folder, and boom—you can persist, or stick\r\naround, between reboots. \r\nThis provides an easy way for legitimate programs to stick around and keep running. Given its simplicity and\r\nstealth, it’s a common place that attackers will place malware and malicious files that they want to stick around.\r\nWant to learn more about persistence? Download our eBook Persistence: The Key to Cybercriminal\r\nStealth, Strategy and Success.\r\nHere’s a snippet of what we saw:\r\nc:\\\\users\\\u003cusername\u003e\\appdata\\roaming\\microsoft\\windows\\startmenu\\programs\\startup\\sysmon.lnk\r\nThis is a .lnk file (also known as a shortcut file), which redirects to another file or command on the system.\r\nInspecting the.lnk file can tell us where it points to.\r\nWhen we inspected sysmon.lnk, we found that it was redirecting to a suspicious “ctfmon.exe” with “update.py”\r\npassed as an argument. Both were residing in a suspicious-looking directory:\r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 2 of 25\n\nc:\\users\\\u003cusername\u003e\\appdata\\roaming\\PpvcbBQh\\ctfmon.exe\r\nc:\\Users\\\u003cusername\u003e\\AppData\\Roaming\\PpvcbBQh\\update.py\r\nSo, we retrieved the files and did some analysis.\r\nFile Analysis\r\nFirst, we noticed that the hash of ctfmon.exe had 0 detections on VirusTotal, which we found interesting at first\r\nbut were able to understand after looking at the file’s information. (Typically we can’t trust file version\r\ninformation without a valid signature, but in this case, the information made sense).\r\nThe information suggested that ctfmon.exe is a renamed Python interpreter—specifically, an IronPython\r\ninterpreter, which utilizes a branch of Python with access to .NET libraries. This allows Python code to access\r\ndeep Windows OS functionality typically reserved for .NET or PowerShell. This was interesting and provided\r\nenough information to confidently move on to the Python file. \r\nWe can see that the original file ctfmon.exe had 0 detections on VirusTotal, as technically it’s a legitimate\r\ninterpreter and not a malicious file.\r\nBelow, we can see the file description, indicating that it was a renamed IronPython interpreter. Alternatively, we\r\ncould have also discovered this information using PeStudio or a similar tool. \r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 3 of 25\n\nThis was enough information to determine the purpose of the ctfmon.exe file, so we moved on to the Update.py\r\nfile, which we’ll refer to as stage1.py.\r\nStage1.py\r\nWe first moved the Python file into a text editor within a Virtual Machine just in case it was malicious—and\r\nspoiler alert: it was. 😅 \r\nThis led us to a relatively small script with a large obfuscated string and some obfuscated variable names. We can\r\nsee the full script in this screenshot:\r\nThis wasn’t super pleasant to read, so we cleaned it up a bit and added comments, which left this script:\r\nIf we inspect closer, we can see that the script achieves four main things:\r\nBase64 decodes an obfuscated string\r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 4 of 25\n\nIt converts the Base64-decoded string into a bytearray of hex values\r\nThen, it decreases the value of each byte by 12 (decimal)\r\nFinally, it executes the resulting data\r\nBy copying out the obfuscated string and recreating the logic in CyberChef, we were able to retrieve another\r\nPython script—which we saved and named as stage2.py. The decoding logic can be seen below:\r\nStage2.py\r\nWe copied the resulting script out of CyberChef and opened up stage2 in a text editor, where we quickly noticed\r\nanother obfuscated string, as well as some imported libraries related to reflection. (In case you’re not familiar,\r\nreflection is a common technique used to execute code from memory without needing to save it to disk—in this\r\ncase, the “something” would be the obfuscated string containing malware.)\r\nBased on this information, we assumed that the script was decoding the string and loading the results into memory\r\nfor execution.\r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 5 of 25\n\nIn the middle of the above screenshot, we can observe two main operations used to decode the string:\r\nReplacing all “!” exclamation marks with the letter “A”\r\nBase64 decoding the results\r\nThis didn’t seem too complicated, so we moved back to CyberChef and recreated the decoding logic. This resulted\r\nin the appearance of an MZ header, indicating that we had successfully decoded the data and retrieved an\r\nexecutable file. We saved this file and named it stage3.bin.\r\nStage3.bin\r\nSaving stage3 as an executable file, we were able to do some basic inspection using PeStudio and Detect-It-Easy\r\n(DIE). This quickly led us to the conclusion that this was a .NET file and likely another stager (based on the\r\npresence of a path referencing injector.pdb). \r\nBelow, we can see that DIE recognized the file as a .NET executable, which meant we could use Dnspy or ILspy\r\nfor analysis. \r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 6 of 25\n\nBelow, we can also see the PDB path with references to “injector.pdb”, indicating that this is likely another stager\r\ndoing some kind of injection:\r\nSince we now knew that this was a .NET file, we moved over to Dnspy where we could view the source code of\r\nthe file. This can be seen below.\r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 7 of 25\n\nJust looking at the function names alone, we got a strong indication of what the file was going to do. We can see\r\nfunctions indicative of Injection (VirtualAlloc, WriteProcessMemory, etc.), Dynamic Library/Function loading\r\n(GetProcAddress, LoadLibrary) and decoding (compress, decompress, base64_encode). Without looking at the\r\ncode in detail, we could already assume the core functionality: an obfuscated payload is going to be decoded and\r\ninjected into a process. \r\nBrowsing to the main function, we quickly found the encoded payload. Combined with the preceding function\r\ncalls (Load, Decompress, Base64), we can assume that the data is being Base64 decoded and then decompressed\r\nand loaded into memory. \r\nBelow, we can see the encoded string and related function calls:\r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 8 of 25\n\nTowards the end of the encoded data, we also observed a reference to msbuild.exe. This became important later, as\r\nit turned out to be the second argument passed to the Mandark.Load method. \r\nNext, we browsed to the Mandark.Load method to find out what else was happening—and to determine the\r\nsignificance of that msbuild.exe argument.\r\nThis led us to the conclusion that the second argument passed to the load method becomes the target process for\r\nthe injection. We also noted the use of ZwUnmapViewOfSection, indicating that this style of injection is process\r\nhollowing. MITRE ATT\u0026CK defines process hollowing:\r\n“Adversaries may inject malicious code into suspended and hollowed processes in order to evade\r\nprocess-based defenses. Process hollowing is a method of executing arbitrary code in the address space\r\nof a separate live process.”\r\nWe believe that MSBuild was likely targeted as it is often allowed to execute by default application whitelisting\r\ntools, including Microsoft's own Applocker. \r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 9 of 25\n\nWith this new knowledge, we decided to move back to the main function and try to decode the injected payload.\r\nWe already noted that Base64 encoding and compression was used.\r\nWe quickly inspected the decompress method to confirm the compression type—in this case, it was Gzip. \r\nCombining the above information together, we were able to decode the next payload using CyberChef. This\r\nresulted in another MZ header for an executable file. We saved this file and named it stage4.bin. Note that this\r\npayload would likely have been injected into the msbuild.exe process.\r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 10 of 25\n\nStage4.bin\r\nLoading up stage4.bin, we performed some basic static analysis and determined that it was not another .NET file,\r\nso we weren’t able to use Dnspy. \r\nBelow, we can see the detected compiler using DIE, which suggested that it was written in C++/C and not .NET. \r\nUsing PeStudio, we noticed this exported function, which stood out to us as it indicated that this was likely\r\nanother loader (given away by the term “ReflectiveLoader”).\r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 11 of 25\n\nWe noted this and kept going. \r\nBrowsing further, we noticed this reference in the debug section of the file. This contained another PDB path, and\r\na very git-like folder structure. \r\nSome googling of keywords in the PDB path led us to believe that the file was likely an execute-assembly loader,\r\nwhich is an open-source re-implementation of the Cobalt Strike execute-assembly module:\r\nIf the GitHub repository is anything to go by, this is an extremely well-featured and interesting loader that\r\nincorporates some really cool evasion tactics. We could almost dedicate an entire blog to the capabilities of this\r\nloader, but today, we’ll stick to its loading capabilities and try to focus on finding the next payload.\r\nWithin the rest of the GitHub repository documentation, there was this particular tidbit (see below) which really\r\nstood out. It indicated the structure of embedded payloads, which should be in the format of\r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 12 of 25\n\n“0|0|0|0|1|sizeofpayload.b64_encoded_compressed_payload”.  (Note: The payload is going to be in Gzip\r\ncompressed and Base64 encoded format.)\r\nThis was super interesting because there was a very large string within the file, which matched that exact\r\ndescription (and was 64983 bytes in size—more than enough room for another payload).\r\nWe copied that string into CyberChef and re-implemented the decoding routine (Base64 and Gzip decompress),\r\nwhich resulted in yet another executable file. \r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 13 of 25\n\nYou know the drill by now—we saved this file and named it stage5.bin.\r\nStage5.bin\r\nPerforming our usual static analysis of our latest file, we soon realized that it was another .NET (yay). Luckily, we\r\ncould jump back into Dnspy and view the source code.\r\nMoving into Dnspy, we noted that there weren’t many functions this time—only six in total:\r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 14 of 25\n\nNavigating to the main function, we noted two large obfuscated strings:\r\nThe first one was just Base64 encoded and turned out to be an anti-malware scan interface (AMSI) patching\r\nscript. Implemented by Microsoft, AMSI provides a framework for security tooling to monitor PowerShell script\r\nactivity. The goal of an AMSI patch is to bypass this framework and reduce the chances of an antivirus or EDR\r\ndetecting any malicious PowerShell activity. (Later, we’ll see that the malware does use PowerShell scripts, so this\r\npatch likely allows them to execute without being detected.)\r\nBelow, we can see the full AMSI patching script, which was lightly obfuscated. \r\nWe were able to decode the script, which loosely translated to this below. \r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 15 of 25\n\nThe second string was far more interesting, as it incorporated a custom encoding routine alongside the Base64 and\r\ncompression that we’ve been so far accustomed to. This was an indication that we need more than just CyberChef\r\nalone to decode our next payload.\r\nIn order to get a better understanding of the obfuscation, we inspected the Cipher method and found the encoding\r\nroutine. It didn’t look standard, and clearly, it was something custom-built—although not extremely complicated\r\nto decode. Routines like this are often used to evade automated analysis, as the non-standard nature hinders some\r\nautomated tooling—often requiring manual intervention and analysis to decode properly.\r\nBelow, we can see the full custom routine, which takes an encoded string, a key and an encipher flag. \r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 16 of 25\n\nBrowsing back to our main function, we quickly found the key “avyhk” and encipher flag, which was set to false.\r\nWe decided not to pursue CyberChef for this. After some careful inspection and analysis, we were able to re-implement the routine using the equivalent Python code included below.\r\nUsing our new Python script, we wrote a wrapper around our cipher function and we were able to dump the\r\ndecoded content to a new file. Using this, we ended up with another executable file: stage6.bin. \r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 17 of 25\n\nStage6.bin\r\nWe saved and loaded the stage6.bin file into PeStudio and DIE for some static analysis and saw that we had\r\nanother .NET file. (Yay for Dnspy again!)\r\nOverall, we didn’t find anything of particular use within PeStudio, so we moved on to Dnspy. We were able to\r\ndetermine that the file was a remote access Trojan (RAT), likely from the URSU family of malware.\r\nThis malware had all the typical functionality of a RAT, which included the ability to gather and enumerate system\r\ninformation, as well as download files and commands from a remote command-and-control server.\r\nAnalysis of the RAT\r\nBelow, we can see a graphic overview of the functionality of the final RAT payload.\r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 18 of 25\n\nDecrypting the Configuration\r\nAfter determining that this malware was likely a RAT, we decided to look for indicators of the C2 server and any\r\nconfiguration settings that we could use as indicators of compromise. Analyzing the RAT code within Dnspy, we\r\nfound an “InitializeSettings” method that was loading config data from values encrypted with AES256, and then\r\nencoding using Base64.\r\nHere’s the code for decrypting config data within the InitializeSettings method:\r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 19 of 25\n\nBelow, we can see the AES256 encrypted and Base64-encoded values being loaded. \r\nAfter playing around with the decryption code, we were able to decrypt the config and pull out the following\r\nvalues—including a port number, mutex name, version and grouping numbers, as well as three domains of C2\r\nservers. \r\nMachine Enumeration\r\nThrough a combination of queries made to the OS, mostly via WMI queries, the malware gathered the following\r\ninformation to send to the C2 server:\r\nCurrently running antiviruses and security products\r\nUser privileges\r\nWhether the victim was connected to a domain\r\nExternal IP of the current machine\r\nNames of open windows and active processes\r\nAnti-Analysis Checks\r\nAfter enumerating system information, the malware then executed some anti-analysis checks to see if it was\r\nrunning inside of a virtual machine or analysis environment. \r\nThe malware contained several methods and functions for detecting this. These were relatively simple and\r\nconsisted of five main checks:\r\nDetectManufacturer: Looks for VMware or VirtualBox in hardware descriptions\r\nDetectDebugger: Checks “Debugger.IsAttached” flag, also checks for the presence of a  dnspy.xml file in\r\nthe %appdata% directory\r\nDetectSandboxie: Looks for Sandboxie drivers (sbiedll.dll)\r\nIsSmallDisk: Checks if Disk Size is less than 61GB\r\nIsXP: Checks if the current OS is Windows XP\r\nIf any of the above checks are true, then the malware cleans up and terminates itself with the “failFast” method.\r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 20 of 25\n\nBelow, we can see the names of the anti-analysis functions being called. \r\nNone of them were particularly interesting or complex, and all followed a similar structure to the screenshot\r\nbelow.\r\nFinal Persistence: Run Keys and Scheduled Tasks\r\nOnce the anti-analysis checks were completed, the malware established further persistence via scheduled tasks and\r\nrun keys, depending on the current privilege level.\r\nIf admin privileges were available, then an elevated scheduled task is created. This would allow the malware to\r\npersist with admin-level privileges across reboots, without the need for UAC prompts each time. \r\nIf only standard user privileges were available, a .bat script would be placed into the current user’s run key, which\r\nwould provide persistence with standard user privileges. \r\nUsing these indicators, we were able to find other artifacts left by the malware and develop detections that could\r\nbe used to alert on similar activity. \r\nYou can check for similar persistence via scheduled tasks and run keys by regularly reviewing the following run\r\nkey and scheduled task locations:\r\nHKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\r\nHKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\r\nc:\\windows\\system32\\tasks\r\n(Alternatively, sign up for a free trial and we’ll take a look for you!)\r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 21 of 25\n\nC2 Commands and Functionality\r\nOnce persistence had been established, the malware then contacted the command and control servers for further\r\ncommands. These commands could be...\r\nUpdate: Download new malware via PowerShell, start it, then kill the current process\r\nSavePlugin: Download and load a remote DLL\r\nUnload: Send a kill command over a named pipe\r\nRestart: Kill the current process and force a restart via a scheduled task\r\nSelf-delete: Remove all persistence and kill the current process\r\nSome short snippets of this functionality are in the screenshots below:\r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 22 of 25\n\nVirusTotal Check of Domains: 0/3\r\nAt the time of initial analysis (May 2021), all of the domains had 0/85 detections on VirusTotal—although one of\r\nthem was marked as suspicious by one vendor.\r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 23 of 25\n\nRecommendations and Final Comments\r\nThat wraps up our analysis of this malware. We hope you enjoyed it as much as we did. Hopefully, you learned\r\nsomething new and will soon be able to implement some of these analysis techniques for yourself.\r\nAs we saw, even a relatively simple payload (like a RAT) can be implemented in a way that is highly complex and\r\ndifficult to detect, especially when using customized or unique files and domains that slip past automated security\r\ntooling. Although automated tooling has its place, the days are gone where you can rely on such tooling alone.\r\nYou should make sure that proactive and human-driven methods of threat hunting are built into your security stack\r\nalongside layered tooling to hinder and decrease the likelihood of a successful compromise.\r\nTo wrap things up, we’d like to make a few recommendations for dealing with this type of malware:\r\nAvoid relying on static signatures to detect malicious activity. This applies for both network and file-based indicators of compromise. All running executables and domains in this investigation were\r\n“legitimate” and likely would not be blocked on hash alone.\r\nMonitor and manually review suspicious files executing from runkeys, scheduled tasks and persistent\r\nstartup folders.  \r\nMonitor for process creation events where a Python file is being passed to a non-Python or text editor\r\nexecutable.\r\nInspect any suspicious or non-standard process creation events. Baseline which processes are expected\r\nto launch msbuild.exe, and alert on anything outside of this baseline.\r\nWhen analyzing suspicious files and domains, make sure to incorporate manual analysis and\r\ndecoding into your process. Avoid relying solely on automated tooling such as VirusTotal or online\r\nsandboxes.\r\nIndicators of Compromise\r\nDomains:\r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 24 of 25\n\nwindowsupdatecdn[.]cn\r\ngjghvga7ffgb[.]xyz\r\nhuugbbvuay4[.]cn\r\nHashes:\r\nctfmon.exe: 3e442cda613415aedf80b8a1cfa4181bf4b85c548c043b88334e4067dd6600a6\r\nUpdate.py: dd1fa3398a9cb727677501fd740d47e03f982621101cc7e6ab8dac457dca9125\r\nstage2: 2CCADFC32DB49E67E80089F30C81F91DFFF4B20B8FC61714DF9E2348542007FD\r\nstage3: 4591EDA045E3587A714BB11062EB258F82EE6F0637E6AA4D90F2D0B447A48EF7\r\nstage4: 4417298524182564AED69261B6C556BDCE1E5B812EDC8A2ADDFC21998447D3C6\r\nstage5: 9B775DFC58B5F82645A3C3165294D51C18F82EC1B19AC8A41BB320BEE92484ED\r\nstage6: 169F5DBCD664C0B4FD65233E553FF605B30E974B6B16C90A1FB03404F1B01980\r\nSource: https://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nhttps://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader\r\nPage 25 of 25",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"references": [
		"https://www.huntress.com/blog/snakes-on-a-domain-an-analysis-of-a-python-malware-loader"
	],
	"report_names": [
		"snakes-on-a-domain-an-analysis-of-a-python-malware-loader"
	],
	"threat_actors": [],
	"ts_created_at": 1775439019,
	"ts_updated_at": 1775791285,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/b18fb49c70ea7b907ed033af2b733209ec2142db.pdf",
		"text": "https://archive.orkl.eu/b18fb49c70ea7b907ed033af2b733209ec2142db.txt",
		"img": "https://archive.orkl.eu/b18fb49c70ea7b907ed033af2b733209ec2142db.jpg"
	}
}