{
	"id": "3022756a-7e72-430b-98bf-84dab6393a5f",
	"created_at": "2026-04-06T00:10:28.643177Z",
	"updated_at": "2026-04-10T13:12:58.938522Z",
	"deleted_at": null,
	"sha1_hash": "ed49c34f707d6360d58b3e038f5fef83a969e388",
	"title": "Decoding Cobalt Strike: Understanding payloads",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 7143680,
	"plain_text": "Decoding Cobalt Strike: Understanding payloads\r\nBy Threat Research TeamThreat Research Team\r\nArchived: 2026-04-05 14:38:33 UTC\r\nIntro\r\nCobalt Strike threat emulation software is the de facto standard closed-source/paid tool used by infosec teams in\r\nmany governments, organizations and companies. It is also very popular in many cybercrime groups which\r\nusually abuse cracked or leaked versions of Cobalt Strike. \r\nCobalt Strike has multiple unique features, secure communication and it is fully modular and customizable so\r\nproper detection and attribution can be problematic. It is the main reason why we have seen use of Cobalt Strike in\r\nalmost every major cyber security incident or big breach for the past several years.\r\nThere are many great articles about reverse engineering Cobalt Strike software, especially beacon modules as the\r\nmost important part of the whole chain. Other modules and payloads are very often overlooked, but these parts\r\nalso contain valuable information for malware researchers and forensic analysts or investigators.\r\nThe first part of this series is dedicated to proper identification of all raw payload types and how to decode and\r\nparse them. We also share our useful parsers, scripts and yara rules based on these findings back to the\r\ncommunity. \r\nRaw payloads\r\nCobalt Strike’s payloads are based on Meterpreter shellcodes and include many similarities like API hashing (x86\r\nand x64 versions) or url query checksum8 algo used in http/https payloads, which makes identification harder.\r\nThis particular checksum8 algorithm is also used in other frameworks like Empire.\r\nLet’s describe interesting parts of each payload separately.\r\nPayload header x86 variant\r\nDefault 32bit raw payload’s entry points start with typical instruction CLD (0xFC) followed by CALL instruction\r\nand PUSHA (0x60) as the first instruction from API hash algorithm.\r\nhttps://decoded.avast.io/threatintel/decoding-cobalt-strike-understanding-payloads/\r\nPage 1 of 16\n\nx86 payload\r\nPayload header x64 variant\r\nStandard 64bit variants start also with CLD instruction followed by AND RSP,-10h and CALL instruction.\r\nx64 payload\r\nWe can use these patterns for locating payloads’ entry points and count other fixed offsets from this position.\r\nDefault API hashes\r\nRaw payloads have a predefined structure and binary format with particular placeholders for each customizable\r\nvalue such as DNS queries, HTTP headers or C2 IP address. Placeholder offsets are on fixed positions the same as\r\nhard coded API hash values. The hash algorithm is ROR13 and the final hash is calculated from the API function\r\nname and DLL name. The whole algorithm is nicely commented inside assembly code on the Metasploit\r\nrepository.\r\nPython implementation of API hashing algorithm\r\nWe can use the following regex patterns for searching hardcoded API hashes:\r\nDefault API hashes\r\nhttps://decoded.avast.io/threatintel/decoding-cobalt-strike-understanding-payloads/\r\nPage 2 of 16\n\nWe can use a known API hashes list for proper payload type identification and known fixed positions of API\r\nhashes for more accurate detection via Yara rules.\r\nPayload identification via known API hashes\r\nComplete Cobalt Strike API hash list:\r\nAPI hash list 1/2\r\nAPI hash list 2/2\r\nComplete API hash list for Windows 10 system DLLs is available here.\r\nCustomer ID / Watermark\r\nBased on information provided on official web pages, Customer ID is a 4-byte number associated with the Cobalt\r\nStrike licence key and since v3.9 is embedded into the payloads and beacon configs. This number is located at the\r\nend of the payload if it is present. Customer ID could be used for specific threat authors identification or\r\nhttps://decoded.avast.io/threatintel/decoding-cobalt-strike-understanding-payloads/\r\nPage 3 of 16\n\nattribution, but a lot of Customer IDs are from cracked or leaked versions, so please consider this while looking at\r\nthese for possible attribution.\r\nDNS stager x86\r\nTypical payload size is 515 bytes or 519 bytes with included Customer ID value. The DNS query name string\r\nstarts on offset 0x0140 (calculated from payload entry point) and the null byte and max string size is 63 bytes. If\r\nthe DNS query name string is shorter, then is terminated with a null byte and the rest of the string space is filled\r\nwith junk bytes.\r\nDnsQuery_A API function is called with two default parameters:\r\nAnything other than the default values are suspicious and could indicate custom payload.\r\nPython parsing:\r\nDefault DNS payload API hashes:\r\nYara rule for DNS stagers:\r\nSMB stager x86\r\nThe default payload size is 346 bytes plus the length of the pipe name string terminated by a null byte and the\r\nlength of the Customer ID if present. The pipe name string is located right after the payload code on offset 0x015A\r\nin plaintext format.\r\nhttps://decoded.avast.io/threatintel/decoding-cobalt-strike-understanding-payloads/\r\nPage 4 of 16\n\nCreateNamedPipeA API function is called with 3 default parameters:\r\nDefault SMB payload API hashes:\r\nYara rule for SMB stagers:\r\nTCP Bind stager x86\r\nThe payload size is 332 bytes plus the length of the Customer ID if present. Parameters for the bind API function\r\nare stored inside the SOCKADDR_IN structure hardcoded as two dword pushes. The first PUSH with the sin_addr\r\nvalue is located on offset 0x00C4. The second PUSH contains sin_port and sin_family values and is located on\r\noffset 0x00C9 The default sin_family value is AF_INET (0x02).\r\nhttps://decoded.avast.io/threatintel/decoding-cobalt-strike-understanding-payloads/\r\nPage 5 of 16\n\nDefault TCP Bind x86 payload API hashes:\r\nYara rule for TCP Bind x86 stagers:\r\nTCP Bind stager x64\r\nThe payload size is 510 bytes plus the length of the Customer ID if present. The SOCKADDR_IN structure is hard\r\ncoded inside the MOV instruction as a qword and contains the whole structure. The offset for the MOV instruction\r\nis 0x00EC.\r\nDefault TCP Bind x64 payload API hashes:\r\nhttps://decoded.avast.io/threatintel/decoding-cobalt-strike-understanding-payloads/\r\nPage 6 of 16\n\nYara rule for TCP Bind x64 stagers:\r\nTCP Reverse stager x86\r\nThe payload size is 290 bytes plus the length of the Customer ID if present. This payload is very similar to TCP\r\nBind x86 and SOCKADDR_IN structure is hardcoded on the same offset with the same double push instructions so\r\nwe can reuse python parsing code from TCP Bind x86 payload.\r\nDefault TCP Reverse x86 payload API hashes:\r\nYara rule for TCP Reverse x86 stagers:\r\nhttps://decoded.avast.io/threatintel/decoding-cobalt-strike-understanding-payloads/\r\nPage 7 of 16\n\nTCP Reverse stager x64\r\nDefault payload size is 465 bytes plus length of Customer ID if present. Payload has the same position as the\r\nSOCKADDR_IN structure such as TCP Bind x64 payload so we can reuse parsing code again.\r\nDefault TCP Reverse x64 payload API hashes:\r\nYara rule for TCP Reverse x64 stagers:\r\nHTTP stagers x86 and x64\r\nDefault x86 payload size fits 780 bytes and the x64 version is 874 bytes long plus size of request address string\r\nand size of Customer ID if present. The payloads include full request information stored inside multiple\r\nplaceholders.\r\nRequest address\r\nThe request address is a plaintext string terminated by null byte located right after the last payload instruction\r\nwithout any padding. The offset for the x86 version is 0x030C and 0x036A for the x64 payload version. Typical\r\nformat is IPv4.\r\nRequest port\r\nhttps://decoded.avast.io/threatintel/decoding-cobalt-strike-understanding-payloads/\r\nPage 8 of 16\n\nFor the x86 version the request port value is hardcoded inside a PUSH instruction as a  dword. The offset for the\r\nPUSH instruction is 0x00BE. The port value for the x64 version is stored inside MOV r8d, dword instruction on\r\noffset 0x010D.\r\nRequest query\r\nThe placeholder for the request query has a max size of 80 bytes and the value is a plaintext string terminated by a\r\nnull byte. If the request query string is shorter, then the rest of the string space is filled with junk bytes. The\r\nplaceholder offset for the x86 version is 0x0143 and 0x0186 for the x64 version.\r\nCobalt Strike and other tools such as Metasploit use a trivial checksum8 algorithm for the request query to\r\ndistinguish between x86 and x64 payload or beacon. \r\nAccording to leaked Java web server source code,  Cobalt Strike uses only two checksum values, 0x5C (92) for\r\nx86 payloads and 0x5D for x64 versions. There are also implementations of Strict stager variants where the\r\nrequest query string must be 5 characters long (including slash). The request query checksum feature isn’t\r\nmandatory.\r\nPython implementation of checksum8 algorithm:\r\nMetasploit server uses similar values:\r\nYou can find a complete list of Cobalt Strike’s x86 and x64 strict request queries here.\r\nRequest header\r\nThe size of the request header placeholder is 304 bytes and the value is also represented as a plaintext string\r\nterminated by a null byte. The request header placeholder is located immediately after the Request query\r\nplaceholder. The offset for the x86 version is 0x0193 and 0x01D6 for the x64 version.\r\nThe typical request header value for HTTP/HTTPS stagers is User-Agent. The Cobalt Strike web server has\r\nbanned user-agents which start with lynx, curl or wget and return a response code 404 if any of these strings are\r\nfound.\r\nhttps://decoded.avast.io/threatintel/decoding-cobalt-strike-understanding-payloads/\r\nPage 9 of 16\n\nAPI function HttpOpenRequestA is called with following dwFlags ( 0x84600200 ):\r\nPython parsing:\r\nDefault HTTP x86 payload API hashes:\r\nDefault HTTP x64 payload API hashes:\r\nYara rules for HTTP x86 and x64 stagers:\r\nhttps://decoded.avast.io/threatintel/decoding-cobalt-strike-understanding-payloads/\r\nPage 10 of 16\n\nHTTPS stagers x86 and x64\r\nThe payload structure and placeholders are almost the same as the HTTP stagers. The differences are only in\r\npayload sizes, placeholder offsets, usage of InternetSetOptionA API function (API hash 0x869e4675) and\r\ndifferent dwFlags for calling the HttpOpenRequestA API function.\r\nThe default x86 payload size fits 817 bytes and the default for the x64 version is 909 bytes long plus size of\r\nrequest address string and size of the Customer ID if present.\r\nRequest address\r\nThe placeholder offset for the x86 version is 0x0331 and 0x038D for the x64 payload version. The typical format\r\nis IPv4.\r\nRequest port\r\nThe hardcoded request port format is the same as HTTP.  The PUSH offset for the x86 version is 0x00C3. The\r\nMOV instruction for x64 version is on offset 0x0110.\r\nRequest query\r\nThe placeholder for the request query has the same format and length as the HTTP version. The placeholder offset\r\nfor the x86 version is 0x0168 and 0x01A9 for the x64 version.\r\nRequest header\r\nThe size and length of the request header placeholder is the same as the HTTP version. Offset for the x86 version\r\nis 0x01B8 and 0x01F9 for the x64 version.\r\nAPI function HttpOpenRequestA is called with following dwFlags ( 0x84A03200 ):\r\nhttps://decoded.avast.io/threatintel/decoding-cobalt-strike-understanding-payloads/\r\nPage 11 of 16\n\nInternetSetOptionA API function is called with following parameters:\r\nDefault HTTPS x86 payload API hashes:\r\nDefault HTTPS x64 payload API hashes:\r\nhttps://decoded.avast.io/threatintel/decoding-cobalt-strike-understanding-payloads/\r\nPage 12 of 16\n\nYara rule for HTTPS x86 and x64 stagers:\r\nThe next stage or beacon could be easily downloaded via curl or wget tool:\r\nYou can find our parser for Raw Payloads and all according yara rules in our IoC repository.\r\nRaw Payloads encoding\r\nCobalt Strike also includes a payload generator for exporting raw stagers and payload in multiple encoded\r\nformats. Encoded formats support UTF-8 and UTF-16le. \r\nTable of the most common encoding with usage and examples:\r\nDecoding most of the formats are pretty straightforward, but there are few things to consider. \r\nValues inside Decimal and Char Array are splitted via “new lines” represented by “\\s_\\n” (\\x20\\x5F\\x0A).\r\nCommon compression algorithms used inside PowerShell scripts are GzipStream and raw DeflateStream.\r\nPython decompress implementation:\r\nhttps://decoded.avast.io/threatintel/decoding-cobalt-strike-understanding-payloads/\r\nPage 13 of 16\n\nXOR encoding\r\nThe XOR algorithm is used in three different cases. The first case is one byte XOR inside PS1 scripts, default\r\nvalue is 35 (0x23).\r\nThe second usage is XOR with dword key for encoding raw payloads or beacons inside PE stagers binaries.\r\nSpecific header for xored data is 16 bytes long and includes start offset, xored data size, XOR key and four 0x61\r\njunk/padding bytes.\r\nWe can create Yara rule based on XOR key from header and first dword of encoded data to verify supposed values\r\nthere:\r\nThe third case is XOR encoding with a rolling dword key, used only for decoding downloaded beacons. The\r\nencoded data blob is located right after the XOR algorithm code without any padding. The encoded data starts\r\nwith an initial XOR key (dword) and the data size (dword xored with init key).\r\nThere are x86 and x64 implementations of the XOR algorithm. Cobalt Strike resource includes xor.bin and\r\nxor64.bin files with precompiled XOR algorithm code. \r\nDefault lengths of compiled x86 code are 52 and 56 bytes (depending on used registers) plus the length of the junk\r\nbytes. The x86 implementation allows using different register sets, so the xor.bin file includes more than 800\r\ndifferent precompiled code variants.\r\nhttps://decoded.avast.io/threatintel/decoding-cobalt-strike-understanding-payloads/\r\nPage 14 of 16\n\nYara rule for covering all x86 variants with XOR verification:\r\nThe precompiled x64 code is 63 bytes long with no junk bytes. There is also only one precompiled code variant.\r\nYara rule for x64 variant with XOR verification:\r\nhttps://decoded.avast.io/threatintel/decoding-cobalt-strike-understanding-payloads/\r\nPage 15 of 16\n\nYou can find our Raw Payload decoder and extractor for the most common encodings here. It uses a parser from\r\nthe previous chapter and it could save your time and manual work. We also provide an IDAPython script for easy\r\nraw payload analysis.\r\nConclusion\r\nAs we see more and more abuse of Cobalt Strike by threat actors, understanding how to decode its use is\r\nimportant for malware analysis.\r\nIn this blog, we’ve focused on understanding how threat actors use Cobalt Strike payloads and how you can\r\nanalyze them.\r\nThe next part of this series will be dedicated to Cobalt Strike beacons and parsing its configuration structure.\r\nA group of elite researchers who like to stay under the radar.\r\nSource: https://decoded.avast.io/threatintel/decoding-cobalt-strike-understanding-payloads/\r\nhttps://decoded.avast.io/threatintel/decoding-cobalt-strike-understanding-payloads/\r\nPage 16 of 16",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://decoded.avast.io/threatintel/decoding-cobalt-strike-understanding-payloads/"
	],
	"report_names": [
		"decoding-cobalt-strike-understanding-payloads"
	],
	"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": 1775434228,
	"ts_updated_at": 1775826778,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/ed49c34f707d6360d58b3e038f5fef83a969e388.pdf",
		"text": "https://archive.orkl.eu/ed49c34f707d6360d58b3e038f5fef83a969e388.txt",
		"img": "https://archive.orkl.eu/ed49c34f707d6360d58b3e038f5fef83a969e388.jpg"
	}
}