Practical Use of eBPF in Security Tool

While working with several forensic tools such as Kunai, Jibril, and Tracee, I encountered a recurring term: eBPF. This led me to explore what eBPF actually is, how it operates, and why it plays such a significant role in these tools. The following writing is a summary of my findings and insights into eBPF.
So, what is eBPF? and what is it used for?
eBPF stands for extended Berkeley Packet Filter, which is a version of BPF with expanded functionality. What does extended mean here? Previously, BPF was used only for network packet filtering, such as performing tcpdump, wireshark, and similar tools. Now, eBPF is like a “small VM” that hooks into the kernel, which is not limited to just filtering network packets, but also syscalls, cgroup hooks for resource control, tracepoints, user space functions (uprobes), kprobes, XDP Hook, and others.

The mechanism of the illustration above is depicted as follows: an eBPF program is written in the C language and compiled by Clang + LLVM. The result of the compilation is eBPF bytecode, which is inserted into the kernel via the bpf() syscall and passes through JIT + Verifier to check the control flow so that the code can be executed in kernel space. Then, it will be executed at the netdevice level (XDP hook) or in the traffic ingress/egress path (TC hook). Subsequently, each event triggered by the eBPF program will be stored in MAPS for storing data and processing it, for example, calculating packets from incoming IPs, so that it can be read at the user space level, which can be in the form of observation results, logging, security alerts, etc.
cat /sys/kernel/security/lsm
If there is no bpf in it then we can check whether the kernel supports it :
grep BPF_LSM /boot/config-$(uname -r)
If the result is like the image below, it means the kernel supports activating BPF:

GRUB_CMDLINE_LINUX_DEFAULT=”lsm=lockdown,capability,landlock,yama,apparmor,bpf”

After that, we can update grub, reboot, and recheck.
sudo update-grub
reboot
For implementation information on BPF, limitations exist on xlated size, instruction limit (jited), and memlock. Here, I’m taking a sample from the kunai tools, which utilizes kprobe and tracepoint hook to explore kunai program events, which can be seen on the Kunai Event page. Information related to the respective sizes of xlated, jited, memlock, and maps_id is obtained for each program loaded, which needs to be considered when running more than one eBPF-based tool.

