WorksComputer Science
InterceptFuzz: Interception-based Stateful Fuzzing of Single-Process Targets
Abstract
Stateful protocol implementations are commonly fuzzed by tools that act as a network client and exchange messages with the target over sockets. Some wireless and IoT protocol stacks instead provide a single-process simulation platform, in which all nodes run in one binary and exchange packets in memory. Such simulations are deterministic and fast, which suits fuzzing, but offer no socket to which existing stateful fuzzers can attach. Meanwhile, fuzzing infrastructure such as Google's OSS-Fuzz supports only stateless engines. This paper presents InterceptFuzz, a middleware that enables stateful fuzzing of single-process targets on top of an unmodified stateless fuzzing engine. InterceptFuzz requires only a hook where the target passes packets between nodes. Through this hook, it records benign traffic and injects fuzzed packets. It organizes the observed packet sequences in a Prefix Tree. Each seed carries a State ID that selects a node of this tree, and InterceptFuzz deterministically replays the recorded prefix before delivering the mutated packet. The tree grows with newly discovered sequences, while redundant states are pruned to keep its growth in check. InterceptFuzz powered by AFL++ is evaluated on the Nexus test framework of OpenThread and on the LTE and Wi-Fi models of ns-3, against an interception-based adaptation of AFLNet and against Concat, an AFL++-based baseline that mutates a whole message sequence as one input. On OpenThread and ns-3 Wi-Fi, InterceptFuzz covers significantly more branches than both baselines. Seven previously unknown crash signatures were found in OpenThread. Concat, despite its simplicity, covers more branches than AFLNet in every campaign on OpenThread and ns-3 Wi-Fi, and on OpenThread it exposes the same crash signatures as InterceptFuzz, although some of them in fewer campaigns.
Introduction
OSS-Fuzz, Google’s continuous fuzzing service for open-source software, has helped identify and fix over 13,000 vulnerabilities and 50,000 bugs across 1,000 projects [1]. Yet every engine it supports—libFuzzer [2], AFL++ [3], Honggfuzz [4], and Centipede [5]—is stateless. For programs that process each input independently, this is sufficient. For protocol implementations, however, whose stateful logic is reachable only through a specific sequence of prior messages, these engines offer no built-in support.
This limitation reflects a broader divide in the fuzzing landscape. While stateful fuzzers exist in academia, several drawbacks hinder their industrial adoption. Existing stateful fuzzers almost universally assume a classic client-server architecture, acting as a malicious client that sends packets over OS-level network sockets. Furthermore, they are predominantly research prototypes, and many are not maintained beyond their initial publication. Adapting these tools to novel protocols or non-networked architectures therefore requires substantial manual engineering effort. A prominent class of targets highlighting this gap comprises systems in which stateful communication occurs entirely within a single process. This architectural pattern enables deterministic, high-throughput testing and is used in in-process simulators for network and IoT protocols.
To bridge these gaps, this paper presents InterceptFuzz, a stateful fuzzing middleware designed for single-process environments. InterceptFuzz is not a fuzzer itself but a library linked into the target. It delegates input mutation and scheduling to an existing stateless fuzzing engine, such as AFL++ or libFuzzer. This architectural choice mitigates the maintenance issues of academic fuzzers, as InterceptFuzz inherits new features of the underlying stateless engine without requiring any modifications to it. For the same reason, InterceptFuzz can use the engines already deployed in OSS-Fuzz, a prerequisite for bringing stateful fuzzing to its targets.
InterceptFuzz interacts with the target through interception hooks, i.e., single-line calls into the InterceptFuzz library that a developer places at any point of interest in the code. These hooks are designed to serve two purposes: monitoring benign traffic and injecting fuzzed inputs into the execution flow. From the traffic observed through these hooks, InterceptFuzz incrementally builds a Prefix Tree, a record of the packet sequences exchanged by the target. The tree grows by periodically adding the minimized corpus of the underlying stateless engine. To keep this growth in check, states that are no longer used are pruned. Each input generated by the stateless engine carries a State ID that points to a node in the Prefix Tree. The input is delivered only after InterceptFuzz replays the recorded prefix leading to that node, so mutations never corrupt the path to the targeted state. This mechanism ensures that inputs constructed by the underlying stateless engine reach deep, sequence-dependent code paths.
InterceptFuzz is evaluated on three targets built on two single-process protocol simulators: the LTE and Wi-Fi modules of the ns-3 network simulator [6], which is used mainly in networking research, and the Nexus test framework of OpenThread, an open-source Thread stack used in commercial products [7]. On these targets, it is compared against two baselines. The first baseline is a delimiter-based concatenation fuzzer, referred to as Concat in the following. It implements the seed concatenation strategy described in § 2-B and represents the most straightforward method to perform stateful testing with a stateless engine. In this evaluation, both InterceptFuzz and Concat use AFL++ as their underlying engine. The second baseline represents dedicated stateful fuzzers and is based on AFLNet [8], the first code- and state-coverage-guided protocol fuzzer and the reference point on which much of the subsequent work in stateful fuzzing builds [9]. Specifically, the evaluation uses the LibAFL [10] implementation of AFLNet released with Fizzle [11], which reproduces AFLNet’s message-sequence inputs and response-based state-model inference, combined with the packet-level mutations and power scheduling of LibAFL. For this evaluation, its socket communication is replaced by the same interception points used by InterceptFuzz. AFLNet distinguishes protocol states by the response codes of the target, such as the status codes of FTP or RTSP, which the protocols evaluated in this paper do not provide. Following the recommendation of the AFLNet authors [9], a dissector written for each target therefore maps every intercepted response to a code that identifies its message type. All three fuzzers share one delivery mechanism, so that the comparison isolates how each handles protocol state.
The contributions of this paper are summarized as follows:
InterceptFuzz, an interception-based middleware that enables stateful fuzzing of single-process targets on top of unmodified stateless fuzzing engines, such as AFL++ or libFuzzer. Unlike existing stateful fuzzers, it requires no client-server setup or network sockets: it integrates with a target through a single hook function, called where packets pass between nodes, requires only slight modifications of the target code, and can reuse the existing integration tests of the target as fuzzing harnesses.
A Prefix Tree state model with a compact State ID seed encoding, which lets a stateless engine address previously observed protocol states and reach them deterministically, together with an epoch-based loop that alternately extends, exploits, and prunes this model.
An empirical evaluation on three single-process simulated targets (ns-3 LTE, ns-3 Wi-Fi, and OpenThread Nexus) against a concatenation baseline (Concat) and an interception-based adaptation of AFLNet. On OpenThread Nexus and ns-3 Wi-Fi, InterceptFuzz covers significantly more branches than both baselines.
Seven previously unknown crash signatures in OpenThread. Responsible disclosure and open source. All seven crash signatures found in OpenThread were reported to the OpenThread maintainers, who have since fixed them. InterceptFuzz and all experimental artifacts are released as open source at https://github.com/KULeuven-COSIC/InterceptFuzz.
Background & Related Work
Foundations: From Stateless to Stateful Fuzzing
Fuzzing, first introduced by Miller et al. [12], is a fundamental technique for automated vulnerability discovery. It works by feeding unexpected or malformed inputs to a target program while monitoring for abnormal behavior, such as crashes, hangs, or assertion violations. Contemporary fuzzers like AFL++ and libFuzzer employ coverage-guided mutation: rather than generating inputs from scratch, they mutate a seed corpus and track code execution. Inputs uncovering new code paths are prioritized and added to the corpus, while redundant seeds are discarded. Crucially, these conventional engines operate under a stateless assumption, treating each input as an isolated, self-contained test case.
While stateless fuzzers excel at testing independent input parsers, they fall short when applied to stateful systems. In stateful software, the processing of any given input depends on the execution history and the internal state built by prior interactions. Testing such targets with isolated, stateless inputs is ineffective and inevitably leaves deeper functional logic unexplored. Consequently, stateful fuzzing must treat inputs not as single test cases, but as ordered sequences of inputs that systematically drive the target through prerequisite states before injecting mutations into those deeper execution paths.
Stateful Paradigms via Stateless Engines
Stateless engines can be applied to stateful targets without modification if the handling of state is moved elsewhere: either into the structure of the input, or into the test harness. Both strategies are used in practice, and the second one is the closest precursor of InterceptFuzz.
The Input-Encoded Sequences (Seed Concatenation) strategy moves state handling into the input by packing several sequential messages into a single buffer, separated by a delimiter (e.g., message1<delim>...<delim>messageN) [13]. During execution, a custom wrapper function parses the mutated seed, splits the byte array along delimiter boundaries, and delivers the discrete packets sequentially to the target. While this approach is easy to set up, stateless engines lack semantic awareness of the delimiters and cannot focus mutations on specific packets. Consequently, standard stateless mutations frequently corrupt the delimiter tokens or invalidate early prefix messages. This fragility disrupts the message sequence and limits the fuzzer’s ability to explore deeper execution states [13].
The Harness-Driven State Driver strategy moves state handling into the harness. The simplest approach is to manually write a custom wrapper (or harness) that programmatically drives the target into a specific state before delivering the fuzzed payload. While this protects the prerequisite trace from mutation and ensures deterministic state reachability, it scales poorly, as the analyst must manually decide whether a state is interesting and write dedicated harnesses for each state of interest. Furthermore, this strategy precludes the discovery of vulnerabilities triggered by complex relations between different mutated messages within the same trace.
To give an early intuition of its design, InterceptFuzz can be viewed as an automated variant of the harness-driven strategy. Like harness-driven state drivers, it protects the prerequisite trace from mutation, avoiding the fragility of seed concatenation. Unlike manually written harnesses, however, it learns these traces automatically from observed traffic and lets them accumulate mutated messages over time. § 3 describes the architecture of InterceptFuzz in detail.
Stateful Fuzzing Engines
Dedicated stateful fuzzers augment traditional stateless engines with state-aware feedback. AFLNet [8] infers protocol states from server response codes, and StateAFL [14] derives them from snapshots of long-lived memory. SGFuzz [15] instead identifies enum-typed state variables in the source code and tracks the sequences of values they take. ChatAFL [16] further uses large language models to construct grammars for each message type and to mutate messages or predict the next message in a sequence. All of these approaches achieve statefulness by modifying an existing stateless fuzzing engine: AFLNet, StateAFL, and ChatAFL all derive from AFL, while SGFuzz extends libFuzzer. As a result, they do not automatically benefit from later advances in stateless fuzzing, such as those in AFL++.
For standalone, network-accessible server applications, socket-based tools such as AFLNet and StateAFL offer an effective testing strategy, but they suffer from performance bottlenecks caused by OS-level network sockets, protocol timeouts, and continuous process teardowns [11], [17]. To reduce this overhead, researchers have explored two established acceleration strategies, each with its own trade-offs, and a more recent approach has proposed a third.
The first strategy removes the kernel network stack from the fuzzing loop by intercepting the target’s system or library calls in user space. SnapFuzz [17] uses load-time binary rewriting to replace Internet sockets with UNIX domain sockets and make network communication synchronous, snapshots the target at the latest safe point, and redirects file operations to an in-memory filesystem, reporting speedups of up to over AFLNet. Lightweight preload libraries such as libdesock [18] follow the same principle, replacing socket APIs with in-memory emulation. Fizzle [11] goes further, preloading a shared library that emulates sockets, threading, timing, and entropy sources in user space, serializing multi-threaded servers to obtain fully deterministic and reproducible executions at substantially higher speed. Such executors, however, address only how inputs are delivered: stateful input generation is left to the fuzzing engine, and inputs are still
exchanged through (emulated) socket endpoints of a client-server application.
The second strategy relies on hypervisor-based systems, such as Nyx-Net [19], which utilize rapid virtual machine snapshotting to avoid standard network I/O. While this resolves the reset bottleneck, it imposes heavy deployment overheads, requiring custom kernels, modified hypervisors, and hardware-assisted virtualization.
A third, more recent approach, LibAFLstar [20], keeps the server running in persistent mode, so that many messages can be fuzzed in a selected state without restarts. As its authors note, however, a fuzzed message may move the server into another state, so later messages are not guaranteed to reach the selected state.
All of these approaches, however, remain tied to the socket-based client-server model. § 2-E discusses a class of targets for which this assumption does not hold.
Interception-Based Fuzzing
The stateful engines discussed above act as an external peer that delivers each message as an opaque byte buffer through the target’s network interface, typically a socket. This abstraction becomes a liability for multi-layer protocol stacks, which are the norm in wireless and IoT communication. To fuzz a single layer while leaving the rest of the stack intact, every mutated message must still carry valid lower-layer framing, sequence numbers, checksums and, where applicable, encryption. Achieving this from outside the target requires custom mutators or wrappers that re-encapsulate each mutated payload. Such components are protocol-specific and must be rewritten for every new target.
Interception-based fuzzing avoids this problem by placing the fuzzer inside the communication path. Rather than generating complete messages externally, it installs a hook at a point where the target produces or consumes packets and operates on packets in flight: the protocol implementation itself generates benign traffic, and the hook selectively modifies, drops, or replaces packets at the chosen layer before they continue along the normal processing path. Three properties of this design are relevant to this work. First, mutations are layer-selective: since the hook sits at a specific layer boundary, the surrounding stack performs encapsulation, and the fuzzer never has to reconstruct well-formed outer headers. Second, the approach is transport-agnostic: the hook can be placed on a radio link, at a driver boundary, or inside a single process, without assuming OS-level sockets. Third, a single hook serves both observation and injection: it can record unmodified traffic to capture how the protocol behaves, and later overwrite packets with fuzzed payloads.
Interception is widely used in wireless protocol fuzzing, with tools targeting Wi-Fi (Greyhound [21]), Bluetooth Low Energy (SweynTooth [22], BLESuite [23]), Bluetooth Classic (BrakTooth [24]), Bluetooth controller firmware (Frankenstein [25]), 5G NR (5Ghoul [26]), 4G LTE and 5G NR (CovFUZZ [27]), LTE-M and NB-IoT [28], and Thread (ThreadFuzzер [29]). Beyond wireless protocols, interception has also been applied at the hardware–OS boundary of device drivers (PeriScope [30]).
Despite this breadth, existing interception-based protocol fuzzers share two limitations. First, each is built for a single protocol, such as Wi-Fi, Bluetooth, 5G NR, or Thread, and applying it to a different protocol requires additional protocol-specific engineering. Second, to the best of the authors’ knowledge, none of them maintains an explicit model of observed states in which a particular state can be selected and reached deterministically by replaying recorded traffic. Instead, deep states are reached through live interaction with a peer device, physical or simulated, sometimes guided by a protocol-specific state model that is either written manually from the specification (Greyhound) or learned from traces using hand-crafted mapping rules (5Ghoul). The following subsection examines a class of targets in which both limitations matter most.
In-Process Protocol Simulation
Wireless and IoT protocol stacks such as 4G/5G, Wi-Fi, Bluetooth, and Thread are ultimately deployed on dedicated radio hardware, yet testing them on physical devices is expensive, slow, and difficult to reproduce: testbeds are costly to scale, over-the-air links introduce nondeterministic loss and timing, and the internal state of a device is hard to observe. For this reason, mature protocol projects commonly provide a simulation platform that replaces the radio and hardware drivers with a software model, allowing the protocol logic to be exercised on a commodity machine. Such platforms follow one of two architectures.
In multi-process simulations, each simulated node runs as a separate OS process, and nodes exchange frames over an inter-process channel. OpenThread’s POSIX simulation platform follows this model, running one process per device with the IEEE 802.15.4 radio driver implemented on top of UDP sockets [31]. Open-source cellular stacks such as srsRAN similarly connect separate user-equipment and base-station processes through a virtual radio link [32], and the same holds for the 5G stack of the srsRAN Project [33] and for the Open5GS core network, whose test suite starts every network function as a separate process [34]. For Bluetooth, Zephyr is simulated with BabbleSim, which runs every device and the physical layer in separate processes [35], and the Wi-Fi test framework of hostapd and wpa_supplicant combines the mac80211_hwsim kernel module with several user-space processes [36]. From a fuzzing perspective, these setups inherit the drawbacks discussed in § 2-C: every execution involves inter-process communication, multiple process restarts, and timing coordination across processes.
In single-process simulations, by contrast, all nodes and the medium connecting them reside in one address space, frames are passed between node models as in-memory objects, and execution advances along a discrete virtual timeline rather than wall-clock time. Resetting the entire simulated network therefore amounts to resetting a single process, one binary exposes the code of every node to coverage instrumentation, and, given a fixed random seed, execution is fully deterministic.
These properties make single-process simulations particularly well suited to high-throughput fuzzing, yet they are the least accessible to existing tools. Socket-based stateful fuzzers and their accelerated variants have no socket to attach to, and the interception approaches reviewed in § 2-D are each built for a single protocol. Moreover, even where an interception point can be placed, these approaches provide no means to select a particular state and reach it deterministically. What is missing is a generic interception mechanism that can be placed at an arbitrary in-memory packet boundary and that serves not only to mutate traffic but also to route the target deterministically into previously observed states. InterceptFuzz, described in the next section, is designed to fill this gap.
Among open-source wireless protocol stacks, at the time of research, single-process simulators remain rare. This paper evaluates InterceptFuzz on two such targets. ns-3 [6] is a discrete-event network simulator widely used in networking research. It executes complete topologies—including link-layer models, routing protocols, and transport protocols—inside a single process, with packets exchanged between simulated nodes through channel objects and scheduled on an internal event queue. OpenThread [7] is an open-source implementation of the Thread mesh networking protocol. In addition to its multi-process POSIX simulation, OpenThread provides the Nexus test framework, in which multiple Thread nodes run within a single process and exchange IEEE 802.15.4 frames through in-memory buffers. This environment is used in the evaluation.
InterceptFuzz Architecture
High-Level Overview
InterceptFuzz is a middleware designed to enable stateful fuzzing by leveraging standard stateless engines such as AFL++ or libFuzzer. Rather than operating as an independent fuzzer, it is compiled directly into the target binary as a linked library. This design is tailored specifically for targets exhibiting in-process stateful communication, such as mock-I/O integration tests and single-process network simulators. InterceptFuzz assumes that such targets execute deterministically for a given input, i.e., replaying a recorded prefix triggers the same sequence of hook invocations and drives the target into the same state. Single-process simulators such as ns-3 and the Nexus framework of OpenThread satisfy this assumption when run with a fixed random seed, as discussed in § 2-E. Determinism also allows recorded packets containing freshness-dependent fields, such as frame counters or message integrity codes, to be replayed verbatim.
InterceptFuzz executes in iterative cycles called epochs, as illustrated in Figure 1. An external driver script orchestrates each epoch by running three operational phases strictly in sequence, with Phase III’s output re-seeding Phase I of the next epoch to close the loop.

