One of the drawbacks of direct & indirect syscalls is that it's clear from the callstack that you bypassed the EDR's user mode hook.
As you can see from the last image, when a call is done through a hooked function the return address for the EDR's hook appears in the callstack.
What if I do call the hooked function, but do it in such a way that the EDR isn't able to properly inspect the call parameters.
If we can time the change to occur after the EDR has finishing inspecting the parameters, but before the syscall instruction, we can bypass the hook without actually bypassing it.
Pick a ntdll function I want to call that's hooked by the EDR, then place a hardware breakpoint on the syscall instruction.
By placing an execute breakpoint on the syscall instruction we'll be able to intercept execution after the EDR has done its checks, but before the system call occurs.
What we'll be able to do is call a hooked function with benign parameters that won't trigger a detection, then swap out the parameters with malicious ones after the EDR has already inspected the call.
We can even, if we want, change the system call number to invoke a different syscall than the one the EDR thinks we're making.
The hardware breakpoint will be triggered right after the EDR has inspected our fake parameters, but before the syscall instruction transitions to kernel mode.
The second breakpoint handler can then change the parameters back to prevent the modifications being caught by any post-call inspection the EDR might do.
In many cases the EDR won't bother with post-call inspection if the call failed, so we could also just change the EAX register to something like STATUS NOT FOUND, STATUS INVALID PARAMETER, or in homage to the TDSS rootkit: STATUS TOO MANY SECRETS. An example of code flow from a hooked NtWriteFile function.
EDR passes control back to the hooked Nt function to perform a syscall.
The EDR performs any post-call inspection and only sees benign parameters.
A snippet of the EDR's NtSetContextThread hook handler.
We already know the EDR inspects the context struction whenever we call NtSetContextThread(), so let's use that to our advantage.
From a combination of the crashdump and our earlier disassembly, we already know the EDR is trying to read the context->Rcx field into the RDX register.
We could use a disassembler to make a more generic bypass, but since this is just a PoC, we'll hardcode it to this specific EDR version.
So there we have it, two ways to bypass EDR hooks without bypassing EDR hooks.
I'm not sure how practical or easy it would be to turn the forced exception method into a generic EDR bypass.
Since we can't easily change pointers back after the syscall, and it only works with calls where the EDR reads pointers, it's fairly limited.
This Cyber News was published on malwaretech.com. Publication date: Wed, 27 Dec 2023 04:13:05 +0000