{
	"id": "fa4eb32f-64dc-478d-b615-d8774f99f9fe",
	"created_at": "2026-04-06T00:19:35.013306Z",
	"updated_at": "2026-04-10T03:24:23.819876Z",
	"deleted_at": null,
	"sha1_hash": "307f91db1aaec77b04e5c21f941b2920a6937c46",
	"title": "Hackers No Hashing | Huntress",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 7475507,
	"plain_text": "Hackers No Hashing | Huntress\r\nArchived: 2026-04-05 13:29:22 UTC\r\nWhile researching Application Programming Interface (API) hashing techniques commonly used in popular\r\nmalware (particularly Metasploit and Cobalt Strike), the Huntress ThreatOps Team found that hackers are sticking\r\nto the default settings that come with hacker tooling. Our research has suggested that many detection/antivirus\r\n(AV) vendors have realized this and have built their detection logic around the presence of artifacts left by these\r\ndefaults.\r\nWith a bit of tinkering and curiosity, we found that if trivial changes are made to those defaults, a large number of\r\nvendors will fail to detect otherwise simple and commodity malware. As a result, simple and commodity malware\r\nsuddenly starts approaching FUD status. 😅\r\nIn this post, we’ll dive into how we discovered those minor changes and how you can implement them yourself to\r\ntest your detection tooling. We have included a script that automates a large portion of this process, as well as a\r\nYARA rule which will detect most modifications made using this technique. \r\nWhether you’re on Team Red, Team Blue, or anywhere in between, we hope this blog provides some useful\r\ninsight into an interesting bypass and detection technique. \r\nIf screenshots like this excite you, read on.\r\nTechnical TL;DR\r\nOur research suggests that a large number of vendors have based their Cobalt Strike and Metasploit shellcode\r\ndetection capability on the presence of ROR13 API hashes. By making trivial changes to the ROR13 logic and\r\nupdating the hashes accordingly, a large number of vendor detections can be seemingly bypassed without\r\nbreaking code functionality. \r\nhttps://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection\r\nPage 1 of 20\n\nIn order to detect this behavior, YARA rules that previously detected ROR13 hashes can be modified to detect\r\nblocks of code associated with typical ROR-based hashing. This move to detection of ROR blocks can provide a\r\nmore robust means of detection than detecting on hashes alone.\r\nGraphical TL;DR\r\nBut First, A Quick Refresher on API Hashing\r\nAPI hashing is a technique often used by malware to disguise the usage of suspicious APIs (essentially functions)\r\nfrom the prying eyes of a detection analyst or security engineer.\r\nTraditionally, if a piece of software needed to call a function of the Windows API (for example, if it wanted to use\r\nCreateFileW to create a file), the software would need to reference the API name “directly” in the code. This\r\ntypically looks like the screenshot below.\r\nhttps://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection\r\nPage 2 of 20\n\nBy “directly” using an API, the name of the API is left present in the code. This enables an analyst to easily\r\nidentify what the piece of suspicious code might be doing. In this case, that suspicious action is creating or\r\nopening a file.\r\nWhen a “direct” API call is used, it also leaves the API present in the import table of the file. This import table can\r\nbe easily viewed within PeStudio or any other analysis tool and looks like the screenshot below. Note we can also\r\nsee the other APIs that the malware is using. \r\nIf you’re an attacker trying to hide the creation of a malicious file, then neither of these situations is ideal. It would\r\nbe far better if you could hide your API usage away from an analyst who may see `CreateFileW` and then go\r\nsearching for suspicious files. \r\nIf an attacker doesn’t want their API to show up in an import table, then the alternative is to load the APIs\r\ndynamically (when the malware actually runs). The easiest way to do this is to use a Windows function called\r\nGetProcAddress. This method avoids leaving suspicious APIs in the import table as we saw above in PeStudio.\r\nA quick caveat using dynamic loading is that although the original suspicious API `CreateFileW` would be absent\r\nfrom the import table, the usage of “GetProcAddress” will now be in the import table instead. A keen-eyed\r\nhttps://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection\r\nPage 3 of 20\n\nanalyst who sees the presence of GetProcAddress can run the malware in a debugger and find out what is being\r\nloaded. \r\nWith a well-placed breakpoint, an analyst can view the arguments being passed to the GetProcAddress function\r\nand find out what is being loaded. Upon running the suspicious code, a debugger would then present something\r\nlike this, revealing the usage of CreateFileW and indicating to an analyst that they should go looking for\r\nsuspicious files that may have been created. \r\nA common means of avoiding both of these situations is to use a technique known as API hashing. \r\nThis is a technique where attackers will implement their own version of GetProcAddress that will load a function\r\nby a hash rather than a name. This avoids leaving suspicious APIs in import tables and avoids using suspicious\r\nAPIs that can be easily viewed in a debugger. \r\nhttps://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection\r\nPage 4 of 20\n\nIf an analyst wants to find out what’s going on, they would need to get familiar with x86 assembly. \r\nThe TL;DR Takeaways\r\nThere are multiple ways to load suspicious APIs; however, most will leave easy-to-find indicators for\r\nmalware analysts\r\nAPI hashing uses unique hashes rather than function names. This hinders the analysis of function names\r\nthat target strings or arguments at breakpoints\r\nHashing Indicators\r\nNow that we know why someone might want to use API hashing, we can take a look at how to deal with it when\r\nanalyzing suspicious code. It is relatively easy to identify, as you will often see random hex values pushed to the\r\nstack, followed by an immediate call to a register value. \r\nTypically, this call will resemble call rbp, but the register could technically be any value. Below is a screenshot\r\ntaken from some Cobalt Strike shellcode where API hashing was used. \r\nhttps://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection\r\nPage 5 of 20\n\nIn the screenshot, we can see two hex values pushed to a register prior to a `call rbp`. These are the hashes that\r\nwill be resolved and used to load suspicious functions used by malware. \r\nThe hashes above correspond to 0x726774c (LoadLibraryA) and 0xa779563a (InternetOpenA). \r\nIf you were to find the value of rbp in this situation, you would find that it points to the “manual” implementation\r\nof GetProcAddress, which then resolves the hash and calls the associated API.\r\nAt a high level, the hash resolution logic is similar to the below pseudo code.\r\nAdditionally, you would find that the Calculatehash Logic, which is largely based on the ror13 hashing algorithm,\r\nis similar to this. The value of 0xd (13) is important here as later we will change this value to generate new hashes\r\nthat can bypass detection.\r\nhttps://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection\r\nPage 6 of 20\n\nThis is a simplification, and the actual logic is slightly more complex. If you’re interested in understanding the\r\nlogic in more detail, there are some great write-ups on the topic from Nviso and Avast.\r\nAfter analyzing numerous malware samples using API hashing in shellcode, we noticed that similar malware\r\nfamilies will often use extremely similar hashing logic to calculate and resolve API hashes. \r\nIn particular, we found that most Cobalt Strike, Msfvenom and Metasploit use exactly the same hashing logic for\r\nresolving API hashes. Since they utilize the same logic, they produce the same hashes for any given function.\r\nFor example, both Cobalt Strike and Metasploit will use the hash 0x726774c when resolving “LoadLibraryA”.\r\nThe TL;DR Takeaways\r\nAPI hashing is relatively simple to identify through static analysis, although it is difficult to find what the\r\nhashes resolve to\r\nSimilar hashing logic is often used across similar malware families \r\nThe exact same hashing logic is often across samples from MsfVenom, Metasploit and Cobalt Strike\r\nPoking a Bit Further\r\nWe eventually found that it was easy to identify shellcode that was generated by Cobalt Strike or Metasploit\r\nsimply by googling the hash values present in the code. \r\nIf we were to google the value of 0x726774c (LoadLibraryA), we would immediately get hits for the Metasploit\r\nframework (which shares code with Cobalt Strike). We see the same if we google the hash for 0xa779563a\r\n(InternetOpenA).\r\nhttps://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection\r\nPage 7 of 20\n\nGenerating our own shellcode samples from these frameworks, we observed that the hashes present in our\r\npayloads were consistently identifiable as those used by Metasploit and Cobalt Strike. \r\nThe TL;DR Takeaways \r\nMetasploit and Cobalt Strike (at least by default) use the same API hashing routine and will produce the\r\nsame hash values when using the same function\r\nThese hashes introduce unique hex values that can be used to easily identify the malware families by using\r\nGoogle \r\nYARA Rules\r\nFrom the perspective of a security analyst or detection engineer, this was great information. Without performing a\r\ndeep dive into shellcode and assembly, we could easily identify that a payload likely belonged to either Metasploit\r\nor Cobalt Strike.\r\nThis got us thinking—if these hash values are unique to tools like Cobalt Strike and Metasploit… what if those\r\nhashes are unique enough to be used for YARA rules?\r\nhttps://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection\r\nPage 8 of 20\n\nWe found a fantastic article from Avast that captured the same idea. Their article details the use of these same API\r\nhashes to detect Cobalt Strike and Metasploit shellcode. Below we can see a YARA rule from Avast which relies\r\nlargely on the hashes we previously identified (as well as the other hashes required for an HTTP stager). \r\nTesting these YARA rules against our raw Cobalt Strike and Metasploit shellcode (without any encoders enabled),\r\nwe confirmed the Avast YARA ruleset reliably detected and identified all of our generated payloads. Great news\r\nfor Team Blue—and great work from the Threat Intel Team at Avast. \r\nThe TL;DR Takeaways\r\nAPI hashes present in shellcode are reliable indicators that can be used for detection\r\nVendors are actively using these indicators to detect malicious shellcode \r\nBut What if the Hashes in the Shellcode Are Changed?\r\nAt face value, the usage of API hashes for detection is a great idea. But that got us thinking, what happens if those\r\nhash values were to change?\r\nAs an initial proof-of-concept, we took our payloads and rather crudely changed the hashes to 0x11111111. We\r\nknew this would break the shellcode as the hashes would no longer resolve—but it would allow us to check how\r\nwell the shellcode is detected without the presence of known API hashes.\r\nOur new shellcode would contain hashes like this in place of the actual hashes seen before.\r\nhttps://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection\r\nPage 9 of 20\n\nWe then did a before and after check on a Cobalt Strike HTTP payload using Virustotal, and found that 15\r\nvendors failed to detect the shellcode after these changes were made.\r\nAs a proof-of-concept, this was pretty interesting. But as an attacker, this is largely useless. In its current modified\r\nstate, the shellcode would no longer resolve hashes and would not be able to find the APIs it requires in order to\r\nexecute—turning our shellcode into a nice digital paperweight. \r\nhttps://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection\r\nPage 10 of 20\n\nThe TL;DR Takeaways\r\nAt least some vendors are using API hashes to detect Cobalt Strike and similar malware\r\nIf these defaults are changed, at least some vendors will fail to detect previously detected payloads\r\nCrudely modifying API hashes will break your code\r\nBut What if Modified Hashes Could Resolve Properly?\r\nAfter confirming our suspicion that vendors were using API hashes to detect shellcode, we decided to explore\r\nwhat would happen if the hashes were modified less crudely, in a way that would still enable the modified hashes\r\nto resolve and execute. \r\nFirst, we needed to understand exactly how the hashes were generated. Our ThreatOps team was able to discover\r\nthis through a combination of the Metasploit source code and by analyzing the assembly instructions present in\r\nsamples of shellcode. \r\nBy nature of how hashing works, we theorized that it should only take minor changes to the hashing logic to\r\nproduce vastly different hashes. In the end, rather than getting fancy with any entirely new hashing routines, we\r\ndecided to just change the rotation value in the existing logic from 0xd/13 to 0xf/15.\r\nIn theory, this would result in entirely new hashes, while maintaining largely the same logic and hashing\r\nstructure. \r\nWe then created a script to generate new hashes according to our new rotation value of 0xf. This logic can be\r\nfound in the final script included in this post. \r\nAfter generating new hash values, we then updated our shellcode to correspond to our new hashes, and our new\r\nror value of 0xf. Note that our shellcode structure is still largely intact, the only thing that changed is the hash and\r\nrotation (ror) values. \r\nhttps://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection\r\nPage 11 of 20\n\nWe then confirmed that our code was still able to function as expected. This process was vastly sped up using the\r\nSpeakeasy tool from FireEye. \r\nBelow we can see a screenshot of the APIs still successfully resolving in our newly modified shellcode. \r\nUsing a combination of netcat and the BlobRunner tool from OAlabs, we did an extra check to confirm that our\r\nshellcode still worked and would “call out” as expected.\r\nhttps://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection\r\nPage 12 of 20\n\nAfter confirming that our code definitely still worked, we uploaded it to VirusTotal. And found that we still had\r\ntwo vendors remaining, the same two vendors from our previous dummy value testing. \r\nThis was pretty interesting, since this was now functioning Cobalt Strike shellcode—with 15 fewer detections\r\nthan before it was modified. \r\nFor a sanity check, we re-ran the same process using a TCP bind shell from Metasploit (no encoders enabled).\r\nAfter confirming that the code still worked, we submitted it to VirusTotal and found that 26 vendors had failed to\r\ndetect the modified payload. \r\nhttps://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection\r\nPage 13 of 20\n\nDuring our analysis, it was interesting to note that the two remaining vendors differed between the modified\r\npayloads.\r\nAt this point, we also checked that the original YARA rules were no longer detecting our payloads. And confirmed\r\nthat they were no longer being detected.\r\nThe TL;DR Takeaways\r\nA large number of vendors are using default ror13 hashes to detect Cobalt Strike and Metasploit/Msfvenom\r\npayloads.\r\nModifying these hashes has a considerable impact on detection rates.\r\nhttps://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection\r\nPage 14 of 20\n\nWhen done properly, modifying these hashes will not break shellcode functionality.\r\nThis technique works well on both Msfvenom and Cobalt Strike. Hence likely works on other malware\r\nfamilies too.\r\nSo What About Those Remaining Vendors?\r\nRather than leave it at 2/55, we decided to tackle the two remaining vendors detecting our shellcode. \r\nFirst, we noted that the remaining vendors were detecting generic shellcode and not Cobalt Strike or Metasploit\r\nspecifically. This led us to believe that they were detecting generic shellcode indicators, rather than anything\r\nspecific to our family of malware. \r\nWe theorized the following might be targeted by the remaining vendors, since they are behaviors typically\r\nassociated with shellcode. \r\nCLD/0xfc being the first instructions executed - (CLD is used to reset direction flags used in byte/string\r\ncopy operations)\r\nSuspicious calls to registers (eg call rbp)\r\nPresence of library names in stack strings\r\nTo test, we slightly modified these indicators in our remaining payload. We achieved this by\r\nMoving the initial CLD instruction to another location in our shellcode, so that it still executed but was no\r\nlonger the first instruction. (Assuming CLD executes before any string operations, this should have no\r\nimpact on shellcode functionality)\r\nInserting a NOP/0x90 in place of the original CLD\r\nInserting an uppercase character in the arguments to the initial call to LoadLibraryA. (Since LoadLibraryA\r\nis not case sensitive, this shouldn’t break any functionality)\r\nBelow, we have a before and after of the modified shellcode. Note the minor changes from “wininet” (all lower\r\ncase) to “wIninet” (one upper case I). As well as the CLD instruction now located after our pop rbp.\r\nhttps://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection\r\nPage 15 of 20\n\nWe then confirmed that our shellcode still functioned, and then resubmitted it to VirusTotal.\r\nFinally, we had hit 0/55 detections without breaking our code.\r\nWe then checked the same with Antiscan and found that we had also hit zero detections for our Cobalt Strike\r\nshellcode—whereas a non-modified copy had 13 detections. \r\nhttps://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection\r\nPage 16 of 20\n\nThe TL;DR Takeaways\r\nVendors are definitely using API hashes to identify Cobalt Strike shellcode\r\nhttps://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection\r\nPage 17 of 20\n\nRemoving API hashes will remove most—but not all—VirusTotal detections\r\nLacking hashes, some vendors will detect on other generic shellcode indicators\r\nWe can modify these remaining indicators to achieve zero detections\r\nAutomating the Process\r\nSince the hashing replacement process could be achieved with a byte-level search and replace, the Huntress\r\nThreatOps team developed a script to automate the process. \r\nThis script…\r\nTakes a raw shellcode file as input (no encoders present)\r\nAutomates the hash replacement process, using a randomized ror value between one and 255\r\nSince a different ror value is used each time, a unique file and hash is generated upon each run, allowing\r\nmultiple files to be created for a single piece of shellcode\r\nWe decided not to automate the process of upper-casing the library name and moving the CLD/0xfc, so you will\r\nneed to do those manually if you wish to have zero detections. Both activities can be done manually and with\r\nminimal effort using a hex editor. \r\nIn order to use the script, generate a raw payload with Msfvenom or Cobalt Strike (make sure your output is raw\r\n—do NOT use any encoders), save it to a raw binary file and then pass it as an argument to the Python script. The\r\nscript will handle the hash replacement process with a random ror value and unique hashes. \r\nAn example of how to generate a simple reverse shell payload using msfvenom. Note the use of “--format raw” to\r\navoid using encoders. \r\nBelow is an example of how to use the script to modify the shellcode file. \r\nhttps://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection\r\nPage 18 of 20\n\nNotes and Limitations of This Script\r\nThis script only replaces hashes and the hashing logic. If there are other suspicious indicators in your\r\nshellcode, you may need to find your own method to hide them \r\nThis script is NOT an encoder, so you will still need to deal with bad characters and null bytes within your\r\nshellcode \r\nUsing a public and well-known encoder (like Shikata ga nai) will introduce its own indicators which will\r\nwork against you \r\nDetection of Modified Shellcode\r\nAfter confirming that our script for generating new shellcode works for bypassing generic detections, we then\r\ndeveloped a YARA rule for detecting shellcode generated by our script.\r\nBelow we’ve included a copy of a YARA that detected all Msfvenom and Cobalt Strike payloads that we tested\r\nwith, regardless of whether they had been modified by our script. In our testing, we did not hit any false positives\r\nwithin our test set of binaries, but you may wish to modify the rule to fit your needs if false positives arise. \r\nHow It Works\r\nSince existing detection rules detect hashes generated by the hashing routine (which can be easily changed), this\r\nrule detects the hashing routine itself. This allows for slightly more robust detection of Cobalt Strike and\r\nMetasploit shellcode. \r\nAs with any detection, this rule is not bulletproof. A determined attacker can introduce more complex changes to\r\nthe hashing routine which will break this YARA rule. We have allowed for minor variations in our rule, but more\r\ncomplex changes will still defeat it. \r\nFinal Comments\r\nClearly, detections aren’t always perfect, and a well-determined attacker will always be able to sneak through. If\r\nyou’re a defender, make sure you’re always testing and updating your detection rules (you never know what might\r\nsneak past). \r\nIf you’re an attacker (a Red Teamer, of course), don’t rely on defaults to get you by—simple changes can have a\r\nsignificant impact on your chances of being detected.\r\nAnd finally, a few key takeaways for Blue and Red Teamers, respectively:\r\nTeam Blue\r\nContinuously test and update your detection logic\r\nActively threat hunt! No alerts ≠ no malware\r\nSearch through a variety of log sources—an AV may not have caught this, but the network traffic might\r\nstand out like a sore thumb \r\nhttps://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection\r\nPage 19 of 20\n\nTeam Red\r\nDon’t use defaults! Tinker with everything\r\nDon’t be afraid to get familiar with assembly!\r\nScripts/YARA Rules\r\nYARA\r\nMain Script (view the full script here)\r\nReferences\r\nhttps://decoded.avast.io/threatintel/decoding-cobalt-strike-understanding-payloads/\r\nhttps://github.com/rapid7/metasploit-framework/blob/master/external/source/shellcode/windows/x86/src/hash.py\r\nhttps://blog.nviso.eu/2021/09/02/anatomy-and-disruption-of-metasploit-shellcode/\r\nhttps://www.boozallen.com/insights/cyber/shellcode/shikata-ga-nai-encoder.html\r\nhttps://www.youtube.com/watch?v=Tk3RWuqzvII\r\nSource: https://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection\r\nhttps://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection\r\nPage 20 of 20",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia",
		"MITRE"
	],
	"references": [
		"https://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection"
	],
	"report_names": [
		"hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection"
	],
	"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": 1775434775,
	"ts_updated_at": 1775791463,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/307f91db1aaec77b04e5c21f941b2920a6937c46.pdf",
		"text": "https://archive.orkl.eu/307f91db1aaec77b04e5c21f941b2920a6937c46.txt",
		"img": "https://archive.orkl.eu/307f91db1aaec77b04e5c21f941b2920a6937c46.jpg"
	}
}