This blog post describes a generic technique I called internally on our red team assessment “Divide and Conquer”, which can be used to bypass behavioral based NextGen AV detection. It works by splitting malicious actions and API calls into distinct processes.
Back in 2019 I was part of a red team, where our daily activity was to bypass a specific NextGen AV. I had this idea I called “divide and conquer”. I was so excited about it, that I had to name it :) That time I didn’t want to publicly write about it, and I also haven’t found any sources on the Internet that describes this idea (which describes my poor searching skills). Time passed, and by the time I could have write about it, I forgot. Then a few days ago Adam (@Hexacorn) posts this:
spawn a cascade of processes where new instance does a single atomic operation that may not be enough to trigger detection&disrupts operation/process attribution
1 - Spawn Notepad
2 - Open process handle (pass to child process)
3 - Allocate buffer in notepad memory, pass handle and address to child process
4 - Mark it RWX and pass handle and address to child process
5-N - each instance injecting a few bytes of payload
N+1 - Triggers payload
He perfectly described what I did. Based on replies to his tweet, others were doing the same stuff silently (like me). Also turned out that Kyriakos Ispoglou (ispo) and Mathias Payer developed malWASH, which does the same in a more scalable way. That was published 5 years ago, so I guess, no matter what you do in InfoSec, someone has likely done that before :)
With that I thought it’s time to release some details about what I did.
NextGen AVs try to detect processes acting maliciously, so they are full time monitoring their activities. Many times we find that a meterpreter shell can live, until you do something bad, like a hashdump, or lsass access, at that moment your process is being killed. The monitoring is achieved via two ways:
- There is some monitoring at the kernel level, but Windows is not really friendly in that space. You can only do a limited set of callbacks, for file and registry operations or process loading. You can’t monitor API calls generically, which is a big problem if you are an AV vendor.
- Because of these limitations, every AV will place hooks in user mode, typically in ntdll, but also plenty of other places, typically in APIs which are being abused by malware. The AV’s DLL is being enforced into our process from kernel mode, so we can’t really avoid it.
Overall the detection will rely on monitoring the API calls we make, and if they match a certain pattern, we will be detected and killed with fire. For example one of the most typical process injection happens with calling the following API calls:
If we do these, we are dead.
A typical bypass is unhooking the user mode hooks or simply doing direct system calls from our code, avoiding ntdll completely. The AV we dealt with figured out a way to detect these unhooks or when we made direct system call, so we had to adapt, and this idea was born.
Since the detection is relying on detecting our API calls in a process, why not to split the API calls between multiple processes? We can inject a code to a remote process in two (or more) steps. We prepare our “malware” for doing different things based on the input it gets initially. Using the previous process injection example, here is what we can do.
- On first run we will call the following APIs:
- On second run:
- OpenProcess (although with handle inheritance I think this call could be eliminated)
That’s it. The AV will see 2 different processes both doing only part of the injection, but none of them makes the full blown stuff. So if there is an operation that involves multiple API calls, and we can split it, we win. The particular AV is very strong in detecting process injections, so I adopted 3 different injection techniques to this method and I managed to bypass the AV in every scenario. As I know this is being detected since then on that particular AV, but I will still avoid talking about it.
We found that splitting the API calls into two is sufficient for the bypass, but as Adam (@Hexacorn) described, we could go more crazy and split API calls even more.
Here is a dummy POC for performing process injection using Sections with the above method: Divide and Conquer NextGen AV bypass · GitHub. The processes will call itself and pass the step count and the injected memory address to the child process.