Figure 1. Overview of InterceptFuzz. Rows show the phases of one epoch, columns the components involved. Dashed violet arrows mark hand-offs between phases. Whenever the target runs, (1) the fuzz entry passes the seed to the InterceptFuzz core, which (2)–(3) looks up the prefix of the seed’s State ID in the Prefix Tree, and (4) at each hook call replaces the intercepted packet with the next prefix packet or the payload, passing the suffix through unchanged, as detailed in Figure 2. (5) In Phase I only, the payload and suffix extend the Prefix Tree, and each new edge is exported as a seed. In Phase II, the engine mutates the entire seed, including the State ID (the example shows an unchanged ID). Phase III minimizes the engine queue by replaying every seed (IIIa) and then trims the Prefix Tree and renumbers the State IDs once (IIIb).
All three phases operate over a shared state representation: a Prefix Tree that records the target’s observed protocol behavior, together with a compact seed encoding that lets the underlying stateless engine address specific states within that tree. § 3-B defines both formally. The remaining phases build on this shared model as follows.
Phase I: Prefix Tree Extension builds and expands the Prefix Tree by replaying the seeds retained in the previous epoch and recording the traffic they produce, and exports the newly discovered transitions as seeds for the underlying stateless engine.
Phase II: Stateful Fuzzing and Dynamic Routing is where the actual fuzzing takes place. The stateless engine mutates the seeds exported by Phase I, and for each resulting input, InterceptFuzz first routes the target to the correct state before delivering it — effectively performing stateful fuzzing.
Phase III: Minimization and Trimming consolidates the epoch’s results: the stateless engine’s output queue is minimized, and the Prefix Tree is trimmed, aiming to mitigate the tree’s unbounded growth without sacrificing the quality of the seeds it retains.
Epoch schedules. The number of epochs and the length of each epoch in a fuzzing campaign are hyperparameters and can be chosen depending on the target’s complexity. InterceptFuzz supports two schedules for distributing the fuzzing time of a campaign, , across epochs. Under the uniform schedule, every epoch receives the same Phase II budget of . Under the linear schedule, the budget grows with the epoch index: epoch receives , where . The linear schedule builds on the observation that the Prefix Tree changes most during the first epochs, when many new transitions are still being discovered. Short early epochs pass the newly found seeds to Phase I sooner, so that the tree is extended quickly, whereas the long later epochs, in which the tree grows only slowly, give the stateless engine more time to explore the accumulated states in depth. The uniform schedule, in contrast, refreshes the Prefix Tree at regular intervals throughout the campaign. Section IV-F compares both schedules.
The remainder of this section is organized as follows. § 3-B formally defines the Prefix Tree state model and the seed encoding used to address it. § 3-C describes InterceptFuzz’s interception mechanism, the substrate shared by all three phases. § 3-D–III-F then detail each phase in turn.
State Model: The Prefix Tree
Prior to describing the individual operational phases, this section formally establishes the state representation shared across all three: the Prefix Tree, its corresponding seed encoding, as well as the seed replay mechanism and resulting suffixes used to navigate and expand the model. Throughout this section, a packet denotes a single protocol message passing through an interception point.
Prefix Tree. InterceptFuzz models the target’s observed execution histories as a rooted, directed tree. Each node denotes an abstract execution point of the target, reachable from the root by a unique sequence of intercepted packets. Nodes themselves carry no semantic meaning, while every transition is labeled with the concrete payload of the intercepted packet. For example, in the path , replaying packet from deterministically reaches , and replaying from reaches . For state , the sequence of packets labeling the unique path from to is called its prefix, and together with all of its descendants its subtree.
Node Identity. InterceptFuzz ensures that each node’s outgoing edges are labeled by pairwise-distinct packets: encountering an observed packet on an existing edge reuses child rather than creating a duplicate branch. Consequently, any packet sequence identifies at most one path from the root, which guarantees deterministic routing during seed replay, a mechanism defined later in this section. InterceptFuzz intentionally adopts a coarse, history-based node identity instead of semantic state tracking, as recovering true equivalence across execution points would require target-specific internal knowledge that InterceptFuzz does not assume. The trade-off is potential unbounded tree growth, as syntactically distinct yet semantically redundant paths accumulate across epochs. § 3-F describes how Phase III bounds this growth through minimization and trimming.
Seed Encoding. To let the underlying stateless engine address a specific node in the Prefix Tree, InterceptFuzz serializes every seed as a [State ID][payload] pair: a 4-byte, big-endian State ID identifying a node , followed by the packet payload to be delivered once the target reaches . State IDs start at 1 for the root , and the ID 0 is reserved for the null seed described in § 3-D. The [State ID][payload] pair is the sole interface between the Prefix Tree model and the underlying stateless engine: the engine only ever sees and mutates flat byte buffers in that format, while InterceptFuzz is responsible for resolving a State ID back to a concrete node and for keeping the ID space consistent across epochs, as described in § 3-F.
Seed Replay. Executing a seed—routing the target to a designated prefix state before delivering a payload—is the core operation shared by Phase I and Phase II, termed seed replay. InterceptFuzz implements this mechanism via its interception hook.
hook. Specifically, on each successive hook trigger, the InterceptFuzz middleware injects the next packet along the path to the target node. Once the prefix is exhausted, it delivers the seed’s payload. For example, consider the seed [3][D] evaluated against the Prefix Tree . The state identifier 3 corresponds to node , which has the prefix sequence . InterceptFuzz sequentially injects on the first hook trigger and on the second to reach , and finally injects payload on the third trigger upon prefix completion, as illustrated in Figure 2a. Under the determinism assumption, replaying a prefix triggers the hook at exactly the same packet boundaries as during recording, so InterceptFuzz implements no divergence handling.

