{
	"id": "dfdcc7f9-01a4-4572-8dc3-16ca415eb61f",
	"created_at": "2026-04-06T00:11:47.704479Z",
	"updated_at": "2026-04-10T03:24:24.424805Z",
	"deleted_at": null,
	"sha1_hash": "df0db69345fc40afa61958d5052b1659192a0dc3",
	"title": "Cobalt Strike: Decrypting Obfuscated Traffic – Part 4",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 3771267,
	"plain_text": "Cobalt Strike: Decrypting Obfuscated Traffic – Part 4\r\nBy Didier Stevens\r\nPublished: 2021-11-17 · Archived: 2026-04-05 21:14:10 UTC\r\nEncrypted Cobalt Strike C2 traffic can be obfuscated with malleable C2 data transforms. We show how to\r\ndeobfuscate such traffic.\r\nThis series of blog posts describes different methods to decrypt Cobalt Strike traffic. In part 1 of this series, we\r\nrevealed private encryption keys found in rogue Cobalt Strike packages. In part 2, we decrypted Cobalt Strike\r\ntraffic starting with a private RSA key. And in part 3, we explain how to decrypt Cobalt Strike traffic if you don’t\r\nknow the private RSA key but do have a process memory dump.\r\nIn the first 3 parts of this series, we have always looked at traffic that contains the unaltered, encrypted data: the\r\ndata returned for a query and the data posted, was just the encrypted data.\r\nThis encrypted data can be transformed into traffic that looks more benign, using malleable C2 data transforms. In\r\nthe example we will look at in this blog post, the encrypted data is hidden inside JavaScript code.\r\nBut how do we know if a beacon is using such instructions to obfuscate traffic, or not? This can be seen in the\r\nanalysis results of the latest version of tool 1768.py. Let’s take a look at the configuration of the beacon we started\r\nwith in part 1:\r\nhttps://blog.nviso.eu/2021/11/17/cobalt-strike-decrypting-obfuscated-traffic-part-4/\r\nPage 1 of 13\n\nFigure 1: beacon with default malleable C2 instructions\r\nWe see for field 0x000b (malleable C2 instructions) that there is just one instruction: Print. This is the default, and\r\nit means that the encrypted data is received as-is by the beacon: it does not need any transformation prior to\r\ndecryption.\r\nAnd for field 0x000d (http post header), we see that the Build Output is also just one instruction: Print. This is the\r\ndefault, and it means that the encrypted data is transmitted as-is by the beacon: it does not need any transformation\r\nafter encryption.\r\nLet’s take a look at a sample with custom malleable C2 data transforms:\r\nhttps://blog.nviso.eu/2021/11/17/cobalt-strike-decrypting-obfuscated-traffic-part-4/\r\nPage 2 of 13\n\nFigure 2: beacon with custom malleable C2 instructions\r\nHere we see more than just a Print instruction: “Remove 1522 bytes from end”, “Remove 84 bytes from begin”,\r\n…\r\nThese are instructions to transform (deobfuscate) the incoming traffic, so that it can then be decrypted. To\r\nunderstand in detail how this works, we will do the transformation manually with CyberChef. However, do know\r\nthat tool cs-parse-http-traffic.py can do these transformations automatically.\r\nThis is the network capture for a single GET request by the beacon and reply from the team server (C2):\r\nhttps://blog.nviso.eu/2021/11/17/cobalt-strike-decrypting-obfuscated-traffic-part-4/\r\nPage 3 of 13\n\nFigure 3: reply transformed with malleable C2 instructions to look like JavaScript code\r\nWhat we see here, is a GET request by the beacon to the C2 (notice the Cookie with the encrypted metadata) and\r\nthe reply by the C2. This reply looks like JavaScript code, because of the malleable C2 data transforms that have\r\nbeen used to make it look like JavaScript code.\r\nWe copy this reply over to CyberChef in its input field:\r\nhttps://blog.nviso.eu/2021/11/17/cobalt-strike-decrypting-obfuscated-traffic-part-4/\r\nPage 4 of 13\n\nFigure 4: CyberChef with obfuscated input\r\nThe instructions we need to follow, to deobfuscate this reply, are listed in tool 1768.py’s output:\r\nFigure 5: decoding instructions\r\nSo let’s get started. First we need to remove 1522 bytes from the end of the reply. This can be done with a\r\nCyberChef drop bytes function and a negative length (negative length means dropping from the end):\r\nFigure 6: dropping 1522 bytes from the end\r\nhttps://blog.nviso.eu/2021/11/17/cobalt-strike-decrypting-obfuscated-traffic-part-4/\r\nPage 5 of 13\n\nThen, we need to remove 84 bytes from the beginning of the reply:\r\nFigure 7: dropping 84 bytes from the beginning\r\nAnd then also dropping 3931 bytes from the beginning:\r\nFigure 8: dropping 3931 bytes from the beginning\r\nAnd now we end up with output that looks like BASE64 encoded data. Indeed, the next instruction is to apply a\r\nBASE64 decoding instructions (to be precise: BASE64 encoding for URLs):\r\nhttps://blog.nviso.eu/2021/11/17/cobalt-strike-decrypting-obfuscated-traffic-part-4/\r\nPage 6 of 13\n\nFigure 9: decoding BASE64/URL data\r\nThe next instruction is to XOR the data. To do that we need the XOR key. The malleable C2 instruction to XOR,\r\nuses a 4-byte long random key, that is prepended to the XORed data. So to recover this key, we convert the binary\r\noutput to hexadecimal:\r\nFigure 10: hexadecimal representation of the transformed data\r\nThe first 4 bytes are the XOR key: b7 85 71 17\r\nWe use that with CyberChef’s XOR command:\r\nhttps://blog.nviso.eu/2021/11/17/cobalt-strike-decrypting-obfuscated-traffic-part-4/\r\nPage 7 of 13\n\nFigure 11: XORed data\r\nNotice that the first 4 bytes are NULL bytes now: that is as expected, XORing bytes with themselves gives NULL\r\nbytes.\r\nAnd finally, we drop these 4 NULL bytes:\r\nFigure 12: fully transformed data\r\nWhat we end up with, is the encrypted data that contains the C2 commands to be executed by the beacon. This is\r\nthe result of deobfuscating the data by following the malleable C2 data transform. Now we can proceed with the\r\ndecryption using a process memory dump, just like we did in part 3.\r\nhttps://blog.nviso.eu/2021/11/17/cobalt-strike-decrypting-obfuscated-traffic-part-4/\r\nPage 8 of 13\n\nFigure 13: extracting the cryptographic keys from process memory\r\nTool cs-extract-key.py is used to extract the AES and HMAC key from process memory: it fails, it is not able to\r\nfind the keys in process memory.\r\nOne possible explanation that the keys can not be found, is that process memory is encoded. Cobalt Strike\r\nsupports a feature for beacons, called a sleep mask. When this feature is enabled, the process memory with data of\r\na beacon (including the keys) is XOR-encoded while a beacon sleeps. Thus only when a beacon is active\r\n(communicating or executing commands) will its data be in cleartext.\r\nWe can try to decode this process memory dump. Tool cs-analyze-processdump.py is a tool that tries to decode a\r\nprocess memory dump of a beacon that has an active sleep mask feature. Let’s run it on our process memory\r\ndump:\r\nFigure 14: analyzing the process memory dump (screenshot 1)\r\nhttps://blog.nviso.eu/2021/11/17/cobalt-strike-decrypting-obfuscated-traffic-part-4/\r\nPage 9 of 13\n\nFigure 15: analyzing the process memory dump (screenshot 2)\r\nThe tool has indeed found a 13-byte long XOR key, and written the decoded section to disk as a file with\r\nextension .bin.\r\nThis file can now be used with cs-extract-key.py, it’s exactly the same command as before, but with the decoded\r\nsection in stead of the encoded .dmp file:\r\nFigure 16: extracting keys from the decoded section\r\nAnd now we have recovered the cryptographic keys.\r\nNotice that in figure 16, the tool reports finding string sha256\\x00, while in the first command (figure 13), this\r\nstring is not found. The absence of this string is often a good indicator that the beacon uses a sleep mask, and that\r\ntool cs-analyze-processdump.py should be used prior to extracting the keys.\r\nNow that we have the keys, we can decrypt the network traffic with tool cs-parse-http-traffic.py:\r\nhttps://blog.nviso.eu/2021/11/17/cobalt-strike-decrypting-obfuscated-traffic-part-4/\r\nPage 10 of 13\n\nFigure 17: decrypting the traffic fails\r\nThis fails: the reason is the malleable C2 data transform. Tool cs-parse-http-traffic.py needs to know which\r\ninstructions to apply to deobfuscate the traffic prior to decryption. Just like we did manually with CyberChef, tool\r\ncs-parse-http-traffic.py needs to do this automatically. This can be done with option -t.\r\nNotice that the output of tool 1768.py contains a short-hand notation of the instructions to execute (between\r\nsquare brackets):\r\nFigure 18: short-hand notations of malleable C2 instructions\r\nFor the tasks to be executed (input), it is:\r\n7:Input,4,1:1522,2:84,2:3931,13,15\r\nAnd for the results to be posted (output), it is:\r\n7:Output,15,13,4\r\nhttps://blog.nviso.eu/2021/11/17/cobalt-strike-decrypting-obfuscated-traffic-part-4/\r\nPage 11 of 13\n\nThese instructions can be put together (using a semicolon as separator) and fed via option -t to tool cs-parse-http-traffic.py:\r\nFigure 19: decrypted traffic\r\nAnd now we finally obtain decrypted traffic. There are no actual commands here in this traffic, just “data jitter”:\r\nthat is random data of random length, designed to even more obfuscate traffic.\r\nConclusion\r\nWe saw how malleable C2 data transforms are used to obfuscate network traffic, and how we can deobfuscate this\r\nnetwork traffic by following the instructions.\r\nWe did this manually with CyberChef, but that is of course not practical (we did this to illustrate the concept). To\r\nobtain the decoded, encrypted commands, we can also use cs-parse-http-traffic.py. Just like we did in part 3,\r\nwhere we started with an unknown key, we do this here too. The only difference, is that we also need to provide\r\nthe decoding instructions:\r\nhttps://blog.nviso.eu/2021/11/17/cobalt-strike-decrypting-obfuscated-traffic-part-4/\r\nPage 12 of 13\n\nFigure 20: extracting and decoding the encrypted data\r\nAnd then we can take one of these 3 encrypted data, to recover the keys.\r\nThus the procedure is exactly the same as explained in part 3, except that option -t must be used to include the\r\nmalleable C2 data transforms.\r\nAbout the authors\r\nDidier Stevens is a malware expert working for NVISO. Didier is a SANS Internet Storm Center senior handler\r\nand Microsoft MVP, and has developed numerous popular tools to assist with malware analysis. You can find\r\nDidier on Twitter and LinkedIn.\r\nYou can follow NVISO Labs on Twitter to stay up to date on all our future research and publications.\r\nSource: https://blog.nviso.eu/2021/11/17/cobalt-strike-decrypting-obfuscated-traffic-part-4/\r\nhttps://blog.nviso.eu/2021/11/17/cobalt-strike-decrypting-obfuscated-traffic-part-4/\r\nPage 13 of 13",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://blog.nviso.eu/2021/11/17/cobalt-strike-decrypting-obfuscated-traffic-part-4/"
	],
	"report_names": [
		"cobalt-strike-decrypting-obfuscated-traffic-part-4"
	],
	"threat_actors": [
		{
			"id": "610a7295-3139-4f34-8cec-b3da40add480",
			"created_at": "2023-01-06T13:46:38.608142Z",
			"updated_at": "2026-04-10T02:00:03.03764Z",
			"deleted_at": null,
			"main_name": "Cobalt",
			"aliases": [
				"Cobalt Group",
				"Cobalt Gang",
				"GOLD KINGSWOOD",
				"COBALT SPIDER",
				"G0080",
				"Mule Libra"
			],
			"source_name": "MISPGALAXY:Cobalt",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		}
	],
	"ts_created_at": 1775434307,
	"ts_updated_at": 1775791464,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/df0db69345fc40afa61958d5052b1659192a0dc3.pdf",
		"text": "https://archive.orkl.eu/df0db69345fc40afa61958d5052b1659192a0dc3.txt",
		"img": "https://archive.orkl.eu/df0db69345fc40afa61958d5052b1659192a0dc3.jpg"
	}
}