---
title: "Kernel-Level Packet Sniffing with eBPF/XDP: Real-Time Credential Extraction and Exposure Auditing"
description: "How eBPF and XDP bypass userspace packet-capture bottlenecks to intercept authentication handshakes, NetNTLMv2 hashes, and cleartext credentials at line rate: architecture, protocol dissectors, and exposure auditing with driftnet2."
date: 2026-11-09
author: Sevban Dönmez (@jankesec)
canonical: https://jankesec.com/posts/driftnet2-ebpf-xdp-network-sniffing/
---

## The bottleneck of userspace packet capture

Traditional network sniffing tools—such as `tcpdump`, `dsniff`, and legacy `driftnet`—were designed for an era when 100 Mbps or 1 Gbps networks were the ceiling. On modern 10 Gbps and 40 Gbps enterprise backbones, traditional packet capture architectures fail.

The core problem is the journey a packet takes through the Linux network stack:

1. **NIC Interrupt:** The network interface card receives a frame and triggers a hard interrupt (IRQ).
2. **SKB Allocation:** The kernel allocates a `struct sk_buff` (socket buffer) in memory. This allocation incurs lock contention, cache misses, and significant memory overhead.
3. **Network Stack Traversal:** The packet moves up through IP tables, firewall hooks, routing tables, and transport layers.
4. **Userspace Copy:** The packet reaches the raw socket or `libpcap` buffer, forcing a context switch and a memory copy from kernel space to userspace.

Under sustained gigabit traffic, the CPU spends most of its cycles allocating and freeing `sk_buff` structures. When packet arrival rates exceed the userspace consumer's processing capacity, socket receive buffers overflow and **packets are silently dropped**. For red-team operators, dropped packets mean missed authentication handshakes; for defensive teams, they mean blind spots in network visibility.

This is where **eBPF (Extended Berkeley Packet Filter)** and **XDP (eXpress Data Path)** rewrite the architecture.

## The dual-path architecture of driftnet2

