About the heuristic crash signatures

The signature is currently implemented as a tuple of some elements that can uniquely identify a crash, at least to some practical extent, so crashes generated by the same bug will not be included more than once. It’s supposed to be opaque to the user of the class, so it can easily be changed to reflect different heuristics without breaking existing code.

The goal is to detect seemingly duplicated crashes in a large set of them. This typically happens when fuzzing an application with little or no robustness, or when fuzzing a robust application with a very large fuzzing farm - the amount of crashes quickly becomes hard to manage. Depending on how many crashes are generated and how valuable each crash is, you may want to simply use this for classification, or to directly filter out potential duplicates before sending them to the database.

This simple implementation should suit most users needs, however if your project requires something more elaborated, just derive from the Crash class and reimplement the signature() method with your own custom algorithm.

These are the elements included in the signature:

  • Processor architecture:

    For different platforms the locations in the code would change. So if the locations are the same, it may be just a coincidence rather than the same crash.

    But most importantly, even if it were the same crash we’d like to know we can trigger it in multiple platforms.

  • Event code and exception code:

    Wouldn’t make sense not to include them. :)

  • Program counter (EIP/RIP):

    The same fault in different places of the code are most likely different bugs. However, different faults in the same place are not necessarily the same bug, so we can’t rely on this alone.

    To avoid problems with ASLR in Vista and above and DLL relocations in XP and below, a label is used instead of a memory address whenever possible.

  • Stack trace (EIP/RIP values only):

    This heuristic is actually meant to detect different ways of triggering the same bug, rather than different bugs. But it’s also useful to detect heap overflows, since all of them will be triggered at the same set of EIPs (where the heap routines are located) but coming from different parent functions.

    To avoid problems with ASLR in Vista and above and DLL relocations in XP and below, a label is used instead of a memory address whenever possible.

  • Debug string:

    Different debug strings mean most likely different bugs. There’s a catch: if the debug string is generated from something else (like the value of some variable we don’t care about), this heuristic may fail and give us more crashes than we really wanted. This is the case for strings generated by heaps in debug mode, as they often include the heap chunk addresses. If this becomes a problem you can filter out the unwanted debug string events before storing them in the database.

These are the elements NOT included in the signature:

  • Exception address:

    Most exceptions caught are page faults, and in that case we’re more interested in the program counter, since a page fault is generally triggered by corrupting a pointer, and the corrupted value itself isn’t really useful to uniquely identifying the crash it produces.

  • First chance or second chance:

    Generally second chance exceptions are exactly the same as first chance exceptions, they simply mean the application didn’t handle them. Depending on the application you’re debugging you could be interested in logging either first chance or second chance exceptions only, but rarely both.

  • Process and thread IDs:

    One might say, two processes could crash at the same address because of different bugs. But the problem is, the process and thread IDs are dependent on a particular execution of the target application, and we want to be able to compare crashes from multiple executions. And the chances of collision are still slow thanks to all the other elements that factor in the signature.

  • Stack contents and register values:

    Both are most likely to contain garbage we’re not interested in (for the signature, that is) plus many values are dependent on a particular execution of the application.

    By ignoring this we might be missing different ways to trigger the same bug, though. But the main goal of the signature is to eliminate noise when fuzzing an application that crashes too often, so false positives are not much of an issue. In a scenario when a crash is rare we wouldn’t want filtering by signature at all.

  • Operating system version:

    Doesn’t tell if it’s the same crash or not, unless we’re fuzzing the OS itself - and in that case we’d be more interested in the names and versions of the binary files.