eBPF for Windows
eBPF for Windows
- eBPF for Windows: Linux 환경에서 사용되던 eBPF 생태계의 Toolchain 과 API 를 Windows 위에서 사용할 수 있도록 만든 프로젝트
- 기존 eBPF 는 주로 Linux Kernel 에서 사용되었지만, eBPF for Windows 는 Windows Kernel 에서도 eBPF Program 을 로드하고 실행할 수 있도록 중간 계층을 제공한다.
- 즉, 개발자는 기존처럼 C 로 eBPF Program 을 작성하고 Clang 으로 eBPF Bytecode 를 생성할 수 있다.
- 그 후 Windows 환경에서는 해당 Bytecode 를 검증하고, Windows Kernel 에서 실행 가능한 형태로 변환하거나 로드해서 사용한다.
- eBPF for Windows 는 Windows Kernel 자체를 직접 수정하지 않고도 네트워크, 관측, 보안 정책 처리 같은 기능을 동적으로 확장할 수 있게 만드는 것을 목표로 한다.
eBPF for Windows 가 필요한 이유
eBPF 는 원래 Linux Kernel 에서 발전한 기술이다.
Linux 에서는 커널 내부의 특정 지점에 eBPF Program 을 Attach 해서 패킷 처리, 시스템 콜 추적, 성능 관측, 보안 정책 적용 등을 수행할 수 있다.
하지만 Windows 는 Linux 와 커널 구조가 다르다.
Linux 의 eBPF Program 을 그대로 Windows Kernel 에 올릴 수는 없다.
그렇기 때문에 Windows 에서는 다음과 같은 요소들이 필요하다.
- eBPF Bytecode 를 Windows 에서 해석할 수 있는 Runtime
- eBPF Program 을 검증할 수 있는 Verifier
- Windows Kernel 의 Hook 지점과 eBPF Program 을 연결하는 계층
- eBPF Map, Helper Function 같은 eBPF 객체를 Windows 에서 구현하는 계층
- 기존 Linux eBPF Toolchain 과 최대한 비슷한 개발 경험
eBPF for Windows 는 이런 요소들을 제공해서 Windows 에서도 eBPF 기반 프로그래밍 모델을 사용할 수 있게 한다.
eBPF for Windows 의 전체 구조
eBPF for Windows 의 흐름은 대략 다음과 같다.
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 실행
개발자는 일반적으로 C 로 eBPF Program 을 작성한다.
그 후 Clang 을 이용해서 BPF Target 으로 컴파일하면 ELF 형식의 Object File 이 생성된다.
이 ELF 파일 안에는 eBPF Bytecode, Map 정의, Section 정보 등이 들어있다.
Windows 는 이 ELF 파일을 그대로 커널에 올리는 것이 아니라, 먼저 Verifier 를 통해 안전성을 검증한다.
검증을 통과한 Program 은 실행 방식에 따라 Native Windows Driver 로 변환되거나, JIT 또는 Interpreter 방식으로 실행된다.
eBPF Program 작성
eBPF Program 은 일반적으로 C 로 작성된다.
예를 들어 단순히 0 을 반환하는 eBPF Program 이 있다고 하면 다음과 같은 형태가 될 수 있다.
int func()
{
return 0;
}
이 코드는 일반적인 Windows 실행 파일로 컴파일되는 것이 아니라, eBPF ISA 에 맞는 Bytecode 로 컴파일된다.
컴파일은 보통 다음과 같이 한다.
clang -target bpf -O2 -g -Werror -c bpf.c -o bpf.o
여기서 중요한 부분은 -target bpf 이다.
- target bpf 는 Clang 에게 이 코드를 x86-64 나 ARM64 기계어가 아니라 eBPF Bytecode 로 컴파일하라고 알려주는 옵션이다.
컴파일 결과로 생성되는 bpf.o 는 ELF 형식의 Object File 이다.
ELF 와 eBPF Bytecode
eBPF for Windows 에서도 eBPF Program 은 ELF 파일 형태로 다뤄진다.
ELF 파일은 Linux 에서 주로 사용되는 실행 파일 및 Object File 형식이지만, eBPF 생태계에서는 eBPF Bytecode 와 Map 정의를 담는 컨테이너 형식으로 많이 사용된다.
eBPF for Windows 는 이 ELF 파일을 입력으로 받아서 내부에 들어있는 eBPF Program 과 Map 정보를 읽는다.
ELF 파일에는 일반적으로 다음과 같은 정보들이 들어갈 수 있다.
- eBPF Bytecode
- Program Section
- Map 정의
- BTF 정보
- Relocation 정보
- License 정보
eBPF for Windows 는 이런 정보를 읽어서 Program 을 검증하고, Kernel Execution Context 에서 실행 가능한 형태로 준비한다.
Verifier
Verifier 는 eBPF Program 이 커널에서 안전하게 실행될 수 있는지 검사하는 구성 요소이다.
eBPF 는 커널에서 실행될 수 있기 때문에, 잘못된 Program 이 로드되면 커널 메모리를 손상시키거나 시스템 전체를 불안정하게 만들 수 있다.
그래서 eBPF Program 은 실행되기 전에 Verifier 를 통과해야 한다.
Verifier 는 대략 다음과 같은 내용을 검사한다.
- 허용되지 않은 메모리 접근이 있는지
- 포인터를 안전하게 사용하는지
- Program 이 종료될 수 있는지
- Stack 사용이 제한 범위를 넘지 않는지
- Helper Function 호출 규칙을 지키는지
- Program Type 에 맞는 Context 접근만 수행하는지
eBPF for Windows 는 PREVAIL Verifier 를 사용해서 eBPF Program 의 안전성을 분석한다.
PREVAIL 은 Abstract Interpretation 기반의 eBPF Verifier 이다.
즉, Program 을 실제로 실행하지 않고 정적으로 분석해서 해당 Program 이 안전한지 판단한다.
PREVAIL
PREVAIL 은 eBPF Bytecode 를 분석하는 Verifier 이다.
Linux Kernel 에 내장된 Verifier 와 달리, eBPF for Windows 는 Windows 환경에서 eBPF Program 을 검증해야 하기 때문에 별도의 Verifier 를 사용한다.
PREVAIL 은 eBPF Program 의 제어 흐름과 레지스터 상태, 메모리 접근 가능 범위 등을 분석한다.
예를 들어 eBPF Program 이 Packet Buffer 를 읽는다고 하면, Verifier 는 해당 접근이 Packet Boundary 를 넘지 않는지 확인해야 한다.
if (data + sizeof(struct ethhdr) > data_end)
return XDP_DROP;
위와 같은 Boundary Check 가 있으면 Verifier 는 이후의 Packet 접근이 안전하다고 판단할 수 있다.
반대로 Boundary Check 없이 Packet Buffer 를 직접 읽으면, Packet 길이를 넘어선 접근이 될 수 있기 때문에 Verifier 가 거부할 수 있다.
즉, Verifier 는 eBPF Program 이 커널 내부에서 실행되기 전에 최소한의 안전성을 보장하는 역할을 한다.
eBPF for Windows 의 실행 방식
eBPF for Windows 에서는 eBPF Bytecode 를 처리하고 커널에서 실행하기 위한 방식이 여러 가지로 나뉜다.
eBPF for Windows 실행 방식
- Native eBPF Program
- JIT Compiler
- Interpreter
각 방식은 eBPF Bytecode 를 어떻게 실행 가능한 형태로 바꾸는지에 따라 다르다.
Native eBPF Program
Native eBPF Program 은 eBPF Bytecode 를 C 코드로 변환한 뒤, 다시 Windows Driver 형태로 빌드해서 실행하는 방식이다.
이 방식에서는 bpf2c 라는 도구가 사용된다.
대략적인 흐름은 다음과 같다.
eBPF C Program
|
v
Clang
|
v
ELF File
|
v
PREVAIL Verifier
|
v
bpf2c
|
v
Generated C Code
|
v
Windows Driver (.sys)
|
v
Kernel Load
여기서 핵심은 eBPF Program 이 최종적으로 .sys 형태의 Windows Driver 로 만들어진다는 점이다.
Windows 는 커널에서 실행되는 코드에 대해 서명과 무결성 검사를 중요하게 다룬다.
특히 HVCI 같은 보안 기능이 활성화된 환경에서는 동적으로 생성된 커널 코드를 실행하는 JIT 방식이 제한될 수 있다.
그래서 eBPF for Windows 에서는 Native eBPF Program 방식이 중요한 의미를 가진다.
Native 방식은 eBPF Program 을 미리 검증하고, Windows Driver 로 변환한 뒤, 서명된 PE 이미지로 배포할 수 있다.
bpf2c
bpf2c 는 eBPF Bytecode 를 C 코드로 변환하는 도구이다.
이름 그대로 BPF 를 C 로 바꾸는 역할을 한다.
eBPF Bytecode 는 eBPF ISA 의 명령어들로 구성되어 있다.
예를 들어 eBPF 에서는 r0, r1, r2 같은 가상 레지스터를 사용한다.
bpf2c 는 이런 eBPF 명령어들을 하나씩 대응되는 C 코드로 변환한다.
대략적으로는 다음과 같은 변환이 일어난다고 볼 수 있다.
eBPF Register
|
v
C Local Variable
eBPF Instruction
|
v
C Statement
eBPF Helper Call
|
v
Indirect Helper Function Call
즉, eBPF Bytecode 의 각 명령어를 C 문장으로 옮기고, 이 C 코드를 다시 Windows Driver 로 컴파일한다.
이 방식은 단순히 eBPF Program 을 해석해서 실행하는 것이 아니라, Windows 가 이해할 수 있는 Native Code 형태로 만들어서 실행한다는 점에서 중요하다.
JIT Compiler
JIT Compiler 방식은 eBPF Bytecode 를 Runtime 에서 Native Code 로 컴파일해서 실행하는 방식이다.
JIT 는 Just-In-Time Compilation 의 약자이다.
즉, Program 을 로드하는 시점에 eBPF Bytecode 를 실제 CPU 에서 실행 가능한 코드로 변환한다.
이 방식은 Interpreter 보다 빠를 수 있다.
하지만 Windows Kernel 환경에서는 동적으로 생성한 코드를 커널 주소 공간에 배치하고 실행 권한을 부여해야 한다.
이 과정은 Windows 의 코드 무결성 정책과 충돌할 수 있다.
특히 HVCI 가 활성화된 환경에서는 커널에서 동적으로 생성된 실행 코드를 사용하는 방식이 제한될 수 있다.
그래서 eBPF for Windows 에서는 보안적인 이유로 Native eBPF Program 방식이 더 선호된다.
Interpreter
Interpreter 방식은 eBPF Bytecode 를 그대로 읽으면서 명령어 단위로 실행하는 방식이다.
JIT 처럼 Native Code 로 변환하지 않고, Runtime 이 Bytecode 를 하나씩 해석해서 실행한다.
Interpreter 방식은 구현과 디버깅 측면에서는 단순할 수 있다.
하지만 성능이 낮고, 커널 주소 공간 안에서 Interpreter 를 실행하는 것 자체가 보안적으로 부담이 될 수 있다.
그래서 eBPF for Windows 에서는 Interpreter 가 주로 Debug Build 에서만 사용되는 방식으로 다뤄진다.
eBPFSvc.exe
eBPFSvc.exe 는 eBPF for Windows 의 User Mode Service 이다.
eBPF Program 을 로드하거나 관리할 때 User Mode 와 Kernel Mode 사이의 중간 역할을 수행한다.
eBPF for Windows 에서는 User Mode Application 이 직접 Kernel 내부 구조를 조작하는 것이 아니라, API 와 Service 를 통해 Program 을 로드하고 관리한다.
대략적인 흐름은 다음과 같다.
Application / bpftool / netsh
|
v
ebpfapi.dll
|
v
eBPFSvc.exe
|
v
Kernel-mode Execution Context
eBPFSvc.exe 는 Program 로드, Verifier 연동, JIT 처리, 서비스 등록 같은 역할과 관련된다.
즉, eBPF for Windows 에서 User Mode 와 Kernel Mode 사이를 연결하는 관리 계층이라고 볼 수 있다.
ebpfapi.dll
ebpfapi.dll 은 eBPF for Windows 에서 User Mode Application 이 eBPF 기능을 사용할 수 있도록 제공되는 API Library 이다.
Linux eBPF 생태계에서는 libbpf API 를 통해 eBPF Program 을 로드하고 Map 을 조작하는 경우가 많다.
eBPF for Windows 는 기존 eBPF 개발자가 익숙한 API 를 최대한 유지하기 위해 libbpf 와 유사한 API 를 제공한다.
예를 들어 Application 은 ebpfapi.dll 을 통해 다음과 같은 작업을 수행할 수 있다.
- eBPF Program 로드
- eBPF Program Attach
- eBPF Map 생성
- eBPF Map 조회
- eBPF Map 업데이트
- eBPF Link 관리
- Program 정보 조회
즉, ebpfapi.dll 은 User Mode 에서 eBPF 객체를 다루기 위한 진입점이라고 볼 수 있다.
Kernel-mode Execution Context
Kernel-mode Execution Context 는 eBPF Program 이 실제로 실행되는 커널 측 실행 환경이다.
Verifier 를 통과한 eBPF Program 은 이 실행 환경에 로드된다.
그 후 특정 Hook 이 발생하면 eBPF Program 이 호출된다.
예를 들어 XDP Hook 에 eBPF Program 이 Attach 되어 있다면, 네트워크 패킷이 들어오는 시점에 해당 Program 이 실행될 수 있다.
Socket Bind Hook 에 Attach 되어 있다면, 프로세스가 특정 포트에 bind 하려고 할 때 Program 이 실행될 수 있다.
즉, Kernel-mode Execution Context 는 eBPF Program 을 Windows Kernel 안에서 실행하고, Hook 과 Program 을 연결하는 핵심 구성 요소이다.
Hook
Hook 은 eBPF Program 이 실행될 수 있는 커널 내부 지점이다.
eBPF Program 은 아무 때나 실행되는 것이 아니라, 특정 이벤트나 커널 경로에 Attach 되어 실행된다.
예를 들어 다음과 같은 Hook 이 있을 수 있다.
Hook 예시
- Packet receive
- Socket bind
- Socket connect
- Socket accept
- Process create
- Process delete
- Network event
eBPF Program 은 이런 Hook 에 Attach 되어 특정 이벤트가 발생했을 때 실행된다.
이 구조 덕분에 커널 전체를 수정하지 않고도 특정 지점의 동작만 확장하거나 관측할 수 있다.
Program Type
Program Type 은 eBPF Program 이 어떤 종류의 Hook 에 Attach 될 수 있는지를 나타낸다.
eBPF Program 은 Program Type 에 따라 전달받는 Context 와 사용할 수 있는 Helper Function 이 달라질 수 있다.
eBPF for Windows 에서 사용되는 Program Type 은 다음과 같은 것들이 있다.
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 는 들어오는 패킷을 빠르게 처리하기 위한 Program Type 이다.
BPF_PROG_TYPE_BIND 는 Socket bind 작업을 처리하기 위한 Program Type 이다.
BPF_PROG_TYPE_CGROUP_SOCK_ADDR 는 connect, accept, bind 같은 Socket 주소 기반 작업을 처리하기 위한 Program Type 이다.
BPF_PROG_TYPE_SOCK_OPS 는 Socket 관련 이벤트를 처리하기 위한 Program Type 이다.
BPF_PROG_TYPE_NETEVENT 는 네트워크 이벤트를 처리하기 위한 Program Type 이다.
BPF_PROG_TYPE_PROCESS 는 프로세스 생성이나 종료 같은 이벤트를 처리하기 위한 Program Type 이다.
BPF_PROG_TYPE_SAMPLE 은 주로 테스트나 샘플 확장에서 사용되는 Program Type 이다.
Attach Type
Attach Type 은 Program Type 보다 더 구체적으로 어느 지점에 Program 을 붙일지 나타낸다.
예를 들어 같은 Socket 관련 Program 이라도 connect 시점에 붙을 수도 있고, bind 시점에 붙을 수도 있다.
대표적인 Attach Type 은 다음과 같다.
Attach Type 예시
- 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
Program Type 이 Program 의 종류를 나타낸다면, Attach Type 은 실제로 붙는 위치를 나타낸다고 볼 수 있다.
eBPF Map
eBPF Map 은 eBPF Program 과 User Mode Application 이 데이터를 공유하기 위해 사용하는 Key-Value 저장소이다.
eBPF Program 은 커널에서 실행되고, User Mode Application 은 일반 사용자 공간에서 실행된다.
두 환경은 메모리 공간이 분리되어 있기 때문에 직접 데이터를 공유할 수 없다.
이때 eBPF Map 을 사용하면 커널에서 실행되는 Program 과 User Mode Application 이 안전하게 데이터를 주고받을 수 있다.
예를 들어 특정 프로세스가 사용한 포트 개수를 기록하고 싶다면, eBPF Program 은 Map 에 프로세스 ID 와 포트 사용량을 저장할 수 있다.
User Mode Application 은 이 Map 을 읽어서 현재 상태를 확인할 수 있다.
eBPF Program
|
v
eBPF Map
^
|
User Mode Application
eBPF for Windows 에서도 Hash Map, Array Map, LRU Hash, Ring Buffer 같은 여러 Map Type 이 제공된다.
Helper Function
Helper Function 은 eBPF Program 이 커널 기능을 안전하게 사용하기 위해 호출하는 함수이다.
eBPF Program 은 일반적인 커널 함수를 마음대로 호출할 수 없다.
만약 eBPF Program 이 임의의 커널 함수를 호출할 수 있다면, Verifier 가 안전성을 보장하기 어렵고 커널 안정성도 깨질 수 있다.
그래서 eBPF Runtime 은 Program Type 별로 허용된 Helper Function 을 제공한다.
eBPF Program 은 이 Helper Function 을 통해 제한된 기능만 사용할 수 있다.
예를 들어 다음과 같은 Helper Function 이 있을 수 있다.
Helper Function 예시
- 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
즉, Helper Function 은 eBPF Program 이 커널 기능을 사용할 수 있게 해주면서도, 허용된 범위 안에서만 동작하도록 제한하는 인터페이스이다.
Extension Driver
eBPF for Windows 에서는 Hook 과 Helper 를 확장하기 위해 Extension Driver 구조를 사용한다.
Windows Kernel 은 Linux Kernel 과 내부 구조가 다르기 때문에, Linux 에 존재하는 Hook 을 그대로 Windows 에 복사할 수 없다.
그래서 Windows 의 특정 커널 기능을 eBPF Program 에 노출하려면, 해당 기능을 eBPF Runtime 과 연결하는 Extension 이 필요하다.
Extension Driver 는 대략 다음과 같은 역할을 한다.
- 새로운 Program Type 제공
- 새로운 Attach Type 제공
- 새로운 Helper Function 제공
- Windows Kernel 이벤트를 eBPF Runtime 에 전달
- eBPF Program 의 반환값을 바탕으로 정책 적용
예를 들어 프로세스 생성 이벤트를 eBPF 로 처리하고 싶다면, 프로세스 생성 이벤트를 감지하고 이를 eBPF Program 호출로 연결하는 Extension 이 필요하다.
즉, eBPF for Windows 에서 Extension Driver 는 Windows Kernel 기능을 eBPF 생태계에 노출하는 어댑터 역할을 한다.
NMR
NMR 은 Network Module Registrar 의 약자이다.
Windows 에서는 커널 컴포넌트들이 서로 인터페이스를 등록하고 연결하기 위해 NMR 구조를 사용할 수 있다.
eBPF for Windows 의 Native Driver 구조에서도 NMR 이 사용된다.
Native eBPF Program 으로 생성된 .sys 파일은 로드될 때 DriverEntry 를 통해 초기화된다.
이때 Driver Skeleton 은 NMR Client 로 등록되어 eBPF Runtime 에 Program, Map, Helper Function 관련 Metadata 를 전달한다.
대략적인 흐름은 다음과 같다.
Native eBPF Driver (.sys)
|
v
DriverEntry
|
v
NMR Client 등록
|
v
eBPF Runtime 과 연결
|
v
Program / Map / Helper Metadata 전달
즉, NMR 은 Native eBPF Program 과 eBPF Runtime 이 서로 연결되는 데 사용되는 Windows Kernel 내부 연결 메커니즘이라고 볼 수 있다.
XDP on Windows
XDP 는 eXpress Data Path 의 약자이다.
Linux 에서는 NIC Driver 또는 네트워크 스택의 매우 이른 지점에서 패킷을 처리하기 위해 사용된다.
eBPF for Windows 에서도 XDP Program Type 을 제공해서 들어오는 패킷을 빠르게 처리할 수 있도록 한다.
XDP Hook 에 Attach 된 eBPF Program 은 패킷을 보고 다음과 같은 결정을 내릴 수 있다.
XDP 동작 예시
- 패킷 허용
- 패킷 드롭
- 패킷 수정
- 패킷 리다이렉트
예를 들어 DNS Flood 공격을 막고 싶다면, 들어오는 UDP 패킷을 XDP 단계에서 검사하고 비정상 패킷을 Drop 할 수 있다.
이 방식은 패킷이 네트워크 스택의 더 깊은 계층까지 올라가기 전에 처리할 수 있기 때문에, 네트워크 방어와 성능 최적화 측면에서 의미가 있다.
Socket 관련 Hook
eBPF for Windows 는 Socket 관련 Hook 도 제공한다.
Socket 관련 Hook 은 프로세스가 네트워크 연결을 만들거나 포트를 바인딩하려고 할 때 실행될 수 있다.
예를 들어 어떤 프로세스가 UDP Port 를 계속 bind 해서 포트를 고갈시키는 상황이 있다고 하면, eBPF Program 은 bind 시점에 실행되어 해당 작업을 관측하거나 제한할 수 있다.
대략적인 흐름은 다음과 같다.
Process
|
v
socket bind()
|
v
eBPF Hook
|
v
eBPF Program 실행
|
v
허용 / 차단 / 기록
이 구조를 사용하면 특정 포트 사용량을 제한하거나, 특정 조건의 네트워크 연결을 차단하거나, 네트워크 동작을 관측할 수 있다.
Process 관련 Hook
eBPF for Windows 는 Extension 을 통해 프로세스 생성 및 종료 이벤트도 다룰 수 있다.
프로세스 관련 Hook 은 보안 관점에서 중요하다.
예를 들어 특정 프로세스가 실행될 때 이를 기록하거나, 정책적으로 의심스러운 프로세스 생성을 감지할 수 있다.
대략적인 흐름은 다음과 같다.
Process Create Event
|
v
Extension Driver
|
v
eBPF Runtime
|
v
Process eBPF Program
|
v
관측 / 정책 판단
Linux 의 eBPF LSM 이 보안 정책 집행에 활용될 수 있는 것처럼, Windows 에서도 Extension 을 통해 특정 OS 이벤트를 eBPF Program 으로 연결할 수 있다.
다만 Windows 의 eBPF 생태계는 Linux eBPF 만큼 오래 성숙한 구조는 아니기 때문에, 사용 가능한 Hook 과 기능은 Windows 쪽 구현 상태에 따라 달라질 수 있다.
HVCI 와 Native Code Generation
Windows 에서는 커널 코드 무결성이 매우 중요하다.
HVCI 는 Hypervisor-protected Code Integrity 의 약자이다.
HVCI 가 활성화되면 커널에서 실행되는 코드가 신뢰할 수 있는 서명을 가지고 있는지 더 엄격하게 확인한다.
JIT 방식은 Runtime 에서 동적으로 코드를 생성하기 때문에, HVCI 환경과 충돌할 수 있다.
그래서 eBPF for Windows 에서는 Native Code Generation 이 중요한 방식으로 사용된다.
Native Code Generation 은 eBPF Program 을 다음과 같은 흐름으로 처리한다.
eBPF Bytecode
|
v
Verifier
|
v
C Code 생성
|
v
COFF Object 생성
|
v
PE Driver 생성
|
v
Driver Signing
|
v
Kernel Load
이 방식은 eBPF Program 을 Windows Driver 로 만들어서 Windows 의 기존 Driver Signing 모델을 따르게 한다.
즉, eBPF Program 을 Windows 보안 모델에 맞는 형태로 배포할 수 있게 하는 방식이다.
Proof of Verification
Proof of Verification 은 Native eBPF Program 이 실제로 검증된 eBPF Program 에서 생성되었는지 확인하기 위한 개념이다.
Native 방식에서는 eBPF Bytecode 를 C 코드로 바꾸고 다시 Driver 로 빌드한다.
이때 중요한 문제는 최종적으로 로드되는 .sys 파일이 정말 Verifier 를 통과한 eBPF Program 에서 생성된 것인지 확인하는 것이다.
만약 검증되지 않은 코드가 Native Driver 로 위장해서 로드된다면, eBPF 의 안전성 모델이 깨질 수 있다.
그래서 Native eBPF Program 에서는 검증 결과와 서명, Metadata 를 통해 해당 Program 이 검증된 경로를 거쳤는지 확인하는 구조가 필요하다.
즉, Proof of Verification 은 Native eBPF Program 의 신뢰 체인을 유지하기 위한 장치라고 볼 수 있다.
Linux eBPF 와 eBPF for Windows 의 차이
Linux eBPF 와 eBPF for Windows 는 같은 eBPF 개념을 기반으로 하지만, 내부 구현은 다르다.
Linux 에서는 eBPF Runtime 과 Verifier 가 Linux Kernel 안에 깊게 통합되어 있다.
반면 eBPF for Windows 는 Windows Kernel 위에 eBPF 생태계를 올리기 위해 별도의 Runtime, Service, Extension Driver, API Layer 를 제공한다.
차이를 정리하면 다음과 같다.
Linux eBPF
- Linux Kernel 에 내장된 eBPF Runtime 사용
- Kernel 내부 Verifier 사용
- Linux Hook, Tracepoint, Kprobe, XDP, LSM 등과 직접 연결
- libbpf, bpftool, BCC 등 성숙한 생태계 존재
eBPF for Windows
- Windows 위에서 eBPF 를 실행하기 위한 별도 Runtime 사용
- PREVAIL Verifier 사용
- Windows Kernel Hook 과 연결하기 위해 Extension Driver 사용
- ebpfapi.dll, eBPFSvc.exe, netsh, bpftool 등과 연동
- Native Driver 방식으로 Windows 보안 모델과 통합
즉, eBPF for Windows 는 Linux eBPF 를 그대로 복사한 것이 아니라, eBPF 의 프로그래밍 모델을 Windows Kernel 환경에 맞게 이식한 구조라고 볼 수 있다.
eBPF for Windows 의 활용
eBPF for Windows 는 커널 수준에서 동작을 관측하거나 제어해야 하는 곳에 활용될 수 있다.
대표적인 활용 분야는 다음과 같다.
- 네트워크 패킷 필터링
- DDoS 방어
- Socket 연결 관측
- Port 사용량 제한
- Process 생성 및 종료 관측
- 보안 정책 적용
- 시스템 관측
- 성능 분석
- 네트워크 트래픽 제어
정리
eBPF for Windows 는 Linux 중심으로 발전한 eBPF 프로그래밍 모델을 Windows 에서 사용할 수 있도록 만든 구현체이다.
개발자는 C 로 eBPF Program 을 작성하고, Clang 을 이용해 eBPF Bytecode 가 들어있는 ELF 파일을 생성한다.
그 후 eBPF for Windows 는 해당 Program 을 Verifier 로 검증하고, 실행 방식에 따라 Native Driver, JIT, Interpreter 방식으로 처리한다.