There are 4 types of hooks typically used for tracing :
Tracepoints, static hooks that have been inserted into the kernel’s source code.
Kprobes, dynamic hooks that track the execution of kernel functions in real-time.
fentry/fexit, hooks used to trace the entry and exit of kernel functions, which is useful in profiling (observing how long a specific function runs) or debugging (observing the input or output of kernel functions).
Uprobes, hooks that function in tracking user behavior, such as input to an application.
USDT Hooks, static tracepoints in user-space applications, for example, the Java runtime, which has USDT tracepoints for making query executions.
To see the capabilities of ebpf in security tools, I will show telemetry results by triggering events from the following 3 security tools with this malware sample, while monitoring activity for anomalies in /usr/bin/bsd-port/knerl.
- Jibril
Telemetry on Jibril is able to provide a range of information related to the os_status_fingerprint event , from which information is obtained that the kernel is masquerading by pretending to be systemd, and is communicating outbound to 208.98.40.40:45000 (360.baidu.com.9kpk.com).
{"uuid": "0ff7c99fe7af993ed147c212382a16498d9f884231167dccd898fb4b96bb4d77",
"timestamp": "2025-09-18T19:50:53Z",
"note": "Last os_status_fingerprint_high_3 for the same ancestry.",
"metadata": {
"kind": "os_status_fingerprint",
"name": "os_status_fingerprint_high_3",
"format": "file_access",
"version": "1.0",
"description": "OS status fingerprint",
"importance": "high",
"documentation": "https://garnet.gitbook.io/jibril/detections/file-access/os_status_fingerprint",
"tactic": "discovery",
"technique": "system_information_discovery",
"subtechnique": "none"
},
"background": {
"files": {
"root": {
"path": "/",
"dirs": [
{
"path": "/etc",
"base": "etc",
"dirs": [
{
"path": "/etc/init.d",
"base": "init.d",
"files": [
{
"path": "/etc/init.d/selinux",
"base": "selinux",
"actions": [
"open",
"write",
"close"
],
"mode": "rwxr-xr-x",
"owner": {
"uid": 0,
"gid": 0
},
"metadata": {
"size": 36,
"access": "2025-09-18 19:50:51",
"change": "2025-09-18 19:50:51",
"creation": "2025-09-18 19:50:51"
}
}
]
}
]
},
{
"path": "/proc",
"base": "proc",
"dirs": [
{
"path": "/proc/1656",
"base": "1656",
"dirs": [
{
"path": "/proc/1656/net",
"base": "net",
"files": [
{
"path": "/proc/1656/net/arp",
"base": "arp",
"actions": [
"open",
"read",
"close"
],
"mode": "r--r--r--",
"owner": {
"uid": 0,
"gid": 0
},
"metadata": {
"size": 0,
"access": "2025-09-18 19:50:51",
"change": "2025-09-18 19:50:51",
"creation": "2025-09-18 19:50:51"
}
},
{
"path": "/proc/1656/net/dev",
"base": "dev",
"actions": [
"open",
"read",
"close"
],
"mode": "r--r--r--",
"owner": {
"uid": 0,
"gid": 0
},
"metadata": {
"size": 0,
"access": "2025-09-18 19:50:52",
"change": "2025-09-18 19:50:52",
"creation": "2025-09-18 19:50:52"
}
},
{
"path": "/proc/1656/net/route",
"base": "route",
"actions": [
"open",
"read",
"close"
],
"mode": "r--r--r--",
"owner": {
"uid": 0,
"gid": 0
},
"metadata": {
"size": 0,
"access": "2025-09-18 19:50:51",
"change": "2025-09-18 19:50:51",
"creation": "2025-09-18 19:50:51"
}
}
]
}
]
}
],
"files": [
{
"path": "/proc/cpuinfo",
"base": "cpuinfo",
"actions": [
"open",
"read",
"close"
],
"mode": "r--r--r--",
"owner": {
"uid": 0,
"gid": 0
},
"metadata": {
"size": 0,
"access": "2025-09-18 17:47:21",
"change": "2025-09-18 17:47:21",
"creation": "2025-09-18 17:47:21"
}
},
{
"path": "/proc/meminfo",
"base": "meminfo",
"actions": [
"open",
"read",
"close"
],
"mode": "r--r--r--",
"owner": {
"uid": 0,
"gid": 0
},
"metadata": {
"size": 0,
"access": "2025-09-18 17:47:22",
"change": "2025-09-18 17:47:22",
"creation": "2025-09-18 17:47:22"
}
},
{
"path": "/proc/stat",
"base": "stat",
"actions": [
"open",
"read",
"close"
],
"mode": "r--r--r--",
"owner": {
"uid": 0,
"gid": 0
},
"metadata": {
"size": 0,
"access": "2025-09-18 17:47:26",
"change": "2025-09-18 17:47:26",
"creation": "2025-09-18 17:47:26"
}
}
]
},
{
"path": "/run",
"base": "run",
"dirs": [
{
"path": "/run/systemd",
"base": "systemd",
"dirs": [
{
"path": "/run/systemd/resolve",
"base": "resolve",
"files": [
{
"path": "/run/systemd/resolve/stub-resolv.conf",
"base": "stub-resolv.conf",
"actions": [
"open",
"read",
"close"
],
"mode": "rw-r--r--",
"owner": {
"uid": 102,
"gid": 103
},
"metadata": {
"size": 930,
"access": "2025-09-18 18:46:09",
"change": "2025-09-18 18:46:09",
"creation": "2025-09-18 18:46:09"
}
}
]
}
]
}
]
},
{
"path": "/usr",
"base": "usr",
"dirs": [
{
"path": "/usr/bin",
"base": "bin",
"dirs": [
{
"path": "/usr/bin/bsd-port",
"base": "bsd-port",
"files": [
{
"path": "/usr/bin/bsd-port/conf.n",
"base": "conf.n",
"actions": [
"open",
"write",
"close"
],
"mode": "rw-r--r--",
"owner": {
"uid": 0,
"gid": 0
},
"metadata": {
"size": 69,
"access": "2025-09-18 19:50:51",
"change": "2025-09-18 19:50:51",
"creation": "2025-09-18 19:50:51"
}
},
{
"path": "/usr/bin/bsd-port/knerl.conf",
"base": "knerl.conf",
"actions": [
"open",
"write",
"truncate",
"close"
],
"mode": "rwxr-xr-x",
"owner": {
"uid": 0,
"gid": 0
},
"metadata": {
"size": 4,
"access": "2025-09-18 19:50:50",
"change": "2025-09-18 19:50:50",
"creation": "2025-09-18 19:50:50"
}
}
]
}
]
}
]
}
]
}
},
"flows": {
"ip_version": 4,
"protocols": [
{
"proto": "TCP",
"pairs": [
{
"nodes": {
"local": {
"address": "192.168.228.150",
"name": "192.168.228.150",
"names": [
"192.168.228.150"
]
},
"remote": {
"address": "208.98.40.40",
"name": "360.baidu.com.9kpk.com",
"names": [
"208.98.40.40",
"360.baidu.com.9kpk.com"
]
}
},
"port_matrix": [
{
"src_port": 48608,
"dst_port": 45000,
"phase": {
"direction": "egress",
"status": "ongoing"
}
}
]
}
]
},
{
"proto": "UDP",
"pairs": [
{
"nodes": {
"local": {
"address": "127.0.0.1",
"name": "localhost",
"names": [
"127.0.0.1",
"localhost"
]
},
"remote": {
"address": "127.0.0.53",
"name": "localhost",
"names": [
"127.0.0.53",
"localhost"
]
}
},
"port_matrix": [
{
"src_port": 36111,
"dst_port": 53,
"phase": {
"direction": "both",
"status": "ongoing"
}
}
]
}
]
}
]
},
"ancestry": [
{
"start": "2025-09-18T17:48:03Z",
"exit": "running",
"retcode": 0,
"uid": 0,
"pid": 1,
"ppid": 0,
"comm": "systemd",
"cmd": "systemd",
"exe": "/usr/lib/systemd/systemd",
"args": "/sbin/init",
"envs": "BOOT_IMAGE=/boot/vmlinuz-5.15.0-153-generic HOME=/ NETWORK_SKIP_ENSLAVED= PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PWD=/ TERM=linux drop_caps= init=/sbin/init rootmnt=/root"
},
{
"start": "2025-09-18T19:50:50Z",
"exit": "running",
"retcode": 0,
"uid": 0,
"pid": 1656,
"ppid": 1,
"comm": "knerl",
"cmd": "systemd",
"exe": "/usr/lib/systemd/systemd",
"args": "/sbin/init",
"envs": "BOOT_IMAGE=/boot/vmlinuz-5.15.0-153-generic HOME=/ NETWORK_SKIP_ENSLAVED= PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PWD=/ TERM=linux drop_caps= init=/sbin/init rootmnt=/root"
}
]
},
"file": {
"path": "/proc/meminfo",
"dir": "/proc",
"basename": "meminfo",
"type": "regular",
"owner": {
"uid": 0,
"gid": 0
},
"actions": {
"actions": [
"open",
"read",
"close"
],
"open": true,
"read": true,
"write": false,
"exec": false,
"create": false,
"unlink": false,
"rename": false,
"link": false,
"truncate": false,
"fsync": false,
"flock": false,
"mmap": false,
"close": true,
"async": false,
"seek": false
},
"permissions": {
"mode": "r--r--r--",
"owner_read": true,
"owner_write": false,
"owner_exec": false,
"group_read": true,
"group_write": false,
"group_exec": false,
"other_read": true,
"other_write": false,
"other_exec": false
},
"special": {
"setuid": false,
"setgid": false,
"sticky": false
},
"metadata": {
"size": 0,
"access": "2025-09-18 17:47:22",
"change": "2025-09-18 17:47:22",
"creation": "2025-09-18 17:47:22"
}
}
}
- Tracee
Telemetry in tracee shows that the security_socket_connect event by utilizing kprobe hook to indicates the kernel successfully established two-way communication with IP 208.98.40.40:45000.