Figure 2. Running example. (a) Seed replay of [3][D]: InterceptFuzz injects the recorded prefix of at the first two hook calls, delivers the payload at the third, and passively observes the suffix . (b) Phase I adds the payload and the suffix to the Prefix Tree and exports one seed per new edge.
Suffix. The sequence of packets emitted by the target after seed replay completes constitutes that seed’s suffix. Whereas the prefix replays recorded traffic and the payload originates from the seed, the suffix is neither injected nor altered by InterceptFuzz but passively observed, reflecting the target’s autonomous protocol behavior at the reached execution state. Continuing the previous example, the suffix of seed [3][D] comprises any packets that the target emits following the delivery of , such as in Figure 2a.
Relation to AFLNet. Seed replay is structurally close to the input model of AFLNet, which also replays the messages that lead to a selected state unchanged and mutates the messages after them. The two approaches differ in three respects. First, they differ in what they consider a state. AFLNet identifies a state by the response code of the target, which requires a protocol whose responses carry such codes, or a protocol-specific dissector that derives them. In InterceptFuzz, the state model is much more generic and follows from the coverage feedback, i.e., new states are added to the Prefix Tree based on the inputs retained in the corpus of the underlying engine. InterceptFuzz therefore requires no knowledge of the protocol to identify states and is not tied to any particular protocol. Second, they differ in how a state is selected. AFLNet adds a dedicated state scheduler to the engine, which first selects a state and then a seed that reaches it. InterceptFuzz instead encodes the state in the input as a State ID, so that selecting a seed also selects its state. State selection is thus left to the seed scheduling of the unmodified engine, and InterceptFuzz benefits from any improvement to it. Third, AFLNet acts as a client that sends its messages to a server over a socket, whereas InterceptFuzz delivers packets at an in-memory interception point, as described next.
Interception and Execution Model
To interact with in-process targets that communicate without OS-level sockets, InterceptFuzz utilizes a minimal source-level integration strategy. InterceptFuzz exposes a single interception hook function, which can be inserted wherever traffic is to be fuzzed—typically at the points where the target produces or consumes packets. In the simplest case, each insertion is a single line that calls the hook, size_t InterceptFuzz::intercept(buf, buf_len, buf_max_len), on the packet buffer. For targets with strict memory ownership models (e.g., read-only packet structures), a variant of the hook, InterceptFuzz::intercept_copy, returns the resulting packet as a copy, and a few additional lines write it back into the execution flow. When the hook is invoked from several code locations or on behalf of several simulated nodes, InterceptFuzz treats all invocations as a single, globally ordered sequence in which every intercepted packet becomes an edge of the Prefix Tree. Determinism guarantees that this order is identical across executions. To fuzz only part of the traffic, e.g., packets sent by a single node acting as a malicious peer, the developer can simply call the hook inside an if statement on that condition.
The InterceptFuzz::intercept hook introduced above functions as InterceptFuzz’s exclusive gateway for both data observation and manipulation: it inspects or overwrites the packet in buf and returns the resulting payload length. When invoked, the InterceptFuzz library evaluates the target buffer against the current execution phase. During the first epoch, when the Prefix Tree is still empty, the hook passively observes and records benign packets, as described in Section III-D. During Phase I expansion and Phase II fuzzing alike, the hook executes seed replay and leaves the resulting suffix untouched. The two phases differ only in what is recorded: Phase I adds the delivered payload and the resulting suffix to the Prefix Tree as newly discovered transitions, while Phase II records neither. Finally, to preserve the memory safety of the simulation, InterceptFuzz truncates all injected payloads to buf_max_len, the maximum payload size that the developer allows at that particular hook call. This value therefore depends on the target and on where the hook is placed.
The hook thus governs what happens within a single execution. Starting executions and resetting the target between them is left entirely to the underlying fuzzing engine, and InterceptFuzz implements no reset mechanism of its own. Whether the engine isolates executions with a fork server, as AFL++ does, or relies on an in-process reset harness, as libFuzzer does, InterceptFuzz works the same way. Likewise, coverage feedback is provided entirely by the engine: the target, including all simulated nodes, is compiled with the engine’s instrumentation, so the engine decides which inputs to retain exactly as it would for a stateless target.
Phase I: Prefix Tree Extension
The task of Phase I is to populate the Prefix Tree with new transitions. It takes as input the minimized queue of seeds handed off by the previous epoch’s Phase III and performs seed replay on each seed in turn. Because the prefix is replayed from the Prefix Tree, it is already recorded there, so InterceptFuzz only adds the delivered payload and the resulting suffix to the tree. At the end of this process, the newly discovered transitions are exported as initial seeds, using the [State ID][payload] encoding, for the underlying stateless engine to be mutated in Phase II. Concretely, each newly added edge yields the seed [i][P], so that the packet observed at —benign or previously mutated—becomes the starting point for mutations delivered at that state. Since the replayed queue has already been minimized in Phase III, the number of new edges, and hence of exported seeds, is bounded by the number of retained seeds and the lengths of their suffixes. For example, replaying [3][D] on the Prefix Tree adds the edge and, for a suffix , the edges and , yielding the seeds [3][D], [5][E], and [6][F], as shown in Figure 2b.
In the first epoch, when the Prefix Tree is empty and no seeds yet exist, InterceptFuzz uses a reserved null seed — State ID 0 paired with an empty payload — which triggers an iteration where InterceptFuzz simply observes the target’s benign traffic from the very beginning, building the initial Prefix Tree skeleton from it.
Phase II: Stateful Fuzzing and Dynamic Routing
Once Phase I has extended the Prefix Tree and exported the new seeds, InterceptFuzz transitions to Phase II, where the underlying stateless engine mutates these seeds while InterceptFuzz routes the target to the state each input addresses. Because the engine operates on inputs encoded as [State ID][payload], InterceptFuzz must interpret each mutated input before dispatching execution.
This decoding takes place in InterceptFuzz’s fuzz-entry function—the harness callback (e.g., an LLVMFuzzerTestOneInput-style entry point) invoked directly by the stateless engine. The function extracts the initial 4-byte State ID before the target harness is invoked. If the input is shorter than 4 bytes or specifies an ID that does not map to an existing node in the Prefix Tree, the callback returns immediately, making invalid inputs cheap to reject. For valid IDs, InterceptFuzz performs seed replay to route the target to the designated state and then delivers the rest of the mutated input as the payload, truncated to buf_max_len. Each execution thus mutates only a single packet, as the prefix is replayed exactly as recorded. However, inputs that increase coverage are retained in the engine’s queue and replayed in the next epoch’s Phase I, where their mutated payloads become edges of the Prefix Tree. As a result, a trace can accumulate multiple mutated packets over successive epochs.
The length of Phase II in each epoch is set by the epoch schedule introduced in § 3-A: by default, it grows linearly, so that early epochs return new seeds to Phase I quickly and later epochs explore the accumulated states longer. Furthermore, the State ID remains deliberately exposed to mutation by the underlying fuzzing engine. Because the identifier occupies only a small fraction of the input buffer, it is mutated infrequently. Moreover, since IDs are assigned contiguously, most mutations of its high-order bytes yield an invalid ID that is rejected at negligible cost, as described above. When a mutation does produce another valid ID, the same payload is delivered at a different state, i.e., a packet observed in one protocol state is replayed in another. Such out-of-context packets exercise the target’s handling of unexpected messages and thus benefit exploration.
Phase III: Minimization and Trimming
When a Phase II fuzzing campaign concludes, the stateless engine yields a queue of interesting seeds—mutated [State ID][payload] inputs that uncovered new branch coverage. However, directly incorporating every coverage-increasing input into the Prefix Tree would cause substantial redundancy and rapid growth of the tree. InterceptFuzz mitigates this growth through two complementary techniques.
First, the fuzzer’s output queue is distilled to the minimal corpus exercising the newly discovered coverage using the engine’s corpus minimizer (e.g., afl-cmin for AFL++). Next, InterceptFuzz trims the Prefix Tree against this reduced queue: a node is pruned if and only if neither nor any of its descendants are referenced by any retained seed. Evaluating survival across the entire subtree ensures an ancestor is never removed while a descendant remains active, preserving the prefix path required for deterministic seed replay. Consequently, the root is never pruned as long as at least one valid node survives.
Because trimming leaves holes in the State ID sequence, the pass concludes by renumbering the nodes to restore contiguity: the root remains , while the surviving non-root nodes are reassigned contiguous IDs from to . To keep the minimized queue consistent, InterceptFuzz rewrites each surviving seed’s 4-byte State ID field in place to match its node’s updated ID. This relabeled queue then loops back into Phase I to drive expansion in the next epoch.
Evaluation
This section evaluates InterceptFuzz on the two single-process protocol simulators introduced in § 2-E against two baseline fuzzers, Concat and AFLNet, and answers the following research questions:
RQ1 (Coverage): Does InterceptFuzz cover more code of stateful single-process targets than the baselines?
RQ2 (Bug finding): Does InterceptFuzz expose more crash signatures than the baselines, and does it expose them more reliably across campaigns?
RQ3 (State model): How does the Prefix Tree evolve across epochs, and how effectively does Phase III keep its growth in check?
RQ4 (Epoch settings): How sensitive is InterceptFuzz to the number of epochs and to the epoch schedule?
RQ5 (Integration effort): How much manual effort does it take to integrate InterceptFuzz into a new target?
Experimental Setup
Targets. The evaluation uses three targets built on the two single-process simulators: the Nexus test framework of OpenThread and the Wi-Fi and LTE modules of ns-3. The evaluation uses OpenThread at commit 01bce95b and ns-3 at commit be3db7b6. The targets cover two kinds of software. As discussed in § 2-E, ns-3 is a simulator used mainly in networking research, whereas OpenThread is an open-source protocol stack, which is also used in commercial Thread products. The targets also differ in how much code their scenarios can reach beyond the benign trace, from a few hundred branches on ns-3 LTE to several thousand on OpenThread Nexus. In each target, the harness runs a minimal scenario in which one node attaches to, or associates with, another, so that the fuzzed traffic covers the procedure from its first message onward:
OpenThread Nexus: a leader forms the network, and a Thread device then joins it as a full end device through the MLE attach procedure. The benign run contains four messages: Parent Request, Parent Response, Child ID Request, and Child ID Response.
ns-3 Wi-Fi: one station associates with one access point within 300 ms of simulated time. The benign run contains seven frames: a Probe Request and Probe Response, an Association Request and Association Response, and three Beacons.
ns-3 LTE: one user equipment (UE) attaches to one eNB, and the run ends as soon as the RRC connection is established or times out after 500 ms of simulated time. As ns-3 does not implement the NAS logic, the fuzzed exchange consists of the RRC messages between the UE and the eNB. In the benign run, these are RRC Connection Request, RRC Connection Setup, and RRC Connection Setup Complete.
In all three targets, every intercepted packet can be mutated, regardless of which node sent it. All executions are deterministic: every input is executed in a freshly forked process, *Added/removed lines with respect to an existing OpenThread integration test.
and at the start of each execution every harness reseeds the C standard library’s pseudo-random number generator (srand).
Hook placement. In each target, the hook is placed at a point where outgoing packets are available in serialized form, before they reach the receiving node:
OpenThread Nexus: a single hook is placed in the simulated radio of the Nexus platform, where it intercepts every IEEE 802.15.4 frame on its way to the receiving node and may mutate the entire frame. The frame check sequence is set to a fixed value, as the fuzzing build does not verify it. Besides MLE messages, this includes messages of the Thread Management Framework (TMF), which nodes exchange over CoAP once they are attached and which do not occur in the benign run.
ns-3 Wi-Fi: a single hook is placed in the MAC transmit queue (
WifiMacQueue::Enqueue), through which every frame of both the access point and the station passes. The hook mutates only the frame body and keeps the MAC header, so that frames still reach their receiver. Frames without a body, such as acknowledgments, are not intercepted.ns-3 LTE: every RRC message is serialized in a dedicated send function of the UE or the eNB, such as
DoSendRrcConnectionRequest. Each of the 11 send functions receives a one-line call to a small helper, which copies the serialized message into a mutable buffer and invokes the hook.
Table 1 summarizes the three targets.
| Target | Version | Benign packets | Hook calls | Target lines | Harness lines |
| ns-3 LTE | be3db7b6 | 3 | 11 | 21 | 59 |
| ns-3 Wi-Fi | be3db7b6 | 7 | 1 | 11 | 48 |
| OpenThread Nexus | 01bce95b | 4 | 1 | 6 | +19/−47* |
Table 1. Evaluation targets and integration effort.
Baselines. InterceptFuzz is compared against two baselines, both of which use the same interception points as InterceptFuzz.
Concat: a concatenation baseline, implemented specifically for this evaluation, which realizes the seed concatenation strategy of § 2-B on top of AFL++. A harness first splits each input into messages at a delimiter, and the -th hook call then replaces the intercepted packet with the -th message. Once all messages have been delivered, the remaining packets pass through unchanged, and surplus messages are discarded.
AFLNet: the LibAFL reimplementation released with Fizzle [11] (artifact commit
e1104bc), whose socket communication is replaced by the interception points. AFLNet infers its state model from response codes, which the evaluated protocols do not provide. Therefore, for each target, a separate dissector was written. It maps every captured response to a code that identifies its message type, following the recommendation of the AFLNet authors to expose an explicit state identifier when responses carry no state information. In preliminary experiments, several dissectors that distinguish the responses at different levels of detail were tried, and the evaluation uses the one that performed best. On OpenThread Nexus, the size of mutated messages is additionally capped at the maximum IEEE 802.15.4 frame size of 127 bytes, so that mutations are not spent on bytes that a frame cannot carry.
All three fuzzers therefore share one delivery mechanism, so that differences in the results stem from the fuzzers themselves rather than from how their inputs reach the target.
Initial seeds. All three fuzzers start from the same benign traffic of an unfuzzed run of each target. Because the targets are deterministic, a single run suffices. Both baselines natively expect this run to be recorded beforehand. For Concat, the recorded packets are stored as a single delimiter-separated input. For AFLNet, a bootstrapping step, which is not part of the Fizzle release, was added for this evaluation. It records the run at the start of each campaign and stores it as a message sequence. InterceptFuzz records the run without any such extension, by replaying the null seed into its Prefix Tree in the first epoch. The initial seeds of all three fuzzers thus contain the same packets.
Configuration. InterceptFuzz and Concat both use AFL++ [3] 4.32c (commit 247e8241) as the underlying engine. Each campaign runs for 24 hours on OpenThread Nexus and for 10 hours on the two ns-3 targets, whose fuzzed scenarios are smaller, and is repeated 10 times per fuzzer and target. By default, InterceptFuzz divides each campaign into five epochs under the linear schedule of § 3-A, so that the Phase II budgets grow from 1.6 to 8 hours on OpenThread Nexus (1.6, 3.2, 4.8, 6.4, and 8 hours) and from 40 minutes to 3.3 hours on the ns-3 targets (40, 80, 120, 160, and 200 minutes). Phases I and III run in addition to these budgets but take only 0.4% of the campaign time on OpenThread Nexus and 0.3–1.1% on the ns-3 targets. Section IV-F compares this default with three alternative settings on OpenThread Nexus: five epochs under the uniform schedule, and ten epochs under the linear and under the uniform schedule. All experiments run on an HP EliteBook 850 G8 laptop with an Intel Core i7-1165G7 CPU (4 cores, 8 hardware threads) and 16 GB of RAM, running Ubuntu 22.04. Each campaign runs in its own Docker container with two dedicated hardware threads and a single fuzzer instance, and four campaigns run in parallel.
Metrics. The comparison with the baselines uses two metrics: code coverage and the distinct crash signatures that each fuzzer exposes. Code coverage is measured by replaying the final queue of each campaign on a separate gcov build of the target. Branch coverage is the primary metric. Following common recommendations for fuzzer evaluation [37], [38], the results report absolute numbers of covered branches, and differences are tested with the Mann-Whitney test and the Vargha-Delaney effect size. The fuzzing builds are compiled without AddressSanitizer (ASan), as its instrumentation slows down every execution and would reduce the throughput of all three fuzzers. Crashes during fuzzing are therefore limited to failures that terminate the uninstrumented binary, such as assertion failures and segmentation faults. To expose memory errors that do not crash it, the non-minimized queue of each campaign is afterwards replayed on an ASan build, so that no input is lost to minimization. Note, this setup can miss some memory errors from the inputs that are not kept in the queue, for example inputs that corrupt memory without reaching new coverage. Therefore, this approach is a compromise between throughput and the detection of memory errors. But, as it affects all three fuzzers in the same way, the comparison nevertheless stays fair. Finally, crashes are deduplicated into crash signatures, given by the assertion message or, for sanitizer reports, by the function in which the fault occurs, and every crash signature was confirmed manually to be reproducible.
| Fuzzer | Execs/s | Branches Covered | Branches | Lines Covered | Lines | Functions Covered | Functions | IF vs. baseline | IF vs. baseline |
| OpenThread Nexus (10 runs) | |||||||||
| Benign trace | — | 2,634 | — | 11,162 | — | 3,312 | — | ||
| AFLNet | 308 [295–308] | 4,323 [4,219–4,555] | +1,689 | 14,350 [14,056–14,973] | +3,188 | 3,850 [3,789–3,991] | +538 | <0.001 | 1.00 |
| Concat | 952 [685–1,067] | 6,603 [6,248–6,790] | +3,969 | 18,666 [17,972–19,203] | +7,504 | 4,613 [4,474–4,732] | +1,301 | 0.001 | 0.94 |
| InterceptFuzz | 723 [699–752] | 6,915 [6,715–7,116] | +4,281 | 19,265 [18,953–19,690] | +8,103 | 4,727 [4,676–4,834] | +1,415 | ||
| ns-3 Wi-Fi (10 runs) | |||||||||
| Benign trace | — | 40,496 | — | 46,106 | — | 13,567 | — | ||
| AFLNet | 29 [27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38] | 40,729 [40,699–40,863] | +233 | 46,315 [46,238–46,503] | +209 | 13,599 [13,587–13,628] | +32 | <0.001 | 1.00 |
| Concat | 29 [28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38] | 41,251 [41,143–41,389] | +755 | 46,794 [46,669–47,084] | +688 | 13,646 [13,639–13,689] | +79 | 0.011 | 0.84 |
| InterceptFuzz | 34 [33–44] | 41,363 [41,054–41,459] | +867 | 47,041 [46,585–47,106] | +935 | 13,687 [13,631–13,696] | +120 | ||
| ns-3 LTE (10 runs) | |||||||||
| Benign trace | — | 34,375 | — | 40,955 | — | 12,219 | — | ||
| AFLNet | 36 [36–47] | 34,989 [34,820–35,035] | +614 | 41,997 [41,768–42,038] | +1,042 | 12,370 [12,341–12,378] | +151 | 0.791 | 0.46 |
| Concat | 29 [28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38] | 34,900 [34,886–34,927] | +525 | 41,947 [41,904–42,038] | +992 | 12,349 [12,345–12,352] | +130 | 0.225 | 0.67 |
| InterceptFuzz | 69 [64–94] | 34,922 [34,913–35,113] | +547 | 42,021 [41,994–42,216] | +1,066 | 12,352 [12,352–12,385] | +133 |
Table 2. Final code coverage, median and [min–max] over the campaigns, and gain of the median over the benign trace (). Execs/s: executions per second, median [min–max]. InterceptFuzz uses five epochs under the linear schedule. The highest median coverage per target is in bold. The last two columns compare the branch coverage of InterceptFuzz with that of each baseline (two-sided Mann-Whitney test and Vargha-Delaney , the probability that a run of InterceptFuzz covers more branches than a run of the baseline).
Attacker Model
Whether a crash found by fuzzing is a vulnerability depends on who can send the messages that trigger it. This section therefore defines the attacker for each target and discusses how the fuzzing setup relates to this attacker.
OpenThread. The attacker is a malicious device that already possesses the shared network key and can therefore communicate with the other devices of the Thread network. It attacks a node during the MLE attach procedure by sending it malformed messages. Since the attacker holds the key, it can encrypt and authenticate any message it sends. The fuzzing builds define FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION, the standard macro for fuzzing builds, under which OpenThread skips the decryption and integrity check of received MLE messages. This only removes a step that the attacker would pass anyway. How the key is obtained during commissioning is out of scope.
ns-3. ns-3 is a research simulator and is not deployed in production, so the crashes found in ns-3 LTE and ns-3 Wi-Fi are not vulnerabilities in practice. If the protocol code of ns-3 were used on a real device, the corresponding attacker would be a device within radio range of the victim: for LTE, a rogue base station attacking a UE or a malicious UE attacking a base station, and for Wi-Fi, a rogue access point or a malicious station.
Mutations in both directions. All three fuzzers mutate packets in both directions of the exchange, whereas a real attacker controls only the packets sent by its own device. A crashing input may therefore combine mutations of packets that the attacker cannot control, such as packets sent by the victim itself, and such a crash may not be reproducible against a real device. In general, deciding whether a given crash is reachable by the attacker is difficult, because a crash that requires a mutated packet from the victim on one path may still be reachable on another path that requires only packets from the attacker. Such crashes are nevertheless real security findings: the crashing node fails on a message that it can receive, and a later change to the protocol or to the code may make them reachable by an attacker. These crashes should therefore be fixed as well.
RQ1: Coverage
RQ1 compares the code coverage that the three fuzzers reach on each target. Table II lists the final branch, line, and function coverage of each fuzzer, both in total and beyond the benign trace, which isolates the code reached only through fuzzing, together with the throughput of each fuzzer. Branch coverage is the primary metric, while line and function coverage show whether the differences in branch coverage carry over to coarser measures. The results are presented per target. Figure 3 shows how the branch coverage develops over the course of the campaigns.

