Introduction
During our research, CyberArk Labs encountered a strange behavior in the file scanning process of Windows Defender. This problem may possibly exist in other anti-viruses, which we have not yet tested.
This behavior led us to investigate the Antivirus scanning process over SMB shares and the outcome is a surprising cause for concern.
Now you see me, no… you don’t (tl;dr).
Imagine a situation where you double-click a file and Windows loads that file, but your Antivirus scans another file or even scans nothing at all. Sounds weird, right? Depends on who you ask; the folks at Microsoft Security Response Center (MSRC) think there should be a feature request to handle this situation. We will get to this, let’s start by understanding how this is possible. To be clear, the techniques presented in this blog allow any known malware to bypass Windows Defender and possibly other Antiviruses.
When you run an executable, most Antiviruses will catch the operation by a kernel callback (nt!PspCallProcessNotifyRoutines and nt!PsCallImageNotifyRoutines) and then scan the file, most commonly by requesting its user-mode agent using to do so, using ioctls/fastio/sharedmem/APC/etc.
Once an executable file is already present on disk, the Antivirus will not scan it on process creation since it already scanned it on file creation. However, running an executable from a SMB share requires the Antivirus to scan the file even on process creation.
In this blog post, we will walk through several ways to bypass Windows Defender. We are going to achieve this goal by implementing our own SMB server.
Our testing tools
As mentioned above, one of our attack vectors is to fool the Antivirus to scan a different file than the one actually executing. Before jumping into the technical details, let’s see how Windows Defender or any other windows process will go about and access a file in an SMB server, and then we’ll differentiate Antivirus access from regular access. Since we will be running as the SMB server itself, once we identified whether the request originated from an Antivirus or a different process, we’ll serve a malicious or a benign file respectively. In order to examine our attempts to fool the Antivirus, we wrote a simple filter driver that enables us to inspect the file that was actually served to the target machine running the Antivirus. For simplicity ‘s sake, our driver will check 3 bytes of the DOS-Stub message (‘run’ string which is at offset 0x65), which are usually identical in any PE. We will test our method by serving the Antivirus a file with modified 3 bytes at 0x65 and verifying this modification in our filter driver. The malicious file remains untouched (and contains “run” like a normal PE file), and the legitimate file is changed to something else so we can differentiate between them. Let’s register a filter driver with this function to the post operation of IRP_MJ_READ:
*NOTE: The driver is NOT necessary for the attack to succeed and was only used to help the verification process.
Please note that PsGetProcessImageFileName is undocumented, so you will have to declare it in order to use it:
char* PsGetProcessImageFileName(PVOID p);
Next, to receive the information provided by this function, we will choose a designated process and write to its STDOUT from our filter driver. We will have to find the process and save its EPROCESS structure address, so we can attach to it later in order to write to it. Here’s the function responsible for this in our driver:
The STDOUT pseudo-handle on my Windows machine is 0x48.
Finally, since our filter function is called at a high IRQL, we need to use a work-item for the writing:
Bypassing Windows Defender
Let’s begin with the first attack vector. We want to serve different files, one for Windows PE Loader and another for the Windows Defender Antivirus over SMB. We can achieve that using a custom implemented SMB server. When a process creation is made by Windows PE Loader, a request will be made to the SMB server for the executable file, and we will serve file A, which is malicious. When Windows Defender requests the executed file, we will serve file B, which is benign. This way, file B will be scanned while file A will be executed. But at first, we have to identify which request is made by whom.
- First, the Windows PE Loader requests the file’s content to create the process.
- The SMB server identifies the request coming from Windows PE Loader and serves the malicious file.
- Windows Defender requests the file content to scan it.
- The SMB server identifies the request coming from Windows Defender and either serves a benign file or blocks the handle creation request.
In the example below, we can see that detectme.exe, detected by 49 anti-viruses so far, is detected by EICAR:
Windows Defender detected Mimikatz as EICAR because we replaced the file in the SMB server side. EICAR was only used as an illustration, and in fact, it can be any other file, for example calc.exe which is absolutely clean.
Let’s give a brief overview of the SMB protocol. We can skip the negotiation and session creation process as these parts are irrelevant to this blog post. The SMB protocol provides a transparent integration into Windows, which means that accessing a remote file is performed like accessing a local file. First you have to create a handle to the file using NtCreateFile, and then you can perform any operations using NtReadFile, NtWriteFile, CreateFileMapping and so on.
As mentioned above, we can replace a file for each operation, but what’s more interesting is something we saw while investigating the Windows Defender’s mpengine.dll. If you purposely fail the handle creation for the scanned object, Windows Defender will just “give up” and let the file run normally:
There are several ways to identify Windows Defender’s requests among Windows native operations requests. For example, when Windows Defender is attempting to create a handle to the scanned object, it requests the file with the FILE_RESERVE_OPFILTER flag. This way, we can identify requests made by Windows Defender.
In order to abuse Windows Defender, an attacker would have to implement the SMB protocol and create a “pseudo-server” that can differentiate Windows Defender’s request from normal requests. For instance, decline the oplock request and return STATUS_OPLOCK_NOT_GRANTED. This will fail the scan and the malicious file will just execute without any interruptions:
Another way is to block all handle creation requests (NtCreateFile) with the impersonation level SEC_IDENTIFY. Which is as simple as:
if(request.ImpersonationLevel == ImpersonationLevel.SEC_IDENTIFY) {
createStatus = (NTStatus)0xC00000E2;
}
This blocks Windows Defender from scanning the file:
Creating a Dropper that takes advantage of this bypass would require calling only a single WINAPI, as simple as:
Microsoft’s Response on CyberArk Labs findings on IllusionGap1
“Thanks for your email. Based on your report, successful attack requires a user to run/trust content from an untrusted SMB share backed by a custom server that can change its behavior depending on the access pattern. This doesn’t seem to be a security issue but a feature request which I have forwarded to the engineering group.
Thanks again for reporting security issues to Microsoft responsibly and we appreciate your effort in doing so.”