Lynx Fuzzer
Lynx Fuzzer
3.1 Tracer
The tracer is the first component of LynxFuzzer to be ex- Figure 4: Fuzzer sub-components.
ecuted, as its task is finding out what to fuzz, i.e., it must
identify which of the target methods can be invoked. This The fuzzer is the main component of LynxFuzzer. After
information is contained into the dispatch tables of the tar- the tracer and the sniffer obtained all the ancillary informa-
get kext. Being able to locate the dispatch table of a kext, tion needed to properly fuzz one (or more) kext, the fuzzer
5.Valid Inputs 7.Panic Inputs/Statistics
Data Manager
3.Dispatch Tables
6.IoConnectCall
Sniffer Tracer Fuzzer
User mode
2.Dispatch Table
8.Coverage Info.
Kernel mode
4.System
Requests
1.IoConnectCall
Entries
Target
Inspect/Intercept
Non-root mode
Root mode
Hypervisor
Figure 3: Architecture of LynxFuzzer and interactions between its components (gray areas).
creates test cases (i.e., set of inputs) for the kext methods target method. Then, it generates pseudo-random inputs
and invoke them through the IOKit device interface. Fig- and fill the structure that are then sent to the target, in-
ure 4 reports the inner architecture of the component, that voking the method through IoConnectCallMethod(). If the
has three main parts: a request generator, a set of fuzzing system does not crash, then the procedure begins anew.
engines and a monitor.
The request generator is an extremely versatile compo- 3.3.2 Mutation Engine (ME)
nent: it must operate independently from the target kext This second fuzzing approach follows a principle that is
and the selected fuzzing engine. In a typical execution, it the opposite of the previous one: every new input is gener-
receives a test case from the engine, checks that it respects ated from valid inputs collected by the sniffer component.
what is specified in the dispatch table entry of the target The fuzzing process is roughly made of the following steps.
method and properly crafts the argument for an IoConnect- Valid inputs that were previously gathered by the sniffer are
CallMethod() that eventually executes the target method in mutated with different functions. Then, request containing
the kext. such forged inputs are sent to the target method. If the
If the test case does not cause a crash, the kext sends back system does not crash, the monitor checks the response of
an answer that is received by the monitor. Depending on the kext, possibly excluding values that caused the kext to
the received answer and on the engine currently in use, the return an error from next mutations. This greatly increases
monitor may decide to alter the engine so that the next test the efficiency of the fuzzer, in the case of inputs structures
case will depend on the result of the previous one. As we with a variable size, because it gradually eliminates those
will see later in this section, both the mutation engine and that are not accepted because of checks performed in the
the evolution engine leverage this information. code of the target method. The set of mutation functions
LynxFuzzer, furthermore, implements the concept of session- used by this engine includes: bit flipping, byte flipping, byte
based fuzzing: we do not save just the request that triggers swapping and size change.
the bug, but we record every request that we make from the
beginning of the fuzzing session. This practice is common 3.3.3 Evolution Engine (EE)
when fuzzing stateful network protocols [8], but is also use-
ful in our scenario. There are indeed kexts that maintain The evolution engine tries to overcome the limitations of
a “state” that is changed by a number of different fuzzing the previous ones. In an effort to reduce the use of pseudo-
requests until an invalid state is reached and a bug is trig- randomness, it leverages concepts of evolutionary algorithms
gered. For this reason, recording communication sessions, to generate new inputs.
instead of single requests, greatly improves the reproducibil- The hearth of any evolutionary algorithm is the fitness
ity of a bug. To record communication sessions between the function, that depicts the fittest elements that will con-
fuzzer and a target kext, every request is stored in the data tribute to build new generations. In LynxFuzzer, we devised
manager. Finally, the fuzzing engine is responsible for the two different fitness functions: one that measures the code
production of input values (or input vectors) that will be coverage of an input vector and one that measures the dis-
used by the request generator. Details of the three different tance of an input from an ideal target vector (input that
engines that we implemented in LynxFuzzer are described in crashes the system). In the first case, we strive to create a
the following sections. set of input vectors that can give us the best code-coverage
rate possible. The second, on the other hand, is useful when
we want to individuate inputs similar to a given one (e.g., a
3.3.1 Generation Engine (GE) vector that is known to trigger a bug in the target).
This is the simplest and quickest engine of the three. Its
generation process may be summed up as follows. At first, Code coverage analysis. Our code coverage analysis meth-
it builds data structures that can contain the input for the od works as follows. Before starting to invoke kext methods,
Code Coverage Perc. No. of Rps w/o Rps w/ Over-
Kext Methods Estimation Full
Kext
Inst. Tracing Tracing head