Bug Swatter: A lightweight convenient blackbox fuzzer to make life easier

by Nicolas Wu(@NVamous)

1. Design concept

Bug Swatter is a blackbox fuzzer focusing on improving fuzzing conveniency. It applies a creative hybrid instrumentation for code coverage collection, which can help us fuzz close-source binaries more conveniently.

2. Core framework of hybrid instrumentation

The hybrid instrumentation is a combination of static code rewriting and dynamic hook. Compared to those instrumentations implemented in a static or dynamic way, our hybrid instrumentation is found to be more convenient to be used to get the code coverage of closed-source binaries. And this is the key point of Bug Swatter.

First of all, we invented a delicate static code rewriting method, which is designed to be as brief as possible. Especially, it gets rid of instruction relocation which can be a tricky problem we may encounter when performing static code rewriting. This makes the instrumentation much more convenient to be used and easier to be understood.

Second, we accomplish the code coverage collection function with the dynamic hook. Compared to static instrumentation tools, this helps us to get rid of hard complicated static code rewriting work which may hinder many hackers from fuzzing closed-source binaries easily.

We will introduce the core parts of static code rewriting and dynamic hook in the following sections briefly to show the core framework of hybrid instrumentation.

2.1 Static code rewriting

To collect the code coverage information, we need to perform a static hook to each code block of the binary. Before the static hook, we assigned an unique index to each code block. Taking a simple code block as an example, the original code block could be like figure 1:

figure1
Figure 1

The cb_index_n stands for code block of which the index is n. As you can see, it’s a simple ARM64 code block with three instructions. The static hook operation performed to the code block is like this:

figure2
Figure 2

Figure 2 shows the result of the static hook operation. When doing the hook, we will choose a hook point in the code block, and modify the instruction at hook point to a jump instruction which jumps to a trampoline corresponding to this code block. The trampoline is used to record the execution state of this code block, and it will do several steps to finish the aim:

Step1. Execute original instruction;
Step2. Reserve registers that might use later;
Step3. Call code coverage collection function to record the execution of this code block;
Step4. Restore registers;
Step5. Jump to next instruction of hook point, recover the execution of program.

Every code block will have its corresponding trampoline, so we can monitor the execution state of each code block which finally comes together to generate a complete code coverage information of a program.

This static hook operation looks brief, but there is a common issue we will encounter when performing the static code rewriting operation. Every original instruction overwritten by the jump instruction must be executed at the trampoline. But as we know, many kinds of instructions are position-dependent, which means we cannot move these kinds of instructions somewhere else and then execute them directly. This would lead to errors. A common method to fix this issue is to relocate position-dependent instructions with the new position and then execute the relocated instructions instead. However, the relocation of instructions can be very complicated and error-prone because machine codes are far more complex.

To solve this tricky issue, we come up with a method to get rid of it. You may notice that there are many instructions inside a code block, so we can scan the whole code block to find those position-independent instructions and mark the address of the very first position-independent instruction as the hook point. Because it doesn’t matter where the hook point locates in one code block, the method would work fine. Ever since then, the issue won’t bother us because we won’t overwrite position-dependent instructions at all. This is one of the key methods in Bug Swatter to make it more convenient to be used.

2.2 Dynamic hook

Taking AFL for example, it uses shared memory to record code coverage information of a program. This would be easy with the source code of a program. But we are now dealing with source-closed binaries, it can be difficult. Most of all, it would be hard for us to implement the code coverage collection function in a static way. Normally, we need to use some syscall or API of libc to finish the function of code coverage collection. This can be hard because we can only insert codes in assembly level, especially, calling external API of libc is almost impossible.

To solve this issue, we come up with a delicate method to finish code coverage collection. The method is a combination of reusing the import function of binary and preload hook. Taking a closed-source Android binary as a reference, usually there will be many import functions in the binary, for example, most Android binaries would refer to function __android_log_print of liblog.so to do log printing. What if we hook the import function __android_log_print of the close-source binary and implement code coverage collection in the fake __android_log_print ?

figure3
Figure 3

Figure 3 shows how we finish the code coverage collection. When performing “call code_coverage_collect_func” step in trampoline, it will call function __android_log_print actually. And we will hook __android_log_print dynamically with preload hook, and code coverage collection will be finished in our libhook.so successfully. The whole process is brief, and it gets rid of the hard way we mentioned. This is one of the key methods in Bug Swatter to make it more convenient to be used.

As for details about code coverage collection, we reuse the AFL method, so no more description about that.

2.3 Other works

There are other operations we haven’t discussed, for example, code block recognition, executable file rewriting, and so on, because those operations can be finished in many ways, and there are many known methods we can use directly, so no more further description about those operations here.

3. Fuzz Android Media Extractor with Bug Swatter

Here are the fuzzing results of Android Media Extractor with the help of Bug Swatter:

Qualcomm:

18 CVE assigned:

CVE-2022-22086,CVE-2022-22059,CVE-2022-25658,CVE-2022-25657,CVE-2022-25686,CVE-2022-25668,
CVE-2022-25676,CVE-2022-25653,CVE-2022-22085,CVE-2022-22084,CVE-2021-35100,CVE-2022-25688,
CVE-2022-25687,CVE-2022-25662,CVE-2022-22083,CVE-2022-22087,CVE-2022-22082,CVE-2022-25659

Samsung:

11 CVE assigned:

SVE-2021-20274, SVE-2021-20154, SVE-2021-20183, SVE-2021-20184, SVE-2021-20185, SVE-2021-20202,
SVE-2021-22094, SVE-2021-22278, SVE-2021-22291, SVE-2021-22343,SVE-2021-22412

MediaTek:

24 CVE assigned:

CVE-2021-0408,CVE-2021-0409, CVE-2021-0410, CVE-2021-0411, CVE-2021-0412, CVE-2021-0413,
CVE-2021-0414,CVE-2021-0613, CVE-2021-0614, CVE-2021-0615, CVE-2021-0616, CVE-2021-0617,
CVE-2021-0618,CVE-2021-0619, CVE-2021-0620, CVE-2021-0621, CVE-2021-0622, CVE-2021-0623,
CVE-2021-0624, CVE-2021-0574, CVE-2021-0573, CVE-2021-0577, CVE-2021-0576

Unisoc:

2 CVE assigned:
CVE-2021-0636, CVE-2021-0635

OPPO:

One vulnerability rewarded