{
	"id": "8ed93e4d-8b92-44c3-b08b-851c0b729751",
	"created_at": "2026-04-06T00:06:45.282913Z",
	"updated_at": "2026-04-10T13:13:05.949861Z",
	"deleted_at": null,
	"sha1_hash": "fdb0c11777bbf169c41f65c86477642d3aea502f",
	"title": "BugSleep network protocol reversing",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 969559,
	"plain_text": "BugSleep network protocol reversing\r\nArchived: 2026-04-05 16:08:49 UTC\r\nBack in July cp\u003cr\u003e Check Point Research team released a technical analysis of, at the time, a new backdoor they\r\nobserved in phishing campaigns leveraged by the threat actor MuddyWater, also tracked by other Intel shops as\r\nATK51 or TA450.\r\nBased on their investigation and analysis, they named the malware family BugSleep due to the many calls to the\r\nWindows API Sleep and bugs observed in some of its functionalities.\r\nThe article goes into great details peeling out the different layers of the attack chain while focusing on BugSleep\r\nloader and payload components. Now, something I thought would have been interesting to investigate, was the set\r\nof supported commands the C2 can instruct BugSleep to execute and more precisely how the two components (the\r\nclient and the server) interact with each other.\r\nOn the top of this, as I could not find at the time any pcap with some live C2 traffic, this would have also served\r\nme well as a playground for developing a fake C2 server from scratch and trigger on-demand function of the\r\nbackdoor, which based on Check Point report, seems to be between 10 or 11 commands depending on the version\r\nof the backdoor.\r\nGiven that the malware family does not come obfuscated, it uses sockets for communication and it employs a\r\nweak data encryption strategy it sounds reasonable to treat this family as a guinea pig to get hands dirty on\r\nnetwork protocol RE.\r\nSo, without further ado, fasten your seat belt and let’s get started! 👾\r\nA Bug that sleeps\r\nThe sample we are going to investigate can be found in the blog and has a SHA256 hash value of\r\nb8703744744555ad841f922995cef5dbca11da22565195d05529f5f9095fbfca .\r\nAlso, keep in mind that\r\nall the initial reversing analysis, such as understanding the dynamic Windows API loading mechanism (by\r\nparsing the PEB), strings decryption algorithm, etc., is not documented in this blog and instead we will\r\njump straight into the reversing of the functions handling the network communication part, also;\r\nthe tool of choice is IDA but as you may have heard many times, similar tools will work just fine - 🐉 ;)\r\nand even if there are no screenshots for this, x64dbg was used down the road for debugging.\r\nThe function in charge of managing the network connection and in general handling C2 operations is\r\nsub_1400012C0 . After cleaning-up a bit the IDA database by applying the right data types, and renaming\r\nfunctions with meaningful names, the pseudo-c code becomes more readable, getting from something like in the\r\nscreenshot below\r\nhttps://raw-data.gitlab.io/post/bugsleep_netprotocol/\r\nPage 1 of 17\n\nto a more talkative pseudo code like this\r\nhttps://raw-data.gitlab.io/post/bugsleep_netprotocol/\r\nPage 2 of 17\n\nScrolling to the button of this function, subroutines sub_140003D80 and sub_140028A0 stores respectively C2\r\ncheck-in and C2 message dispatcher logic. We will focus first on the easiest of the two, the C2 check-in part, as it\r\nsimply sends a \"hello\" message to the server so that the client can be enrolled into the bots pool manged on the\r\nserver side.\r\nsub_140003D80 (C2 check-in)\r\nBy analyzing this function, at its core it can be seen how packets are crafted before being sent to the server. A first\r\nmessage, 4 bytes in size, stores the size of the data that will follow, finally the data message itself is sent. The\r\noverall structure can be broken down as sketched below\r\nWhat follows is a light commented function which should provide a high level overview of the initial\r\ncommunication process and messages exchange between the backdoor and the C2 server.\r\nhttps://raw-data.gitlab.io/post/bugsleep_netprotocol/\r\nPage 3 of 17\n\nWhat can be observed from this first part, it’s that 1) a string composed of the infected Computer Name and the\r\nWindows User name (running the backdoor) is sent to the C2 server and that 2) BugSleep expects some reply\r\nwhich content is not really used but the size of the same matters, as we will shortly see.\r\nBy inspecting the function sub_1400034C0 here renamed into mw_EncSendMsgToC2 it can be seen how the\r\nexchanged packets between the client and the C2 are not only based on a custom protocol but they are also “light\r\nencrypted”, with a sort of Caesar cipher. The encryption, which follows the same implementation used for hiding\r\nstrings in the binary, subtracts in this case hex 0x3 to every processed byte.\r\nhttps://raw-data.gitlab.io/post/bugsleep_netprotocol/\r\nPage 4 of 17\n\nIf we were intercepting C2 check-in traffic, a possible message could look like this\r\n00000000 11 fd fd fd ....\r\n00000004 41 30 50 48 51 2d 4d 2a 51 2d 3e 2e 2e 42 4f 2c A0PHQ-M*Q-\u003e..BO,\r\n00000014 52 70 30 4f Rp0O\r\nthe message can be easily interpreted on the C2 side by simply adding hex 0x3 to every byte.\r\nimport sys\r\nfrom typing import List\r\ndef decodeMsgs(encStrings: List[str])-\u003e None:\r\n for encString in encStrings:\r\n szEncString = list(encString)\r\n for i in range(len(szEncString)):\r\n szEncString[i] = chr(ord(szEncString[i]) + 3)\r\n print(''.join(szEncString))\r\nif __name__ == \"__main__\":\r\n encStrings = [\r\n \"A0PHQ-M*Q-\u003e..BO,Rp0O\"\r\n ]\r\n sys.exit(decodeMsgs(encStrings))\r\nThe string A0PHQ-M*Q-\u003e..BO,Rp0O is so decrypted into D3SKT0P-T0A11ER/Us3R. While, for what\r\nconcerns instead the size of the data, which is stored in the first part of the message being sent, and in this\r\nexample is set to 11 fd fd fd , the conversion follows the same logic.\r\nhttps://raw-data.gitlab.io/post/bugsleep_netprotocol/\r\nPage 5 of 17\n\nIt requires adding hex value 0x3 to every byte of the sequence. By converting the first hex byte 0x14 to an\r\ninteger in base10 we get 20 , which is - correctly - the size of the submitted string.\r\n\u003e\u003e\u003e hex(len(\"D3SKT0P-T0A11ER/Us3R\"))\r\n'0x14'\r\nFinally, to successfully complete the C2 check-in handshake, the server must reply with a message which length\r\nmust be greater than 3 bytes, otherwise the backdoor will simply terminate itself by calling ExitProcess(0) .\r\nThe overall C2 check-in handshake is summarised in the following diagram\r\nsub_140028A0 (C2 message dispatcher)\r\nWith the C2 check-in operation out of the way, it’s now time to interact with the C2 server and inspect the logic\r\nwhich glues together transmitted C2 commands to the respective backdoor’s operative functions.\r\nWe will not cover all instructions offer by the analyzed variant, but definitely of interest are the first three, which\r\nare\r\nCommand hex\r\ncode\r\nExpected parameter Functionalities description\r\n0x0 Full path of a file on disk\r\nUploads a file from the infected system into the C2 by\r\nreading it in chunks\r\nhttps://raw-data.gitlab.io/post/bugsleep_netprotocol/\r\nPage 6 of 17\n\nCommand hex\r\ncode\r\nExpected parameter Functionalities description\r\n0x1\r\nFull path to a file to be\r\ndropped on the host\r\nDownloads a file from the C2 and stores it into the\r\nlocation defined by the operator\r\n0x2\r\nCommand to execute on the\r\nhost\r\nGives operator Hands-On-Keyboard by starting a\r\nreverse shell on the host\r\nInspecting sub_140028A0 reveals the main logic which reads incoming messages from the server and branches\r\ninto specialised functions in charge of actively interact with the infected system.\r\nAt this stage, BugSleep expects the following\r\nA first message, 4 bytes in size, must be sent from the server. The message stores the (encrypted) command\r\nthat the backdoor will interpreter and for which it will execute an associated function;\r\nOnce the first 4 bytes are decrypted, the returned value is decremented by one and checked against a jump\r\ntable which will branch into the right function depending on returned value of the subtraction\r\nFor instance, if the result of the subtraction is 0x0 , the backdoor will call a function which uploads a file from\r\nthe infected host into the C2 server, while if a 0x1 is returned instead, a file is downloaded from the C2 server\r\ninto the host, and so on.\r\nLet’s go step by step, shall we? ;)\r\nCmd 0x0\r\nIn this first case\r\nthe overall logic looks like this\r\nwith the first call to mw_ReadAndDecryptC2Msg , the size of the data message that follows is extracted;\r\nthe second call to mw_ReadAndDecryptC2Msg reads the data message based on the decrypted content of\r\nlpBufferC2MsgSize (which stores the data size) extracted from the previous call;\r\nfinally, the memcmp function in conjunction with the logical AND condition, ensures that the first 5 bytes\r\nof the decrypted message are not equal to the string exit (mind that the C strings are null terminated,\r\nfrom here the 5 bytes)\r\nhttps://raw-data.gitlab.io/post/bugsleep_netprotocol/\r\nPage 7 of 17\n\nIf all three conditions are met, the function here renamed into mw_wrap_ReadFromFile is called, giving in input a\r\npointer to the buffer storing the decrypted data message, which will be a string describing a full Windows path to a\r\nfile.\r\nAll in all, the full message sent from the C2 to trigger code handled by the case 0, looks like this\r\nLet’s now investigate what happens within the renamed function mw_wrap_ReadFromFile .\r\nThe function argument, as we now know, it’s a pointer to the buffer storing the decrypted received data\r\nfrom the C2. It stores a full Windows path to a file on the infected system, this can be for instance\r\nsomething like C:\\\\Users\\\\\u003cWindowsUserName\u003e\\\\Desktop\\\\ExfilData.zip ;\r\nCreateFileA is called to get a handle on the file and retrieve its size, by calling GetFileSize ;\r\nCreateFileW is called to get a new handle on the file, and if the operation is successful, it will\r\nsend first an integer of value 1, sleep for 10 milliseconds, and;\r\nsend another integer set this time to 0, but in both cases, messages are encrypted and packed as per\r\nusual in a 4 bytes packet\r\nIn the next step, some code logic calculates the size of the file once again and determines the number of\r\nblocks (expressed in 1 KB) required to transmit the size of the file, followed by the size of the last block. It\r\nwill than pack the information in a 8 bytes and 4 bytes message respectively.\r\nhttps://raw-data.gitlab.io/post/bugsleep_netprotocol/\r\nPage 8 of 17\n\nFinally, content of the file is streamed, via sockets, to the C2\r\nThe full message exchange process for case 0 can so be broken down in 5 steps which are depicted in the diagram\r\nbelow and as it can be seen, at this stage the C2 is passively waiting for data and processing it on its end but\r\nnothing else.\r\nhttps://raw-data.gitlab.io/post/bugsleep_netprotocol/\r\nPage 9 of 17\n\nCmd 0x1\r\nIn this second case instead, a file can be downloaded from the C2 into the infected host\r\nThe initial C2 command extraction logic is the same as described previously, but this time the variable\r\nlpBufferC2MsgData will sore instead a Windows full path to a file which will be filled, so to speak, with some\r\ncontent defined on the C2 side, let’s investigate mw_wrap_WriteToFile .\r\nThe function argument, it’s a pointer to the buffer storing the decrypted string of a Windows full path to a file, let’s\r\npretend something like C:\\\\Users\\\\\u003cWindowsUserName\u003e\\\\Desktop\\\\2ndStagePayload.bin ;\r\nCreateFileW is called, and the same prepares an empty file based on the defined path and returns, if\r\nsuccessful, an open handle to it;\r\nhttps://raw-data.gitlab.io/post/bugsleep_netprotocol/\r\nPage 10 of 17\n\nthe C2 is notified by sending sequentially, two integers of value 1, packed in a 4 bytes packet each;\r\nAfter, two additional messages are sent from the server, one storing the number of 1 KB blocks to write\r\nwhile the second, the last block to be written in the file\r\ncontent of the file can be finally streamed in chunks, and written to disk. Now, there is something\r\ninteresting going on here, when the first set of chunks are written to disk, with the for loop , the 3rd\r\nargument of mw_WriteFile is set to 0x3FC , which is 1020 in base10, but few lines above it can be seen\r\nhow 0x400 (1024) bytes are read instead out from the socket, so it seems that 4 bytes are used to track the\r\ntransmitted chunks, while 1020 bytes stores the actual binary content of the file itself as showcased in the\r\nsketch below\r\nhttps://raw-data.gitlab.io/post/bugsleep_netprotocol/\r\nPage 11 of 17\n\nA light commented function is reported below to showcase the transmission process\r\nWhile implementing the fake C2 server I was not successful at the beginning to correctly transmit a file of\r\nwhatever size from the server to the infected host without losing some bytes during the process, by refining how\r\nchunks were forged on the server side I eventually reach a point where only the last 4 bytes of the original\r\ntransmitted file were missing.\r\nI am not sure if it’s a standard implementation on the C2 side or a bug ( 😉 ) in how the last block is written to\r\ndisk but, by looking a the pseudo-c code line ⤵️\r\nmw_WriteFile(hObject, (file_content + 1), v11 - 4, 0LL, 0LL);\r\nit seems that v11 - 4 is likely skipping 4 bytes. This aligns with the behaviour observed during the transmission\r\ntests, were the last 4 bytes were always missing from the sent file.\r\nhttps://raw-data.gitlab.io/post/bugsleep_netprotocol/\r\nPage 12 of 17\n\nTo address the case, the fake C2 implements some padding strategy, with 4 null bytes ( b'\\x00' * 4 ) added “on-demand”, so ensuring that the final message is always 1024 bytes long even if the last data block is smaller.\r\nIn this way, when the binary reaches BugSleep, the last 4 padded bytes will be skipped but the original content of\r\nthe file will be preserved and correctly stored to disk.\r\nCmd 0x2\r\nTwo down, one to go! this last command starts a reverse shell giving Hands-On-Keyboard access to the operator.\r\nhttps://raw-data.gitlab.io/post/bugsleep_netprotocol/\r\nPage 13 of 17\n\nIt leverages common Windows APIs such as CreatePipe , PeekNamedPipe , SetHandleInformation ,\r\nSetInformationJobObject , CreateProcessW , and ReadFile among other to setup and handle the shell. The\r\nsame is based on the creation of a new Command Prompt instance ( cmd.exe ) which stdErr, stdOut and stdIn are\r\nencapsulated within the socket connection, allowing the operator to directly interact “live” with the compromised\r\nhost.\r\nAs also observed in previous cases, BugSleep will notify the C2 server by sending an integer of value 1, letting\r\nthe back-end know that the control command ( 0x2 ) was correctly received and the reverse shell is being created.\r\nThe StdOut is read in chunks, here again by using the same strategy of 0x400 bytes per block with a final call for\r\nsending the remaining chunk.\r\nhttps://raw-data.gitlab.io/post/bugsleep_netprotocol/\r\nPage 14 of 17\n\nFinally, when the transmission is completed, the client will notify the server once again by sending this time a 4\r\nbytes message filled with zeros, e.g. 0x0000 .\r\nThis final part (sending 4 zero bytes) plays definitely an interesting role on the handling of reverse shell logic on\r\nthe fake C2 side. There is no need to meticulously check every single chunk message transmitted from the client,\r\nas it will be enough to read data out from the socket until a “marker” of 0x0000 is sent to notify the end of the\r\nmessage itself.\r\nAlso interesting to mention is that, BugSleep will inspect every single message received during reverse shell\r\nsession to ensure no command terminate\\n is being transmitted, in which case, it will simply exit the created\r\nsession and wait for a new control message from the C2.\r\nAlso in this case, the communication flow can be sketch like this\r\nhttps://raw-data.gitlab.io/post/bugsleep_netprotocol/\r\nPage 15 of 17\n\nFollows an example of the interactive shell offered by the BugSleepC2Emulator, as it can be seen it’s far from\r\nbeing stable, as status messages of the executed commands on the client side are not handled (read this as simply\r\nhide command and size of the same from user view within the custom shell), nevertheless, mission accomplished\r\nas we have now access to the host and with directory listing capabilities it’s now easy to download (by sending\r\ncommand 0x1 ) or upload (by sending command 0x0 ) a file from/to the endpoint.\r\nhttps://raw-data.gitlab.io/post/bugsleep_netprotocol/\r\nPage 16 of 17\n\nFinal thoughts\r\nIt was a fun ride! when implementing a fake C2 server from scratch there are different ways one can follow to\r\nslowly build all the required functionalities, definitely FakeNet-NG is one of them thanks to the base custom\r\nresponse modules, but also creating your custom one from scratch if time is not a constraint works just fine as in\r\nsome cases, code snippets are all what you need to tests stuff out, but of course it depends on the complexity of the\r\nmalware protocol and how you can trigger some behaviour on-demand on the malware (client) side.\r\nBeing able to interact with the backdoor provides also some visibility on how - possibly - a live C2 traffic would\r\nlook like allowing the creation of network detection rules for the observed network pattern, Lua scripting - paired\r\nwith the right tool - might be come in handy …\r\nℹ️ Companion code, the BugSleep C2 emulator, can be found here\r\nSource: https://raw-data.gitlab.io/post/bugsleep_netprotocol/\r\nhttps://raw-data.gitlab.io/post/bugsleep_netprotocol/\r\nPage 17 of 17",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://raw-data.gitlab.io/post/bugsleep_netprotocol/"
	],
	"report_names": [
		"bugsleep_netprotocol"
	],
	"threat_actors": [
		{
			"id": "02e1c2df-8abd-49b1-91d1-61bc733cf96b",
			"created_at": "2022-10-25T15:50:23.308924Z",
			"updated_at": "2026-04-10T02:00:05.298591Z",
			"deleted_at": null,
			"main_name": "MuddyWater",
			"aliases": [
				"MuddyWater",
				"Earth Vetala",
				"Static Kitten",
				"Seedworm",
				"TEMP.Zagros",
				"Mango Sandstorm",
				"TA450"
			],
			"source_name": "MITRE:MuddyWater",
			"tools": [
				"STARWHALE",
				"POWERSTATS",
				"Out1",
				"PowerSploit",
				"Small Sieve",
				"Mori",
				"Mimikatz",
				"LaZagne",
				"PowGoop",
				"CrackMapExec",
				"ConnectWise",
				"SHARPSTATS",
				"RemoteUtilities",
				"Koadic"
			],
			"source_id": "MITRE",
			"reports": null
		},
		{
			"id": "2ed8d590-defa-4873-b2de-b75c9b30931e",
			"created_at": "2023-01-06T13:46:38.730137Z",
			"updated_at": "2026-04-10T02:00:03.08136Z",
			"deleted_at": null,
			"main_name": "MuddyWater",
			"aliases": [
				"TEMP.Zagros",
				"Seedworm",
				"COBALT ULSTER",
				"G0069",
				"ATK51",
				"Mango Sandstorm",
				"TA450",
				"Static Kitten",
				"Boggy Serpens",
				"Earth Vetala"
			],
			"source_name": "MISPGALAXY:MuddyWater",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		},
		{
			"id": "156b3bc5-14b7-48e1-b19d-23aa17492621",
			"created_at": "2025-08-07T02:03:24.793494Z",
			"updated_at": "2026-04-10T02:00:03.634641Z",
			"deleted_at": null,
			"main_name": "COBALT ULSTER",
			"aliases": [
				"Boggy Serpens ",
				"ENT-11 ",
				"Earth Vetala ",
				"ITG17 ",
				"MERCURY ",
				"Mango Sandstorm ",
				"MuddyWater ",
				"STAC 1171 ",
				"Seedworm ",
				"Static Kitten ",
				"TA450 ",
				"TEMP.Zagros ",
				"UNC3313 ",
				"Yellow Nix "
			],
			"source_name": "Secureworks:COBALT ULSTER",
			"tools": [
				"CrackMapExec",
				"Empire",
				"FORELORD",
				"Koadic",
				"LaZagne",
				"Metasploit",
				"Mimikatz",
				"Plink",
				"PowerStats"
			],
			"source_id": "Secureworks",
			"reports": null
		},
		{
			"id": "3c430d71-ab2b-4588-820a-42dd6cfc39fb",
			"created_at": "2022-10-25T16:07:23.880522Z",
			"updated_at": "2026-04-10T02:00:04.775749Z",
			"deleted_at": null,
			"main_name": "MuddyWater",
			"aliases": [
				"ATK 51",
				"Boggy Serpens",
				"Cobalt Ulster",
				"G0069",
				"ITG17",
				"Mango Sandstorm",
				"MuddyWater",
				"Operation BlackWater",
				"Operation Earth Vetala",
				"Operation Quicksand",
				"Seedworm",
				"Static Kitten",
				"T-APT-14",
				"TA450",
				"TEMP.Zagros",
				"Yellow Nix"
			],
			"source_name": "ETDA:MuddyWater",
			"tools": [
				"Agentemis",
				"BugSleep",
				"CLOUDSTATS",
				"ChromeCookiesView",
				"Cobalt Strike",
				"CobaltStrike",
				"CrackMapExec",
				"DCHSpy",
				"DELPHSTATS",
				"EmPyre",
				"EmpireProject",
				"FruityC2",
				"Koadic",
				"LOLBAS",
				"LOLBins",
				"LaZagne",
				"Living off the Land",
				"MZCookiesView",
				"Meterpreter",
				"Mimikatz",
				"MuddyC2Go",
				"MuddyRot",
				"Mudwater",
				"POWERSTATS",
				"PRB-Backdoor",
				"PhonyC2",
				"PowGoop",
				"PowerShell Empire",
				"PowerSploit",
				"Powermud",
				"QUADAGENT",
				"SHARPSTATS",
				"SSF",
				"Secure Socket Funneling",
				"Shootback",
				"Smbmap",
				"Valyria",
				"chrome-passwords",
				"cobeacon",
				"prb_backdoor"
			],
			"source_id": "ETDA",
			"reports": null
		}
	],
	"ts_created_at": 1775434005,
	"ts_updated_at": 1775826785,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/fdb0c11777bbf169c41f65c86477642d3aea502f.pdf",
		"text": "https://archive.orkl.eu/fdb0c11777bbf169c41f65c86477642d3aea502f.txt",
		"img": "https://archive.orkl.eu/fdb0c11777bbf169c41f65c86477642d3aea502f.jpg"
	}
}