{
	"id": "e3006452-6256-4fdb-8adc-1dd70a078799",
	"created_at": "2026-04-06T00:20:09.428911Z",
	"updated_at": "2026-04-10T03:28:02.977463Z",
	"deleted_at": null,
	"sha1_hash": "5838a93b133088fcdb5b91fffdcc93add6bb7e5a",
	"title": "Snakes in the Castle: Inside the Walls of Python-Driven CastleLoader Delivery",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 84239,
	"plain_text": "Snakes in the Castle: Inside the Walls of Python-Driven CastleLoader\r\nDelivery\r\nArchived: 2026-04-05 19:05:55 UTC\r\nThe Blackpoint SOC recently responded to an incident initiated through the tried-and-true ClickFix technique; a social\r\nengineering method consistently leveraged across numerous campaigns this past year. These lures convince users to\r\npress Win + R to open the Windows Run dialog box, then enter a command presented as a harmless “human verification”\r\nstep or similar prompt. This pattern has been repeatedly used to deploy everything from information stealers to remote\r\naccess trojans (RATs), and it has also become one of the primary delivery vectors for a newer loader family known\r\nas CastleLoader. \r\nCastleLoader is a malware loader family that emerged in early 2025, primarily delivered through these\r\nsame ClickFix campaigns. Its role is simple but dangerous, retrieve an encrypted second stage from attacker infrastructure,\r\ndecrypt it in memory, and pass execution to whatever payload the operator chooses. Campaigns have\r\nleveraged CastleLoader to deploy a wide range of malware including remote access trojans\r\nlike CastleRAT, SectopRAT, NetSupport RAT, and WarmCookie, along with credential stealing families such\r\nas Stealc, RedLine, Rhadamanthys, and MonsterV2.\r\nIn this case, the ClickFix command downloaded a small archive and staged its contents inside the user’s AppData directory\r\nbefore invoking a bundled copy of pythonw.exe to execute one of the extracted files. That script served as a simple Python\r\nstager whose only job was to rebuild and execute a CastleLoader payload. It decoded a Base64 encoded blob, applied a\r\nXOR routine using a hardcoded key, reconstructed the shellcode in memory, and transferred execution to it without ever\r\nwriting an executable to disk.\r\nAfter the shellcode took over, CastleLoader moved into its next phase of retrieving the attacker’s final payload. This\r\nstage leverages a technique known as PEB Walking to locate loaded modules and resolve all required APIs entirely at\r\nruntime, bypassing the normal Windows import system that security tools often watch closely. Once its function table was\r\nbuilt, it contacted attacker infrastructure using a hardcoded GoogeBot User-Agent header, downloaded the final stage,\r\ndecrypted it using the first 16 bytes of the blob as the XOR key, and executed it directly in memory.\r\nKey Findings\r\nA ClickFix campaign leveraged a Python-based delivery to stage CastleLoader.\r\nExecution was proxied through conhost.exe, which invoked a cmd.exe downloader.\r\nThe downloader fetched and unpacked a tar archive into an AppData staging folder.\r\nA windowless Python interpreter then ran python.cat, a compiled Python bytecode file.\r\nThe Python stager decoded a Base64 blob and XOR-reconstructed the CastleLoader shellcode in memory.\r\nCastleLoader used PEB Walking and hashed API resolution to build its function table.\r\nThe loader contacted attacker infrastructure using a GoogeBot User-Agent header.\r\nThe final payload was downloaded, XOR-decoded using the first 16 bytes as the key, and executed in memory.\r\nObserved Kill Chain\r\nhttps://blackpointcyber.com/blog/python-driven-castleloader-analysis/\r\nPage 1 of 9\n\nThe infection chain began with a ClickFix style Run dialog command that the user was socially engineered into executing.\r\nThis single line launched a hidden conhost.exe window, which proxied an execution chain of several built in Windows\r\nutilities. It first used curl.exe to download an archive originally named p2.tar from an attacker IP address, saving it\r\nas pt.tar in the user’s AppData\\Roaming directory. The command then created a new folder named etc and extracted the\r\narchive’s contents into that location using the tar utility, all without displaying any visible window or prompt to the user. \r\nFigure 1: Initial ClickFix command executed via conhost.exe.\r\nOnce the files were staged, the command invoked the bundled pythonw.exe inside the extracted folder. This is the\r\nwindowless version of the Python interpreter, meaning it runs Python code without displaying a console window or any\r\nvisible output to the user. The interpreter executed a file named python.cat, which is actually compiled Python bytecode.\r\nBytecode is a lower-level representation of a Python script that the interpreter can run directly, and\r\nit doesn’t contain readable source code, making the program logic opaque without decompiling it.\r\nFigure 2: Confirming python.cat is compiled Python bytecode.\r\nTo analyze the loader’s behavior, the compiled bytecode needs to be decompiled back into a readable form. Python\r\nbytecode can’t be understood directly, but it can be translated back into approximate source code using decompilation tools.\r\nA tool such as Decompyle++ can take the bytecode from python.cat and reconstruct the script logic well enough to review\r\nits flow and understand the functions implemented by the attacker. \r\nFigure 3: Recovering readable script logic from python.cat using Decompyle++.\r\nhttps://blackpointcyber.com/blog/python-driven-castleloader-analysis/\r\nPage 2 of 9\n\nWith the bytecode decompiled, the recovered script reveals a compact loader that hides its real logic behind a block of text\r\nmixing normal Latin letters with Cyrillic characters. The script runs this encoded string through a series of .replace() calls,\r\nswapping each Cyrillic glyph back to another Latin character or digit until the text becomes a valid Base64 string. After\r\nreconstructing the clean Base64 data, the script decodes it with base64.b64decode() and immediately executes the result\r\nwith exec(), meaning the visible script is just a wrapper, and the actual loader logic lives inside the decoded payload. \r\nFigure 4: Loader reconstructing the payload via Cyrillic replacements. \r\nAfter decoding the obfuscated wrapper, the resulting Python script is a straightforward in-memory shellcode loader. It\r\ndefines a xor_decrypt() function that takes two-byte arrays, loops over each byte of the encrypted payload, and XORs it\r\nwith a repeating key. The script then Base64 decodes two hardcoded blobs, the first of which is encrypted shellcode, and the\r\nsecond is the key used to decrypt it. Passing both into xor_decrypt() produces a decrypted shellcode buffer, which is\r\nwrapped in a bytearray for further use.\r\nThe rest of the script uses ctypes to pass the decrypted bytes directly into Windows’ low level memory routines.\r\nIt calls VirtualAlloc to create a new memory region with read, write, and execute permissions, large enough to hold the\r\nshellcode. After that, it builds a C-style buffer containing the decrypted bytes and uses RtlMoveMemory to copy them into\r\nthe allocated region, a common technique used by in-memory loaders. Finally, the script uses CFUNCTYPE to create a\r\nfunction pointer and cast the allocated memory address to it, allowing Python to treat that region as a callable function. The\r\nscript calls the function pointer, completing the transfer of execution into the decoded payload running inside\r\nthe pythonw.exe process.\r\nFigure 5: The decoded Python loader decrypting and executing shellcode in memory.\r\nTo examine the next stage of the kill chain, the loader’s decryption logic can be reused outside of the pythonw.exe process.\r\nBy taking the same Base64 blobs and XOR routine shown earlier and removing the in-memory execution step,\r\nthe script can be made to simply write the decrypted bytes to disk instead. This produces a standalone shellcode sample that\r\ncan be analyzed further to understand its behavior.\r\nFigure 6: Modified script that decrypts the shellcode and writes it to shellcode.bin.\r\nLoading the decrypted shellcode into a disassembler immediately reveals that it targets a 32-bit x86 environment. The entry\r\nroutine uses the classic 32-bit function prologue (push ebp followed by mov ebp, esp) and works entirely with registers\r\nlike eax, ecx, and edx, with stack variables referenced using 4-byte offsets. These registers belong to the 32-bit register set,\r\nhttps://blackpointcyber.com/blog/python-driven-castleloader-analysis/\r\nPage 3 of 9\n\nwhich indicates that the shellcode is built for an x86 execution context. This is a common choice for loader malware, since\r\n32-bit shellcode runs consistently on modern 64-bit systems under WoW64 and lets attackers rely on simpler, well tested\r\nmethods.\r\nFigure 7: Decompiled routine showing 32-bit register usage and stack-based operations.\r\nAfter cleaning up the decompiled shellcode into a more readable form by renaming functions and variables, the execution\r\nflow becomes straightforward to follow. The primary function we’ll refer to as loaderEntry() builds the hardcoded\r\ndomain dperforms[.]info, appends the path /service/download/load_1, and passes the resulting URL to a function renamed\r\nto httpDownloadPayload(). The server’s response is stored in variables renamed as responseBuf and responseSize.\r\nFigure 8: loaderEntry constructing the hardcoded staging URL and initiating the download.\r\nExamining the httpDownloadPayload() function to better understand the network request reveals it serves as a lightweight\r\nHTTP client that resolves WinINet APIs at runtime, issues the outbound request, and streams the response into a heap\r\nbuffer. The routine also writes the hardcoded string GoogeBot into a small buffer used as the User-Agent header. Based on\r\nthe parameters passed in from loaderEntry() such as the destination buffer, the size pointer, and the full staging URL, the\r\nfunction initializes the request with those values and returns the downloaded contents through outBuf and outSize.\r\nhttps://blackpointcyber.com/blog/python-driven-castleloader-analysis/\r\nPage 4 of 9\n\nFigure 9: GoogeBot User-Agent and WinINet setup in the httpDownloadPayload function.\r\nThere are two distinct overlaps in this stage matching indicators observed in CastleLoader campaigns. The hardcoded User-Agent string GoogeBot has appeared in prior CastleLoader traffic, where it is used to disguise outbound requests with a\r\ncrawler style identifier. The staging path assembled by the loader, which includes /service/download/, is another pattern that\r\nhas been repeatedly observed in CastleLoader network requests. Both traits are well known CastleLoader markers, and\r\ntheir presence in this Python based chain is not coincidental.\r\nCastleLoader is a newer loader family that emerged in early 2025, most commonly delivered through ClickFix style Run\r\ndialog social engineering. Its job is to retrieve an encrypted second stage from attacker infrastructure, decrypt it in memory,\r\nand pass execution to whatever payload the operator chooses. Campaigns using this tooling have historically deployed a\r\nbroad mix of malware, including remote access tools like NetSupport RAT, SectopRAT, CastleRAT, and WarmCookie,\r\nas well as information stealers such as StealC, RedLine, Rhadamanthys, DeerStealer, MonsterV2, and others.\r\nMoving back to the shellcode, one of its core behaviors is the use of a technique known as PEB Walking to resolve every\r\nAPI it needs at runtime. Typically, programs list their Windows API calls in an Import Table and the OS loads them\r\nautomatically. CastleLoader avoids that by reading the Process Environment Block (PEB), iterating through the modules\r\nalready loaded in memory, and manually locating function addresses by hashing each exported name until a match is found.\r\nThis allows the shellcode to retrieve memory allocation routines, networking functions, and other capabilities without\r\nrelying on static imports.\r\nThe PEB Walking implementation relies on two helper functions. The first, which was renamed to getModuleBase(),\r\nworks by selecting a DLL name based on an integer identifier, constructing that name in memory, and then scanning the\r\nPEB’s module list to locate the corresponding loaded module. This gives the shellcode direct access to the base address of\r\nlibraries like ntdll.dll, kernel32.dll, and wininet.dll without relying on a traditional import table.\r\nhttps://blackpointcyber.com/blog/python-driven-castleloader-analysis/\r\nPage 5 of 9\n\nFigure 10: The getModuleBase() function locating DLLs by walking the PEB module list.\r\nThe second function, renamed to resolveApiByHash(), takes that module base and walks the module’s Export Table,\r\nhashing each exported name and comparing it to constants embedded in the loader. When a match is found, it returns\r\nthe function’s address as a callable pointer. Used in tandem, the two functions enable the shellcode’s PEB Walking logic,\r\nwhere getModuleBase() retrieves the base address of a target DLL and resolveApiByHash() uses that base to resolve\r\nindividual API functions dynamically.\r\nhttps://blackpointcyber.com/blog/python-driven-castleloader-analysis/\r\nPage 6 of 9\n\nFigure 11: resolveApiByHash() parsing export entries and matching hashed names.\r\nAfter the staging request returns to loaderEntry(), the downloaded blob is split into two sections rather than being treated as\r\na single buffer. The first 16 bytes at buf become the XOR key, and the remaining bytes form the encrypted payload\r\nbeginning at buf + 0x10. Those values, along with the adjusted length size – 0x10, are passed into a function renamed\r\nto xorDecrypt(), which applies the key across the encrypted region and writes the decoded bytes back into the same\r\nmemory. Once the loop completes, the decrypted payload is fully reconstructed in place at buf + 0x10, ready for execution.\r\nFigure 12: Decryption step in loaderEntry() using the first 16 bytes as the key.\r\nWith the payload decrypted and staged in memory, the loader simply hands execution to the function pointer built\r\nfrom buf + 0x10. This is the point where control shifts entirely into whatever final stage the operator intended to deliver.\r\nUnfortunately, by the time this sample was analyzed, the staging domain was no longer returning a payload, leaving the\r\nactual final malware unknown. The final payload would have been decoded and executed directly in memory, consistent\r\nwith the behavior of the rest of the chain.\r\nMethodology \u0026 Attribution\r\nThe CastleLoader attribution is backed by two very specific indicators observed in the loader’s HTTP request behavior. The\r\nhardcoded GoogeBot User-Agent directly overlaps with both GoogleBot and GoogeBot strings observed in\r\nhttps://blackpointcyber.com/blog/python-driven-castleloader-analysis/\r\nPage 7 of 9\n\nmultiple CastleLoader campaigns throughout 2025. The staging URL assembled by the loader\r\nalso contains the /service/download/ path, a distinctive pattern that appears across several confirmed CastleLoader samples.\r\nSeeing both traits in the same request creates a strong match with known CastleLoader infrastructure and tooling.\r\nThe delivery method further supports this alignment. Earlier CastleLoader campaigns leveraged ClickFix techniques that\r\ndropped a ZIP archive containing AutoIt scripts, which in turn loaded the CastleLoader shellcode into memory. In this\r\ninfection chain, the AutoIt loader has simply been replaced with a small Python dropper that writes and executes the\r\nshellcode, but the overall delivery concept remains the same. The delivery technique here matches the same\r\napproach CastleLoader has used across earlier samples.\r\nFinally, the loader’s behavior follows the same pattern. Historical CastleLoader samples relied on hashed DLL names,\r\nhashed API identifiers, and PEB Walking for export resolution, the same approach taken by this shellcode. Furthermore,\r\nwhile earlier CastleLoader samples relied on a static hardcoded key to decrypt the final payload, this variant\r\nslightly modifes the technique by deriving the key from the first 16 bytes of the downloaded blob. Even with that deviation,\r\nthe core design is unchanged. The loader still retrieves an encrypted payload, decrypts it in memory, and stages it for\r\nexecution, following the same overall pattern seen in earlier CastleLoader samples.\r\nIn conclusion, each stage of this chain lines up cleanly with known CastleLoader behavior, from the ClickFix delivery, to the\r\ndistinctive GoogeBot user agent, staging path, and loader design. The Python dropper simply replaces the AutoIt stagers\r\nseen in earlier campaigns, but the shellcode it delivers follows the same architectural patterns, API resolution techniques,\r\nand execution flows seen previously. This combination of traits firmly aligns the activity with the CastleLoader family,\r\ndelivered through a modified implementation of its rapidly evolving delivery technique.\r\nRecommendations \r\nEducate end users on ClickFix style social engineering, especially lures that prompt them to open the Run dialog and\r\nenter “verification” commands.\r\nUse Group Policy to restrict or disable access to the Run dialog for users who do not require it operationally.\r\nRestrict access to cmd.exe, PowerShell, and Python runtimes for users who do not need them as part of normal\r\nworkflow.\r\nMonitor or alert on suspicious LOLBin sequences, such as conhost.exe spawning cmd.exe, followed by curl, tar, or\r\npythonw.exe.\r\nImplement DNS monitoring and filtering to block newly registered domains or low reputation TLDs commonly used\r\nfor malware staging.\r\nMonitor for Python binaries executed from atypical directories, such as AppData, where legitimate Python\r\ninstallations are not typically located.\r\nMonitor and audit outbound network connections from pythonw.exe, since its windowless execution is commonly\r\nabused for stealthy staging activity.\r\nIndicators of Compromise (IOCs)\r\nNetwork\r\nType Indicator Context / Notes\r\nIP 78.153.155[.]131 Stage 1 Delivery\r\nDomain dperforms[.]info Final Stage Domain\r\nURL hxxp[://]78.153.155[.]131/service/download/p2.tar Malicious Tarball\r\nURL hxxps[://]dperforms[.]info/service/download/load_1 Final Stage Payload\r\nhttps://blackpointcyber.com/blog/python-driven-castleloader-analysis/\r\nPage 8 of 9\n\nFiles\r\nFilename  Hash  Context / Not\r\npt.tar 8A539355D317BD8A490F470319410E5D2A2851A38828C900F357FBAC9083583C Malicious Tar\r\npython.cat 0F5C3AC4B4F997ACD2CD71C451082CD8FBD1CBDB1A6DB2BDF470714F2E7EF4BB Python Stager\r\nN/A BFEA06A7EF5B25B40178CFFFD802D8AB4F5EE35CA5CD8D2B9FF29B4E201B3B7F CastleLoader\r\nSource: https://blackpointcyber.com/blog/python-driven-castleloader-analysis/\r\nhttps://blackpointcyber.com/blog/python-driven-castleloader-analysis/\r\nPage 9 of 9",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://blackpointcyber.com/blog/python-driven-castleloader-analysis/"
	],
	"report_names": [
		"python-driven-castleloader-analysis"
	],
	"threat_actors": [
		{
			"id": "1a76ed30-4daf-4817-98ae-87c667364464",
			"created_at": "2022-10-25T16:47:55.891029Z",
			"updated_at": "2026-04-10T02:00:03.646466Z",
			"deleted_at": null,
			"main_name": "IRON LIBERTY",
			"aliases": [
				"ALLANITE ",
				"ATK6 ",
				"BROMINE ",
				"CASTLE ",
				"Crouching Yeti ",
				"DYMALLOY ",
				"Dragonfly ",
				"Energetic Bear / Berserk Bear ",
				"Ghost Blizzard ",
				"TEMP.Isotope ",
				"TG-4192 "
			],
			"source_name": "Secureworks:IRON LIBERTY",
			"tools": [
				"ClientX",
				"Ddex Loader",
				"Havex",
				"Karagany",
				"Loek",
				"MCMD",
				"Sysmain",
				"xfrost"
			],
			"source_id": "Secureworks",
			"reports": null
		}
	],
	"ts_created_at": 1775434809,
	"ts_updated_at": 1775791682,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/5838a93b133088fcdb5b91fffdcc93add6bb7e5a.pdf",
		"text": "https://archive.orkl.eu/5838a93b133088fcdb5b91fffdcc93add6bb7e5a.txt",
		"img": "https://archive.orkl.eu/5838a93b133088fcdb5b91fffdcc93add6bb7e5a.jpg"
	}
}