{"timestamp":1758226733894019269,"threadStartTime":1758224411597134184,"processorId":3,"processId":4655,"cgroupId":6311,"threadId":4708,"parentProcessId":1,"hostProcessId":4655,"hostThreadId":4708,"hostParentProcessId":1,"userId":0,"mountNamespace":4026531841,"pidNamespace":4026531836,"processName":"knerl","executable":{"path":""},"hostName":"tracee","containerId":"","container":{},"kubernetes":{},"eventId":"736","eventName":"security_socket_connect","matchedPolicies":[""],"argsNum":3,"returnValue":0,"syscall":"socketcall","stackAddresses":null,"contextFlags":{"containerStarted":false,"isCompat":true},"threadEntityId":736220747,"processEntityId":2871376327,"parentEntityId":2615671859,"args":[{"name":"sockfd","type":"int","value":4},{"name":"type","type":"int","value":1},{"name":"remote_addr","type":"struct sockaddr*","value":{"sa_family":"AF_INET","sin_addr":"208.98.40.40","sin_port":"45000"}}]}
- Kunai
Telemetry generated by kunai shows a connect event where systemd spawns a knerl process, and knerl successfully establishes a connection with 208.98.40.40:45000, which is indicated as a C2 server.

{
"data": {
"ancestors": "/usr/lib/systemd/systemd",
"command_line": "/usr/bin/bsd-port/knerl",
"exe": {
"path": "/usr/bin/bsd-port/knerl"
},
"socket": {
"domain": "AF_INET",
"type": "SOCK_STREAM",
"proto": "TCP"
},
"src": {
"ip": "192.168.228.142",
"port": 47892
},
"dst": {
"hostname": "?",
"ip": "208.98.40.40",
"port": 45000,
"public": true,
"is_v6": false
},
"community_id": "1:gvs3lauRA5y1qbUURT+vbi8Nc5E=",
"connected": true
},
"info": {
"host": {
"uuid": "21326639-3fba-5b66-ac5e-59538ca37e83",
"name": "kunai",
"container": null
},
"event": {
"source": "kunai",
"id": 60,
"name": "connect",
"uuid": "811384eb-f394-b1ec-01f8-21ccd02fc16e",
"batch": 1632
},
"task": {
"name": "knerl",
"pid": 3004,
"tgid": 2939,
"guuid": "5b049c24-9605-0000-8920-97ec7b0b0000",
"uid": 0,
"user": "root",
"gid": 0,
"group": "root",
"namespaces": {
"mnt": 4026531841
},
"flags": "0x400040",
"zombie": false
},
"parent_task": {
"name": "systemd",
"pid": 1,
"tgid": 1,
"guuid": "775d921f-0000-0000-8920-97ec01000000",
"uid": 0,
"user": "root",
"gid": 0,
"group": "root",
"namespaces": {
"mnt": 4026531841
},
"flags": "0x400100",
"zombie": false
},
"utc_time": "2025-09-18T19:33:03.644619447Z"
}
}
Based on the mechanisms and explanations above, it is clear that some security tools with functions for forensics, monitoring, and observability utilize the capabilities of eBPF because the program runs without needing to directly modify the kernel. It has the flexibility to perform security analysis because it can capture several hook points such as kprobes, uprobes, tracepoints, XDP and etc.
Source :





