{
	"id": "6c60dbdf-4aa7-4a1b-8611-50c1cb622b24",
	"created_at": "2026-04-06T00:22:01.272645Z",
	"updated_at": "2026-04-10T03:24:23.856477Z",
	"deleted_at": null,
	"sha1_hash": "53a718985ce194d8ab8eb1361310328e233e56b7",
	"title": "Using Kaitai Struct to Parse Cobalt Strike Beacon Configs",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 416140,
	"plain_text": "Using Kaitai Struct to Parse Cobalt Strike Beacon Configs\r\nBy Justin Warner\r\nPublished: 2021-04-07 · Archived: 2026-04-05 18:10:31 UTC\r\nI have seen a definite uptick in security researchers hunting Cobalt Strike servers, and tweeting/sharing indicators\r\nor config data. There are two popular config parsing methods I have seen: the Nmap NSE script written by\r\n@notwhickey and the Sentinel One parser by @gal_kristal (yes, I am aware many organizations have custom\r\nparsers). In the case of these two public parsers, I sometimes find myself desiring configuration data that the\r\nparsers were missing and when engaging with researchers, I discovered a few did not understand that these tools\r\nwere not extracting the full configs or did not have a complete understanding of how things worked beneath the\r\nhood. That led me to tweet this:\r\nThankfully, in this thread, Lee Holmes (@Lee_Holmes) responded and pointed me to Kaitai Struct\r\n(http://kaitai.io/) as a possible resource for building a parser. Kaitai Struct is a declarative language-neutral method\r\nof developing parsers for binary structures. It is open-source and includes many samples for popular formats. This\r\nblog will not focus on infrastructure hunting, interrogating servers, proper attacker infrastructure setup, or\r\nanything of the likes. This blog is to share my journey using Kaitai Struct to comprehensively parse Beacon\r\nconfigs and dive deep into the binary structure of Malleable C2. Tl;dr, Kaitai Struct is awesome and I built a\r\ncomprehensive proof-of-concept parser (there are *many* improvements that could be made). Realistic outcomes:\r\nI don’t expect anyone to change. The parser you are using is probably just fine, and you should keep using\r\nwhatever works best for you :). Just learn how it works and what collection blindness you might have!\r\nParsing Beacon Configs: Basics\r\nTo Start\r\nWhen staged from a server, the Beacon DLL is encoded/packed with a stub loader. You might also find this DLL\r\nin memory dumps or in fully-staged Beacon payloads in common malware repositories. Both the NSE script and\r\nSentinel One’s parser have the ability to unpack the actual Beacon code and XOR decode the config (0x69 or 0x2e\r\ndepending on version). I will start from the point that you have decoded config data. We will be focusing on an in-the-wild sample I discovered and uploaded to MalwareBazaar for those who want to follow along. The sample is\r\nalready unpacked and XOR decoded. The sample uses a popular Amazon Malleable C2 profile created by\r\n(Twitter). To get to the config data in the sample, open it in your favorite hex editor and Ctrl/Cmd-F for the hex\r\nstring “00 01 00 01 00 02”.\r\nBinary Config Structure\r\nhttps://sixdub.medium.com/using-kaitai-to-parse-cobalt-strike-beacon-configs-f5f0552d5a6e\r\nPage 1 of 7\n\nConfigs are built up of multiple records in the format of ( Index# | Type | Length | Value ). This is common to the\r\nusual “TLV” structure used in binary structures. Within this structure, the type is either 0x1 for short, 0x2 for int,\r\nor 0x3 for byte array / blob. The blob (0x3 type) can vary in actual interpretation depending on index/field #,\r\nranging from string to byte array to sub-structures. We can look at a couple of simple examples of this I-TLV in\r\npractice from a real world config.\r\nPress enter or click to view image in full size\r\nTwo I-TLV structures at the beginning of the sample config.\r\nIn the image above, we see two I-TLV structures, the first is (00 01 | 00 01 | 00 02 | 00 00). This structure can be\r\nbroken down to:\r\nIndex # = 0x01 (Beacon Type) | Type = 0x01 (Short) | Length = 0x02 | Value = 0x00 (HTTP Beacon)\r\nThe second is (00 02 | 00 01 | 00 02 | 00 50) which can be broken down to:\r\nIndex # = 0x02 (Port) | Type = 0x01 (Short) | Length = 0x02 | Value = 0x50 (Port 80)\r\nHTTP beacon using port 80, makes sense! Now for the example of a blob value:\r\nPress enter or click to view image in full size\r\nConfig field 0x8 showing an example blob structure in the sample data\r\nIn the image, we see the structure (00 08 | 00 03 | 01 00| \u003cDATA\u003e). This breaks down to a Field with Index #8\r\nwhich refers to the C2Server, with 256 bytes of “blob” data. The value is a string containing the IP address and\r\nURI of the c2 server. This can be found in the Malleable profile here: set uri “/s/ref=nb_sb_noss_1/167–\r\n3294888–0262949/field-keywords=books”; .\r\nGet Justin Warner’s stories in your inbox\r\nJoin Medium for free to get updates from this writer.\r\nRemember me for faster sign in\r\nModeling this in Kaitai Struct is relatively straight forward. Our highest level sequence is a “Config” struct that is\r\nmade up entries. Each entry is the I-TLV structure where the type of value is variable based upon the fieldtype.\r\nWithin this structure, we handle the “bytes” sections different depending on what config entry it is denoted by the\r\nhttps://sixdub.medium.com/using-kaitai-to-parse-cobalt-strike-beacon-configs-f5f0552d5a6e\r\nPage 2 of 7\n\n“index”. Beacon itself knows how to interpret blobs but this knowledge is “assumed” and therefore as the analyst,\r\nI have to decide which are arrays, which are strings, and which are substructures. I left a default of a byte array so\r\nif it is a new field or an “unknown” one, at least you get the bytes. This is an example of one way we could model\r\nthis in Kaitai Struct (slightly different than my final version for simplicity):\r\nconfig_entry:\r\n seq:\r\n - id: index\r\n type: u2\r\n - id: fieldtype\r\n type: u2\r\n - id: fieldlength\r\n type: u2\r\n - id: fieldvalue\r\n size: fieldlength\r\n type:\r\n switch-on: fieldtype\r\n cases:\r\n 1: u2\r\n 2: u4\r\n 3: bytesbytes:\r\n seq:\r\n - id: byte_val\r\n type:\r\n switch-on: _parent._parent.index\r\n cases:\r\n 11: transform_blocks\r\n 12: req_malleablec2\r\n 13: req_malleablec2\r\n 42: gargle_section\r\n 46: procinj_transform\r\n 47: procinj_transform....... SUB STRUCTURES CONTINUE\r\nBeacon Configs: Malleable C2\r\nIt might seem simple so far, but things get complicated quick: the Malleable C2 block. These are actually sub-structures that get interpreted and used by Beacon to alter various aspects of the command and control protocols\r\nthrough its own transform language. To understand these, I highly highly recommend reading the Cobalt Strike\r\ndocs on Malleable C2. Doing binary analysis on these structures actually made me appreciate the beauty of Cobalt\r\nStrike. For the sake of this blog, we will only decompose one of the types, the Malleable C2 Request block, which\r\nis used in the profile’s protocol sections, specifically in the “client” blocks (used in index #12 and #13 of the\r\nbinary structure).\r\nMalleable C2 Request Blocks\r\nhttps://sixdub.medium.com/using-kaitai-to-parse-cobalt-strike-beacon-configs-f5f0552d5a6e\r\nPage 3 of 7\n\nWe will use the following images for our walkthrough. On the left, we have the binary structure that is\r\nrepresenting the actual malleable config on the right, inside of the client section. The binary config starts off with\r\nthe standard I-TLV (0xd | 0x3 | 0x200 | BLOB). Inside the blob is the malleable C2 request structure.\r\nThe malleable c2 request structure is made up of commands that have a form of ( Command # | Length | Value ).\r\nThe commands at the top level of the client block will either be a _Parameter (#9), _Header (#10), _HostHeader\r\n(#16) or BuildTransform (#7). _Parameter, _Header and _HostHeader are sub-structures of format (Length |\r\nString) whereas the BuildTransform type is a bit more complicated we will review later. Before going further, let’s\r\nlook at some of the commands in the example above.\r\n(0xa or _Header| 0xb | “Accept: */*”)\r\n(0xa or _Header | 0x16 | “Content-Type: text/xml”)\r\n(0xa or _Header | 0x20 | “X-Requested-With” “XMLHttpRequest”)\r\n(0x10 or _HostHeader | 0x14 | “Host: www.amazon.com”)\r\n(0x9 or _Parameter | 0xa | “sz=160x600”)\r\n…. And so on\r\nI modeled this structure in Kaitai Struct with two types, the 4-byte command “statement” and the value which will\r\neither be a (Length | String) structure or a DataTransform structure (next section).\r\nmalleable_block:\r\n seq:\r\n - id: statement\r\n type: u4\r\n enum: transform_actions\r\n - id: statement_value\r\n type:\r\n switch-on: statement\r\n cases:\r\n transform_actions::uheader: length_val_string\r\n transform_actions::uparameter: length_val_string\r\n transform_actions::build: data_transform\r\n transform_actions::uhostheader: length_val_string\r\n if: statement != transform_actions::stop\r\nDataTransform Blocks\r\nThe DataTransform block is another substructure that is described well in the Cobalt Strike docs in the “Data\r\nTransform” section. Within the Malleable C2 profiles, these will be in the “id”, “output”, or “metadata” blocks and\r\ndescribe how to transform the data CS produces. Profile authors can prepend or append bytes, change encodings,\r\nxor mask, etc. For our example, we will focus on the “output” block of the http-post section in the Amazon config.\r\noutput {\r\n base64;\r\nhttps://sixdub.medium.com/using-kaitai-to-parse-cobalt-strike-beacon-configs-f5f0552d5a6e\r\nPage 4 of 7\n\nprint;\r\n}\r\nWithin the binary structure, the DataTransform block is indicated by an command code of 0x7. This substructure\r\nwill contain a 4-byte code to indicate what section this is and a variable number of Transform Actions to transform\r\ndata. The Cobalt Strike documentation lists all of the possible transform actions including the termination actions\r\nthat will always signal the end of transform. The majority of them will simply be a 4 byte action code but some\r\nalso have variables and therefore will also have a (Length | String) association. Our example above from the\r\nAmazon profile is pretty simple and can be broken down like:\r\n(0x07 = DataTransform) (0x01 = Output Section) (0x03 = Base64) (0x04 = Terminate_Print)\r\n…. And now, in Kaitai Struct :\r\n data_transform:\r\n seq:\r\n - id: type_code\r\n type: u4\r\n - id: transform_statement\r\n type: transform_statement\r\n repeat: until\r\n repeat-until: _.action == \u003cTERMINATION ACTION\u003e\r\n transform_statement:\r\n seq:\r\n - id: action\r\n type: u4\r\n enum: transform_actions\r\n - id: action_args\r\n type:\r\n switch-on: action\r\n cases:\r\n transform_actions::append: length_val_bytes\r\n transform_actions::prepend: length_val_bytes\r\n transform_actions::termination_header: length_val_string\r\n transform_actions::termination_parameter: length_val_string\r\n if: \u003cNOT A TERMINATION ACTION\u003e\r\nResults \u0026 Future Work\r\nMy full proof-of-concept KSY file for Beacon config data can be found here:\r\nhttps://gist.github.com/sixdub/a5361168ba7acecf7a7a214bf7e5d3d3.\r\nPress enter or click to view image in full size\r\nhttps://sixdub.medium.com/using-kaitai-to-parse-cobalt-strike-beacon-configs-f5f0552d5a6e\r\nPage 5 of 7\n\nGraphViz output of my parser structs. This is not very pretty but so easy to generate!\r\nI also put together a simple static HTML/Javascript util to parse configs using the Kaitai Struct JS lib (I mean… if\r\nit makes it easy enough to use in Javascript, it must be okay). Here is a screenshot, please don’t judge Kaitai Struct\r\nby my HTML:\r\nPress enter or click to view image in full size\r\nScreenshot of simple HTML/Javascript wrapper POC using my Kaitai Struct Beacon Config Parser.\r\nSome wrapping functionality is required to display and pretty print the structure information.\r\nTHIS IS A PROOF OF CONCEPT… and far from perfect. This weekend was the *first time* I ever used Kaitai\r\nStruct and I really just wanted to demonstrate how to use it for this purpose.\r\nNoted constraints:\r\nhttps://sixdub.medium.com/using-kaitai-to-parse-cobalt-strike-beacon-configs-f5f0552d5a6e\r\nPage 6 of 7\n\nThis parser will break if CS adds sub-structures to existing assumed formats such as Malleable C2 Req. For\r\nexample, if there is a new Malleable Transform Action that has a (length | value) argument, this proof-of-concept will not know about it today. This should handle new “fields” at the higher level just fine, it will\r\ntreat them as blobs.\r\nThe current parser assumes you start the input at the config start (it does not handle unpacking, xor\r\ndecoding, or seeking the config).\r\nPossible future work areas:\r\nI believe my structure definition is overly nested and complicated. I’d bet that it could be optimized many\r\nways by someone with more time in the language. It could definitely be prettier.\r\nI would love to extend the parser to handle Beacon dlls or stages entirely without the need to first parse the\r\nstart of the config bytes. I didn’t implement this for the sake of time.\r\nThere are probably more blob structures that have meaning to the Beacon agent and could use sub-structures. I implemented the obvious ones.\r\nValidate all of the bytes fields that should be strings are properly casted.\r\nWould I do it again?\r\nOverall, I found it simple and intuitive to use. I really appreciated the declarative methodology and the support for\r\nmultiple languages. I could see this being much easier to maintain as a parser format with a wrapper that just loads\r\nthe updates. If I were honest, this is potentially a bit of overkill to just hack together a Beacon parser but to build,\r\nsupport, and maintain a long term parsing project, I could totally see the value (or to teach others the structure).\r\nOne potential downside was that I still needed to write a wrapper to use and display the Kaitai Struct object\r\n(outside of a ruby gem). I did not find a universal toJson type capability that gave you a pretty representation of\r\nthe resulting object (absent the _root _child _this type object used for traversal).\r\nI could easily see Kaitai Struct being a regular part of my life going forward. I could see direct value in creating\r\ndefinitions for popular forensic artifacts, malware structures, file analysis work, etc. Hope this is helpful or fun!\r\nHappy hacking.\r\nSource: https://sixdub.medium.com/using-kaitai-to-parse-cobalt-strike-beacon-configs-f5f0552d5a6e\r\nhttps://sixdub.medium.com/using-kaitai-to-parse-cobalt-strike-beacon-configs-f5f0552d5a6e\r\nPage 7 of 7",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://sixdub.medium.com/using-kaitai-to-parse-cobalt-strike-beacon-configs-f5f0552d5a6e"
	],
	"report_names": [
		"using-kaitai-to-parse-cobalt-strike-beacon-configs-f5f0552d5a6e"
	],
	"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": 1775434921,
	"ts_updated_at": 1775791463,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/53a718985ce194d8ab8eb1361310328e233e56b7.pdf",
		"text": "https://archive.orkl.eu/53a718985ce194d8ab8eb1361310328e233e56b7.txt",
		"img": "https://archive.orkl.eu/53a718985ce194d8ab8eb1361310328e233e56b7.jpg"
	}
}