Figure 3. Branch coverage over time on (a) OpenThread Nexus, (b) ns-3 Wi-Fi, and (c) ns-3 LTE: median (line) and min–max (shaded band) over ten campaigns per fuzzer. The dark horizontal line marks the coverage of the benign trace without fuzzing, from which all fuzzers start at time zero. The light vertical lines mark the ends of the five epochs of InterceptFuzz (E1–E5) under the linear schedule.
1) OpenThread Nexus: InterceptFuzz performs the best, covering a median of 4,281 branches beyond the benign trace, against 3,969 for Concat and 1,689 for AFLNet. Both differences are statistically significant ( with , and with ), and every InterceptFuzz campaign covers more branches than the median Concat campaign. The same holds for lines and functions (). Over time, Concat covers more branches than InterceptFuzz for most of the first epoch, in which InterceptFuzz fuzzes only the states of the benign trace, as Figure 3a shows. InterceptFuzz overtakes it after about 1.8 hours, shortly after the first extension of its Prefix Tree, and then stays ahead for the rest of the campaign. AFLNet remains far below both fuzzers throughout.
The coverage analysis showed that most of the additional branches of InterceptFuzz lie in the handling of TMF messages, which the nodes exchange only after the attach and which the benign run does not contain. InterceptFuzz first drives the nodes into exchanging such messages and then fuzzes them in the states recorded in its Prefix Tree. A more detailed breakdown of the coverage can be seen in Appendix B.
2) ns-3: On ns-3 Wi-Fi, InterceptFuzz also performs the best, covering a median of 867 branches beyond the benign trace, against 755 for Concat and 233 for AFLNet, and both differences are statistically significant ( and ). The same holds for lines and functions (). The course of the campaigns, shown in Figure 3b, resembles that on OpenThread Nexus. InterceptFuzz overtakes Concat after 2 hours, at the end of the second epoch, and stays ahead from then on, while AFLNet again remains far below both fuzzers.
On ns-3 LTE, all three fuzzers cover between 525 and 614 branches beyond the benign trace, and none of the differences in branch coverage is statistically significant (). The likely explanation is the limited scope of the LTE model of ns-3, which implements only a small part of the protocol: it lacks the NAS logic, and its RRC implementation supports only a subset of the RRC messages. The code that the fuzzed messages can reach is therefore small, and all three fuzzers appear to largely exhaust it within the campaign. Figure 3c shows that they get there at different paces. Concat reaches nearly its final coverage within the first 30 minutes and gains only about ten branches afterwards. InterceptFuzz overtakes it after about 2 hours, at the end of the second epoch, and changes little in the later epochs. AFLNet starts lowest but keeps gaining branches, overtakes both fuzzers after about 7 hours, and ends with the highest median.
Answer to RQ1: InterceptFuzz explores more of the targets than both baselines on two of the three targets. On OpenThread Nexus and ns-3 Wi-Fi, it covers significantly more branches beyond the benign trace than Concat and AFLNet. On ns-3 LTE, whose reachable code is small, all three fuzzers reach similar coverage.
RQ2: Bug Finding
RQ2 compares the crash signatures that the three fuzzers expose on each target, deduplicated as described in Section IV-A. It has two parts, each answered by its own measure. The number of distinct signatures, across all campaigns and per campaign, shows whether a fuzzer exposes more crashes. The number of campaigns in which each signature appears shows how reliably it exposes them. Table III reports this number for each signature in OpenThread Nexus, together with the median time to first exposure as additional information. Table IV reports the number of distinct signatures for the ns-3 targets. The results are presented per target.
| ID | Function | Type | Campaigns Concat | Campaigns IF | Median TTE (h) Concat | Median TTE (h) IF |
| C1 | Remove | Assertion | 9/10 | 10/10 | 4.1 | 3.7 |
| C2 | PrepareMeshFrame | Assertion | 10/10 | 10/10 | 0.5 | 1.7 |
| C3 | IsRunning | SEGV (R) | 6/10 | 9/10 | 8.6 | 4.2 |
| C4 | SetRloc | SBO (W) | 7/10 | 7/10 | 8.9 | 10.9 |
| C5 | IsStable | SBO (R) | 1/10 | 3/10 | 10.2 | 9.9 |
| C6 | IsDhcp | SBO (R) | 7/10 | 7/10 | 7.8 | 4.9 |
| C7 | GetNext | SBO (R) | 4/10 | 4/10 | 6.6 | 5.3 |
| Signatures per campaign (median) | 5 | 5 |
Table III. Distinct crash signatures in OpenThread Nexus over h campaigns: number of campaigns that exposed each signature and median time to first exposure (TTE) over those campaigns. AFLNet exposed none. IF: InterceptFuzz (five epochs, linear schedule). SEGV: segmentation fault. SBO: stack buffer overflow. R/W: read/write.
Table IV. Distinct crash signatures exposed in the ns-3 targets (10 campaigns per fuzzer): union over all campaigns and median [min–max] per campaign. InterceptFuzz uses five epochs under the linear schedule. All signatures are assertion failures in ns-3.
1) OpenThread Nexus: Table III lists the seven distinct crash signatures found in OpenThread Nexus. All signatures except C2 lie in the handling of Thread Network Data. C1 and C2 are assertion failures, raised when Network Data is removed and when a message is framed for transmission. C3 is an invalid read in the timer code, triggered when the Leader processes a Network Data registration. C4–C7 are stack buffer overflows with a shared call path and likely a shared root cause: when a node appends its Network Data to an MLE message, it walks malformed TLVs past the end of a stack buffer, and the type of the first TLV accessed past the end determines the signature. All seven signatures lie in the OpenThread core (src/core) rather than in the Nexus platform, so they are not artifacts of the simulation and they might be reachable by the attacker described in Section IV-B. This also applies to the assertion failures C1 and C2. In builds with assertions enabled, a failed assertion in OpenThread halts the node, which enables a denial of service.
Both Concat and InterceptFuzz expose the same seven signatures. InterceptFuzz exposes every signature in at least as many campaigns as Concat, and the gap is largest for the rarer signatures: C3 is found in 9 instead of 6 campaigns and C5 in 3 instead of 1. In total, InterceptFuzz exposes the seven signatures 50 times over its ten campaigns, against 44 times for Concat. Per campaign, this difference is too small to be statistically significant: both fuzzers expose a median of five distinct signatures (2–7 for InterceptFuzz and 1–7 for Concat, ). The median time to first exposure is lower for InterceptFuzz on five of the seven signatures. Note, the median times are computed only over the campaigns that exposed a signature and should therefore be read together with the campaign counts.
AFLNet exposes none of the seven signatures. The final coverage shows that AFLNet does not reach the code in which the signatures lie. In none of its campaigns does it execute a TMF handler, the processing of Network Data registrations by the Leader, or the function in which C2 lies. AFLNet thus remains within the MLE attach, whereas all signatures except C2 lie in the handling of Network Data that follows it. The state model of AFLNet does contain one TMF message type, an Address Query, but the handler of this message is not executed in any of its campaigns either. Why AFLNet does not progress beyond the attach on this target remains unclear and is left for future work.
2) ns-3: In the ns-3 targets, all crash signatures are assertion failures, most of them raised during the deserialization of frames and their information elements. Unlike in OpenThread, these crashes are not vulnerabilities, because, as discussed in the attacker model of Section IV-B, ns-3 is not deployed in production. They are nevertheless counted, with the same deduplication as above, as a measure of how many distinct failure sites each fuzzer reaches. Table IV summarizes the results for both ns-3 targets.
On ns-3 Wi-Fi, AFLNet exposes 21 distinct signatures across all campaigns, InterceptFuzz 20, and Concat 17. AFLNet thus exposes the most signatures on this target, even though it reaches the lowest coverage of the three fuzzers. Per campaign, InterceptFuzz exposes significantly more signatures than Concat (, ), whereas the difference to AFLNet is not statistically significant ().
On ns-3 LTE, InterceptFuzz and AFLNet each expose 11 distinct signatures across all campaigns, against 10 for Concat. Per campaign, InterceptFuzz exposes a median of 9 signatures against 8.5 for both baselines, and neither difference is statistically significant ( and ).
On both ns-3 targets, InterceptFuzz thus exposes at least as many distinct crash signatures per campaign as either baseline, and significantly more than Concat on ns-3 Wi-Fi. Unlike on OpenThread Nexus, InterceptFuzz and AFLNet expose a comparable number of crash signatures on both ns-3 targets. A likely reason is that most of these signatures are raised during the deserialization of frames, which every received frame passes through. Exposing them therefore depends less on reaching deep states than on mutating the frame contents.
Answer to RQ2: Partly. On OpenThread Nexus, InterceptFuzz and Concat both expose the same seven signatures, while AFLNet exposes none. InterceptFuzz exposes each signature in at least as many campaigns as Concat, and the rarer ones in clearly more. On the ns-3 targets, its advantage is smaller. It exposes significantly more signatures per campaign than Concat only on ns-3 Wi-Fi, and AFLNet exposes about as many signatures as InterceptFuzz on both targets, with one more across all campaigns on ns-3 Wi-Fi.
RQ3: Prefix Tree Dynamics
The Prefix Tree is the state model of InterceptFuzz: in every epoch, Phase I adds new states to it, Phase II fuzzes the target from these states, and Phase III removes redundant ones. If Phase III did not keep the growth of the tree in check, the engine would have to divide its time in Phase II among an ever larger number of seeds, and thus of states. This section therefore examines how the tree evolves over a campaign in the default configuration on OpenThread Nexus.
Table V shows the size and depth of the tree after each epoch. In the first epoch, the tree consists of the four packets of the benign trace. Phase III already removes the last of them, as no transitions can follow the final packet. After the first fuzzing epoch, Phase I extends the tree by about 2,100 nodes, of which Phase III keeps about a quarter. In the following epochs, the tree fuzzed in Phase II levels off at about 3,000 nodes, while the part that survives minimization keeps growing, from 483 to 1,565 nodes, and Phase III removes a decreasing share of the tree, from 77% to 49%. Phase III thus keeps the growth of the tree in check: summed over the five epochs, Phase I adds a median of about 8,300 nodes per campaign, whereas the tree after the last Phase I comprises a median of 3,084 nodes. At the same time, minimization does not discard the progress of earlier epochs, as the retained part of the tree grows in every epoch. The tree also grows deeper: the deepest state retained after the last epoch lies a median of 46 packets after the start of the procedure, compared with 4 packets in the benign trace. InterceptFuzz therefore reaches and keeps states far beyond the benign trace, and keeps them reachable through the stored
| Epoch | End (h) | Ph. I | Ph. III | Rem. | Max. depth | Branches |
| 1 | 1.6 | 4 [4, 4] | 3 [3, 3] | 25% [25, 25] | 3 [3, 3] | 5,194 [5,002–5,284] |
| 2 | 4.8 | 2,071 [1,844–2,408] | 483 [400–661] | 77% [73–79] | 11 [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28] | 6,191 [5,975–6,405] |
| 3 | 9.6 | 2,748 [2,554–3,094] | 966 [815–1,189] | 65% [62–68] | 20 [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39] | 6,599 [6,375–6,728] |
| 4 | 16.0 | 2,952 [2,680–3,339] | 1,339 [1,059–1,632] | 56% [51–60] | 28 [17–47] | 6,780 [6,604–6,990] |
| 5 | 24.0 | 3,084 [2,700–3,433] | 1,565 [1,337–1,845] | 49% [45–52] | 46 [18–67] | 6,915 [6,715–7,116] |
Table V. Prefix tree per epoch on OpenThread Nexus (InterceptFuzz, five epochs, linear schedule). Ph. I: tree nodes (excluding the root) after phase I, i.e., the tree that phase II fuzzes. Ph. III: tree nodes after phase III, i.e., the tree that phase I of the next epoch extends. Rem.: share of nodes removed by phase III. Max. depth: depth of the deepest node after phase III. Branches: branches covered at the end of the epoch. Medians over ten campaigns, with min–max in brackets.
prefixes rather than through the fuzzer’s own mutations. On the two ns-3 targets, the tree evolves in the same way at a smaller scale, ending with a median of 370 nodes after the last Phase I on ns-3 Wi-Fi and 29 on ns-3 LTE. Appendix A lists the per-epoch values of both targets.
The last column of Table V relates this growth to branch coverage. In the first epoch, which fuzzes only the states of the benign trace, InterceptFuzz covers 5,194 branches, fewer than the 5,481 branches that Concat covers in the same 1.6 hours. After the first tree extension, the second epoch adds 997 branches in 3.2 hours, and from then on InterceptFuzz stays ahead of Concat, with 6,191 against 5,979 branches after 4.8 hours. Every InterceptFuzz campaign reaches the final median coverage of Concat (6,603 branches after 24 hours), after a median of 10.5 hours [7.3–16.0]. In the later epochs, in which the fuzzed tree grows only from 2,748 to 3,084 nodes, the gains shrink to 408, 181, and 135 branches, although the epochs become longer. Diminishing gains are typical of any fuzzing campaign, so these numbers alone do not show how much of the slowdown is due to the slower tree growth. The comparison with Concat in Figure 3a, however, shows that InterceptFuzz overtakes the baseline only once the tree has been extended beyond the benign trace.
Answer to RQ3: On OpenThread Nexus, the Prefix Tree grows quickly once the first fuzzing epoch has ended and then levels off. Phase III removes a large share of the nodes in every epoch and thus keeps the growth of the tree in check, without discarding the progress of earlier epochs, as the retained tree grows in size and depth in every epoch. On the two ns-3 targets, the tree follows the same pattern at a smaller scale.
RQ4: Epoch Settings
For OpenThread Nexus, Table VI compares the default setting, five epochs under the linear schedule (5L), with five epochs under the uniform schedule (5U) and ten epochs under the linear (10L) and under the uniform schedule (10U). The default (5L) reaches the highest median branch coverage, but only the difference to 10L is statistically significant (, ). For 5U (, ) and 10U (), ten campaigns are not sufficient to establish a difference. The number of crash signatures per campaign differs little between the settings, with medians of 4.5 to 5, and no difference to the default is statistically significant. Appendix A lists the per-epoch values of all four settings. All four settings end with Prefix Trees of similar size: after the last Phase I, the medians range from 2,844 to 3,084 nodes, and after the last Phase III from 1,366 to 1,694 nodes.
Table VI. Epoch settings on OpenThread Nexus (10 campaigns each): final branch coverage, median [min–max], compared with the default 5L (two-sided Mann-Whitney test and , the probability that a 5L campaign covers more branches), and distinct crash signatures per campaign, median [min–max], and over all campaigns. L: linear schedule. U: uniform schedule.
The coverage at the end of each epoch shows where the settings diverge. Under 5U, the tree is extended for the first time only after 4.8 hours. At that point, 5U covers a median of 5,479 branches, whereas 5L and 10U, whose trees have already been extended after 1.6 and 2.4 hours, cover 6,191 and 6,113 branches. 5U narrows this gap in the later epochs but does not close it. This supports the rationale of the linear schedule: the earlier the tree grows beyond the benign trace, the earlier the fuzzer reaches the states that the trace does not contain.
10L extends the tree after only 26 minutes and leads 5L during the first hours, but falls behind between 6.5 and 9.2 hours and never catches up. A plausible reason is that its short first epoch leaves the states of the benign trace under-explored. After the first tree extension, these states have a median of 160 child nodes under 10L, against 229 to 277 under the other settings, and this gap persists until the end of the campaign. The number of child nodes is not the only factor, as 5U has the most children but not the highest coverage, and confirming the mechanism would require recording how much fuzzing time each state receives. The length of the first epoch thus appears to be a trade-off: a long first epoch delays the first tree extension (5U), whereas a very short one leaves the states of the benign trace under-explored (10L).
Answer to RQ4: The experiments on OpenThread Nexus show that InterceptFuzz is only moderately sensitive to the epoch settings. Of the three alternatives to the default, only ten epochs under the linear schedule reach significantly lower coverage. The length of the first epoch appears to have a larger impact than the number of epochs or the schedule as such.
RQ5: Integration Effort
As Table I shows, integrating InterceptFuzz required 11 hook calls in ns-3 LTE and a single hook call each in ns-3 Wi-Fi and OpenThread Nexus. This difference reflects how the transmit paths of the targets are structured rather than the protocols themselves. ns-3 LTE serializes every RRC message in a dedicated send function, whereas ns-3 Wi-Fi passes all frames through one MAC queue and OpenThread Nexus through one simulated radio. Line counts include only code added or modified for fuzzing, excluding comments, debugging output, build files, and changes that any fuzzing campaign of the target needs, such as disabling logging or encryption. Changes to the target and to the fuzzing harness are reported separately.
The changes to the target are small. In ns-3 LTE, an 8-line helper copies the serialized RRC message, passes it to the hook, and wraps the result in a new packet. Each of the 11 send functions then calls this helper, for 21 lines in total. In ns-3 Wi-Fi, the hook in the MAC queue mutates the frame body and rebuilds the frame with the original MAC header, in 11 lines. In OpenThread Nexus, the hook in the simulated radio takes 6 lines, including the selection of the nodes whose packets are fuzzed.
The harnesses for ns-3 are short new programs of 59 and 48 lines, built from the standard ns-3 helpers. They set up the two nodes, seed ns-3’s random number generator, and run the scenario from the entry point. In OpenThread Nexus, the harness reuses the existing integration test test_form_join.cpp: 19 lines were added and 47 removed. The changes drop the nodes not involved in the scenario together with logging and assertions, and replace main() with the INTERCEPTFUZZ_TEST macro, which provides the libFuzzer-style entry point. Any other simulation test can be turned into a harness in the same way.
Answer to RQ5: Integrating InterceptFuzz requires little manual effort. The changes to the targets range from 6 to 21 lines, and the harnesses are either short programs built from standard helpers or an existing integration test adapted with a few dozen changed lines.
Discussion
The evaluation shows that InterceptFuzz enables stateful fuzzing of single-process targets without modifying the underlying engine and with limited integration effort, and that Phase III keeps the growth of its Prefix Tree in check over long campaigns. This section first discusses how to configure InterceptFuzz in practice and which design choices remain open, and then turns to its limitations and to threats to the validity of the evaluation. It closes with further applications of InterceptFuzz.
Choosing the Epoch Settings
Section IV-F shows that the length of the first epoch matters most: it should be long enough to explore the states of the benign trace, but not so long that it delays the first extension of the Prefix Tree. On the two ns-3 targets, five epochs under the uniform and under the linear schedule perform alike. Five epochs under the linear schedule performed best on OpenThread Nexus. However, the best setting may differ for other targets.
Protecting the State ID
As described in § 3-E, InterceptFuzz deliberately leaves the State ID exposed to mutation, so that a mutated but valid identifier delivers a packet in a different state. A custom mutator could instead protect the identifier and leave the choice of the state entirely to the seed scheduling of the engine. In preliminary experiments, such a variant achieved slightly lower coverage than the default design and was therefore not pursued further. A systematic comparison of both designs is left for future work.
Concatenation as a Baseline
Evaluations of stateful fuzzers commonly include AFLnwe [39] as a baseline, for example in ProFuzzBench [40] and in the evaluation of Nyx-Net [19]. However, AFLnwe is not only stateless but also unaware of message boundaries. It writes the entire input to the target in a single transmission, so whether the target processes more than one message depends on how it reads its input. LightFTP [41], one of the ProFuzzBench targets, for example, treats all bytes received at once as a single command. In contrast, Concat separates the messages by an explicit delimiter and delivers each message separately, as stateful fuzzers do. To the best of the authors’ knowledge, such a delimiter-based concatenation is rarely part of the evaluation of stateful fuzzers, although it would arguably provide a stronger lower bound than AFLnwe. In this evaluation, Concat covers more branches than AFLNet in every campaign on OpenThread Nexus and ns-3 Wi-Fi and exposes the same seven crash signatures in OpenThread as InterceptFuzz. For socket-based targets, such a baseline can be built by keeping the message delivery of AFLNet or another socket-based stateful fuzzer and replacing its mutation algorithm with that of Concat.
Relation to ThreadFuzzer
ThreadFuzzer [29] also fuzzes OpenThread, but it pursues a different goal. It is a dedicated fuzzer for the Thread protocol that primarily targets physical Thread devices, whereas InterceptFuzz uses the single-process Nexus framework. The two also intercept packets at different layers. ThreadFuzzer mutates the fields of MLE messages while the protocol stack constructs them, whereas the hook of InterceptFuzz intercepts complete IEEE 802.15.4 frames, so that every packet exchanged between the nodes can be mutated, including TMF messages. Their fuzzing approaches differ as well. ThreadFuzzer mutates benign packets, either randomly or with per-field mutation probabilities adapted from coverage feedback, without keeping a corpus or a model of the states it reaches. InterceptFuzz instead retains the inputs that increase coverage and delivers them in recorded states. Finally, the two tools were applied to different versions of OpenThread, so their results are not compared directly.
Fuzzing Throughput
In all three targets, the cost of an execution is dominated by the simulation of the scenario rather than by the fuzzer itself, and it therefore depends on how the mutated packets change the course of the exchange. The targets differ in how a disrupted exchange affects this cost. OpenThread Nexus simulates a fixed 18 seconds, in which a successful attach is followed by further traffic between the nodes, so an input that breaks the attach early leaves fewer packets to simulate. ns-3 LTE instead ends the run as soon as the RRC connection is established, so an input that breaks the attach keeps the run going until the timeout. ns-3 Wi-Fi always simulates a fixed 300 ms.
The throughput in Table II is consistent with this difference. Each input of Concat replaces every packet of the exchange that it covers, whereas InterceptFuzz mutates a single packet after an unmodified prefix, so Concat likely breaks the exchange more often. On OpenThread Nexus, this makes its executions cheaper, and it executes 1.3 times as many inputs per second as InterceptFuzz. On ns-3 LTE, it makes them more expensive, and InterceptFuzz executes 2.4 times as many. On ns-3 Wi-Fi, whose simulated time is fixed, the two differ only slightly. The throughput of AFLNet, in turn, may also be affected by its different underlying engine, LibAFL.
The throughput of a fuzzer thus depends not only on the fuzzer but also on how the target reacts to a broken exchange. Concat gains throughput where a broken exchange ends the simulated work early, which partly explains why it is a strong baseline on OpenThread Nexus and a weaker one on the ns-3 targets.
Limitations
Determinism. Like AFLNet [9] and LibAFLstar [20], which also reach states by replaying message sequences, InterceptFuzz assumes that the target behaves deterministically, so that the prefix of a State ID always leads to the same state. As described in Section IV-A, the evaluated harnesses ensure this by fixing all random seeds. Targets whose behavior depends on wall-clock time or thread interleavings, as well as physical devices, fall outside this model. InterceptFuzz does not check whether a replayed prefix triggers the hook calls in the recorded order, so a divergence would go unnoticed and deliver the payload in a different state than intended. How often this occurs was not measured in the evaluation.
Cost of deep states. Like the concatenation baseline, every execution starts from the initial state and replays the full prefix of the addressed state, so executions at deep states are more expensive. Combining InterceptFuzz with snapshot-based techniques [17, 19], which restore a saved state instead of replaying the prefix, could remove this cost and is left for future work.
One freshly mutated packet per execution. Each execution mutates a single packet after the replayed prefix. Sequences with several mutated packets arise only across epochs, as Phase I adds the mutated packets of retained inputs to the Prefix Tree, so that a sequence fuzzed in epoch contains at most mutated packets. Bugs that require several packets to be mutated together may therefore take longer to reach than with Concat, which can mutate every packet of a sequence in a single execution.
Scope of the scenario. InterceptFuzz explores the protocol behavior that the scenario of the harness can lead to, so procedures that the scenario never triggers remain out of reach. The evaluated ns-3 scenarios, for example, contain a single base station and a single access point, so handover in LTE and roaming between access points in Wi-Fi cannot occur. Covering such procedures requires further scenarios, each with its own harness.
Equivalent states. The Prefix Tree identifies a state by the packet sequence that leads to it. Different prefixes may lead to the same internal state, but determining this exactly would require comparing the complete program state, and even then, irrelevant differences such as counters or memory addresses would obscure equivalent states. InterceptFuzz does not attempt to detect equivalent states and instead keeps its design simple, with every prefix forming a node of its own.
Deployment in OSS-Fuzz. InterceptFuzz is designed to run on the engines that OSS-Fuzz deploys, but it has not been deployed there. An input [State ID][payload] reproduces a crash only together with the Prefix Tree that defines its State ID. For local use, InterceptFuzz provides a script that converts such inputs into self-contained inputs that encode the full packet sequence and are replayed by a dedicated harness. Running InterceptFuzz in a continuous fuzzing service would additionally require keeping the Prefix Tree consistent with a corpus that the service manages on its own, for example when it prunes the corpus. This integration is left for future work.
Manual integration. Integrating InterceptFuzz into a target requires placing the hook calls by hand and writing a fuzzing harness that runs the scenario. This effort is limited, however: as Section IV-G shows, the evaluated targets required at most 11 hook calls and 21 changed lines, all at points where packets pass from one node to another. If the target already provides an integration test of the scenario, the harness can be derived from it, as for OpenThread Nexus. A developer familiar with the code base can carry out both steps quickly, and large language models could likely assist with them.
Threats to Validity
With ten campaigns per configuration, small differences may remain undetected, and some results lie close to the significance threshold. The AFLNet baseline is the LibAFL reimplementation released with Fizzle, adapted to the interception points of InterceptFuzz. As AFLNet infers its state model from response codes, which the evaluated protocols do not provide, applying it to these targets requires mapping each captured response to a target-specific code. Without such a mapping, AFLNet cannot be applied to these targets at all. A different mapping, or the original implementation of AFLNet, could nevertheless lead to different results. Finally, the evaluation covers three targets built on two simulators. They show that InterceptFuzz applies to different protocols, but not that its results carry over to every single-process target. The targets were hand-picked by the authors for this evaluation, as no established benchmark covers this setting. ProFuzzBench [40], the common benchmark for stateful fuzzers, consists of network servers that run as separate processes and communicate over sockets, and none of its targets implements a wireless protocol.
Applicability Beyond Protocol Simulators
To apply InterceptFuzz to a target, it suffices that all communicating parties run deterministically within a single process and that their messages pass a point at which they can be intercepted. Beyond protocol simulators, this also holds for deterministic simulation testing of distributed systems such as FoundationDB, which simulates an entire cluster within a single-threaded process [42]. Since InterceptFuzz exposes a libFuzzer-style entry point, other stateless engines, such as libFuzzer itself, can in principle be used in place of AFL++.
Conclusion
Testing wireless protocol stacks efficiently is difficult, as physical devices are slow, costly to scale, and hard to observe. Many stacks therefore provide simulation platforms that replace the radio with a software model. Single-process simulations, in which all nodes run within one process, go one step further. They offer high throughput and deterministic execution, which makes them well suited to fuzzing, but they contain no sockets for existing stateful fuzzers to attach to.
This paper presented InterceptFuzz, which intercepts packets inside such simulations and lets an unmodified stateless fuzzing engine fuzz them in different protocol states. Each seed carries a State ID, which selects the state in which its packet is delivered. As the engine remains unmodified, InterceptFuzz inherits its mutators, scheduling, and instrumentation, as well as their future improvements, without reimplementing any of them for stateful fuzzing. The states are recorded in a Prefix Tree, from which InterceptFuzz reaches each state by deterministically replaying the packets that lead to it. The tree grows over the campaign as fuzzing uncovers new states.
Integrating InterceptFuzz into a target requires only a hook where packets pass between nodes and a harness that runs the scenario, which on the evaluated targets amounted to at most 21 changed lines of target code. InterceptFuzz was evaluated on three targets, OpenThread Nexus and the LTE and Wi-Fi models of ns-3, against the delimiter-based concatenation baseline Concat and an interception-based adaptation of AFLNet. On OpenThread Nexus and ns-3 Wi-Fi, it covers significantly more branches than both baselines. On ns-3 LTE, all three fuzzers perform similarly, likely because the fuzzed scenario contains little code beyond the connection setup. During the experiments, seven previously unknown crash signatures were found in OpenThread, which could affect commercially deployed Thread devices. The experiments also showed that InterceptFuzz is only moderately sensitive to its epoch settings, of which the length of the first epoch matters most.
Future work includes evaluating InterceptFuzz on further single-process targets that, like OpenThread, implement complex protocols with many message types. As § 2-E discusses, such targets are currently rare among open-source wireless stacks. Should more stacks adopt single-process simulation, a benchmark of such targets would allow stateful fuzzers to be compared in this setting, with InterceptFuzz and Concat as reference points.
Acknowledgment
This work was supported by the Flemish Government through the Cybersecurity Research Program with grant number VOEWICS02.
Prefix Tree Dynamics
Table VII extends Table V to all four epoch settings compared in Section IV-F. Table VIII shows the same values for the default setting on the two ns-3 targets.
| Epoch | End (h) | Ph. I | Ph. III | Rem. | Max. depth | Branches |
| 5L: five epochs, linear schedule (default) | ||||||
| 1 | 1.6 | 4 [4, 4] | 3 [3, 3] | 25% [25, 25] | 3 [3, 3] | 5,194 [5,002–5,284] |
| 2 | 4.8 | 2,071 [1,844–2,408] | 483 [400–661] | 77% [73–79] | 11 [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28] | 6,191 [5,975–6,405] |
| 3 | 9.6 | 2,748 [2,554–3,094] | 966 [815–1,189] | 65% [62–68] | 20 [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39] | 6,599 [6,375–6,728] |
| 4 | 16.0 | 2,952 [2,680–3,339] | 1,339 [1,059–1,632] | 56% [51–60] | 28 [17–47] | 6,780 [6,604–6,990] |
| 5 | 24.0 | 3,084 [2,700–3,433] | 1,565 [1,337–1,845] | 49% [45–52] | 46 [18–67] | 6,915 [6,715–7,116] |
| 5U: five epochs, uniform schedule | ||||||
| 1 | 4.8 | 4 [4, 4] | 3 [3, 3] | 25% [25, 25] | 3 [3, 3] | 5,479 [5,333–5,569] |
| 2 | 9.6 | 2,382 [2,208–2,756] | 603 [522–748] | 75% [73–78] | 15 [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] | 6,287 [6,079–6,473] |
| 3 | 14.4 | 2,927 [2,619–3,161] | 1,047 [881–1,131] | 64% [62–67] | 20 [14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37] | 6,641 [6,337–6,814] |
| 4 | 19.2 | 2,928 [2,523–3,215] | 1,262 [1,070–1,526] | 57% [51–59] | 21 [17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41] | 6,750 [6,507–6,891] |
| 5 | 24.0 | 2,931 [2,661–3,454] | 1,366 [1,138–1,692] | 54% [47–57] | 24 [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41] | 6,818 [6,723–6,976] |
| 10L: ten epochs, linear schedule | ||||||
| 1 | 0.4 | 4 [4, 4] | 3 [3, 3] | 25% [25, 25] | 3 [3, 3] | 4,713 [4,554–4,912] |
| 2 | 1.3 | 1,656 [1,477–1,909] | 342 [248–462] | 80% [75–85] | 9 [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36] | 5,343 [5,042–5,611] |
| 3 | 2.6 | 2,034 [1,830–2,478] | 615 [521–806] | 71% [65–73] | 13 [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40] | 5,766 [5,547–6,169] |
| 4 | 4.4 | 2,190 [1,910–2,519] | 813 [645–1,020] | 62% [56–68] | 21 [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39] | 6,154 [5,948–6,407] |
| 5 | 6.5 | 2,441 [2,153–3,273] | 1,128 [928–1,557] | 53% [51–60] | 24 [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39] | 6,354 [6,140–6,480] |
| 6 | 9.2 | 2,493 [2,250–3,252] | 1,220 [1,014–1,676] | 51% [45–58] | 28 [22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39] | 6,468 [6,218–6,693] |
| 7 | 12.2 | 2,551 [2,209–3,342] | 1,313 [1,182–1,813] | 47% [44–51] | 31 [23–45] | 6,565 [6,343–6,827] |
| 8 | 15.7 | 2,710 [2,335–3,461] | 1,491 [1,261–1,977] | 45% [41–49] | 32 [27–49] | 6,669 [6,400–6,846] |
| 9 | 19.6 | 2,757 [2,383–3,332] | 1,652 [1,324–2,042] | 42% [39–45] | 34 [27–61] | 6,691 [6,485–6,880] |
| 10 | 24.0 | 2,844 [2,415–3,447] | 1,694 [1,367–2,075] | 41% [35–43] | 45 [27–61] | 6,737 [6,539–6,934] |
| 10U: ten epochs, uniform schedule | ||||||
| 1 | 2.4 | 4 [4, 4] | 3 [3, 3] | 25% [25, 25] | 3 [3, 3] | 5,235 [4,984–5,324] |
| 2 | 4.8 | 2,155 [2,021–2,426] | 494 [442–631] | 77% [74–79] | 11 [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33] | 6,113 [5,696–6,223] |
| 3 | 7.2 | 2,697 [2,354–2,883] | 898 [760–1,066] | 66% [62–70] | 18 [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] | 6,260 [6,075–6,533] |
| 4 | 9.6 | 2,689 [2,455–2,916] | 1,089 [932–1,223] | 59% [55–64] | 20 [17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35] | 6,363 [6,236–6,867] |
| 5 | 12.0 | 2,716 [2,500–2,930] | 1,223 [1,130–1,536] | 55% [48–59] | 23 [18–52] | 6,496 [6,315–6,997] |
| 6 | 14.4 | 2,732 [2,487–3,060] | 1,309 [1,177–1,559] | 52% [46–57] | 24 [16–51] | 6,550 [6,405–7,011] |
| 7 | 16.8 | 2,813 [2,494–3,111] | 1,475 [1,257–1,648] | 49% [42–54] | 30 [16–55] | 6,699 [6,437–7,019] |
| 8 | 19.2 | 2,846 [2,502–3,127] | 1,547 [1,307–1,774] | 47% [40–52] | 33 [18–55] | 6,760 [6,530–7,022] |
| 9 | 21.6 | 2,841 [2,497–3,175] | 1,575 [1,328–1,758] | 45% [40–51] | 34 [19–55] | 6,870 [6,617–7,113] |
| 10 | 24.0 | 2,864 [2,498–3,165] | 1,614 [1,416–1,828] | 43% [40–52] | 35 [19–55] | 6,883 [6,646–7,145] |
Table VII. Prefix tree per epoch on OpenThread Nexus for all four epoch settings of InterceptFuzz (10 campaigns each). End (h): end of the epoch’s Phase II within the campaign. Ph. I: tree nodes (excluding the root) after Phase I, i.e., the tree that Phase II fuzzes. Ph. III: tree nodes after Phase III, i.e., the tree that Phase I of the next epoch extends. Rem.: share of nodes removed by Phase III. Max. depth: depth of the deepest node after Phase III. Branches: branches covered at the end of the epoch. Medians over ten campaigns, with min–max in brackets.
| Epoch | End (h) | Ph. I | Ph. III | Rem. | Max. depth | Branches |
| ns-3 Wi-Fi | ||||||
| 1 | 0.7 | 7 [7, 7] | 4 [3, 4, 5] | 43% [29–57] | 4 [3, 4, 5] | 40,911 [40,883–41,010] |
| 2 | 2.0 | 277 [230–318] | 77 [63–89] | 72% [65–77] | 9 [7, 8, 9, 10, 11] | 40,994 [40,969–41,100] |
| 3 | 4.0 | 303 [266–345] | 121 [104–153] | 59% [55–66] | 13 [11, 12, 13, 14, 15, 16] | 41,273 [41,046–41,328] |
| 4 | 6.7 | 362 [300–402] | 171 [126–190] | 53% [43–58] | 17 [12, 13, 14, 15, 16, 17, 18, 19] | 41,344 [41,053–41,458] |
| 5 | 10.0 | 370 [323–441] | 201 [160–254] | 46% [38–53] | 18 [15, 16, 17, 18, 19, 20, 21, 22, 23] | 41,363 [41,054–41,459] |
| ns-3 LTE | ||||||
| 1 | 0.7 | 3 [3, 3] | 2 [2, 2] | 33% [33, 33] | 2 [2, 2] | 34,821 [34,809–35,039] |
| 2 | 2.0 | 34 [14–44] | 16 [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] | 53% [53–57] | 3 [3, 3] | 34,889 [34,868–35,091] |
| 3 | 4.0 | 26 [17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29] | 18 [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22] | 33% [24–47] | 5 [5, 5] | 34,917 [34,889–35,111] |
| 4 | 6.7 | 30 [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35] | 21 [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26] | 30% [21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41] | 7 [5, 6, 7, 8] | 34,919 [34,889–35,112] |
| 5 | 10.0 | 29 [22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36] | 22 [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25] | 30% [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38] | 7 [5, 6, 7, 8, 9] | 34,922 [34,913–35,113] |
Table VIII. Prefix tree per epoch on the ns-3 targets (InterceptFuzz, five epochs, linear schedule, 10 campaigns each). Columns as in Table V. Medians over ten campaigns, with min–max in brackets.
Coverage Breakdown on OpenThread Nexus
Table IX breaks down the branch coverage of Table II on OpenThread Nexus by source directory and lists the files with the largest differences between InterceptFuzz and Concat. The breakdown is computed from gcovr reports of a separate replay of the same inputs as Table II. Repeated replays occasionally yield slightly different coverage, so the totals deviate from that table by at most two branches.
| Directory / file | AFLNet | Concat | IF | IF − Concat | IF − AFLNet |
| thread | 2,021 [1,933–2,087] | 3,609 [3,446–3,691] | 3,759 [3,669–3,967] | +150 | +1,738 |
| meshcop | 165 [163–244] | 230 [215–325] | 365 [314–394] | +135 | +200 |
| coap | 36 [36–119] | 151 [111–217] | 245 [243–246] | +94 | +209 |
| net | 527 [516–668] | 716 [561–745] | 573 [562–720] | −143 | +46 |
| mac | 499 [495–505] | 610 [560–623] | 593 [592–626] | −17 | +94 |
| common | 622 [608–652] | 765 [721–784] | 758 [746–780] | −7 | +136 |
| utils | 82 [81–87] | 130 [119–192] | 141 [119–197] | +11 | +59 |
| crypto | 110 [110–110] | 111 [111–111] | 111 [111–111] | 0 | +1 |
| Other | 237 [236–240] | 300 [295–315] | 300 [296–313] | 0 | +63 |
| Total | 4,323 [4,219–4,555] | 6,602 [6,250–6,790] | 6,915 [6,715–7,116] | +313 | +2,592 |
| Files with the largest differences between IF and Concat | |||||
| coap.cpp | 18 [18–48] | 88 [54–132] | 155 [154–156] | +67 | +137 |
| secure_transport.cpp | 14 [14–85] | 14 [14–85] | 79 [77–85] | +65 | +65 |
| address_resolver.cpp | 91 [81–98] | 128 [116–134] | 160 [137–188] | +32 | +69 |
| network_data_leader_ftd.cpp | 25 [25, 25] | 134 [68–143] | 162 [132–234] | +28 | +137 |
| coap_message.cpp | 18 [18–69] | 60 [55–82] | 86 [85–86] | +26 | +68 |
| dataset_manager.cpp | 32 [32, 33] | 51 [43–52] | 74 [50–77] | +23 | +42 |
| tmf.cpp | 15 [14, 15, 16] | 19 [14, 15, 16, 17, 18, 19, 20, 21] | 38 [33–45] | +19 | +23 |
| lowpan.cpp | 343 [311–364] | 367 [321–382] | 383 [360–405] | +16 | +40 |
| dns_types.cpp | 0 [0–72] | 73 [0–75] | 0 [0–75] | −73 | 0 |
| dnssd_server.cpp | 5 [5–65] | 65 [5–69] | 5 [5–68] | −60 | 0 |
| mesh_forwarder.cpp | 212 [206–218] | 269 [257–274] | 244 [242–257] | −25 | +32 |
| ip6_mpl.cpp | 75 [73–77] | 97 [93–97] | 77 [76–77] | −20 | +2 |
| Further files with the largest differences between IF and AFLNet | |||||
| mle.cpp | 384 [379–424] | 799 [789–810] | 790 [758–800] | −9 | +406 |
| mle_ftd.cpp | 356 [338–401] | 692 [645–725] | 686 [641–746] | −6 | +330 |
| network_data.cpp | 18 [18, 18] | 142 [117–147] | 146 [119–148] | +4 | +128 |
| router_table.cpp | 68 [64–95] | 166 [133–177] | 165 [162–184] | −1 | +97 |
| indirect_sender.cpp | 1 [1, 1] | 74 [49–78] | 71 [67–75] | −3 | +70 |
Table IX. TABLE IX. BRANCH COVERAGE ON OPENTHREAD NEXUS PER DIRECTORY OF src/core AND FOR THE FILES WITH THE LARGEST DIFFERENCES BETWEEN INTERCEPTFUZZ (IF) AND CONCAT AND BETWEEN INTERCEPTFUZZ AND AFLNET, MEDIAN [MIN–MAX] OVER TEN CAMPAIGNS. IF − CONCAT AND IF − AFLNET: DIFFERENCES OF THE MEDIANS.
Most additional branches of InterceptFuzz lie in the handling of TMF messages. InterceptFuzz covers a median of 245 CoAP branches against 151 for Concat and 36 for AFLNet, and 365 against 230 and 165 branches in the commissioning and dataset code (src/core/meshcop). Within src/core/thread, the largest gains lie in the Leader’s handling of Network Data and in address resolution. Across all campaigns, 771 branches are covered only by InterceptFuzz, against 173 covered only by Concat. In turn, Concat covers more branches in the network layer (src/core/net), mainly because it reaches the DNS-SD server in six of its ten campaigns, against three for InterceptFuzz.
InterceptFuzz covers a median of 2,592 branches more than AFLNet, two thirds of them in src/core/thread. Most of this difference lies in code that Concat covers as well. AFLNet already covers only about half of the MLE code (mle.cpp and mle_ftd.cpp) that the other two fuzzers reach, and it covers hardly any of the code that runs after a completed attach, such as the handling of Network Data and the indirect transmission to children. Across all campaigns, 2,999 branches are covered by InterceptFuzz but never by AFLNet, 2,229 of which are also covered by Concat. Only eight branches are covered by AFLNet but never by InterceptFuzz.
As the benign run ends shortly after the attach, it contains no TMF message. To see whether the Prefix Trees of InterceptFuzz nevertheless reach the states in which TMF messages are exchanged, their packets are classified by decoding their IEEE 802.15.4, 6LoWPAN, and UDP headers: MLE messages use UDP port 19788 and TMF messages port 61631. The type of a TMF request is taken from the resource in its CoAP Uri-Path options. The tree after Phase I contains no TMF message in the first epoch of any campaign, and a median of 46, 219, 295, and 351 TMF messages in epochs 2 to 5. In the final trees, TMF messages account for 9–18% of all nodes. The most frequent requests are Address Queries, carried by 622 edges, and Network Data registrations, carried by 508 edges, both occurring in all ten campaigns. They are followed by Address Solicit requests and requests for commissioner and dataset information, each occurring in seven or eight campaigns.
References
- [1]Google, “OSS-Fuzz - continuous fuzzing for open source software.” https://github.com/google/oss-fuzz, 2026. [Accessed 30-Oct-2026].
- [2]LLVM Project, “LibFuzzer – a library for coverage-guided fuzz testing.” https://llvm.org/docs/LibFuzzer.html. [Accessed 30-Oct-2026].
- [3]A. Fioraldi, D. Maier, H. Eibfeldt, and M. Heuse, “AFL++: Combining incremental steps of fuzzing research,” in 14th USENIX Workshop on Offensive Technologies (WOOT 20), USENIX Association, Aug. 2020.
- [4]Google, “honggfuzz.” https://github.com/google/honggfuzz, 2026. [Accessed 30-Oct-2026].
- [5]Google, “Centipede: A distributed fuzzing engine.” https://github.com/google/fuzztest/tree/main/centipede, 2026. [Accessed 30-Oct-2026].
- [6]G. F. Riley and T. R. Henderson, “The ns-3 network simulator,” in Modeling and Tools for Network Simulation, pp. 15–34, Springer, 2010.DOI
- [7]Google, “OpenThread.” https://openthread.io/, 2026. [Accessed 30-Oct-2026].
- [8]Van-Thuan Pham and Marcel Böhme and Abhik Roychoudhury, “AFLNet: A Greybox Fuzzer for Network Protocols,” in Proceedings of the 13th IEEE International Conference on Software Testing, Verification and Validation : Testing Tools Track, 2020.DOI
- [9]R. Meng, V.-T. Pham, M. Böhme, and A. Roychoudhury, “AfLnet five years later: On coverage-guided protocol fuzzing,” IEEE Transactions on Software Engineering, vol. 51, no. 4, pp. 960–974, 2025.arxiv.org/abs/2412.20324
- [10]A. Fioraldi, D. C. Maier, D. Zhang, and D. Balzarotti, “LibAFL: A framework to build modular and reusable fuzzers,” in Proceedings of the 2022 ACM SIGSAC Conference on Computer and Communications Security (CCS), pp. 1051–1065, ACM, 2022.
- [11]N. Bennett, T. Tucker, C. Stillman, W. Enck, P. Traynor, and K. Butler, “Fizzle: A framework for deterministic and reproducible network fuzzing,” in 2026 IEEE Symposium on Security and Privacy (SP), 2026.DOI
- [12]Miller, Barton P. and Fredriksen, Lars and So, Bryan, “An empirical study of the reliability of UNIX utilities,” Commun. ACM, vol. 33, p. 32–44, Dec. 1990.DOI
- [13]C. Daniele, “Is stateful fuzzing really challenging?,” 2024.arxiv.org/abs/2406.07071
- [14]R. Natella, “StateAFL: Greybox Fuzzing for Stateful Network Servers,” Empirical Software Engineering, vol. 27, no. 191, 2022.DOI
- [15]J. Ba, M. Böhme, Z. Mirzazomen, and A. Roychoudhury, “Stateful Greybox Fuzzing,” in 31st USENIX Security Symposium (USENIX Security 22), (Boston, MA), pp. 3255–3272, USENIX Association, Aug. 2022.arxiv.org/abs/2204.02545
- [16]R. Meng, M. Mirchev, M. Böhme, and A. Roychoudhury, “Large Language Model guided Protocol Fuzzing,” in Proceedings of the 31st Annual Network and Distributed System Security Symposium (NDSS), 2024.DOI
- [17]A. Andronidis and C. Cadar, “SnapFuzz: High-throughput fuzzing of network applications,” in Proceedings of the 31st ACM SIGSOFT International Symposium on Software Testing and Analysis (ISSTA), pp. 340–351, 2022.
- [18]Fraunhofer FKIE, “libdesock: De-socketing for fuzzing.” https://github.com/fkie-cad/libdesock. [Accessed 30-Oct-2026].
- [19]S. Schumilo, C. Aschermann, A. Jemmett, A. Abbasi, and T. Holz, “Nyx-Net: Network fuzzing with incremental snapshots,” in Proceedings of the Seventeenth European Conference on Computer Systems (EuroSys), 2022.DOI
- [20]C. Daniele, T. Bethe, M. Maugeri, A. Continella, and E. Poll, “LibAFLStar: Fast and state-aware protocol fuzzing,” in Computer Security – ESORICS 2025 (V. Nicomette, A. Benzekri, N. Boulahia-Cuppens, and J. Vaidya, eds.), vol. 16055 of Lecture Notes in Computer Science, (Cham), pp. 105–123, Springer, 2026.DOI
- [21]M. Garbelini, C. Wang, and S. Chattopadhyay, “Greyhound: Directed Greybox Wi-Fi Fuzzing,” IEEE Transactions on Dependable and Secure Computing, vol. PP, pp. 1–1, 08 2020.DOI
- [22]M. E. Garbelini, C. Wang, S. Chattopadhyay, S. Sun, and E. Kurniawan, “SweynTooth: unleashing mayhem over bluetooth low energy,” in Proceedings of the 2020 USENIX Conference on Usenix Annual Technical Conference, USENIX ATC’20, (USA), USENIX Association, 2020.
- [23]W.-C. Kao, Y.-C. Chen, Y.-S. Lin, Y.-C. Yang, C.-Y. Li, and C.-Y. Huang, “BLuEMan: A stateful simulation-based fuzzing framework for open-source RTOS Bluetooth low energy protocol stacks,” in 34th USENIX Security Symposium (USENIX Security 25), 2025.
- [24]M. E. Garbelini, V. Bedi, S. Chattopadhyay, S. Sun, and E. Kurniawan, “BrakTooth: Causing Havoc on Bluetooth Link Manager via Directed Fuzzing,” in 31st USENIX Security Symposium (USENIX Security 22), (Boston, MA), pp. 1025–1042, USENIX Association, Aug. 2022.
- [25]J. Ruge, J. Classen, F. Gringoli, and M. Hollick, “Frankenstein: Advanced Wireless Fuzzing to Exploit New Bluetooth Escalation Targets,” in 29th USENIX Security Symposium (USENIX Security 20), pp. 19–36, USENIX Association, Aug. 2020.arxiv.org/abs/2006.09809
- [26]Matheus E. Garbelini, Zewen Shang, Shijie Luo, Sudipta Chattopadhyay, Sumei Sun, Ernest Kurniawan, “ASSET Research Group: Schoull.” https://asset-group.github.io/disclosures/5ghoul/. [Accessed 30-Oct-2026].
- [27]I. Siroš, D. Singelée, and B. Preneel, “CovFUZZ: Coverage-based fuzzer for 4G&5G protocols,” in 2025 IEEE 10th European Symposium on Security and Privacy (EuroS&P), pp. 737–754, IEEE, 2025.arxiv.org/abs/2410.20958
- [28]I. Siroš, R. Cavalcanti, D. Singelée, and B. Preneel, “Breaking the silence: Fuzzing LTE-M and NB-IoT protocols,” in Computer Security. ESORICS 2025 International Workshops, vol. 16233 of Lecture Notes in Computer Science, pp. 524–543, Springer, 2026.DOI
- [29]I. Siroš, J. Heirwegh, D. Singelée, and B. Preneel, “Threadfuzzer: A fuzzing framework for the thread protocol,” in 2026 22nd International Conference on Distributed Computing in Smart Systems and the Internet of Things (DCOSS-IoT), pp. 464–472, 2026.arxiv.org/abs/2511.17283
- [30]D. Song, F. Hetzelt, D. Das, C. Denspy, Y. Na, S. Volckaert, G. Vigna, C. Kruegel, J.-P. Seifert, and M. Franz, “PeriScope: An effective probing and fuzzing framework for the hardware-OS boundary,” in Network and Distributed System Security Symposium (NDSS), 2019.DOI
- [31]Google, “OpenThread simulation.” https://openthread.io/platforms/simulation. [Accessed 30-Oct-2026].
- [32]Software Radio Systems, “ssrsRAN 4G with ZMQ virtual radios.” https://docs.srsran.com/projects/4g/en/latest/app_notes/source/zeromq/source/index.html. [Accessed 30-Oct-2026].
- [33]Software Radio Systems, “ssrsRAN gNB with srsUE.” https://docs.srsran.com/projects/project/en/latest/tutorials/source/srsUE/source/index.html. [Accessed 30-Oct-2026].
- [34]Open5GS, “Open5GS test framework (tests/common/application.c).” https://github.com/open5gs/open5gs/blob/main/tests/common/application.c. [Accessed 30-Oct-2026].
- [35]Zephyr Project, “BabbleSim.” https://docs.zephyrproject.org/latest/develop/test/bsim.html. [Accessed 30-Oct-2026].
- [36]J. Malinen, “hostapd_supplicant testing with mac80211_hwsim.” https://w1.fi/cgit/hostap/tree/tests/hwsim/README. [Accessed 12-Oct-2026].
- [37]G. Klees, A. Ruef, B. Cooper, S. Wei, and M. Hicks, “Evaluating fuzz testing,” in Proceedings of the 2018 ACM SIGSAC Conference on Computer and Communications Security (CCS), pp. 2123–2138, ACM, 2018.arxiv.org/abs/1808.09700
- [38]A. Arcuri and L. Briand, “A practical guide for using statistical tests to assess randomized algorithms in software engineering,” in Proceedings of the 33rd International Conference on Software Engineering (ICSE), pp. 1–10, ACM, 2011.DOI
- [39]V.-T. Pham, “AFLnwe: A network-enabled version of AFL.” https://github.com/aflnwe/aflnwe. [Accessed 30-Oct-2026].
- [40]R. Natella and V.-T. Pham, “ProFuzzBench: A benchmark for stateful protocol fuzzing,” in Proceedings of the 30th ACM SIGSOFT International Symposium on Software Testing and Analysis (ISSTA), pp. 662–665, 2021.arxiv.org/abs/2101.05102
- [41]hirefox, “LightFTP: Small x86-32/x64 FTP server.” https://github.com/hirefox0x/LightFTP. [Accessed 30-Oct-2026].
- [42]J. Zhou, M. Xu, A. Shraer, B. Namasivayam, A. Miller, E. Tschannen, S. Atherton, A. J. Beamon, R. Sears, J. Leach, D. Rosenthal, X. Dong, W. Wilson, B. Collins, D. Scherer, A. Grieser, Y. Liu, A. Moore, B. Muppana, X. Su, and V. Yadav, “FoundationDB: A distributed unbundled transactional key value store,” in Proceedings of the 2021 International Conference on Management of Data (SIGMOD), 2021.DOI