Package: cpytraceafl
cpytraceaflCPython bytecode instrumentation and forkserver tools for fuzzing python code using AFL.The tools in this repository enable coverage-guided fuzzing of pure python and mixed python/c
code usingAmerican Fuzzy Lop(even better,AFL++).There are three main parts to this:A bytecode rewriter using a technique inspired by inspired by Ned Batchelder's "wicked hack"
detailed athttps://nedbatchelder.com/blog/200804/wicked_hack_python_bytecode_tracing.html.
In this case, the rewriter identifies "basic blocks" in the python bytecode and abuses thecodeobject'slnotab(line-number table) to mark each basic block as a new "line".
These new "lines" are what trigger CPython's line-level trace hooks. The result of this being
that we can get our trace hook executed on every new basic block.A minimal & fast tracehook written in C, tallying visited locations to sysv shared memory.A basic forkserver implementation.Preparing code for fuzzing involves a couple of steps. The first thing that should happen in
the python process is a call toinstall_rewriter(). It's important that this is done very
early as any modules that are imported before this will not be properly instrumented.fromcpytraceafl.rewriterimportinstall_rewriterinstall_rewriter()install_rewriter()can optionally be provided with aselectorcontrolling which code objects
are instrumented and to what degree.Following this, modules can be imported as normal and will be instrumented by the monkeypatchedcompilefunctions. It's usually a good idea to initialize the test environment next,
performing as many setup procedures as possible before the input file is read. This may
include doing an initial run of the function under test to ensure any internal imports or caches
are set up. This is because we want to minimize work that has to be done post-fork - any work
done now only has to be done once,After callingfromcpytraceaflimportfuzz_from_herefuzz_from_here()thefork()will have been made and tracing started. You now simply read your input file and
call your function under test.Examples for fuzzing some common packages are provided inexamples/.As for hooking this script up to AFL, I tend to use the includeddummy-afl-qemu-traceshim script to fool AFL's QEmu mode into
communicating directly with the python process.Fuzzing mixed python/c codeAs of version 0.4.0,cpytraceaflcan gather trace information from C extension modules that
have been compiled with AFL instrumentation (e.g. usingllvm_mode). This means that it can
be used to seamlessly fuzz projects which have a mix of python and C "speedups". This is
important not only because a lot of python format-parsing packages use this approach, but
because issues revealed in native code are far more likely to have security implications.Including instrumented native code requires a little more care when preparing a target for
fuzzing. For instance, it's important to ensure thecpytraceafl.tracehookmodule has been
imported and it has had itsset_map_start(...)function provided with a valid memory
areabeforeany instrumented extension modules are loaded. This is because simply loading an
instrumented native module will cause it to attempt to log its execution trace somewhere.The examplepillow_pcx_example.pydemonstrates a fuzzing
target taking the necessary precautions into account.It's possible that you'reonlyinterested in tracing the native code, usingcpytraceafljust as a driver, in which case you can omit the earlyinstall_rewriter()call and all
the weirdness involved with that.Regular expressionscpytraceafl-regexis a companion,re-replacement regex implementation with added instrumentation that should aid AFL in
generating examples that pass regular expressions used in the target code, or
exercise them in interesting ways. Without this, AFL will just see regular expressions
as a black box that will act as a barrier to path exploration.Q & AIs there any point in fuzzing python? Isn't it too slow?Well, yes and no. My experience has been that fuzzing python code is simply "a bit different"
from fuzzing native code - you tend to be looking for different things. In terms of raw speed,
fuzzing python is certainly not fast, but iteration rates I tend to work with aren't completely
dissimilar to what I'm used to getting with AFL's Qemu mode (of course, no two fuzzing targets
are really directly comparable).Because of the memory-safe nature of pure python code, it's also more uncommon for issues
uncovered through fuzzing to be security issues - logical flaws in parsing tend to lead to
unexpected/unhandled exceptions. So it's still a rather useful tool in simply looking for bugs.
It can be used, for example, to generate a corpus of example inputs for your test suite which
exercise a large amount of the code.However, note that whilepurepython code may be memory safe, as soon as you start using
the C api, Cython, or even start playing with thectypesmodule, it isnot.Does basic block analysis make any sense for python code?From a rigorous academic stance, and for some uses, possibly not - you've got to keep in mind
that half the bytecode instructions could result in calls out to more arbitrary python or
(uninstrumented) native code that could have arbitrary side effects. But for our needs it works
well enough (recall that AFL coverage analysis is robust to random instrumentation
sites being omitted throughAFL_INST_RATIOorAFL_INST_LIBS).Doesn't abusinglnotabbreak python's debugging mechanisms?Absolutely it does. Don't use instrumented programs to debug problematic cases - use it to
generate problematic inputs. Analyze them with instrumentation turned off.I'm gettingundefined symbol: __afl_area_ptrLooks like you're trying to import an (instrumented) native extension module before thecpytraceafl.tracehookmodule has been loaded (which is what provides that symbol).I'm getting Segmentation Faults after importing an instrumented native moduleYou probably also need to providecpytraceafl.tracehook.set_map_start(...)with a valid
writeable memory area before the import. Assuming you're not interested in the trace associated
with the import process, this can just be a dummy which you later discard. I'd recommend either
using anmmapobject orsysv_ipc.SharedMemory. Whenfuzz_from_here()is called, this will
be replaced with right one.It's also possible the instrumented module was built with a different AFLMAP_SIZE_POW2from
that incpytraceafl.MAP_SIZE_BITS.