Test context
This authorised lab study tested a custom shellcode runner against a mature endpoint detection and response product. The vendor is intentionally unnamed. The objective was to understand which execution characteristics were detected and how the runner changed in response.

After employing all the techniques I had previously learned as well as a technique to patch amsiScanBuffer in memory it was clear I had much more research to do.
Now, when it comes to Windows OS privileges there are two main modes: Kernel-Mode (where device drivers and the kernel run) and User-mode (where installed applications run).
Endpoint products can observe the user-mode calls made immediately before execution crosses into kernel mode. One common implementation hooks functions in ntdll.dll, giving the product a point at which to inspect or redirect relevant calls.
The hook can be confirmed by disassembling a function in ntdll.dll and inspecting the jump into the vendor DLL. The comparison below shows the hooked and clean states used in this lab.

We can get around this using a technique called “manual mapping”, this technique and more complex variants of it are covered well here.
At a high level, what we are doing is manually loading the source ntdll.dll file from the native Windows directory into memory, getting a handle to the currently loaded ntdll (hooked) and rewriting it with the clean copy (unhooked, as it has been loaded directly from disk). A deep dive into this process has been covered in MAKOSEC’s blog
The implementation used P/Invoke definitions to compare a baseline program with a version that remapped ntdll.dll. In the remapped process, the tested function no longer jumped to the injected vendor DLL.
The runner combined DLL unhooking, an AMSI bypass and shellcode execution in a generated XML project compiled with MSBuild.exe. Payload bytes and required function names were transformed before use.

The first build was detected during MSBuild compilation when a temporary DLL was written to disk and classified as a potentially unwanted application. The next iteration reduced static imports by resolving functions at runtime through delegates.
The first generated payload produced a basic reverse HTTPS shell, which provided a useful baseline for the next test.


It was, however only a basic reverse HTTPS shell, and the real test would be calling back to my Cobalt Strike instance.
The Cobalt Strike payload was also detected. This result differed from the earlier static finding: the product's machine-learning layer recognised the default payload and command-and-control profile.
I made use of a tool that creates a randomised Cobalt Strike profile and started my Cobalt Strike server again.

I generated a raw payload in Cobalt Strike and once again fed it to my Python script. (It is worth noting that a raw payload generated with the “Windows Executable” package options STILL gets caught)

![]()
Findings and limitations
The tests showed that bypassing one control did not bypass the product. Static indicators, temporary files, imported functions, payload signatures and network behaviour each affected the result. A non-default command-and-control profile changed the final outcome in this lab, but it is not evidence of a durable or portable bypass. Detection performance varies by product version, policy, telemetry and payload.
