Background
Recently I had the opportunity to play around with EDRs and test what works and doesn’t. What struck me is just how many behaviours and actions EDRs actually monitor for, and that unfortunately (for red team at least) most tools are not at all developed to combat the level of monitoring that EDRs provide. Below, I’ve compiled the most common reasons why the tools and payloads I tested were getting flagged as malicious and suspicious by EDRs so that you can hopefully also learn from my experiences.
The big no-no’s
Injecting into other processes
I was surprised, but this was actually the biggest contributor to why every tool I ran got detected again and again. Many tools rely on code injection to work. Opening up a windows process like notepad or running a dll and then injecting code into it is a common method to get around issues with unreliable code, because if the exploit code fails then the host process will almost always die. The EDR will almost always detect the injection of code into another process as malicious trigger all kinds of alerts.
Some techniques I tried like masking the parent process id that the new process runs under was successful in obfuscating which process had done the injection, but nonetheless the subprocess itelf was detected as malicious and instantly created an alert.
Similarly, some applications that implement self-injection were also detected as malicious. Though this was much more reliable than the injection into other processes (which literally never evaded detection), some tools appear to allocate RWX pages for self-injection, which turned out to be a significant contributor to whether a process was considered malicious or not.
RWX Pages
As mentioned, whether memory was set to RWX (read-write-execute) was a huge contributor in if executed code was detected as malicious or suspicious. Simply sticking with NX pages (that is, write OR execute on a page and never both) completely eliminated self-injection and shellcode detection in several cases. I strongly suggest that every precuation is taken to ensure RWX pages are never used anywhere in your tool if you want to avoid EDR detection.
PE/ELF code residing in memory
Simply having PE/ELF code residing in memory somewhere during process execution was enough to get flagged as suspicious in a few test cases. Either hide it, or decrypt it immediately before use and then discard it if you need to use this method.
Obfuscating powershell commands
Running a command under powershell with base64-encoded strings, piping output from a Invoke-WebRequest into a Invoke-Expression, etc, seemed to cause processes to get flagged as suspicious in a majority of cases. I believe this is because there are few legitimate uses for doing this, and it’s all too easy for the EDR to check this because powershell is so heavily instrumented. Funnily enough, in several cases just using cmd instead of powershell was enough to bypass detection, though that’s unlikely to last.
Exfiltration using odd protocols
DNS exfiltration appears to be widely recognized as a threat by EDRs and some effort is taken to detect it and in one of my test cases it was flagged almost immediately. There may be techniques which can bypass detection, but my recommendation is to try to exfiltrate by blending in with more common sources of traffic instead like HTTPS.
High-privilege or high-integrity processes in unexpected places
If you do a privilege escalation or UAC bypass, do your best to make sure that any high-privilege/integrity process you spawn do not look out of place. A cmd.exe instance running as NT Authority\System under Domain\Bob’s notepad.exe will get you detected in most of my tests. Better is making sure that process gets placed under another process owned by something more appropriate, like another process owner by NT Authority\System for example.
Reflection everywhere
Reflection is a common way to execute payloads or tools right now, partially because your payload may never have to touch the disk to execute. In my experiments, EDRs are sensitive to reflection because it’s not very commonly used in legitimate applications. In one case, reflection resulted in a RWX page being allocated and therefore getting detected as shellcode – instantly considered evil – in others, reflection would either evade detection or get flagged as suspicious. The latter case seemed mostly to do with how the code loaded was stored in memory before execution, as floating PE/ELF code was considered suspicious in and of itself. Ironically, writing to disk was the easiest way to evade detection because the EDR appeared to consider it normal disk activity.
High-privilege accounts doing unexpected things
In one case, the reason one of my tests was marked as suspicious was because a normally benign command was being run under NT Authority\System (something which usually never happens). I would therefore suggest you take a least-privilege approach and only use higher privileges accounts for things that require higher privileges to avoid casually getting detected for doing non-malicious actions.
So there you have it. In a vast majority of my test cases, these were the reasons my attmepts got detected. If you take into consideration the above points and design your tools and payloads to take this into account you are likely to be tremendously much more succesful.