{
	"id": "f3d70bad-41b2-4e32-8eb8-52f5ec344828",
	"created_at": "2026-04-06T00:10:16.311254Z",
	"updated_at": "2026-04-10T13:12:35.885008Z",
	"deleted_at": null,
	"sha1_hash": "38530a13d65d312f4fe10342975a8c5316cce5f7",
	"title": "Cobalt Strike: Using Known Private Keys To Decrypt Traffic – Part 2",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 578988,
	"plain_text": "Cobalt Strike: Using Known Private Keys To Decrypt Traffic – Part 2\r\nBy Didier Stevens\r\nPublished: 2021-10-27 · Archived: 2026-04-05 20:30:00 UTC\r\nWe decrypt Cobalt Strike traffic using one of 6 private keys we found.\r\nIn this blog post, we will analyze a Cobalt Strike infection by looking at a full packet capture that was taken during the\r\ninfection. This analysis includes decryption of the C2 traffic.\r\nIf you haven’t already, we invite you to read part 1 first: Cobalt Strike: Using Known Private Keys To Decrypt Traffic – Part\r\n1.\r\nFor this analysis, we are using capture file 2021-02-02-Hancitor-with-Ficker-Stealer-and-Cobalt-Strike-and-NetSupport-RAT.pcap.zip, this is one of the many malware traffic capture files that Brad Duncan shares on his web site Malware-Traffic-Analysis.net.\r\nWe start with a minimum of knowledge: the capture file contains encrypted HTTP traffic of a Cobalt Strike beacon\r\ncommunicating with its team server.\r\nIf you want to know more about Cobalt Strike and its components, we highly recommend the following blog post.\r\nFirst step: we open the capture file with Wireshark, and look for downloads of a full beacon by stager shellcode.\r\nAlthough beacons can come in many forms, we can identify 2 major categories:\r\n1. A small piece of shellcode (a couple of hundred bytes), aka the stager shellcode, that downloads the full beacon\r\n2. The full beacon: a PE file that can be reflectively loaded\r\nIn this first step, we search for signs of stager shellcode in the capture file: we do this with the following display filter:\r\nhttp.request.uri matches “/….$”.\r\nFigure 1: packet capture for Cobalt Strike traffic\r\nWe have one hit. The path used in the GET request to download the full beacon, consists of 4 characters that satisfy a\r\ncondition: the byte-value of the sum of the character values (aka checksum 8) is a known constant. We can check this with\r\nthe tool metatool.py like this:\r\nFigure 2: using metatool.py\r\nMore info on this checksum process can be found here.\r\nThe output of the tool shows that this is a valid path to download a 32-bit full beacon (CS x86).\r\nhttps://blog.nviso.eu/2021/10/27/cobalt-strike-using-known-private-keys-to-decrypt-traffic-part-2/\r\nPage 1 of 8\n\nThe download of the full beacon is captured too:\r\nFigure 3: full beacon download\r\nAnd we can extract this download:\r\nFigure 4: export HTTP objects\r\nhttps://blog.nviso.eu/2021/10/27/cobalt-strike-using-known-private-keys-to-decrypt-traffic-part-2/\r\nPage 2 of 8\n\nFigure 5: selecting download EbHm for saving\r\nFigure 6: saving selected download to disk\r\nOnce the full beacon has been saved to disk as EbHm.vir, it can be analyzed with tool 1768.py. 1768.py is a tool that can\r\ndecode/decrypt Cobalt Strike beacons, and extract their configuration. Cobalt Strike beacons have many configuration\r\noptions: all these options are stored in an encoded and embedded table.\r\nHere is the output of the analysis:\r\nhttps://blog.nviso.eu/2021/10/27/cobalt-strike-using-known-private-keys-to-decrypt-traffic-part-2/\r\nPage 3 of 8\n\nFigure 7: extracting beacon configuration\r\nLet’s take a closer look at some of the options.\r\nFirst of all, option 0x0000 tells us that this is an HTTP beacon: it communicates over HTTP.\r\nIt does this by connecting to 192.254.79[.]71 (option 0x0008) on port 8080 (option 0x0002).\r\nGET requests use path /ptj (option 0x0008), and POST requests use path /submit.php (option 0x000a)\r\nAnd important for our analysis: there is a known private key (Has known private key) for the public key used by this beacon\r\n(option 0x0007).\r\nThus, armed with this information, we know that the beacon will send GET requests to the team server, to obtain\r\ninstructions. If the team server has commands to be executed by the beacon, it will reply with encrypted data to the GET\r\nrequest. And when the beacon has to send back output from its commands to the team server, it will use a POST request with\r\nencrypted data.\r\nIf the team server has no commands for the beacon, it will send no encrypted data. This does not necessarily mean that the\r\nreply to a GET request contains no data: it is possible for the operator, through profiles, to masquerade the communication.\r\nFor example, that the encrypted data is inside a GIF file. But that is not the case with this beacon. We know this, because\r\nthere are no so-called malleable C2 instructions in this profile: option 0x000b is equal to 0x00000004 -\u003e this means no\r\noperations should be performed on the data prior to decryption (we will explain this in more detail in a later blog post).\r\nLet’s create a display filter to view this C2 traffic: http and ip.addr == 192.254.79[.]71\r\nFigure 8: full beacon download and HTTP requests with encrypted Cobalt Strike traffic\r\nThis displays all HTTP traffic to and from the team server. Remark that we already took a look at the first 2 packets in this\r\nview (packets 6034 and 6703): that’s the download of the beacon itself, and that communication is not encrypted. Hence, we\r\nwill filter these packets out with the following display filter:\r\nhttp and ip.addr == 192.254.79.71 and frame.number \u003e 6703\r\nhttps://blog.nviso.eu/2021/10/27/cobalt-strike-using-known-private-keys-to-decrypt-traffic-part-2/\r\nPage 4 of 8\n\nThis gives us a list of GET requests with their reply. Remark that there’s a GET request every minute. That too is in the\r\nbeacon configuration: 60.000 ms of sleep (option 0x0003) with 0% variation (aka jitter, option 0x0005).\r\nFigure 9: HTTP requests with encrypted Cobalt Strike traffic\r\nWe will now follow the first HTTP stream:\r\nFigure 10: following HTTP stream\r\nFigure 11: first HTTP stream\r\nThis is a GET request for /ptj that receives a STATUS 200 reply with no data. This means that there are no commands from\r\nthe team server for this beacon for now: the operator has not issued any commands at that point in the capture file.\r\nRemark the Cookie header of the GET request. This looks like a BASE64 string:\r\nKN9zfIq31DBBdLtF4JUjmrhm0lRKkC/I/zAiJ+Xxjz787h9yh35cRjEnXJAwQcWP4chXobXT/E5YrZjgreeGTrORnj//A5iZw2TClEnt++gLMyMHwgjsnv\r\nhttps://blog.nviso.eu/2021/10/27/cobalt-strike-using-known-private-keys-to-decrypt-traffic-part-2/\r\nPage 5 of 8\n\nThat value is encrypted metadata that the beacon sends as a BASE64 string to the team server. This metadata is RSA\r\nencrypted with the public key inside the beacon configuration (option 0x0007), and the team server can decrypt this\r\nmetadata because it has the private key. Remember that some private keys have been “leaked”, we discussed this in our first\r\nblog post in this series.\r\nOur beacon analysis showed that this beacon uses a public key with a known private key. This means we can use tool cs-decrypt-metadata.py to decrypt the metadata (cookie) like this:\r\nFigure 12: decrypting beacon metadata\r\nWe can see here the decrypted metadata. Very important to us, is the raw key: caeab4f452fe41182d504aa24966fbd0. We will\r\nuse this key to decrypt traffic (the AES adn HMAC keys are derived from this raw key).\r\nMore metadata that we can find here is: the computername, the username, …\r\nWe will now follow the HTTP stream with packets 9379 and 9383: this is the first command send by the operator (team\r\nserver) to the beacon:\r\nFigure 13: HTTP stream with encrypted command\r\nHere we can see that the reply contains 48 bytes of data (Content-length). That data is encrypted:\r\nhttps://blog.nviso.eu/2021/10/27/cobalt-strike-using-known-private-keys-to-decrypt-traffic-part-2/\r\nPage 6 of 8\n\nFigure 14: hexadecimal view of HTTP stream with encrypted command\r\nEncrypted data like this, can be decrypted with tool cs-parse-http-traffic.py. Since the data is encrypted, we need to provide\r\nthe raw key (option -r caeab4f452fe41182d504aa24966fbd0) and as the packet capture contains other traffic than pure\r\nCobalt Strike C2 traffic, it is best to provide a display filter (option -Y http and ip.addr == 192.254.79.71 and frame.number\r\n\u003e 6703) so that the tool can ignore all HTTP traffic that is not C2 traffic.\r\nThis produces the following output:\r\nFigure 15: decrypted commands and results\r\nNow we can see that the encrypted data in packet 9383 is a sleep command, with a sleeptime of 100 ms and a jitter factor of\r\n90%. This means that the operator instructed the beacon to beacon interactive.\r\nDecrypted packet 9707 contains an unknown command (id 53), but when we look at packet 9723, we see a directory listing\r\noutput: this is the output result of the unknown command 53 being send back to the team server (notice the POST url\r\n/submit.php). Thus it’s safe to assume that command 53 is a directory listing command.\r\nThere are many commands and results in this capture file that tool cs-parse-http-traffic.py can decrypt, too much to show\r\nhere. But we invite you to reproduce the commands in this blog post, and review the output of the tool.\r\nhttps://blog.nviso.eu/2021/10/27/cobalt-strike-using-known-private-keys-to-decrypt-traffic-part-2/\r\nPage 7 of 8\n\nThe last command in the capture file is a process listing command:\r\nFigure 16: decrypted process listing command and result\r\nConclusion\r\nAlthough the packet capture file we decrypted here was produced more than half a year ago by Brad Duncan by running a\r\nmalicious Cobalt Strike beacon inside a sandbox, we can decrypt it today because the operators used a rogue Cobalt Strike\r\npackage including a private key, that we recovered from VirusTotal.\r\nWithout this private key, we would not be able to decrypt the traffic.\r\nThe private key is not the only way to decrypt the traffic: if the AES key can be extracted from process memory, we can also\r\ndecrypt traffic. We will cover this in an upcoming blog post.\r\nAbout the authors\r\nDidier Stevens is a malware expert working for NVISO. Didier is a SANS Internet Storm Center senior handler and\r\nMicrosoft MVP, and has developed numerous popular tools to assist with malware analysis. You can find Didier\r\non 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/10/27/cobalt-strike-using-known-private-keys-to-decrypt-traffic-part-2/\r\nhttps://blog.nviso.eu/2021/10/27/cobalt-strike-using-known-private-keys-to-decrypt-traffic-part-2/\r\nPage 8 of 8",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://blog.nviso.eu/2021/10/27/cobalt-strike-using-known-private-keys-to-decrypt-traffic-part-2/"
	],
	"report_names": [
		"cobalt-strike-using-known-private-keys-to-decrypt-traffic-part-2"
	],
	"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": 1775434216,
	"ts_updated_at": 1775826755,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/38530a13d65d312f4fe10342975a8c5316cce5f7.pdf",
		"text": "https://archive.orkl.eu/38530a13d65d312f4fe10342975a8c5316cce5f7.txt",
		"img": "https://archive.orkl.eu/38530a13d65d312f4fe10342975a8c5316cce5f7.jpg"
	}
}