PostHole
Compose Login
You are browsing eu.zone1 in read-only mode. Log in to participate.
rss-bridge 2024-01-31T08:33:14+00:00

Sensecon 23: from Windows drivers to an almost fully working EDR

TL;DR I wanted to better understand EDR’s so I built a dummy EDR and talk about it here.
EDR (Endpoint Detection and Response) is a kind of security product that aims to detect abnormal activities being executed on a computer or a server.
When looking for resources about how EDR’s work, I realised that, even if there is a lot of literature available about EDR’s, there aren’t many articles explaining how an EDR’s is architected and how the different components of a EDR are orchestrated. This article aims to demystify how EDR’s work while building a custom one that will implement a few techniques used by real EDR’s.


TL;DR I wanted to better understand EDR’s so I built a dummy EDR and talk about it here.

EDR (Endpoint Detection and Response) is a kind of security product that aims to detect abnormal activities being executed on a computer or a server.

When looking for resources about how EDR’s work, I realised that, even if there is a lot of literature available about EDR’s, there aren’t many articles explaining how an EDR’s is architected and how the different components of a EDR are orchestrated. This article aims to demystify how EDR’s work while building a custom one that will implement a few techniques used by real EDR’s.

First we will take a look at the history of anti-viruses, see how they worked and why they relied on a kernel driver, then we will see how to create a custom kernel driver and finally how to turn it into a almost fully working EDR.

I/ Virus history

If we take a look at the timeline of computer viruses and worms we’ll learn that that the term “worm” was originally used by John von Neumann in an article called “Theory of self-reproducing automata” published in 1966. In this article, Neumann showed that, in theory, a program could be designed so that it is able to reproduce itself. For this work, Neumann was considered to be the theoretical “father” of computer virology.

The first ever working virus was called “The Creeper” and was created by Bob Thomas. This was the first known worm as it was able to replicate over the network (ARPANET) copying itself to remote systems. Although it is the first detected virus ever, its actions were benign since it only printed the message “I’M THE CREEPER. CATCH ME IF YOU CAN”:

Knowing that such programs could be created, smart people started working on security products that would be able to remove them. For example the “Reaper” whose only purposes was to delete the Creeper from infected hosts by moving across the ARPANET. Technically the Reaper was a worm itself, but a good one sort of… This was the first anti-virus software but a lot more appeared in the late 1980s and they were all aiming the same goal: protecting computers from malware.

II/ How did anti-virus protect computers ?

Back in the 90s, antivirus products were able to detect viruses in two ways:

  • Via a simple heuristics:
  • What is the name of the binary ?
  • What is in the metadata (strings, comments…)

2. Via a signature which is calculated for each binary:

When dropping the binary on disk, the anti-virus would check if its signature was known and categorised as malicious. If so, the binary was quarantined or deleted.

For obvious reasons this was not enough because all of these detection methods are based on information that an attacker can manipulate. If you are blocking binaries called mimikatz.exe, I will just rename it notmimikatz.exe. If you are blocking binaries that contain a specific string, I will strip it! If you are flagging the signature of the binary, I’ll change one byte in the binary and we are good to go. Static analysis was not enough.

In order to detect viruses in a more sophisticated way, it was necessary to be able to analyse the system dynamically and specifically be aware of:

  • Processes being created
  • Libraries being loaded
  • Files being modified
  • Functions being called as well as the parameters they take

If we take a look at how operating systems are architected, we can see that they rely on two spaces:

The user space is where your processes live, where you manipulate a word file, where you call your friends on discord. Each process, running in the user space, has got its own execution environment which means that if discord crashes, word will still work. On the other side is the kernel space where the core of the operating system as well as services and drivers are running. Since the kernel space is where the kernel itself is running, it contains quite a bit of interesting information, stored in structures, useful to inspect. However, as you may have guesses, it is not possible for a user space program to access this information directly since the user space and kernel space are both isolated from each other:

The only way of accessing these specific structures directly is running code in the kernel space itself and the easiest way of doing that, is via a kernel driver.

One of the most heavily targeted structures was the SSDT (Service System Dispatch Table). To understand why, we need to take a look at what the operating system does when you try to open a file. As a user, opening a file is nothing exceptional, you just double click on the file and a program (let’s say notepad or word) would open the file for you. However in order to achieve such a task, the operating system had to go through quite a few steps which is described by the following schema:

As you can see, user applications mostly rely on the WinAPI which consists of a set of developper-friendly functions documented by Microsoft and exposed by multiple DLL’s such as kernel32.dll, user.dll or advapi.dll. So the first step to open a file, is to use the CreateFileA function exposed by the kernel32.dll, whose prototype is the following:

HANDLE CreateFileA(
LPCSTR                lpFileName,
DWORD                 dwDesiredAccess,
DWORD                 dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD                 dwCreationDisposition,
DWORD                 dwFlagsAndAttributes,
HANDLE                hTemplateFile

Its usage is fully documented and the function is pretty easy to use, all you need to do is to specify the path to the file you want to open as well as the desired access on it (read, write or append). Looking at the execution flow of the CreateFileA function we’ll see that, ultimately, it will call another function, NtCreateFile, exposed by the NTDLL.dll and whose prototype is the following:

__kernel_entry NTSTATUS NtCreateFile(
PHANDLE            FileHandle,
ACCESS_MASK        DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK   IoStatusBlock,
PLARGE_INTEGER     AllocationSize,
ULONG              FileAttributes,
ULONG              ShareAccess,
ULONG              CreateDisposition,
ULONG              CreateOptions,
PVOID              EaBuffer,
ULONG              EaLength

As you can see, the prototype of the NtCreateFile function is much more complicated than the one of the CreateFileA function. The reason is that the NTDLL.dll is in fact the user mode reflection of the functions exposed by the kernel itself. As such, the NTDLL.dll is going to add a few others parameters that are needed by the kernel to perform the task of opening a file which are not managed or controlled by the developer.

Once all these parameters are set, the program will have to request the kernel to open the file. That means that the program will have to call the NtCreateFile function exposed by the kernel itself. At the beginning of this article I mentioned that a user space process can not directly access the kernel space, and that is true! However they can request the kernel to perform specific tasks. To request an such action, you will need to trigger a specific mechanism called a system call.

Looking at the disassembly of the NtCreateFile from the NTDLL.dll function we can see the following:

Two things are important. The first one is the second line:

mov eax, 55h

This line moves the value 55 in the EAX register. This value, 55, is called a system call number. Each function from the NTDLL.dll is linked to a specific system call number that varies between the different version of the Windows operating system. The second important line is the syscall instruction itself:

syscall

[...]


Original source

Reply