Fingerprinting the Internet: Blocking Bots and Internet Scanners with eBPF
Commentary on TCP Fingerprinting eBPF PoC
In a recent article, Implementing fast TCP Fingerprinting with eBPF, Alberto Ventafridda introduced a strategy for bot detection based on eBPF.
At the core of this approach is TCP fingerprinting, a technique for identifying unusual patterns or characteristics in web requests.
This topic is becoming particularly relevant due to the growing demand for large-scale web scraping to collect human-generated content for training LLMs.
Beyond the fingerprinting itself, this is also a good entry point for learning eBPF. It combines packet inspection, metadata extraction, and kernel data transfer to user space—concepts utilized in almost any eBPF application.
In essence, TCP fingerprinting is about analyzing the subtle variations in how different clients implement the TCP protocol—such as initial window sizes, TCP options, or packet ordering.
These parameters often reflect design decisions made by different operating system developers within the kernel's network stack, which don’t always strictly follow protocol standards and therefore vary by OS and version.
For instance, each operating system has different combinations of window size, options, and window scale:
Microsoft Windows typically omits TCP Option 8 (Timestamps), whereas Unix-based systems include it.
On iOS, TCP option lists often end with an extra Option 0 ("End of List") to pad the options length to a multiple of 4 bytes—rather than removing a NOP (Option 1), which is the typical pattern on other platforms.
Mobile carriers set different Maximum Segment Size (MSS) values depending on network overhead—letting you infer the carrier from it.
When connected through a VPN, MSS (and sometimes window size) changes depending on overhead of the VPN and encryption ciphers used.
Retransmission behavior—rates and delays—varies by OS and device type e.g. IoT devices may retransmit rapidly, whereas others use exponential back-off patterns (1s, 2s, 4s, ..).
Interestingly, the TCP SYN packet alone carries enough metadata to identify all of these fields.
By observing and storing combinations of these parameters on the server, we can construct a database of fingerprints that helps us distinguish legitimate users from automated bots.
For instance:
Windows 10 → Uses an initial window size of 64240, with TCP options in the order MSS, NOP, Window Scale, NOP, NOP, SACK, an MSS of 1460, and a window scale factor of 8.
Ubuntu 22.04 on Win10 WSL → Uses an initial window size of 64240, with options MSS, SACK, Timestamp, NOP, Window Scale, an MSS of 1460, and window scale factor 7.
Android 13 → Uses an initial window size of 65535, with options MSS, SACK, Timestamp, NOP, Window Scale, an MSS of 1460, and window scale factor 9.
OSX/iOS (all versions) → Uses an initial window size of 65535, with options MSS, SACK, Timestamp, NOP, NOP, Timestamp, SACK, End of Options, an MSS of 1460, and window scale factor 6.
…and so on. These combinations are consistent across devices and provide strong signals of a genuine client.
But how would a bot or internet scanner fingerprint look?
As much as the detection approaches are getting more sophisticated, so are the bots.
But for a trivial example, take tools like Masscan and ZMap .
When scanning the IPv4 space, they prioritize speed over accuracy, sending minimal TCP SYN packets to identify open ports. To maximize throughput, these scanners often use a reduced or hardcoded set of TCP options, rather than the full variety seen in real operating systems.
In other words, SYN packets with stripped-down TCP options or unusual defaults — for example, a fixed MSS, missing SACK, or uncommon window sizes — can be strong indicators of scanning tools rather than legitimate clients.
By contrast, a typical client device or server includes richer metadata in the SYN packet, such as MSS, SACK, timestamps, and window scaling.
Of course, this simple detection method can be trivially bypassed, which is why production systems combine TCP fingerprinting with additional techniques like behavioral correlation, rate limiting and TLS/HTTP fingerprints.
With this in mind, Alberto developed a proof of concept showing how TCP metadata—such as options, MSS, and window scaling—can be captured directly in the kernel using eBPF and send to user space for “fingerprint analysis”.
Complete code is available at robalb/ebpf-web-fingerprint GitHub repository.
Looking at the code, there are three interesting parts that do all the magic:
First, capturing and identifying the TCP SYN packets using an XDP/eBPF program
Storing the TCP data to an eBPF map under a unique key that is build as a combination of the client IP and port:
💡 eBPF maps are shared data structures that let eBPF programs in the kernel exchange information with user space applications.
… and then the same key is built in user space to retrieve the data:
And lastly, parsing the TCP data in kernel and storing it under that unique key:
While this implementation utilizes XDP eBPF program type, the author also thought of using eBPF kprobes.
But kprobes rely on kernel function and struct layouts, which are subject to change between kernel versions. This makes them an unstable interface for reliably capturing TCP data over time.
In theory, one might work around these compatibility issues by writing eBPF with CO-RE (Compile Once – Run Everywhere), which adapts to different kernel ABIs. But, why do this, if there’s a better way (using XDP)?
On the other hand, there is also no existing TCP tracepoint that exposes the data we need. The closest candidate is sock:inet_sock_set_state
, but it does not provide access to the sk_buff
(socket buffer) and therefore cannot supply the required packet-level details:
While this is only a proof of concept, it demonstrates how straightforward it is to capture network-level information with eBPF and make it available to user space for a variety of applications.
In fact, the implementation already includes optional support for capturing TLS ClientHello packets (via PARSE_TLS
) for TLS/HTTP fingerprinting. This adds significantly more entropy to the fingerprinting process.
By combining TCP handshake features with TLS handshake characteristics, it becomes much harder for automated clients to masquerade as legitimate traffic.
⏪ Did you miss the previous issues? I'm sure you wouldn't, but JUST in case:
I hope you find this resource helpful. Keep an eye out for more updates and developments in eBPF in next week's newsletter.
Until then, keep 🐝-ing!