{
	"id": "8ec7d621-55d0-4fa0-afad-f5d7d4d9519e",
	"created_at": "2026-04-06T00:13:33.627476Z",
	"updated_at": "2026-04-10T13:12:34.517364Z",
	"deleted_at": null,
	"sha1_hash": "748f9be92f6186781f2f4882175bb2cf773e518c",
	"title": "Malware Unpacking With Hardware Breakpoints - Cobalt Strike Shellcode Loader",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 7185231,
	"plain_text": "Malware Unpacking With Hardware Breakpoints - Cobalt Strike\r\nShellcode Loader\r\nBy Matthew\r\nPublished: 2023-11-06 · Archived: 2026-04-05 21:33:04 UTC\r\nIn previous posts here and here, we explored methods for extracting cobalt strike shellcode from script-based\r\nmalware.\r\nIn this post, we'll explore a more complex situation where Cobalt Strike shellcode is loaded by a compiled\r\nexecutable .exe file. This will require the use of a debugger (x64dbg) in conjunction with Static Analysis\r\n(Ghidra) in order to perform a complete analysis.\r\nOverview\r\nThe executable is a compiled exe containing hidden and obfuscated Shellcode. The shellcode is decoded using a\r\nsimple XOR routine and a 4-byte key, is then written to a simple buffer created with VirtualAlloc .\r\nWe will explore methods for obtaining the decoded shellcode using a debugger, and we will then explore methods\r\nfor manually locating the Shellcode and associated decryption keys using Ghidra.\r\nWe'll also look at a way to pivot between X64dbg and Ghidra, as well as a method for identifying and analysing\r\nGhidra output using ChatGPT.\r\nObtaining the Sample\r\nYou can follow along by downloading the sample here on Malware Bazaar (pw: infected )\r\nSHA256: 99986d438ec146bbb8b5faa63ce47264750a8fdf508a4d4250a8e1e3d58377fd\r\nAnalysis\r\nWe can begin by saving the file to an analysis machine and unzipping it with the password infected . From here\r\nwe can also create a copy with a shorter file name.\r\nSince the file is a compiled executable, we can attempt to analyse it using a debugger. In this case x64dbg.\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 1 of 23\n\nWe can go ahead and open the file with x64dbg, clicking through until we reach the entry point.\r\nWe can now go ahead and create some breakpoints on APIs that are commonly (but not always) used when\r\nmalware is unpacking.\r\nWe can go ahead and create 2 breakpoints by running bp VirtualAlloc and bp VirtualProtect\r\nAfter creating the breakpoints, we can go ahead and allow the malware to continue (F9)\r\nThe malware will continue to run and trigger a breakpoint on VirtualAlloc .\r\nOur primary purpose here is to obtain the buffer being created by VirtualAlloc, we can do this by using Execute\r\nUntil Return .\r\n\"Execute Until Return\" will allow the VirtualAlloc function to complete, but won't allow any further actions to\r\noccur. This means we can easily obtain the address of the buffer that was created.\r\nViewing Memory Created by VirtualAlloc\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 2 of 23\n\nAfter hitting execute until return . We can observe the address of the newly created buffer inside of RAX .\r\nWe want to go ahead and monitor this buffer for suspicious content and unpacked malware.\r\nWe can begin the monitoring process by right-clicking on the address contained inside of RAX .\r\nFrom here we can select Follow in Dump . This will open the content of the buffer in the bottom-left window.\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 3 of 23\n\nBy clicking \"Follow In Dump\", we can observe the contents of the dump in the bottom-left window.\r\nWe can note here that the buffer is empty and contains only 00 .\r\nMonitoring Memory With Hardware Breakpoints\r\nVirtualAlloc has finished creating an empty buffer and we have successfully found it.\r\nWe can now go ahead and monitor for changes to this buffer by creating a Hardware Breakpoint .\r\nA hardware breakpoint can be created by selecting the first byte in the memory dump and Right Click -\u003e\r\nBreakpoint -\u003e Hardware, Access -\u003e Byte\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 4 of 23\n\nFrom here we can allow the malware to continue to execute.\r\nWe should soon see our hardware breakpoint triggered. With an FC byte contained in the first part of the buffer.\r\nWe can recall from previous blogs that FC is a very common first byte in shellcode.\r\nAt this point we want the malware to continue to fill up the buffer, but we don't want it to do anything after that.\r\nWe can go ahead and use another Execute Until Return . Which will allow the buffer to fill up. At which point\r\nwe can monitor it's contents.\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 5 of 23\n\nBelow we can see the buffer after it has filled. We can see the first byte is 0xFC and there is a wininet string\r\npresent in the initial bytes. From previous blogs (1, 2)we know that this could indicate shellcode.\r\nValidating Shellcode Using a Disassembler\r\nNow that we have a reasonable assumption that the buffer contains shellcode, we can go ahead and try to\r\ndisassemble it using X64dbg.\r\nIf we disassemble the code and there are no glaring errors, then there is a very high chance that we are looking at\r\nshellcode.\r\nWe can achieve this by selecting the first FC byte and Follow in Disassembler .\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 6 of 23\n\nX64dbg will now attempt to disassemble the bytes from our buffer.\r\nBelow, we can observe the buffer disassembled in the top disassembly window. There appear to be no glaring\r\nerrors, and there are valid function calls, loops and overall \"normal\" looking instructions.\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 7 of 23\n\nFinal Validation Using SpeakEasy Emulator\r\nWe now have a very high suspicion that the buffer contains shellcode, so we can emulate it using Speakeasy.\r\nWe could also achieve the same thing with X64dbg, but for shellcode, this is a much more involved\r\nprocess that will be covered in a later blog.\r\nTo emulate the shellcode using speakeasy, we first need to save it.\r\nWe can select our first FC byte, right-click and go to Follow in Memory Map\r\nFrom here we can save the memory buffer to a file.\r\nI will go ahead and save my file as memdump.bin .\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 8 of 23\n\nEmulating the Unpacked Shellcode with Speakeasy\r\nWith the shellcode buffer now saved to a file memdump.bin . We can go ahead and emulate the shellcode using\r\nSpeakeasy.\r\nWe can do this with the command speakeasy -t memdump.bin -r -a x64\r\nspeakeasy - Runs the speakeasy tool\r\n-t - Which file we want to use\r\n-r - (Raw) - Indicates that we are using shellcode\r\n-a x64 - Indicates that our file contains 64-bit instructions. (we know this as we're using x64dbg and not\r\nx32dbg)\r\nUpon running this command, the shellcode is emulated successfully and we are given a lot of information about\r\nit's functionality.\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 9 of 23\n\nThe Speakeasy output shows a C2 address of 116.62[.]138.47 , as well as a partial url of /8yHd .\r\nWe can also see references to a user agent of User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT\r\n5.1; Trident/4.0; InfoPath.2; .NET CLR 2.0.50727)\\r\\n\r\n(This user agent would be a great place to go hunting in proxy logs if you had them available)\r\nLocating the Shellcode Decryption Function In Ghidra\r\nAt the point where the hardware breakpoint was first triggered, the primary executable was likely in the middle of\r\nthe decryption function. We can use this information to locate the same decryption function within Ghidra.\r\nFrom here, we can do some interesting things which are covered in the next 7 sections.\r\nLocating the Shellcode Decryption Function In Ghidra\r\nIdentifying Decryption Routine Logic With ChatGPT\r\nIdentifying the Decryption Key Using Ghidra\r\nLocating the Encrypted Shellcode Using Entropy\r\nPerforming Manual Decoding Using Cyberchef\r\nHunting For Additional Samples Using Decryption Bytes\r\nCreating a Yara Rule Using Decryption Code\r\nLocating the Shellcode Decryption Function In Ghidra\r\nIf we run the malware again, we can stop at the initial hardware breakpoint trigger and scroll up slightly in the\r\ndisassembly window.\r\nThis will reveal the decryption logic used to obtain the shellcode.\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 10 of 23\n\nIn addition to the notes above, we can observe that the instruction pointer RIP is inside of a loop that contains an\r\nXOR instruction.\r\nA looping XOR instruction can be a strong indicator of decryption/decoding logic.\r\nIf we copy the contents of the loop, we can use this to investigate the logic inside of Ghidra.\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 11 of 23\n\nWe can use Right-Click -\u003e Binary -\u003e Edit to copy out the bytecodes associated with the suspected decoding\r\nloop.\r\nWe can load the file within Ghidra and perform a memory search on these suspicious bytes. This can lead us to the\r\ndecryption function.\r\nGhidra -\u003e Search -\u003e Memory - Make sure to use \"hex\" format and \"All Blocks\"\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 12 of 23\n\nBy clicking \"Next\" or Search All within the search menu, we are taken straight to the decryption function\r\ninside of Ghidra.\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 13 of 23\n\nWe can observe the call to VirtualAlloc , VirtualProtect and CreateThread inside of the Decompiler.\r\nWe can also view the decompiled decryption logic inside of the for loop. A primary giveaway here is the ^\r\nxor operator.\r\nIf the above output is confusing, you can increase the readability by disabling type casts. Edit -\u003e Tool Options\r\n-\u003e Decompiler -\u003e Disable Printing of Type Casts .\r\nWe can also see that the variable lpAddress (assigned to the result of VirtualAlloc ) will receive the decoded\r\ncontent (it is assigned the result of the xor ^ operation) and is then modified by VirtualProtect and then\r\nexecuted via CreateThread\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 14 of 23\n\nNow that we've identified the function and logic associated with decryption, we can go ahead and try to identify\r\nthe type of encryption/obfuscation used.\r\nIdentifying Decryption Routine Logic With ChatGPT\r\nUsing ChatGPT, we can attempt to gather additional information about the decompiled code.\r\nWe can copy out the decompiled code, and ask ChatGPT something like In 3 sentences or less, can you\r\nsummarise the purpose of this Ghidra Decompiled code\r\nThis identifies the general gist of the code, but doesn't provide a lot of information about the decryption routine.\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 15 of 23\n\nTo gather more information about the encoding itself, we can take out the contents of the for loop and\r\nsummarise it with ChatGPT.\r\nChatGPT is able to recognize that a simple 4-byte key is used to decrypt some bytes and write them to the buffer\r\nwe identified at lpAddress\r\nIdentifying the Decryption Key Using Ghidra\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 16 of 23\n\nIf we return to the decompiler output, we can observe the 4-byte key param_3 that was referenced by ChatGPT.\r\nWe can confirm that param_3 is part of the for loop used for decoding, and also that the value of param_3 is not\r\nvisible within this function.\r\nBy right-clicking on the function name from the above screenshot, FUN_0040152e we can use \"Show References\r\nTo\" to identify where the function is called.\r\nWe can use this to identify the value that is passed in param_3, which likely contains the 4-byte decryption key.\r\nBy Clicking on the value in the 3rd argument (param_3), we can jump to the location where the 4-byte key is\r\nstored.\r\nThis can be seen in the left window of the below screenshot. The 4 byte decryption key is 32 2f 0d 96\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 17 of 23\n\nLocating the Encrypted Shellcode Using Entropy\r\nThe process of locating the encrypted shellcode is slightly more complex.\r\nCobalt Strike uses a system of named pipes to move around encrypted data. It is quite tedious to locate the\r\nshellcode from the point of the previous screenshot.\r\nInstead, we will use entropy to locate the encrypted shellcode content.\r\nWe can begin this process by\r\nEnabling the entropy view\r\nIdentifying a high-entropy section\r\nLocating the beginning of the high entropy section using \"recent labels\"\r\nWe can begin by enabling the entropy view and clicking on the area with the highest entropy.\r\nTypically high entropy areas are indicated by a red section within the entropy view. However, for some reason,\r\nGhidra also highlights high entropy areas with a bright white colour.\r\n(There is an entropy colour reference within Ghidra but it's blank when using dark mode)\r\nWe can move on by clicking anywhere within the white section.\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 18 of 23\n\nWe will now be somewhere within the encrypted section.\r\nWe want to go to the start of the encrypted region, which we can do by selecting the \"L\" (most recent label) button\r\nin Ghidra.\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 19 of 23\n\nWe should now be at the start of the encrypted content.\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 20 of 23\n\nIf we want to obtain the encrypted content for manual decoding, we can highlight it and select Copy Special -\u003e\r\nByte String\r\nPerforming Manual Decoding Using Cyberchef\r\nFrom here we can paste the encrypted content into CyberChef and decrypt it using the 4-byte key identified from\r\nparam_3 in the previous heading.\r\nIn the CyberChef output, we can observe the same strings previously identified within the decrypted shellcode.\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 21 of 23\n\nHunting For Additional Samples Using Decryption Bytes\r\nDecryption and decoding routines are often unique enough to be used for malware hunting and Yara rules.\r\nIf we go back and take the bytes we obtained from x64dbg and search with Ghidra, we can go hunting for\r\nadditional samples.\r\nFor example, we can search for additional samples using unpac.me. In this case, 58 results were obtained which\r\nall appeared to be Cobalt Strike samples.\r\nThe results from the search all returned 50+ results for Cobalt Strike on Virustotal.\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 22 of 23\n\nCreating a Yara Rule Using Decryption Code\r\nWorking on the (generally safe) assumption that the decryption logic remains the same across similar samples.\r\nWe can use the identified decryption bytes to create a simple Yara rule. This should return the same results as the\r\nprevious byte search with unpacme.\r\nSign up for Embee Research\r\nMalware Analysis and Threat Intelligence Research\r\nNo spam. Unsubscribe anytime.\r\nSource: https://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nhttps://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/\r\nPage 23 of 23\n\nTo\" to identify We can use where the function this to identify is called. the value that is passed in param_3, which likely contains the 4-byte decryption key.\nBy Clicking on the value in the 3rd argument (param_3), we can jump to the location where the 4-byte key is\nstored.       \nThis can be seen in the left window of the below screenshot. The 4 byte decryption key is 32 2f 0d 96\n   Page 17 of 23",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://embee-research.ghost.io/unpacking-malware-with-hardware-breakpoints-cobalt-strike/"
	],
	"report_names": [
		"unpacking-malware-with-hardware-breakpoints-cobalt-strike"
	],
	"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": 1775434413,
	"ts_updated_at": 1775826754,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/748f9be92f6186781f2f4882175bb2cf773e518c.pdf",
		"text": "https://archive.orkl.eu/748f9be92f6186781f2f4882175bb2cf773e518c.txt",
		"img": "https://archive.orkl.eu/748f9be92f6186781f2f4882175bb2cf773e518c.jpg"
	}
}