Giter Club home page Giter Club logo

cpytraceafl's Introduction

cpytraceafl

CPython 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 using American 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 at https://nedbatchelder.com/blog/200804/wicked_hack_python_bytecode_tracing.html. In this case, the rewriter identifies "basic blocks" in the python bytecode and abuses the code object's lnotab (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 to install_rewriter(). It's important that this is done very early as any modules that are imported before this will not be properly instrumented.

from cpytraceafl.rewriter import install_rewriter

install_rewriter()

install_rewriter() can optionally be provided with a selector controlling which code objects are instrumented and to what degree.

Following this, modules can be imported as normal and will be instrumented by the monkeypatched compile functions. 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 calling

from cpytraceafl import fuzz_from_here

fuzz_from_here()

the fork() 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 in examples/.

As for hooking this script up to AFL, I tend to use the included dummy-afl-qemu-trace shim script to fool AFL's QEmu mode into communicating directly with the python process.

Fuzzing mixed python/c code

As of version 0.4.0, cpytraceafl can gather trace information from C extension modules that have been compiled with AFL instrumentation (e.g. using llvm_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 the cpytraceafl.tracehook module has been imported and it has had its set_map_start(...) function provided with a valid memory area before any 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 example pillow_pcx_example.py demonstrates a fuzzing target taking the necessary precautions into account.

It's possible that you're only interested in tracing the native code, using cpytraceafl just as a driver, in which case you can omit the early install_rewriter() call and all the weirdness involved with that.

Regular expressions

cpytraceafl-regex is 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.

Trophy cabinet

cpytraceafl has been used to find:

Q & A

Is 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 while pure python code may be memory safe, as soon as you start using the C api, Cython, or even start playing with the ctypes module, it is not.

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 through AFL_INST_RATIO or AFL_INST_LIBS).

Doesn't abusing lnotab break 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 getting undefined symbol: __afl_area_ptr

Looks like you're trying to import an (instrumented) native extension module before the cpytraceafl.tracehook module has been loaded (which is what provides that symbol).

I'm getting Segmentation Faults after importing an instrumented native module

You probably also need to provide cpytraceafl.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 an mmap object or sysv_ipc.SharedMemory. When fuzz_from_here() is called, this will be replaced with right one.

It's also possible the instrumented module was built with a different AFL MAP_SIZE_POW2 from that in cpytraceafl.MAP_SIZE_BITS.

Do I need a specially-built/instrumented version of cpython to use this?

No, you can use your normal distribution-installed python. If you're just looking at fuzzing pure python, you don't need to even think about building any binaries with funny compilers.

You may be interested in building c/c++/cython-based modules or their underlying native libraries with instrumentation if that's what you're trying to fuzz, but I suspect using a natively-instrumented cpython would be quite complicated and extremely slow.

Do you have any tips on detecting memory errors in cpython extensions?

I have tended to use tcmalloc's debugging modes with TCMALLOC_PAGE_FENCE and TCMALLOC_PAGE_FENCE_NEVER_RECLAIM enabled. In fact I have a fork of gperftools containing some additional tcmalloc hacks I've found useful.

One problem with this of course is that much of cpython's memory is allocated using its own memory pool allocator, which is largely invisible to the malloc implementation. So I've also got a patch for cpython which adds a very basic canary mechanism to its pool allocator (at the slight expense of memory efficiency).

cpytraceafl's People

Contributors

risicle avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

cpytraceafl's Issues

Expected Performance

Are there any expected performance metrics when fuzzing mixed python/c code? I attempted to run the pillow_pcx_example.py. In order to do that, I downloaded Pillow and built it with CC=afl-gcc and CXX=afl-g++ and then installed it.

When running the fuzzer, a single instance of the fuzzer seems to always execute at 3-4 execs/second. That seems slower than what I expected, is their any numbers that you can provide as to what you see there?

Additionally, is there a way to verify that the instrumentation is working? I provided in my input directory multiple files that should cause the code to go down different paths within the C portion of the Pillow code, however the when starting the fuzzer I received a message that no new instrumentation was detected with the additional test cases. Is there something that I could be doing wrong to cause that?

Thank you.

when and how 'rewriting_compile' function is called?

i read "rewriter.py" source code, "builtins.compile" confused me

@functools.wraps(original_compile)
    def rewriting_compile(*args, **kwargs):
        ...
builtins.compile = rewriting_compile

what i understand at first is "compile function has been hooked, when run 'python xxxx.py' and compile the code, builtins.compile will be called first, so rewriting_compile function will be called".

but after i run test code as below, i am confused.

import dis
import functools
import random
import os
import _frozen_importlib_external
import builtins
from sys import version_info
from ast import PyCF_ONLY_AST

original_compile = builtins.compile

@functools.wraps(original_compile)
def rewriting_compile(*args, **kwargs):
    flags = (len(args) >= 4 and args[3]) or kwargs.get("flags") or 0
    print("rewriting_compile")
    return False

builtins.compile = rewriting_compile

print("end")

"rewriting_compile" will not be printed.

so my question is " when and how 'rewriting_compile' function is called".

Instrumentation could be faster

I was looking at tracehook_line_trace_hook in _tracehookmodule.c - the instrumentation you are doing is overly complex and therefore is very, very time consuming.

if the bytecode offset is something that does not change during fuzzing, why not solely use this information? maybe do some assessment of the value and e.g. if its always of minimum increments of 8 then do a (bytecode_offset >> 3) % MAP_SIZE and be done with it.
and if it is important that a lineno exits just check that it exists?
IMHO the multiplication just decreases speed and mixing the different values gives no benefit.

Try to remove everything that is not absolutely needed or can be precomputed somewhere else.

also this is useless:

uint32_t this_loc = state >> (32-afl_map_size_bits);

if the map size would change during a run, afl would not know about it anyway.

How to use this tool?

Is there any steps to install or setup the tool. When I run the examples there are some error"ModuleNotFoundError: No module named 'cpytraceafl'" ,but I can't pip install cpyctraceafl.
And I want to know how to use this with AFL. Thanks very much!

Lack of Demonstration/Fuzzing Example

Hello! This project looks very interesting. I was looking for something just like this. I am looking to test this to see if it could potentially be used for fuzzing some python code that I am auditing. Is there any way that you could show a quick example of the commands that are typically used to launch a fuzzing instance with cpytraceafl? For example, I would find a short tutorial describing how to install and run your existing examples to be perfect for this.

When I attempt to run the examples, I get KeyError: __AFL_SHM_ID I believe that everything is installed correctly, but I am just attempting to run the examples in the incorrect way. However, I am unclear as to whether this is to be run installed with cpython that has already been instrumented by AFL, standard cpython (without instrumentation), or something else so it is possible that I messed up the install as well. Thank you
`

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.