In the article Master of Puppets we have learned a little about the EDR components and there functionality in Windows user space and kernel space. By removing the callback functions from the EDR, which are registered by the EDR minifilter driver, we were able to make changes to the EDR minifilter registry key, change the initialization behavior and disable the main functionality from the EDR. By that we have learned, that kernel callback functions are not just important for prevention by the Antivirus module and detection by the EDR module based on the collection of telemetry on the endpoint. Rater the callbacks could be used by EDR’s to protect there own registry keys against tampering attempts. From red team perspective we achieved all of our defined goals which we have defined.
But still I ask myself, are there more interesting EDR components in kernel space, which could be useful to know about? Which specific component is responsible to protect the EDR user space service (protected service) against tampering attempts. This are questions which I wasn’t able to answer at the moment when I begun to write the article Master of Puppets. Reason enough to dig deeper into the topic protected services and how EDR products use them. To learn more about protected services I asked myself the following questions:
- What are the characteristics of a protected service?
- How do EDR products use protected services in user space?
- Relationship between protected service and other EDR components?
- What are possible weaknesses in the concept of protected services, which could lead to disabling the the protected EDR service?
After answering that questions as good as possible, we check, if there is a ways to disable the protected EDR service from user space without relying on having access to kernel space. Also we want to investigate the impact on the functionality (prevention, detection, host isolation etc.) from EDR’s if we only disable the protected service from an EDR product. Also in this blog post we are not interested in relying on uninstall password, EDR uninstall software (picture below) or using the Windows security center to disable the protected service from the EDR.
I would like to point out, that this is only my personal research and everyone is responsible for their own activities during a penetration test, red team engagement, etc. and must take care about his actions.
- You should understand the functionality of DLL Search Order Hijacking. I highly recommend to read the blog post from @itm4n (Clement Labro) about DLL Hijacking.
- By looking at a possibilities to tamper the protected user space service from the EDR without accessing kernel space, we need a privileged user account, I don’t can provide you a fancy zero day exploit 😅. For me it is more about learning about the functionality from EDR products and protected services.
- The impact could differ from EDR product to EDR product, I do highly recommend, to try this only in your own secure LAB environment. This isn’t really something which I would do in a pentest or red team engagement, it is more curiosity.
Protected Service (ELAM Service)
Because the EDR user space service is an important part of an EDR and it needs some kind of protection against tampering attempts, Microsoft provides the possibility to initialize the EDR service as a protected service. Same as with protected processes (which are connected to the protected EDR service), also in system integrity context we are not allowed to access the address memory of a protected service. Which means, that we are not able to stop the protected service in user space. As additional information, the EDR is fully configured, prevention is set to the highest level and every other option is also turned on.
By using the Windows Code Integrity it isn’t possible to load not trusted code into the address memory of a protected service. In context of loading DLLs, this has the following effects:
- Only Windows DLLs with a valid Microsoft sha256 extended validation certificate are loaded by the protected service
- Non Windows DLLs require a valid 3rd party av/edr vendor sha256 EV certificate (Also they can have an additional Microsoft Certificate which have advantages in case the red team use block DLL).
- An example for this is the edr.dll which is used by the EDR for user space API hooking by injecting the edr.dll into the address memory of other processes in user space.
Furthermore, a protected service in context of the EDR has the following characteristics:
- The protected service is a user space component.
- But the protected service is initialized trough by the EDR kernel component which is called ELAM Driver -> protected service = ELAM service
- Thanks to @NinjaParanoid for the information about ELAM Drivers, I didn’t know about that kind of drivers previously. More about ELAM Drivers a little bit later
Depending on the EDR product the protected service is responsible for:
- Partly responsible (in combination with kernel components) – to detect and remove malware
- Downloading signatures and definitions
- Protecting connected EDR PPL process against tampering attempts (recovery function)
To be sure that a protected EDR service which is connected to the protected EDR process do work correctly, we can do following checks. All creds to @zodiacon (Pavel Yosifovich) for that information, I highly recommend to have at look at the great books from Pavel. Execute Process Explorer in privileged context and look if you are able to see the loaded DLLs in the address memory from the protected EDR process, which is connected to the protected EDR service. If the protected EDR service/protected EDR process works correctly, you should not be able to see any loaded DLLs. Because, also when you execute process explorer in system integrity context, you are not allowed to access the address memory of a protected service or protected process.
Additional, to be really sure that the protected EDR service works correctly, we use the Windows Debugger windbg (kernel debugger module) to debug the kernel of the (virtual) machine, on which the EDR product is installed. To get the result we are looking for, we need to find the protected process light from the EDR and have a look at the protection member of EPROCESS. In the picture below we see, that in our case the protected service works correctly, because by debugging the EPROCESS from the protected EDR process we get the value 0x31 for the _PS_PROTECTION Level and the value 0y001 for the type.
In the blog post Master of Puppets we learned, that the EDR minifilter driver is the key element to permanently disable the main functionality from an EDR. But the EDR minifilter driver is not responsible for initializing the EDR user space service as protected service. Therefore the AV/EDR vendors use a component which is called an ELAM Driver. In the picture bellow we see by using the driverquery command all registered kernel drivers from the EDR product. Derived from that we could say, that the EDR is using a multi driver approach to provide the best possible protection. That means, for different tasks, different (types of) kernel drivers are used. The Agent.sys in our case is the minifilter driver (file system driver) which was more or less the central element in the Master of Puppets article. However, because we want to learn this time more about ELAM Drivers we are more interested in the Boot.sys driver.
Depending on the EDR product you can identify the ELAM driver of the EDR by the driver name (for example elam.sys). However, if the ELAM driver cannot be identified unambiguously or one wants to know it more exactly, one can examine the ELAM driver with Ghidra. I am definitely not a reverse engineer, but I am always fascinated by tools like Ghidra. In the end I just did a string search and looked for the string ELAM. Another possibility is to have a look on the imports/NTOSKRNL.EXE. I was able to identify the function that uses the callback function IoRegisterBootDriverCallback. If I understood it halfway correctly, this function is crucial for the characterization of an ELAM driver and should therefore be sufficient for the identification. If you are more interested in reversing drivers you can use the ntddk_64.gdt library in Ghidra to get a better resolution of the functions.
When we have a closer look in the Microsoft Documentation about ELAM Drivers we see, that the ELAM could be also used to register the kernel callbacks functions like CmRegisterCallbackEx which could be used to protect the EDR registry keys against tampering attempts. Based on the previous research in Master of Puppets, in my case the the ELAM Driver is not responsible to protect the EDR registry keys. I think it make sense from the perspective of the EDR vendor to use multi driver approach to realize different kind of protection mechanisms, because relying on just one driver would not make sens from the defense perspective. At this point I would say, we have in our case the following work distribution in context of the loaded EDR drivers (the firmware and devicecontrol driver excluded):
- Boot.sys = ELAM driver which is responsible for:
- Initialization the EDR user space service as protected service.
- Agent.sys = Minifilter driver which is responsible for:
- Registering kernel callback functions like PsSetCreateProcessNotifyRoutine to collect telemetry and analyze in combination with user space Win32 API-hooking executed code in user space and kernel space.
- Registering kernel callback functions like CmRegisterCallbackEx to protect the EDR registry keys against tampering attempts.
DLL Search Order Hijacking – Tampering Protected Processes
As we already know, we can use a malicious driver like the RTCore64.sys to get access to kernel space, remove the registered callback functions from the EDR and disable the main functionality in context of the Antivirus and EDR module from the EDR product by disabling the minifilter driver. In case of tampering the minifilter driver from the EDR, we need access to kernel space. But is this also necessary if we try to disable the protected user space service from the EDR by modifying the registry key from the protected service. But despite the concept of protected services and trusted code, is there possibility to disable the protected service from user space without relying on access to kernel space? What could be used to disable the protected service from user space? I thought about the concept of DLL Search Order Hijacking and use simply Procmon to proof if our EDR product is „vulnerable“ against DLL hijacking at all. With Procmon we use the following filters and check if the protected EDR user space service tries to load DLLs which are generally not available under Windows or only not available in the respective directory from which it tries to load the DLL.
- [ProcessName] [is] [edr_userspace_process.exe]
- [Result] [is] [NAME NOT FOUND]
- [Path] [ends with] [.dll]
By looking at the picture below we see, that in our case the EDR product is „vulnerable“ against DLL Hijacking. However, since the missing DLLs are only not present in the installation directory from the EDR (from which the EDR product tries to load them), but still present in the c:\windows\system32 directory, there is no possibility to use DLL phantom hijacking (see blog Clement Labro). Because an unprivileged user normally do not have write access to the EDR installation directory, we need a privileged user (for example local admin or another privileged user) for later activities.
We know, that a protected service shoud not load not trusted code into his address memory. But because I am very curious, I still want to try to abuse DLL search order hijacking with one of the missing DLLs (dbghelp.dll) and check if it is possible or what would be the impact on the functionality from the EDR, if the EDR would still try to load not trusted code in form of a non signed DLL? For a proof of concept I simply create a non signed DLL with msfvenom (call it dbghelp.dll) which should open calc.exe after the non signed dbghelp.dll would be executed. As already mentioned we need a privileged user to place the created dbghelp.dll into the directory of the EDR product. Because we are not able to stop and restart the protected service even in system integrity context, we have to restart the host and observe what happens in our case after the restart, when the EDR product starts the initialization of the protected service. As additional information, the EDR was fully configured, prevention was set to the highest level and everything else in the EDR was turned on.
At the beginning of the video we did a small plausibility check if the prevention of the EDR is working correctly. For this we simply executed the pre compiled public variant of Mimikatz. Mimikatz was successfully detected and blocked by the prevention module (Antivirus module) from the EDR. Furthermore we did a check, if the tamper protection of the EDR works correctly. For this, we simply tried to terminate the protect EDR process. We also tried to stop the protected service from the EDR. Also we tried to change the value for the Start DWORD entry from the EDR user space service registry key. And as expected, all actions were unsuccessful from the attacker’s point of view.
Furthermore, we wanted to make sure, that the protected service from the EDR was properly initialized and working. For this we used the Windows debugger windbg (kernel debugger) and examined the kernel of the machine with the installed EDR product. A kernel debugger is necessary, because we don’t have permission to view the address memory of the protected EDR service even in system integrity context. With the kernel debugger we searched for the process of the protected EDR service and then read the EPROCESS address memory of the protected EDR service. We have discovered, that the _PS_PROTECTION level has the value 0x31 and the type has the value 0y001. According to the Microsoft documentation this means, that the protected EDR service was initialized correctly and is working.
As I said at the beginning, I wanted to investigate what happens when the EDR product tries to load a missing non signed DLL (not trusted code) by using the DLL Search Order. For this I simply created a DLL with msfvenom that runs calc.exe after execution. So that the DLL is found and executed after the execution of the EDR protected process/protected service by the DLL Search Order, I have named the DLL after one of the missing DLLs (NAME NOT FOUND). Therefore the DLL was named dbghelp.dll. We copied this non signed dbghelp.dll into the directory of the EDR and did a reboot. If you paid attention, you saw that I renamed dbghelp.dll to dbghelp first, copied it to the EDR directory and then renamed it to dbghelp.dll again. The reason for this is, that I prevent an possibly alert by the EDR. If I would copy executable code like the dbghelp.dll directly into the directory of the EDR, I would trigger an alert created by the EDR product.
After restarting the computer, the EDR showed a relatively interesting behavior. The calc.exe was not executed, but with Procmon we could observe, that the previously missing dbghelp.dll was found by the protected EDR service and wasn’t longer displayed as missing. Why calc.exe did not start is not entirely clear to me at this point, but I have the following guess. I could be, that it would be also necessary that the exact entry point in dbghelp.dll (DllMain) has to be executed or the content of the non signed dbghelp.dll was simply not executed by the protected service of the EDR. But by trying to load the non signed dbghelp.dll, we were able to stop initialization of the protected service from the EDR. As a result, the EDR did not longer register itself in the Windows security center and the associated protected process from the EDR also didn’t get initialization longer. In simple terms, we permanently stopped the protected user space EDR service without relying on access to kernel space by using a non signed DLL (not trusted code) with the help of the Windows DLL search order.
Relatively at the end of the video, however, it is also apparent that the execution of malicious code (for example mimikatz.exe) was still successfully blocked by the EDR. I think the reason for this should be clarified by reading the article Master of Puppets, which showed us, that even if the user space service from the EDR is disabled, but the kernel component (minifilter driver) is still correctly working, executed malicious code is still prevented and detected. But from Master of Puppets I could remember, that despite the disabled user space service (protected service) the host isolation function in the EDR web console was still working. I also tried to isolate the host via EDR webconsole (this is why the video continues to run for a relatively long time after running Mimikatz). The more interesting it was to observe, that the manipulation of the protected service with the non signed dbghelp.dll seems to result, that the host isolation function do not longer work. I’m not quite sure if this is 100% plausible, since I previously thought that the host isolation function is based on the minifilter driver from the EDR. However, I was not able to observe, that the host gets isolated after I executed the host isolation function in the EDR web console.
Of course the particular impact is highly dependent on the particular EDR product. I was just curious what happens when a protected EDR service tries to load a non signed DLL (not trusted code) by using the DLL Search Order. For sure, you can do more research and interesting experiments, but I would like to close the investigation of protected services at this point, because otherwise this would go beyond the scope (and also I do need some sleep 😉).