eBPF for Windows
- eBPF for Windows: A project that allows the toolchain and API of the eBPF ecosystem used in the Linux environment to be used on Windows
- Existing eBPF was mainly used in the Linux Kernel, but eBPF for Windows provides an intermediate layer so that eBPF Programs can be loaded and executed in the Windows Kernel.
- In other words, developers can write eBPF Programs in C and generate eBPF Bytecode in Clang as before.
- Afterwards, the Windows environment verifies the bytecode and converts or loads it into an executable form in the Windows Kernel for use.
- eBPF for Windows aims to enable functions such as network, observation, and security policy processing to be dynamically extended without directly modifying the Windows Kernel itself.
Why eBPF for Windows Is Needed
eBPF is a technology that originally developed from the Linux Kernel.
In Linux, you can attach an eBPF Program to a specific point inside the kernel to perform packet processing, system call tracking, performance observation, and security policy application.
However, Windows has a different kernel structure from Linux.
Linux’s eBPF Program cannot be directly uploaded to the Windows Kernel.
Therefore, Windows requires the following elements.
- Runtime that can interpret eBPF Bytecode on Windows
- Verifier that can verify eBPF Program
- A layer that connects the hook point of the Windows Kernel and the eBPF Program
- Layer that implements eBPF objects such as eBPF Map and Helper Function in Windows
- Development experience as similar as possible to the existing Linux eBPF Toolchain
eBPF for Windows provides these elements so that the eBPF-based programming model can be used on Windows as well.
Overall Architecture of eBPF for Windows
The flow of eBPF for Windows is roughly as follows.
eBPF C Program
|
v
Clang / LLVM
|
v
ELF File
|
v
eBPF Bytecode
|
v
Verifier
|
v
Native Driver / JIT / Interpreter
|
v
Windows Kernel Execution Context
|
v
Hook execution
Developers generally write eBPF Programs in C.
Afterwards, if you compile with BPF Target using Clang, an Object File in ELF format is created.
This ELF file contains eBPF Bytecode, Map definition, Section information, etc.
Windows does not upload this ELF file to the kernel as is, but first verifies its safety through Verifier.
Programs that have passed verification are converted to Native Windows Driver or executed in JIT or Interpreter method depending on the execution method.
Writing an eBPF Program
eBPF Programs are generally written in C.
For example, if there is an eBPF Program that simply returns 0, it could have the following form.
int func()
{
return 0;
}
This code is not compiled into a general Windows executable file, but into Bytecode suitable for eBPF ISA.
Compilation is usually done as follows:
clang -target bpf -O2 -g -Werror -c bpf.c -o bpf.o
The important part here is -target bpf.
- target bpf is an option that tells Clang to compile this code as eBPF Bytecode rather than x86-64 or ARM64 machine language.
bpf.o created as a result of compilation is an Object File in ELF format.
ELF and eBPF Bytecode
In eBPF for Windows, the eBPF Program is also handled in the form of a ELF file.
The ELF file is an executable file and object file format mainly used in Linux, but in the eBPF ecosystem, it is often used as a container format that contains eBPF Bytecode and Map definitions.
eBPF for Windows receives this ELF file as input and reads the eBPF Program and Map information contained within.
The ELF file can generally contain the following information.
- eBPF Bytecode
- Program Section
- Map definition
- BTF information
- Relocation information
- License information
eBPF for Windows reads this information, verifies the program, and prepares it in an executable form in the Kernel Execution Context.
Verifier
Verifier is a component that checks whether the eBPF Program can be safely executed in the kernel.
Because eBPF can run in the kernel, if an incorrect program is loaded, it can damage kernel memory or make the entire system unstable.
Therefore, the eBPF Program must pass the Verifier before being executed.
Verifier checks approximately the following contents.
- Is there an unauthorized memory access?
- Are you using pointers safely?
- Can the Program be terminated?
- Check that stack usage does not exceed the limits
- Are the helper function calling rules followed?
- Is only the context access appropriate for the program type performed?
eBPF for Windows analyzes the safety of the eBPF Program using PREVAIL Verifier.
PREVAIL is an eBPF Verifier based on Abstract Interpretation.
In other words, the program is statically analyzed without actually executing it to determine whether the program is safe.
PREVAIL
PREVAIL is a verifier that analyzes eBPF Bytecode.
Unlike the Verifier built into the Linux Kernel, eBPF for Windows uses a separate Verifier because the eBPF Program must be verified in the Windows environment.
PREVAIL analyzes the control flow, register status, and memory access range of the eBPF Program.
For example, if an eBPF Program reads a Packet Buffer, the Verifier must check whether the access does not exceed the Packet Boundary.
if (data + sizeof(struct ethhdr) > data_end)
return XDP_DROP;
If there is a boundary check like the one above, the verifier can determine that subsequent packet access is safe.
Conversely, if you read the Packet Buffer directly without Boundary Check, the Verifier may reject it because access may exceed the Packet length.
In other words, the Verifier plays the role of ensuring the minimum level of safety before the eBPF Program is executed inside the kernel.
How eBPF for Windows Works
In eBPF for Windows, there are several methods for processing eBPF Bytecode and executing it in the kernel.
How eBPF for Windows runs
- Native eBPF Program
- JIT Compiler
- Interpreter
Each method is different depending on how the eBPF Bytecode is converted into an executable form.
Native eBPF Programs
Native eBPF Program converts eBPF Bytecode into C code, then builds and executes it again in Windows Driver form.
In this method, a tool called bpf2c is used.
The approximate flow is as follows.
eBPF C Program
|
v
Clang
|
v
ELF File
|
v
PREVAIL Verifier
|
v
bpf2c
|
v
Generated C Code
|
v
Windows Driver (.sys)
|
v
Kernel Load
The key point here is that the eBPF Program is ultimately created as a Windows Driver in .sys format.
Windows attaches great importance to signature and integrity checks for code running in the kernel.
In particular, in an environment where security features such as HVCI are activated, the JIT method of executing dynamically generated kernel code may be restricted.
So, in eBPF for Windows, the Native eBPF Program method is important.
The native method verifies the eBPF Program in advance, converts it to a Windows Driver, and distributes it as a signed PE image.
bpf2c
bpf2c is a tool that converts eBPF Bytecode to C code.
As the name suggests, it changes BPF to C.
eBPF Bytecode consists of instructions of eBPF ISA.
For example, eBPF uses virtual registers such as r0, r1, and r2.
bpf2c converts these eBPF instructions one by one into corresponding C code.
Roughly, the following transformation can be seen to occur.
eBPF Register
|
v
C Local Variable
eBPF Instruction
|
v
C Statement
eBPF Helper Call
|
v
Indirect Helper Function Call
In other words, each instruction in eBPF Bytecode is moved to a C statement, and this C code is compiled again with the Windows Driver.
This method is important in that it does not simply interpret and execute the eBPF Program, but creates and executes it in a native code format that Windows can understand.
JIT Compiler
The JIT Compiler method compiles and executes eBPF Bytecode into Native Code at Runtime.
JIT stands for Just-In-Time Compilation.
In other words, at the time of loading the program, eBPF Bytecode is converted into code that can be executed in the actual CPU.
This method can be faster than Interpreter.
However, in the Windows Kernel environment, dynamically generated code must be placed in the kernel address space and execution permission must be granted.
This process may conflict with Windows’ code integrity policies.
In particular, in an environment where HVCI is activated, the use of dynamically generated executable code in the kernel may be restricted.
Therefore, in eBPF for Windows, the Native eBPF Program method is preferred for security reasons.
Interpreter
The interpreter method reads the eBPF Bytecode as is and executes it in units of commands.
Instead of converting to native code like JIT, the runtime interprets and executes the bytecode one by one.
The interpreter method can be simple in terms of implementation and debugging.
However, performance is low, and running the interpreter within the kernel address space itself can be a security burden.
Therefore, in eBPF for Windows, the Interpreter is mainly used only in Debug Build.
eBPFSvc.exe
eBPFSvc.exe is the User Mode Service of eBPF for Windows.
When loading or managing an eBPF Program, it plays an intermediate role between User Mode and Kernel Mode.
In eBPF for Windows, the User Mode Application does not directly manipulate the internal structure of the Kernel, but loads and manages the Program through API and Service.
The approximate flow is as follows.
Application / bpftool / netsh
|
v
ebpfapi.dll
|
v
eBPFSvc.exe
|
v
Kernel-mode Execution Context
eBPFSvc.exe is related to roles such as program loading, verifier linkage, JIT processing, and service registration.
In other words, it can be viewed as a management layer that connects User Mode and Kernel Mode in eBPF for Windows.
ebpfapi.dll
ebpfapi.dll is the API Library provided so that User Mode Application can use eBPF functions in eBPF for Windows.
In the Linux eBPF ecosystem, the eBPF Program is often loaded and the Map is manipulated through libbpf API.
eBPF for Windows provides API, which is similar to libbpf, to maintain as much as possible the API that existing eBPF developers are familiar with.
For example, Application can perform the following tasks through ebpfapi.dll.
- Load eBPF Program
- eBPF Program Attach
- Create eBPF Map
- eBPF Map query
- eBPF Map Update
- eBPF Link Management
- Program information inquiry
In other words, ebpfapi.dll can be viewed as an entry point for handling eBPF objects in User Mode.
Kernel-Mode Execution Context
Kernel-mode Execution Context is the kernel-side execution environment where the eBPF Program is actually executed.
The eBPF Program that has passed the Verifier is loaded into this execution environment.
After that, when a specific Hook occurs, the eBPF Program is called.
For example, if the eBPF Program is attached to the XDP Hook, the program can be executed when a network packet comes in.
If attached to the Socket Bind Hook, the program can be executed when a process tries to bind to a specific port.
In other words, the Kernel-mode Execution Context is a key component that executes the eBPF Program within the Windows Kernel and connects the Hook and Program.
Hook
Hook is a point inside the kernel where an eBPF Program can be executed.
The eBPF Program is not executed at any time, but is executed by attaching to a specific event or kernel path.
For example, there may be a Hook like this:
Hook example
- Packet receive
- Socket bind
- Socket connect
- Socket accept
- Process create
- Process delete
- Network event
The eBPF Program is attached to this Hook and executed when a specific event occurs.
Thanks to this structure, it is possible to extend or observe the behavior of only a specific point without modifying the entire kernel.
Program Type
Program Type indicates what type of Hook the eBPF Program can be attached to.
For eBPF Programs, the received context and available helper functions may vary depending on the program type.
The Program Types used in eBPF for Windows are as follows.
eBPF for Windows Program Type
- BPF_PROG_TYPE_XDP
- BPF_PROG_TYPE_BIND
- BPF_PROG_TYPE_CGROUP_SOCK_ADDR
- BPF_PROG_TYPE_SOCK_OPS
- BPF_PROG_TYPE_NETEVENT
- BPF_PROG_TYPE_PROCESS
- BPF_PROG_TYPE_SAMPLE
BPF_PROG_TYPE_XDP is a Program Type for quickly processing incoming packets.
BPF_PROG_TYPE_BIND is a program type for handling socket bind operations.
BPF_PROG_TYPE_CGROUP_SOCK_ADDR is a program type for socket-address operations such as connect, accept, and bind.
BPF_PROG_TYPE_SOCK_OPS is a program type for handling socket-related events.
BPF_PROG_TYPE_NETEVENT is a Program Type for processing network events.
BPF_PROG_TYPE_PROCESS is a Program Type for processing events such as process creation or termination.
BPF_PROG_TYPE_SAMPLE is a Program Type mainly used in testing or sample expansion.
Attach Type
Attach Type indicates where the Program will be attached more specifically than Program Type.
For example, even the same Socket-related program may be attached at the connect time or at the bind time.
Representative Attach Types are as follows.
Attach Type Example
- BPF_XDP
- BPF_ATTACH_TYPE_BIND
- BPF_CGROUP_INET4_CONNECT
- BPF_CGROUP_INET6_CONNECT
- BPF_CGROUP_INET4_RECV_ACCEPT
- BPF_CGROUP_INET6_RECV_ACCEPT
- BPF_CGROUP_SOCK_OPS
- BPF_ATTACH_TYPE_NETEVENT
- BPF_ATTACH_TYPE_PROCESS
- BPF_CGROUP_INET4_BIND
- BPF_CGROUP_INET6_BIND
If Program Type indicates the type of program, Attach Type can be seen as indicating the location where it is actually attached.
eBPF Map
eBPF Map is a Key-Value storage used by eBPF Program and User Mode Application to share data.
The eBPF Program runs in the kernel, and the User Mode Application runs in general user space.
Because the two environments have separate memory spaces, they cannot directly share data.
At this time, if you use the eBPF Map, the program running in the kernel and the User Mode Application can safely exchange data.
For example, if you want to record the number of ports used by a specific process, the eBPF Program can store the process ID and port usage in Map.
User Mode Application can check the current status by reading this Map.
eBPF Program
|
v
eBPF Map
^
|
User Mode Application
eBPF for Windows also provides several Map Types such as Hash Map, Array Map, LRU Hash, and Ring Buffer.
Helper Function
Helper Function is a function that eBPF Program calls to use kernel functions safely.
The eBPF Program cannot freely call general kernel functions.
If the eBPF Program can call arbitrary kernel functions, it is difficult for the Verifier to guarantee safety and kernel stability may be broken.
Therefore, eBPF Runtime provides helper functions allowed for each program type.
The eBPF Program can only use limited functions through this Helper Function.
For example, there may be a Helper Function like this:
Helper Function Example
- bpf_map_lookup_elem
- bpf_map_update_elem
- bpf_map_delete_elem
- bpf_tail_call
- bpf_get_prandom_u32
- bpf_ktime_get_ns
- bpf_ringbuf_output
- bpf_get_current_pid_tgid
In other words, the Helper Function is an interface that allows the eBPF Program to use kernel functions, but limits its operation to only within the permitted range.
Extension Driver
eBPF for Windows uses the Extension Driver structure to extend Hook and Helper.
Because the Windows Kernel has a different internal structure from the Linux Kernel, hooks that exist in Linux cannot be copied directly to Windows.
So, to expose a specific Windows kernel function to the eBPF Program, an extension is needed to connect the function to the eBPF Runtime.
Extension Driver plays roughly the following roles.
- Provides new Program Type
- New Attach Type provided
- Provides new helper function
- Passing Windows Kernel events to eBPF Runtime
- Apply policy based on the return value of eBPF Program
For example, if you want to process a process creation event with eBPF, you need an extension that detects the process creation event and connects it to an eBPF Program call.
In other words, in eBPF for Windows, the Extension Driver acts as an adapter that exposes Windows Kernel functions to the eBPF ecosystem.
NMR
NMR is the abbreviation for Network Module Registrar.
In Windows, kernel components can use the NMR structure to register and connect interfaces to each other.
NMR is also used in the Native Driver structure of eBPF for Windows.
The .sys file created with the Native eBPF Program is initialized through DriverEntry when loaded.
At this time, Driver Skeleton is registered as NMR Client and delivers metadata related to Program, Map, and Helper Function to eBPF Runtime.
The approximate flow is as follows.
Native eBPF Driver (.sys)
|
v
DriverEntry
|
v
NMR Client Registration
|
v
Connect with eBPF Runtime
|
v
Transfer Program / Map / Helper Metadata
In other words, NMR can be seen as the Windows Kernel internal connection mechanism used to connect Native eBPF Program and eBPF Runtime.
XDP on Windows
XDP is the abbreviation for eXpress Data Path.
In Linux, the NIC Driver is used to process packets at a very early point in the network stack.
eBPF for Windows also provides the XDP Program Type so that incoming packets can be processed quickly.
The eBPF Program attached to the XDP Hook can look at the packet and make the following decisions.
XDP operation example
- Allow packets
- packet drop
- packet modification
- packet redirect
For example, if you want to prevent the DNS Flood attack, you can inspect the incoming UDP packets at the XDP stage and drop abnormal packets.
This approach makes sense in terms of network defense and performance optimization because packets can be processed before they go up to deeper layers of the network stack.
Socket-related Hook
eBPF for Windows also provides Socket-related Hooks.
Socket-related hooks can be executed when a process attempts to create a network connection or bind a port.
For example, if there is a situation where a process is depleting the port by continuously binding the UDP Port, the eBPF Program can be executed at the bind time to observe or limit the operation.
The approximate flow is as follows.
Process
|
v
socket bind()
|
v
eBPF Hook
|
v
Run eBPF Program
|
v
Allow/Block/Record
This structure allows you to limit usage of certain ports, block network connections under certain conditions, or observe network behavior.
Process-Related Hooks
eBPF for Windows can also handle process creation and termination events through extensions.
Process-related hooks are important from a security perspective.
For example, you can record when a specific process is executed or detect the creation of a process that is suspicious based on policy.
The approximate flow is as follows.
Process Create Event
|
v
Extension Driver
|
v
eBPF Runtime
|
v
Process eBPF Program
|
v
Observation/Policy Judgment
Just as Linux’s eBPF LSM can be used to enforce security policies, Windows can also connect specific OS events to the eBPF Program through an extension.
However, since Windows’ eBPF ecosystem is not as mature as Linux eBPF, available hooks and functions may vary depending on the implementation status on Windows.
HVCI and Native Code Generation
Kernel code integrity is very important in Windows.
HVCI stands for Hypervisor-protected Code Integrity.
When HVCI is enabled, the kernel checks more stringently whether code running has a trusted signature.
Because the JIT method dynamically generates code in the runtime, it may conflict with the HVCI environment.
So Native Code Generation is used in an important way in eBPF for Windows.
Native Code Generation processes eBPF Program in the following flow.
eBPF Bytecode
|
v
Verifier
|
v
C Code Generation
|
v
Create COFF Object
|
v
Create PE Driver
|
v
Driver Signing
|
v
Kernel Load
This method makes the eBPF Program a Windows Driver and follows Windows’ existing Driver Signing model.
In other words, it is a method that allows the eBPF Program to be distributed in a form that fits the Windows security model.
Proof of Verification
Proof of Verification is a concept to check whether the Native eBPF Program was actually created from a verified eBPF Program.
In the native method, eBPF Bytecode is changed to C code and built again with Driver.
The important issue at this time is to check whether the .sys file that is finally loaded is truly created from the eBPF Program that passed the Verifier.
If unverified code is loaded disguised as a native driver, the safety model of eBPF may be broken.
Therefore, Native eBPF Program requires a structure to check whether the program has gone through a verified path through verification results, signatures, and metadata.
In other words, Proof of Verification can be seen as a device for maintaining the trust chain of the Native eBPF Program.
Differences between Linux eBPF and eBPF for Windows
Linux eBPF and eBPF for Windows are based on the same eBPF concept, but their internal implementation is different.
In Linux, eBPF Runtime and Verifier are deeply integrated into the Linux Kernel.
On the other hand, eBPF for Windows provides separate Runtime, Service, Extension Driver, and API Layer to build the eBPF ecosystem on top of the Windows Kernel.
The differences are summarized as follows.
Linux eBPF
- Using eBPF Runtime built into Linux Kernel
- Use Kernel Internal Verifier
- Direct connection to Linux Hook, Tracepoint, Kprobe, XDP, LSM, etc.
- Mature ecosystem exists, including libbpf, bpftool, BCC, etc.
eBPF for Windows
- Use of separate runtime to run eBPF on Windows
- Using PREVAIL Verifier
- Use Extension Driver to connect with Windows Kernel Hook
- Linked with ebpfapi.dll, eBPFSvc.exe, netsh, bpftool, etc.
- Integration with Windows security model through native driver method
In other words, eBPF for Windows is not an exact copy of Linux eBPF, but can be seen as a structure that ports the programming model of eBPF to suit the Windows Kernel environment.
eBPF for Windows Use Cases
eBPF for Windows can be used where operation must be observed or controlled at the kernel level.
Representative application areas are as follows.
- Network packet filtering
- DDoS protection
- Socket connection observation
- Port usage limit
- Observation of process creation and termination
- Apply security policy
- system observation
- Performance analysis
- Network traffic control
Summary
eBPF for Windows is an implementation of the eBPF programming model developed mainly on Linux so that it can be used on Windows.
The developer writes the eBPF Program in C and uses Clang to create a ELF file containing the eBPF Bytecode.
Afterwards, eBPF for Windows verifies the program with Verifier and processes it using Native Driver, JIT, or Interpreter methods depending on the execution method.