{
	"id": "b3469160-9a64-4806-91c7-37ffa00e78cb",
	"created_at": "2026-04-06T00:09:50.551384Z",
	"updated_at": "2026-04-10T13:12:21.551689Z",
	"deleted_at": null,
	"sha1_hash": "7ebe87146d492bcc5403dc80e26c230457c6a507",
	"title": "How to Use Ghidra to Analyse Shellcode and Extract Cobalt Strike Command \u0026 Control Servers",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 6820568,
	"plain_text": "How to Use Ghidra to Analyse Shellcode and Extract Cobalt Strike\r\nCommand \u0026 Control Servers\r\nBy Matthew\r\nPublished: 2023-12-08 · Archived: 2026-04-05 13:49:10 UTC\r\nIn previous posts we decoded some Malicious scripts and obtained Cobalt Strike Shellcode.\r\nAfter obtaining the Shellcode, we used SpeakEasy emulation to determine the functionality of the Shellcode. This\r\nis a great method, but it's not ideal to rely on \"automated\" style tooling to determine functionality. Even if it works\r\nwell.\r\nIn this post, we'll delve deeper into a Cobalt Stike Shellcode file and analyse it without relying on emulators. All\r\nanalysis will be done manually with either x32dbg and Ghidra.\r\nOverview\r\nBefore we jump in, here's a summary of the topics covered in this post\r\nObtaining the sample\r\nLoading Into Ghidra and Manually Disassembling\r\nDefining Functions to Fix Decompiler Issues.\r\nLocating function calls via API hashing\r\nResolving Hashes With Google\r\nManually resolving Hashes with a debugger\r\nAdding Comments Into Ghidra\r\nLocating Resolved Hashes Using the Ghidra Graph View\r\nUsing Graph View to Identify API hash routines\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 1 of 28\n\nNotes on Identifying Windows Structures (PEB,TEB etc)\r\nObtaining The Sample\r\nYou can download the shellcode sample from Malware Bazaar here. The password is infected .\r\nSHA256:26f9955137d96222533b01d3985c0b1943a7586c167eceeaa4be808373f7dd30\r\nYou can also follow along with most Cobalt Strike or Metasploit shellcode files as they have a very similar\r\nstructure.\r\n,\r\nThere is a slightly different process for loading shellcode into Ghidra (compared to a regular PE/exe)\r\nWhen loading the file, you will be prompted to select an architecture. For this example, we can pick any of the\r\noptions specifying x86,32,little .\r\nFor Windows code, we should ideally pick the \"Visual Studio\" compiler. but for shellcode, it generally doesn't\r\nmake a difference. The important part is that the architecture (x86), size (32) and Endian-ness (little) are selected.\r\nOnce the correct option is specified, we can go ahead and select \"ok/yes\" on all default options.\r\nDisassembling The Shellcode\r\nOnce the initial analysis has been completed, the primary Ghidra screen will look something like this.\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 2 of 28\n\nSince there are no file headers to tell Ghidra where the \"code\" starts, Ghidra will not decompile the code by\r\ndefault.\r\nWe can fix this by manually disassembling the code, which is as simple as selecting the first byte and pressing D ,\r\n(or right-clicking and selecting Disassemble)\r\nHere is the disassembly option, which we should select on the First byte.\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 3 of 28\n\nAfter disassembling, the primary window should look like this.\r\nNote that the left-hand side will be populated with code, but the right-hand side (Decompiler) may still be empty.\r\nWe can fix this by defining a function at the beginning of our Shellcode.\r\nDefining a Function and Obtaining Decompiler Output\r\nThe decompiler view may still be empty after disassembling the code.\r\nWe can fix this by right-clicking on the First Byte and selecting Create Function , or we can just use the hotkey\r\nF\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 4 of 28\n\nOnce a function is defined on the first byte, the decompiler view (right-hand side) will now be populated with\r\ncode.\r\nAt this stage, the code should now be fully disassembled, decompiled and ready to analyse.\r\nLocating Function Calls\r\nWe can now go ahead and try to identify function calls.\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 5 of 28\n\nFunction calls within ShellCode are almost always made via API hashing. This means that there will be no\r\nfunction names within the code, as all calls are made via a hash and a hash-resolving function.\r\nWe can view the first API Hashes by clicking on the first function call. Shown below at FUN_0000008f\r\nWithin the first function, there are two function calls made via API hashing. We can see the hash values\r\nhighlighted below.\r\nWe can also note that only those two values are API Hashes, the first \"hash-like\" value is actually hex-encoded\r\ntext.\r\nThe API hashes will be those included as arguments to a function, or passed to a variable unaff_retaddr which\r\nwe can see is defined as code (see the code * reference on line 5.\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 6 of 28\n\nBy zooming out and including the disassembly view, we can see that the \"hash\" values are those inside of a PUSH\r\nand immediately prior to a CALL RBP .\r\nThis pattern will differ between Malware, but it is the standard for Cobalt Strike/Metasploit implementations of\r\nShellcode.\r\nIf the shellcode uses a common implementation of API hashing, then you can google the hashes and find out the\r\nvalues that they resolve to.\r\nIn this case, we can see that 0x726774c resolves to LoadLibraryA .\r\nOnce you have an idea of what the hash value resolves to, we can go ahead and add a comment indicating the\r\nresolved function name.\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 7 of 28\n\nWe can google the value 0xa779563a and determine that it resolves to InternetOpenA\r\nWe can then go ahead and add another comment for InternetOpenA .\r\nIf we recall the initial emulation with SpeakEasy, we can see that these two functions line up with the initial\r\noutput.\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 8 of 28\n\nNote on the Loading of Wininet\r\nIf we recall that there was another hex value that looked like an API hash, we can see now that it is actually the\r\n(hex-encoded) name of the library to load wininet .\r\nResolving API Hashes Using a Debugger (x32dbg)\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 9 of 28\n\nThe previous method of obtaining resolved hash names will work for some malware, but not all.\r\nThis is especially the case if the malware is custom, new, or the actor has just put a bit of extra effort into the code.\r\nTo resolve the API Hashes manually, we need to determine the point where the hashes are finally resolved to an\r\nAPI Name.\r\nWe can generally do this by jumping back to the \"first\" function, and looking for CALL or JMP\r\ninstructions. Where the CALL or JMP is directed at a register value.\r\nIf we go back to the initial function, we can see a JMP EAX contained towards the end of the function. This\r\ncorresponds to another code * value inside the decompiler.\r\nThis JMP EAX location is often easier to find by switching to the Graph View.\r\nThe majority of the initial function is responsible for \"resolving\" the hash, with the ending being where\r\nthe resolved hash is executed.\r\nHence, we can look for JMP/CALL instructions by looking at the end of the Graph View.\r\nIf your graph view does not look like this (in the middle), then you can adjust it here with the\r\ninstructions included in Improving Ghidra UI for Malware Analysis\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 10 of 28\n\nZooming in on the Graph, we can observe the same JMP EAX instruction at the very end of the function.\r\nNext, we will use this location to observe function calls using a Debugger.\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 11 of 28\n\nthe\r\nNow we have a suspected location where the resolved hashes are executed.\r\nWe can provide this location to a debugger and observe the value stored in EAX .\r\nTo do this, we first need to find a way to load the shellcode. My favourite method is to use blobrunner from\r\nOALabs. This tool will take the shellcode as an argument, load the shellcode, and provide a location where the\r\nshellcode can be found.\r\nWe can download blobrunner from here. Making sure to download the \"regular\" version and not the x64\r\n(blobrunner64).\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 12 of 28\n\nLoading the Shellcode With Blobrunner\r\nAfter saving the blobrunner file and transferring to a Virtual Machine, we can run it against the shellcode with\r\nblobrunner.exe \u003cshellcode name\u003e\r\nOnce executed, we can see that the shellcode has been loaded at an address of 0x001e0000\r\nNow we need to attach the process to a debugger.\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 13 of 28\n\nWe can do this with x32dbg by opening up x32dbg and selecting File -\u003e Attach and then selecting our\r\nblobrunner process.\r\nWe can then use the bottom left corner to create a breakpoint at the location provided by blobrunner. bp\r\n0x001e0000\r\nIf we recall that the JMP EAX location is at an offset of 0x86, we can also set a breakpoint here with bp\r\n0x001e0000 + 0x86 .\r\nNow we can jump back to blobrunner and press any button to execute the code.\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 14 of 28\n\nWithin x32dbg, we should now have hit a breakpoint at the beginning of the Shellcode.\r\nWe can go ahead and press F7 twice to step into the first function. From here we can set breakpoints on the first\r\ntwo calls to Call EBP .\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 15 of 28\n\nObserving Hash Values in Memory\r\nNow if we press F9 to continue execution, we will hit a breakpoint on the first Call EBP . From here we can\r\nobserve the hash value of 0x726774c contained on the stack.\r\nWe can again hit F9 or Continue to resume execution, which should now stop on our previous JMP EAX\r\nbreakpoint at an offset of 0x86 .\r\nWe can see this below, where the instruction pointer EIP is at 0x1e0000 + 0x86 .\r\nFrom here we can see the EAX value in the right-hand window. Which is annotated by x32dbg with the value\r\nLoadLibraryA .\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 16 of 28\n\nZooming in on that right-hand side, we can see the \"decoded\" value of LoadLibraryA contained in EAX. Which\r\ncorresponds to our output from SpeakEasy and Google.\r\nViewing Decoded API Hashes in Register Windows\r\nIf we observe the stack window below, we can see also see the function arguments. In this case we can see the\r\nwininet string passed to LoadLibraryA .\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 17 of 28\n\nDecoding Additional API Hashes\r\nIf we hit F9 again, we will stop at the second breakpoint we created, corresponding to 0xa779563A , which we\r\nknow from Google resolves to InternetOpenA .\r\nAt this point we can see the hash value of InternetOpenA on the stack.\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 18 of 28\n\nClicking F9 to continue again, we re-hit our \u003cbase\u003e + 0x86 breakpoint containing JMP EAX .\r\nThis again confirms that 0xa779563a corresponds to InternetOpenA .\r\nThe next Call EBP is located at an offset of 0xCA and contains a hash value of 0xC69F8957 .\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 19 of 28\n\nHitting F9 to continue again, we can observe the decoded value of 0xc69f8957 , which corresponds to\r\nInternetConnectA .\r\nWe can also observe a C2 reference to 195.211.98[.]91 .\r\nIf we go back to Ghidra and press G to search, we can jump to the location 0xCA and observe the hash value.\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 20 of 28\n\nWe can use this information to set comments indicating a reference to InternetConnectA .\r\nIf we continue this process, we will continue to see all API hash values and their decoded function names. As well\r\nas any arguments that are passed.\r\nWe can also automate this process using conditional breakpoints, which is something I've detailed in a previous\r\nblog post.\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 21 of 28\n\nUltimately this will result in the same output as Speakeasy and Google. However, this method will work even for\r\nundocumented hash logic where google does not return any results.\r\nThis method will also work against shellcode unsupported by Speakeasy, which is typically cases where anti-debug or anti-emulation measures are implemented in the Shellcode.\r\nNote on Call EBP\r\nIf we reload the shellcode file and step back into FUN_0000008f , we can observe the value of EBP during the\r\nCall EBP operations.\r\nThis location is 0x0000006 , which represents the next instruction after FUN_0000008f is called.\r\nThis is due to the POP EBP instruction contained at the very start of FUN_0000008f. A POP EBP at the start of a\r\nfunction will take the return address (next instruction after the call to FUN_0000008f ) and places this value into\r\nEBP .\r\nThis ensures that the \"initial\" function containing hash resolving logic, can always be resumed and referenced\r\nwhen needed, without needing to hardcode a location.\r\nHere we can see the value of EBP whenever a Call EBP is executed. This value represents the base address of\r\nthe shellcode + 0x6.\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 22 of 28\n\nReturning to Ghidra, we can see this value corresponds to the next instruction after FUN_0000008f is called.\r\nNotes on Identifying API Hashing\r\nIf we go back to the initial function and load the Graph View, we can see that there is a small block containing a\r\nloop. Which indicates that the logic within the block is repeated multiple times.\r\nThis can be used as an indicator of where the hashing takes place and to identify the type of hashing algorithm\r\ninvolved.\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 23 of 28\n\nIf we zoom into that block, we can see the instructions ROR edi,0xd . (0xd is 13 in hex), this corresponds to the\r\nROR 13 hashing logic used by Cobalt Strike and Metasploit.\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 24 of 28\n\nIn some cases, you can google the hashing algorithm (or even just the instruction) to determine the hashing used.\r\nOn occasions, you will encounter decoded API hash lists.\r\nIn this case, googling ror13 hashing returned a great blog from Mandiant that includes Pseudocode and\r\nexplanations of ROR13.\r\n(The below screenshot is from the Mandiant Blog)\r\nYou may also encounter one of my previous blogs. Where I demonstrate how API hashing can be modified to\r\nbypass AV detections.\r\nimage10\r\nAdvanced Notes on Windows Data Structures\r\nIf we go back to the initial function within Ghidra, we can see this line of code.\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 25 of 28\n\nThis is where the Thread Environment Block is accessed to obtain a list of all loaded modules (DLLs). From here,\r\nthe list is enumerated and hashed in order to locate functions.\r\nThe team at Nviso has an excellent blog on this topic, which includes the diagram below showing how the data\r\nstructures are resolved.\r\nNote how this corresponds to the + 0x30 + 0xc + 0x14 seen in the above screenshot.\r\nBy googling for offsets like the 0x30, 0xc, 0x14 seen above, we can determine that the unaff_FS_offset\r\nvalue is a TEB structure.\r\nBy retyping the structure as a pointer to a TEB32 structure TEB32 * , we can significantly improve the\r\nreadability. (You may need to download the TEB32 Header file, which you can find here)\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 26 of 28\n\nBy selecting unaff_FS_offset and right-click -\u003e retype variable , we can declare a TEB pointer with\r\nTEB32 *\r\nWe can then retype the ProcessEnvironmentBlock value as a PEB *\r\nThis will clean up many of the associated structures with their proper named values.\r\nWe won't go much into this today but it's a good thing to know about if you're able to recognize structures being\r\nused. (Typically you can just google offsets and find the corresponding header/structure file)\r\nSign up for Embee Research\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 27 of 28\n\nMalware Analysis and Threat Intelligence Research\r\nNo spam. Unsubscribe anytime.\r\nSource: https://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nhttps://embee-research.ghost.io/ghidra-basics-shellcode-analysis/\r\nPage 28 of 28",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://embee-research.ghost.io/ghidra-basics-shellcode-analysis/"
	],
	"report_names": [
		"ghidra-basics-shellcode-analysis"
	],
	"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": 1775434190,
	"ts_updated_at": 1775826741,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/7ebe87146d492bcc5403dc80e26c230457c6a507.pdf",
		"text": "https://archive.orkl.eu/7ebe87146d492bcc5403dc80e26c230457c6a507.txt",
		"img": "https://archive.orkl.eu/7ebe87146d492bcc5403dc80e26c230457c6a507.jpg"
	}
}