Malware is an important part of an engagement, though as many security solutions are now evolving past rudimentary signature comparisons to using more advanced techniques to detect malicious activity, it is important that we as attackers understand the methods they are using and how we can avoid them.
Consider the following code I wrote for example.
It's an pretty basic bit of malware that will simpily inject a meterpreter stager into
explorer.exe using well known, unsophisticated methods. Despite this it will easily bypass the detections used by major AV vendors. It's not so lucky with EDR solutions though, as within seconds there is critical alerts flying all over the place. So whats different? Why does it beat AV yet fail miserably against EDR?
One of the most common methods EDR solutions will use is API hooking, as it gives it the ability to monitor the functions being called along with the arguments being passed to them. This is a problem for the above code, as by using API hooking its possible to extract the decrypted shellcode while its still in memory but before it has been written to the process, then assess weather it is malicious or if it should be allowed to be written to the remote process.
I have written some PoC code to show how an EDR solution can hook an API call (
kernel32!WriteProcessMemory but could be
ntdll!NtWriteVirtualMemory) then extract the shellcode being written to a remote process.
I compiled this code as a DLL and injecting it into the above meterpreter dropper at start up (which is commonly how an EDR solution will set its API hooks). Looking at the disassembly of the
kernel32!WriteProcessMemoryStub function in a debugger you can see that the functions code has been completely replaced and is now forcing a
jmp to the
r11 register which holds the address of the hook (
Leading to our hook capturing the contents of the buffer
Which when then uploaded to VirusTotal shows it clearly malicious
So how can we beat this? It’s possible that we could restore the functions original code, but if the EDR is performing integrity checks on its hooks then that will cause unwanted alerts when they fail. What would be best is to find a way to either prevent the DLL that places the hooks from being injected or to be able to call the hooked functions without having to get caught by the hooks.
XPN did some good research into how you can use Process Mitigation Policy's to enforce the only Microsoft signed DLLS are allowed to be loaded into your process, or using Arbitrary Code Guard (ACG) to prevent the allocation or modification of executable pages of memory. Using ACG does look very promising, though sadly it's possible to disable ACG in a remote process with elevate privileges.
When tackling this problem I wanted to find a way to prevent the DLL from being injected, while keeping the EDR thinking that the DLL had been loaded successfully (which would not be the case when using Mitigation Policy's or ACG). To do this I figured it would be best to target the process of the DLL being mapped inside my process rather than when its getting injected into my process.
A classic DLL injection method looks like this.
And essentially boils down to creating a thread in the process and calling
LoadLibrary with the argument of the DLL you wish to inject. The
LoadLibrary function is then responsible for mapping the DLL into the process. Originally I was going to target
LoadLibrary but the problem with that is there is a few variations like
kernel32!LoadLibraryW that the loader could use. So instead I decided to target
ntdll!LdrLoadDll which is called by all variations of
The following code will hook
ntdll!LdrLoadDll and check every DLL that is attempted to be loaded against a hard coded whitelist of DLLs that should be allowed inside our process. If its on the list it will be mapped into memory like normal. If its not on this list then it will be ignored but the function will return like it has been mapped successfully.
And as you can see when running it
W:\allowed.dll to be mapped but
W:\functionhooks.dll is blocked.
This technique works well but the downside of it is that there is a bit of a race between how fast the hook can be placed and how fast the EDRs DLL is loaded. If the DLL is loaded before the
LdrLoadDll hook is placed then this technique has little effect. It is also worth pointing out that if the EDR uses a non standard method of loading a DLL - like manually mapping it - this technique probably will not work. So I recommend using a mix of all these techniques as I have done in shad0w
So preventing the DLL from being injected is good, but what about if we are in the worst case scenario and our functions have been hooked? How can we avoid these hooks, while continuing normal executions and keeping there integrity?
This is where syscalls come in. They allow you to directly call the kernel, missing out any Windows API functions. This is extremly useful as the EDRs hooks can only be placed in usermode due to Kernel Patch Protection so by calling the kernel directly we can completely miss them out.
Directly calling sycalls requires a bit more effort because you have to handle everything the windows API would normally do behind the scenes. You will also have to either write and link your own assembly (it will have to be version specific because syscall numbers change between windows versions) or you can dynamically resolve the syscall numbers by reading it directly from ntdll.dll. You can find an example of this in the implementation I wrote for shad0w or in HellsGate. For more robust implementations, dynamically resolving syscalls is much better as without obfuscation it is very easy to signature the syscall assembly.
The full source code for this injector can be found on my github. Here is a snippet of the main code, it includes the
LdrLoadDll hook and syscalls. It is worth noting that this code will only defend against userland hooks, it will trigger a Sysmon Event ID 8 as well as other things so I will leave it as a challenge for the reader to adapt this code and make it better. This could be of use to you if you decide to do that.
And when we execute our new dropper
We can catch a meterpreter session
Or if we used a
windows/x64/meterpreter/reverse_https payload with a set
StagerURILength we can catch a shad0w beacon as well.
Hopefully some of these techniques could be of use to you. If you are interested and want to research further, my C2 Framework shad0w implements many of these techniques and more. Any questions feel free to DM me on twitter @batsec
This blog was originally posted on Jumpsec Labs