To eliminate packet drops and extract credentials at line rate, we developed [`driftnet2`](https://github.com/jankesec/driftnet2). It attaches an eBPF program directly to the network driver via XDP:

<figure class="diagram">
<svg viewBox="0 0 760 220" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="driftnet2 eBPF/XDP dual-path network capture architecture.">
<defs><marker id="dn2-arrow" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L8,4 L0,8 z" class="dg-arrow-crit" /></marker></defs>
<text x="0" y="16" class="dg-muted">KERNEL-LEVEL EBPF/XDP SNIFFING · ZERO-COPY DUAL-PATH INGESTION</text>
<rect x="0" y="40" width="220" height="120" rx="8" class="dg-box" />
<text x="14" y="66" class="dg-label">1 · Ingress Frame</text>
<text x="14" y="88" class="dg-mono dg-compact">NIC Driver Layer</text>
<text x="14" y="108" class="dg-muted">Raw Ethernet Ingress</text>
<line x1="220" y1="100" x2="262" y2="100" class="dg-line-crit" marker-end="url(#dn2-arrow)" />
<rect x="266" y="40" width="230" height="120" rx="8" class="dg-box-accent" />
<text x="280" y="66" class="dg-label">2 · eBPF / XDP Hook</text>
<text x="280" y="88" class="dg-mono dg-compact">In-Kernel L4 Filter</text>
<text x="280" y="108" class="dg-muted">Drops Non-Target Traffic</text>
<line x1="496" y1="100" x2="538" y2="100" class="dg-line-crit" marker-end="url(#dn2-arrow)" />
<rect x="542" y="40" width="218" height="120" rx="8" class="dg-box-danger" />
<text x="556" y="66" class="dg-label">3 · Userspace Engine</text>
<text x="556" y="88" class="dg-mono dg-compact">9 Protocol Parsers</text>
<text x="556" y="108" class="dg-muted">NTLMv2 & Audit Report</text>
</svg>
<figcaption>driftnet2 capture pipeline: in-kernel eBPF/XDP filtering drops irrelevant packets at line rate, feeding bounded records to userspace protocol parsers.</figcaption>
</figure>

### 1. In-kernel XDP filtering (`xdp_sniff.c`)

XDP executes verified bytecode at the lowest possible layer—inside the network device driver before the kernel allocates an `sk_buff`.

The eBPF program parses the Layer 3 and Layer 4 headers directly in the receive ring buffer. If the packet does not match target authentication ports (such as TCP 80, 445, 389, 21, 23, 110, 143, 587 or UDP 53), the hook returns `XDP_PASS` immediately. Only candidate packets are copied into a high-performance **BPF Ring Buffer** (`BPF_MAP_TYPE_RINGBUF`):

```c
// bpf/xdp_sniff.c: Line-rate L4 port filtering
SEC("xdp")
int xdp_credential_filter(struct xdp_md *ctx) {
    void *data = (void *)(long)ctx->data;
    void *data_end = (void *)(long)ctx->data_end;

    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end)
        return XDP_PASS;

    if (eth->h_proto != bpf_htons(ETH_P_IP))
        return XDP_PASS;

    struct iphdr *ip = (void *)(eth + 1);
    if ((void *)(ip + 1) > data_end)
        return XDP_PASS;

    if (ip->protocol == IPPROTO_TCP) {
        struct tcphdr *tcp = (void *)ip + (ip->ihl * 4);
        if ((void *)(tcp + 1) > data_end)
            return XDP_PASS;

        __u16 dport = bpf_ntohs(tcp->dest);
        __u16 sport = bpf_ntohs(tcp->source);

        // Filter: HTTP(80), SMB(445), LDAP(389), FTP(21), Telnet(23), Mail(110,143,587)
        if (is_target_port(dport) || is_target_port(sport)) {
            submit_to_ringbuf(ctx, ip, tcp);
        }
    }
    return XDP_PASS;
}
```

By filtering at driver level, the userspace engine only processes packets likely to contain credentials, reducing CPU consumption by up to 90% during high-throughput saturation.

### 2. Dual-path ingestion & portability

If the target machine does not support native XDP (or when running on macOS), `driftnet2` automatically falls back to an optimized `libpcap` capture loop. Both capture paths feed into the same stream of protocol parsers.

## Dissecting credentials across 9 protocol engines

`driftnet2` includes zero-dependency protocol dissectors written specifically for credential and hash extraction:

| Protocol | Default Ports | Credential Type & Technique | Hashcat Mode | Output Format |
| :--- | :---: | :--- | :---: | :--- |
| **SMB / CIFS** | `445, 139` | NTLMv2 Challenge/Response handshake reconstruction | `5600` | `username::domain:server_challenge:ntproof:blob` |
| **LDAP** | `389` | ASN.1 BER parsing of Simple Bind requests | — | `bind_dn:password` |
| **HTTP** | `80, 8080` | Basic Auth, Form POST fields, Bearer tokens, cookies | — | `user:password` or `Bearer <jwt>` |
| **FTP** | `21` | Cleartext `USER` / `PASS` command sequencing | — | `user:password` |
| **Telnet** | `23` | Interactive session and keystroke reassembly | — | `login:password` |
| **POP3 / IMAP**| `110, 143` | `USER`/`PASS`, `AUTH PLAIN`, and `LOGIN` commands | — | `user:password` |
| **SMTP** | `25, 587` | `AUTH LOGIN` (Base64) and `AUTH PLAIN` exchanges | — | `user:password` |
| **DNS** | `53` | Shannon entropy scoring for covert tunnel detection | — | `subdomain.c2.example.com` |

### Extracting NetNTLMv2 without relay proxies

During an SMB authentication handshake, the client and server exchange three messages:
1. **Type 1 (Negotiate):** The client advertises NTLM capabilities.
2. **Type 2 (Challenge):** The server returns an 8-byte server challenge.
3. **Type 3 (Authenticate):** The client returns the domain, username, an NTProofStr HMAC, and a variable-length target blob containing timestamps and client nonces.

Traditional tools (like Responder) act as rogue servers to force this exchange. `driftnet2`, however, captures this handshake **passively from legitimate traffic**. The dissector correlates Type 2 challenges with Type 3 responses across active TCP sessions, extracting the exact tokens required for cracking:

```text
# Hashcat Mode 5600 NetNTLMv2 output emitted by driftnet2:
CORP\jsmith::CORP-DC:1122334455667788:AABBCCDDEEFF00112233445566778899:0101000000000000...
```

The resulting hash can be fed directly to `hashcat -m 5600 hashes.txt /path/to/wordlist.txt` without post-processing or manual formatting.

### Covert DNS tunnel detection

Adversaries frequently use DNS tunneling (tools like `dnscat2` or `iodine`) to bypass firewall egress restrictions. `driftnet2` evaluates every DNS query against a Shannon entropy threshold:

$$H(X) = -\sum_{i=1}^{n} P(x_i) \log_2 P(x_i)$$

When subdomains exhibit high entropy ($H > 3.8$) alongside excessive label length ($> 50$ characters), `driftnet2` flags the transaction as an active protocol tunneling indicator (`[T1071.004]`).

## Live execution & offline PCAP triage

`driftnet2` operates in both live capture mode and offline analysis mode. When analyzing a network packet capture (`.pcap`), the tool runs with zero root privileges:

```bash
# Analyze test capture, dump credentials, and run exposure audit
./driftnet2 -pcap examples/demo.pcap -output creds.json -audit
```

The console displays captured authentication events in real time:

```text
    ____       _  ______            __   ___  
   / __ \_____(_)/ __/ /_____  ___ / /_ |__ \ 
  / / / / ___/ // /_/ __/ __ \/ _ \ __/ __/ / 
 / /_/ / /  / // __/ /_/ / / /  __/ /_ / __/  
/_____/_/  /_//_/    \__/_/ /_/\___/\__|____/ 

      kernel-level packet sniffer & credential extractor

[*] offline mode: examples/demo.pcap
[FTP] Login 192.0.2.10 → 198.51.100.20:21 — demo:[redacted]
[HTTP] Basic Auth 192.0.2.11 → 198.51.100.21:80 — demo:[redacted]
[POP3] Login 192.0.2.12 → 198.51.100.22:110 — demo@example.test:[redacted]
[IMAP] Login 192.0.2.13 → 198.51.100.23:143 — demo@example.test:[redacted]
[SMTP] AUTH PLAIN 192.0.2.14 → 198.51.100.24:587 — postmaster:[redacted]
[TELNET] Session 192.0.2.15 → 198.51.100.25:23 — [redacted payload]
[LDAP] Simple Bind 192.0.2.16 → 198.51.100.26:389 — cn=admin,dc=corp:[redacted]
[SMB] NTLMv2 192.0.2.17 → 198.51.100.27:445 — [redacted hash]
[DNS] Tunnel Detection 192.0.2.18 → 8.8.8.8:53 — DNS: TXlDMkV4ZmlsUGF5bG9hZERhdGFHb2VzSGVyZQ.c2.example.test

[*] 9 packets processed, 9 unique credentials found in examples/demo.pcap
[*] saved → creds.json
```

<figure class="evidence">
<a href="/images/posts/driftnet2-terminal-capture-evidence.webp" target="_blank" rel="noopener" aria-label="Open the full driftnet2 terminal capture screenshot"><img src="/images/posts/driftnet2-terminal-capture-evidence.webp" alt="Terminal capture of driftnet2 performing multi-protocol extraction across FTP, HTTP, POP3, IMAP, SMTP, Telnet, LDAP, SMB, and DNS" loading="lazy" /></a>
<figcaption><span>driftnet2 Terminal Run</span> Offline PCAP triage capturing credentials across 9 protocol engines simultaneously, followed by automated exposure risk scoring.</figcaption>
</figure>

## Defensive gap analysis: the exposure audit engine

While red teams utilize `driftnet2` for passive credential harvesting during internal engagements, blue teams use the built-in `-audit` engine to quantify **unencrypted exposure risks** across corporate subnets:

```bash
# Generate defensive gap audit directly from a SPAN or TAP port capture
./driftnet2 -pcap network_traffic.pcap -audit -audit-output audit_report.json
```

The audit engine categorizes findings by severity and supplies concrete remediation steps:

```text
Credential Exposure Audit
=========================
9 findings — 7 High, 1 Medium, 1 Low

[HIGH] SMB — Password hash (1)
  Endpoints: 192.0.2.17 -> 198.51.100.27
  Fix: Require SMB signing/encryption, restrict NTLM (prefer Kerberos), and enable Extended Protection for Authentication.

[HIGH] HTTP — Cleartext password (1)
  Endpoints: 192.0.2.11 -> 198.51.100.21
  Fix: Enforce HTTPS/HSTS; never send Basic auth, form credentials, or tokens over cleartext HTTP.

[HIGH] LDAP — Cleartext password (1)
  Endpoints: 192.0.2.16 -> 198.51.100.26
  Fix: Enforce LDAPS (TCP/636) or StartTLS; reject simple bind authentication over cleartext.

[MEDIUM] DNS — DNS tunnel indicator (1)
  Endpoints: 192.0.2.18 -> 8.8.8.8
  Fix: Investigate for tunneling/exfiltration; apply DNS filtering policy and query rate limiting.
```

| Finding Category | Protocol | Threat Mechanism | Remediation |
| :--- | :--- | :--- | :--- |
| **High** | `SMB / NetNTLMv2` | Offline hash cracking via Hashcat dictionary/rule attacks | Enforce Kerberos authentication, disable NTLMv1/v2, mandate SMB signing |
| **High** | `LDAP Simple Bind` | Plaintext directory credential exposure across internal switches | Enforce LDAPS (TCP 636) or require LDAP channel binding and signing |
| **High** | `HTTP Basic / Forms` | Unencrypted authentication credentials readable on local wire | Enforce HTTPS with HSTS, terminate legacy HTTP port 80 listeners |
| **Medium** | `DNS Tunneling` | Covert outbound C2 communication channel bypassing firewalls | Enforce internal DNS resolvers, implement entropy filtering, block external UDP/53 |

## Evidence and limits

All evaluation workflows and packet parsing tests were verified using synthetic, reproducible PCAP test fixtures generated inside isolated laboratory networks. In-kernel eBPF/XDP testing was conducted on Linux kernel 6.x environments using physical and virtual network interfaces. On macOS and non-Linux kernels, packet capture operates via the standard `libpcap` fallback interface. No production networks or unauthorized communications were monitored during tool testing.

## References

- **jankesec, driftnet2 (Kernel-Level Network Sniffer & Auditor):** [github.com/jankesec/driftnet2](https://github.com/jankesec/driftnet2)
- **Linux Kernel Documentation, eXpress Data Path (XDP):** [docs.kernel.org/networking/af_xdp.html](https://docs.kernel.org/networking/af_xdp.html)
- **MITRE ATT&CK, Network Sniffing (T1040):** [attack.mitre.org/techniques/T1040/](https://attack.mitre.org/techniques/T1040/)
- **MITRE ATT&CK, Adversary-in-the-Middle (T1557.001):** [attack.mitre.org/techniques/T1557/001/](https://attack.mitre.org/techniques/T1557/001/)
- **Hashcat, NetNTLMv2 Cracking Guide:** [hashcat.net/wiki/doku.php?id=example_hashes](https://hashcat.net/wiki/doku.php?id=example_hashes)