Basic Windows AV Bypass - Part 1 - Windows Environment
As a final project for my degree, I decided to make a study on how safe are AVs and how to bypass them. This led me to scorch the internet looking for how AVs work and how they can be bypassed. While there is a lot of information online about this topic, there are few tutorials/articles that explain it from the ground up. With this tutorial, I intend to explain the basics of AV bypass in a simple but complete way. To do so I will explain how to build a piece of software that will be able to carry a piece of malware inside and execute it in memory without getting flagged by the antivirus.
Before we can bypass anything we need to understand some basic concepts of Windows.
Windows Architecture
The x86 architecture has hierarchical protection domains also known as protection rings. Each ring has different privileges, increasing as we approach the core, ring 0. These rings help protect the system from faults and malicious behaviour.
This 4 ring architecture is theoretical, modern OS (Operative System) only use 2 rings, since having to rewrite API (Application Programmable Interface) functions for each ring can be a very complex and time-consuming task; the amount of work needed defeats its purpose. Modern OSs go directly from ring 3 to 0. Windows, the OS tested in this project, has only 2 rings. These two rings are also known as User mode (ring 3) and Kernel mode (ring 0). The next figure shows a more detailed view of the architecture.
This figure shows the two protection rings: User Mode and Kernel Mode. The diagram shows that in user mode there are two possible steps before reaching Kernel Mode, those being Subsystem DLL (Dynamic Linked Library) and NTDLL.dll. These DLLs are extra layers of abstraction between the application and the kernel. This abstraction helps to avoid faults and errors. Also, this abstraction helps programmers not to reinvent the wheel each time they want to interact with the machine. Thanks to the WIN32 API, programmers have a function that does the hard work for them. Let’s place an example, when opening a file in C, the well-known fopen()
function is usually used, this will call kernel32.dll CreateFileA()
, which will call ntdll.dll NtCreateFileA()
, and finally this one will call the Windows 11 syscall equivalent 0x0055
. When calling a syscall, the processor will change state from User mode to Kernel mode and execute the instruction. After that, it will return to User mode. The next figure shows the call hierarchy.
The existence of these abstraction levels does not mean ntdll.dll functions can’t be called directly, nor that syscalls can not be called directly, the only issue with calling syscalls directly is that the syscall reference numbers change across different Windows versions. Therefore a simple OS update can change this reference number and leave the software unusable. Nevertheless, this is a good thing, since it forces programmers to use the abstractions, that help keep the OS safe. Although the diagram shows that applications can run in Kernel mode directly, this is not usual and should be done carefully, since an error on a program running in Kernel mode can make the OS crash. User mode applications have restrictions compared with Kernel mode. Applications in User mode cannot access or change any memory sections in the Kernel mode, which means that AV and EDR systems can only monitor application behaviour in User mode, due to the KPP (Kernel Patch Protection). The Kernel Patch Protection also known as PatchGuard is a feature of 64 bit versions of Windows which prevents the kernel from being patched. 32 bit versions of Windows do not have this feature, which means the Kernel is patchable. Modern state of the art EDRs and AVs are able to monitor the Kernel in systems that have PatchGuard, by using Kernell Callbacks. This method is going to be explained in detail further on.
Drivers
A driver is a software component that lets the operative system and a device communicate with each other, sometimes this device can be another piece of software.
When an application wants to access a device, it will call a function implemented by the operative system, which will call a function implemented by the driver. This is usually created by the same manufacturer as the device.
Software Drivers
Sometimes the device does not need to be physical, for example, a piece of core data structures that can only be accessed in kernel mode. That is when a software driver is created. To do so the software is usually split into two pieces: one that runs in user mode, the application requesting the data, and another being a driver running in kernel mode.
Signing a Driver
Since drivers are installed in kernel mode, Windows needs to be sure that they are trustworthy. To avoid malicious drivers, Windows will only install drivers that are signed by a trusted root authority. This means that to release a driver to the public, a manufacturer has to send it to a third party who will check that the driver is safe and has no security issues. Unfortunately, sometimes drivers are released to the public containing security flaws that can be exploited in malicious ways.
Syscalls
As seen before, Windows has several layers of abstraction before reaching syscalls, which is the last layer. The kernel is a very protected space where userland code cannot modify or call kernel functions, except through syscalls. The next figure shows how it jumps from userland to kernel-land and back when a syscall is called.
To call these syscalls, the number associated with the specific syscall is needed. This number changes between versions and service packs of Windows. When a new syscall is added or an old one is removed, all the other syscalls numbers might shift forwards or backwards. Calling a syscall directly is not a good idea, that is why Windows offers the WIN32 native functions or the native functions in the DLLs.
Because a syscall is called from userland but happens in the kernel, the system needs to be very careful not to execute faulty instructions, therefore before executing anything the system goes through a process checking that everything is correct.
The next assembler example shows how to call the SysNtCreateFile syscall directly, knowing the associated identifier.
.code
SysNtCreateFile proc
mov r10, rcx ;Pass the paremeters
mov eax, 55h ;State the syscall identifier 55h (SysNtCreateFile)
syscall ;Reserved keyword for calling the syscall
ret
SysNtCreateFile endp
end
The code is fairly simple and short. The problem is knowing which syscall identifier corresponds to each syscall for the given Windows version and service pack.
Kernel Patch Protection (Patch Guard)
The kernel patch protection or Patch Guard protects the kernel from being patched. The Windows kernel has been designed to allow the drivers to have the same privileges as the kernel. It is expected from them not to modify core structures inside the kernel. This feature is present in x64 systems only, hardening the restrictions so that the kernel is "unpatchable". The PatchGuard periodically checks that no protected structures have been modified.
The forbidden actions include:
Modifying the system service descriptor table.
Modifying the interrupt descriptor table.
Modifying the global descriptor table.
Using kernel stacks not allocated by the kernel.
Modifying or patching code contained within the kernel itself.
Patch Guard tries to protect the kernel from all of the above but does not offer any protection against drivers that patch other drivers.
Microsoft released this feature to:
Decrease serious errors in the kernel.
Avoid reliability issues resulting from multiple programs attempting to patch the same parts of the kernel.
Fight rootkits that user kernel access to embed themselves in the OS, becoming nearly impossible to remove.
Kernel Callbacks
With the addition of the Patch Guard many AV solutions lost the ability to monitor in kernel mode since they can no longer patch the kernel. To continue to monitor the kernel without patching, Microsoft released kernel callbacks. Kernel callbacks act as a flag that activates when a given criteria is met. That way AVs can get triggered when something happens in the kernel. The way these kernel callbacks work is: a driver registers this callback in its code for any supported action and receives pre and post-notification when a certain action is performed.
The next diagram shows the pre-notification callback:
The next diagram shows the post-notification callback:
PE File
There are different executable file extensions, some of the most common are PE (Portable Executable), ELF (Executable and Linkable Format) and MACH-O (Mach Object). Windows uses the PE file format. The PE file format is used to execute software. This format tells Windows how to prepare the binary for execution. It is important to understand the PE file format to comprehend how are executables stored and executed.
PE File Structure
Figure 2.9 shows the structure of the PE file. These different sections contain many different sub-structures.
DOS Header
This 64-byte long structure at the beginning of the file has no purpose on modern Windows systems, however, it is here for backwards compatibility purposes. This header makes it an MS-DOS (Microsoft Disk Operative System) executable to be executed in old MS-DOS machines.
DOS Stub
The DOS Stub is a small MS-DOS program that shows an error compatibility message.
NT Headers
NT Headers is a structure defined in the winnt.h library called IMAGE_NT_HEADERS
. There are two different versions of this structure: x32 and x64.
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
This structure can come in two different versions 32 bits and 64 bits. The main difference between them is the structure IMAGE_OPTIONAL_HEADER
which can be IMAGE_OPTIONAL_HEADER32
for 32-bit executables and IMAGE_OPTIONAL_HEADER64
for 64-bit executables. The NT Headers structure contains:
PE signature: A 4-byte signature that identifies the file as a PE file.
File Header: A standard COFF (Common Object File Format) file header. It holds information about the PE file.
Optional Header: The most important header of the NT Headers structure. It is called Optional Header because some files like object files don't have it. It is required for image files. Contains relevant information for the OS loader.
Sections Table
The sections table is an array of Image Section Headers. This table contains a section header for each section in the PE file.
Sections
The sections are where the actual contents of the file are stored. Such as data, text, the actual code of the program and the resources it uses.
Subscribe to my newsletter
Read articles from Joan Esteban directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by