Ruzzy kiterjesztése LibAFL segítségével
CYBERSECURITY KIEMELT ELEMZÉS

Ruzzy kiterjesztése LibAFL segítségével

FORRÁS

The Trail of Bits Blog

DATE

READ

3 perc olvasás

LibAFL a fúziós közösségben népszerűvé vált azáltal, hogy jobb teljesítményt és Rust alapú tervezést kínál, különösen azért, mert a LLVM-hez tartozó libFuzzer jelenleg karbantartási üzemmódban van. Az szerző célja, hogy …

LibAFL is gaining popularity in the fuzzing community, especially with LLVM’s libFuzzer being used. LibAFL, written in Rust, boasts improved performance, modularity, state-of-the-art fuzzing techniques, and libFuzzer compatibility. I set out to add LibAFL support to Ruzzy, our coverage-guided fuzzer for pure Ruby code and Ruby C extensions. This gives Ruby developers and security researchers access to a more advanced and actively maintained fuzzing engine without changing how they write their fuzzing harnesses. Ruzzy was originally built on LLVM’s libFuzzer, so using LibAFL’s compatibility layer should be straightforward. However, digging into the internals of complex systems is never as simple as it seems. In this post, I will investigate some of the deep plumbing inside these fuzzing engines, take a detour into executable and linkable format (ELF) files, and ultimately add LibAFL support to Ruzzy. Building with libafl_libfuzzer Ruzzy currently supports Linux. To that end, using a similar Dockerfile for LibAFL support is the simplest integration point. LibAFL provides excellent documentation and build scripts to use it as a standalone library. We need to build LibAFL as a standalone library because Ruzzy uses libFuzzer as a library. Following along with the standalone libafl_libfuzzer documentation, and with the build.sh script in hand, we can build libFuzzer.a. This is the archive that will ultimately be linked into Ruzzy’s C extension and used to fuzz our target. After building the fuzzing libraries, Ruby C extension, and Docker image, I got the following error: “No maps available; cannot fuzz!” This LibAFL error occurs when the SanitizerCoverage state is not initialized properly. To understand this discrepancy between LibAFL and libFuzzer, we must first understand what SanitizerCoverage is and how it works. SanitizerCoverage tracks code coverage information during a fuzzing campaign to improve performance. Simple heuristics like “if we’ve discovered new code coverage, then continue to mutate relevant inputs to better explore these code paths” are powerful fuzzing primitives. The underlying theory is that higher code coverage results in more crashes and bugs (I’m oversimplifying, but you get the point). To that end, a fuzzing engine needs a mechanism for initializing and tracking coverage information. SanitizerCoverage offers a variety of ways to track coverage information, all of which require a mechanism to initialize state at the beginning of a fuzzing campaign. For example, the documentation offers pc-guard, 8bit-counters, bool-flag, and pc-table tracing mechanisms, each with a corresponding init function. These init functions are eventually lowered and represented as .init_array entries in ELF files (.init_array strikes again!). Finally, an executable file may have pre-initialization functions. These functions are executed after the dynamic linker has built the process image and performed relocations but before any shared object initialization functions. Pre-initialization functions are not permitted in shared objects. Back to the error at hand: why is LibAFL saying “No maps available; cannot fuzz!” while LLVM’s libFuzzer starts up just fine? The key distinction is that libFuzzer lazily allows new coverage counter arrays to be included at runtime and does not complain if none exist at startup. LibAFL, however, requires that this state already be initialized before it begins fuzzing. After fixing initialization problems, LibAFL finally works.