Coverage-guided fuzzers, like Jazzer, maximize the amount of executed code during fuzzing. This has proven to produce interesting findings deep inside the codebase. Only checking validation rules on the first application layer isn’t providing great benefits, whereas verifying logic in and interactions of deeply embedded components is.
To extend the amount of covered code, the fuzzer tries to mutate its input in such a way that it passes existing checks and reaches yet unknown code paths. Coverage-guided fuzzers rely on feedback from the fuzzed application during runtime to detect if a given mutation reached new code and should be explored further. At this point, the topic of the blog post comes into play, fuzzing hooks.
What Are Fuzzing Hooks Used For?
Fuzzing hooks intercept operators and function calls in the fuzzed application and give feedback to the fuzzer to help it reach new code paths. Jazzer already ships with a variety of predefined fuzzing hooks. There are three main areas in which fuzzing hooks are used.
1. Improving Fuzzer Input
Fuzzing hooks can help improve fuzzer input by intercepting operators and function calls. Jazzer already ships with a variety of predefined hooks. For example, operators on primitive types like equals (==), greater than (>), or less than (<) are directly intercepted on the bytecode level. The used values and the result of the operation are passed on to the fuzzer, so it can detect which part of its input was used and should be mutated. Many methods of common Java types are hooked to do the same. This reaches from the primitive type wrappers like Integer or Long, over the ubiquitous String, to Arrays and Map.
2. Finding Bugs
Hooks can also be used to detect bugs. For example, if a method is not allowed to be called with a specific input, a hook can intercept calls to it and report a finding if the prohibited input is detected. Jazzer also provides many hooks of this type. This ranges from checking that no unescaped data reaches LDAP queries up to detecting the famous Log4Shell bug.
3. Removing Fuzzing Blockers
The third use case for hooks is to remove fuzzing blockers. These are code parts that prevent the fuzzer from making progress. For example, if a check is dependent on random data the fuzzer can not affect its outcome. A hook could be used to return a fixed or deterministic value so that the fuzzer could overcome this blockade.
The remaining part of the blog post describes how you can create your own hooks using Jazzer's hooking framework to reach even deeper code parts in your application and detect even more bugs.
How to Craft Fuzzing Hooks
Jazzer provides the @MethodHook annotation to easily define hooks. It is available in the Jazzer API artifact, which can be downloaded on Jazzer’s release page or included via build systems using the following Maven coordinates from Maven Central:
<dependency>
<groupID>com.code-intelligence</groupId>
<artifactID>jazzer-api</artifactId>
<version>[current version]</version>
</dependency>
Let’s analyze an example hook and see how the mentioned use-cases are handled.
tabindex="0">import com.code_intelligence.jazzer.api.*;
@MethodHook(
type = HookType.REPLACE,
targetClassName = "com.example.DeepThought",
targetMethod = "ask"
)
public static Object askHook(
MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
Object question = arguments[0];
Jazzer.guideTowardsEquality((String) question, "What is the meaning of life?", hookId);
try {
Object result = method.invokeWithArguments(thisObject, question);
if (Objects.equals(result, “42”)) {
Jazzer.reportFindingFromHook(
new FuzzerSecurityIssueHigh("Invalid answer found"));
}
return 0;
} catch (Throwable ignored) {
return null;
}
}
1. Create the Annotation
@MethodHook registers the annotated method as a hook to intercept calls to a target method. The target is specified by the annotation parameters targetClass and targetMethod. targetClass should be the fully qualified name of the containing class and targetMethod the name of the method to which calls should be intercepted. Abstract classes and interfaces are also supported and calls to all their implementations will be hooked. In the case of an overloaded method, targetMethodDescriptor can be used to restrict the application of the hook to a particular overload by providing its signature.
The annotation parameter type determines whether the hook method will be called before (HookType.BEFORE), instead (HookType.REPLACE), or after (HookType.AFTER) every call to the target method.
2. Create the Hook Method
The hook method has to be public static and expects the parameters MethodHandle method, Object thisObject, Object[] arguments, int hookId. AFTER hooks also receive the return value of the target method as an additional Object parameter at the end of the parameter list and REPLACE hooks have to return an Object as a result value.
Multiple BEFORE and AFTER hooks may also point to one target method, but only one REPLACE hook is allowed. Hooks have to take care to not lead the fuzzer in contradictory directions.
It is very important to note that hooks apply to individual call sites. The body of the target method is not modified. Thus, only the calls from classes that are instrumented by the fuzzer will be intercepted. This differs from the approach of tools such as ByteBuddy, but has the advantage that hooks can be applied more selectively and to frequently called methods without a large impact on fuzzing performance. |
3. Handle the Method Call
The example hook shown above replaces calls to DeepThought::ask to always return 0. This can help the fuzzer to overcome checks and reach deeper code.
Furthermore, Jazzer is guided to mutate the input part that represents the first argument to the target, arguments[0], to equal the looked-for question using Jazzer::guideTowardsEquality. This method is normally used to provoke problematic input to the target. Other methods to do this are available as described in the JavaDoc.
The hook calls the actual method, using the provided MethodHandle, and checks if the result equals 42. This “invalid” result is reported as a finding via Jazzer::reportFindingFromHook. The method can use any exception to report a finding. Jazzer provides FuzzerSecurityIssueLow to FuzzerSecurityIssueCritical exceptions for this use-case.
Hooks are normally not applied in Java internal classes, but the additionalClassesToHook annotation property can enforce this behavior.
Let’s have a look at a real hook in Jazzer.
@MethodHook(
type = HookType.BEFORE,
targetClassName = "java.lang.ProcessImpl",
targetMethod = "start",
additionalClassesToHook = {"java.lang.ProcessBuilder"}
)
public static void processImplStartHook(
MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
if (arguments.length > 0) {
String cmd = (String) arguments[0];
if (Objects.equals(cmd, COMMAND)) {
Jazzer.reportFindingFromHook(
new FuzzerSecurityIssueCritical("OS Command Injection"));
} else {
Jazzer.guideTowardsEquality(cmd, COMMAND, hookId);
}
}
}
The snippet shows that calls to ProcessImpl’s start method should be intercepted, as it’s the relevant method for starting sub-processes and should be guarded against unfiltered external data. It’s a Java internal class and only used by ProcessBuilder, which in turn is used by application code. Using additionalClassesToHook calls from ProcessBuilder to ProcessImpl are intercepted properly. This pattern can also be used to hook other Java internal classes.
How to Apply Hooks
Custom hook classes can be built using your preferred build system; they just have to be put on Jazzer’s classpath using the --cp command line parameter. The hooks to actually use during fuzzing have to be specified via the --custom_hooks command line parameter. It takes a colon-separated list of class names to load them from.
Without any configuration, hooks are applied to all classes not in Java internal packages, e.g. java.**, javax.**, sun.**, etc. This can be changed by providing colon-separated package patterns via the command parameters --custom_hook_includes and --custom_hook_excludes.
Hooks are normally applied in combination with coverage instrumentation. This can be done using the command parameters --instrumentation_includes and --instrumentation_excludes and is the preferred way to configure the application of hooks.
Using these parameters hook instrumentation can be restricted to application classes only and hence improve fuzzing performance.
Starting Jazzer with custom hooks looks something like this:
> jazzer \
--target_class=com.example.fuzz.DeepThoughtFuzzer \
--cp=application.jar:custom_hook.jar \
--custom_hooks=com.example.fuzz.DeepThoughtHooks \
--instrumentation_includes=com.example.**
INFO: Loaded 112 hooks from com.code_intelligence.jazzer.runtime.TraceCmpHooks
[...]
INFO: Loaded 1 hooks from com.example.fuzz.DeepThoughtHooks
[...]
INFO: Instrumented com.example.fuzz.DeepThoughtFuzzer (took 12 ms, size +37%)
INFO: libFuzzer ignores flags that start with '--'
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 2735196724
INFO: Loaded 1 modules (512 inline 8-bit counters): 512 [0x15b2640, 0x15b2840),
INFO: Loaded 1 PC tables (512 PCs): 512 [0x159c570,0x159e570),
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: Instrumented com.example.DeepThoughtCli (took 1 ms, size +55%)
INFO: Instrumented com.example.DeepThought (took 1 ms, size +21%)
== Java Exception: com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh: Invalid answer found
at com.example.fuzz.DeepThoughtHooks.askHook(DeepThoughtHooks.java:34)
at com.example.DeepThoughtCli.main(DeepThoughtCli.java:6)
at com.example.fuzz.DeepThoughtFuzzer.fuzzerTestOneInput(DeepThoughtFuzzer.java:30)
DEDUP_TOKEN: cfa783b649baabfe
Catching the Hook
Hooks are a powerful tool to improve fuzzing and detect bugs. Jazzer provides an easy extension mechanism to create custom hooks specifically tailored to your applications. Which security problems can you think of that could be found by a hook? You just saw how easy it is to create your own ones, so just give it a try!
By the way: Jazzer is fully open source and transparently developed in its GitHub repository. We would love to get feedback from you or even some new hooks to integrate! If you have questions about Jazzer, or need help with setting up your first fuzz test, feel free to reach out, as well!