53

GitHub - Shnatsel/libdiffuzz: Custom memory allocator that helps discover reads...

 5 years ago
source link: https://github.com/Shnatsel/libdiffuzz
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

README.md

libdiffuzz: poor man's Memory Sanitizer

This is a drop-in replacement for OS memory allocator that can be used to detect uses of uninitialized memory. It is designed to be used in case Memory Sanitizer is not available for some reason, such as:

  • Your code contains inline assembly or links to proprietary libraries that cannot be instrumented by MSAN
  • You're debugging code that is specific to an exotic CPU architecture where MSAN is not available
  • You're debugging code that is specific to a freaky operating system such as macOS where no sane development tools are available
  • You want to check Rust code for memory disclosure vulnerabilities and Rust standard library still doesn't support MSAN
  • You want to check if the bug MSAN found is actually exploitable, i.e. if the uninitialized memory contents actually show up in the output
  • You want to find vulnerabilities in black-box binaries that you do not have the source code for (not always straightforward, see below)

This is not a drop-in replacement for Memory Sanitizer! It will likely require changes to your code or your testing setup, see below.

How it works

When injected into a process, this library initializes every subsequent allocated region of memory to different values. Using this library you can detect uses of uninitialized memory simply by running a certain operation twice in the same process and comparing the outputs; if they differ, then the code uses uninitialized memory somewhere.

Combine this with a fuzzer (e.g. AFL, honggfuzz) to automatically discover cases when this happens. This is called "differential fuzzing", hence the name.

Naturally, this is conditional on the same operation run twice returning the same results normally. If that is not the case in your program and you cannot make it deterministic - you're out of luck.

TL;DR: usage

  1. Clone this repository, run make; this will build libdiffuzz.so
  2. Make your code run the same operation twice in the same process and compare outputs.
  3. Run your code like this: LD_PRELOAD=/path/to/libdiffuzz.so /path/to/your/binary.
  4. If you're fuzzing with AFL, use AFL_PRELOAD=/path/to/libdiffuzz.so afl-fuzz ... instead. If you're not fuzzing with AFL - you should!
  5. Brag that you've used differential fuzzing to find vulnerabilities in real code

Quick start for Rust code

If your code does not contain unsafe blocks, you don't need to do a thing! Your code is already secure!

However, if you have read from the black book and invoked the Old Ones...

  1. Clone this repository, run make; this will build libdiffuzz.so
  2. Make sure this code doesn't reliably crash when run on its own, but does crash when you run it like this: LD_PRELOAD=/path/to/libdiffuzz.so target/release/membleed
  3. If you haven't done regular fuzzing yet - do set up fuzzing with AFL. It's not that hard.
  4. In your fuzz target run the same operation twice and assert! that they produce the same result. See example fuzz target for lodepng-rust for reference.
  5. Add the following to your fuzz harness:
// Use the system allocator so we can substitute it with a custom one via LD_PRELOAD
use std::alloc::System;
#[global_allocator]
static GLOBAL: System = System;
  1. Run the fuzz target like this: AFL_PRELOAD=/path/to/libdiffuzz.so cargo afl fuzz ...

Auditing black-box binaries

If your target binary lets you feed it the same input several times - stellar! Simply preload libdiffuzz-numbering into a binary, feed it the same input twice and compare the outputs.

However, if your binary only accepts one input and then terminates, you will have to change the u16 alloc_clobber_counter = 0; in libdiffuzz-numbering to something unique to each process, such as milliseconds from system time, replace alloc_clobber_counter++ in memset call with alloc_clobber_counter, then run the entire process twice and compare the outputs from the two runs. If they differ - congratulations, you've found a memory disclosure vulnerability!

Oh - if the output is inherently non-deterministic, you're out of luck.

Limitations and future work

Stack-based uninitialized reads are not detected.

Unlike memory sanitizer, this thing will not make your program crash as soon as a read from uninitialized memory occurs. Instead, it lets you detect that it has occurred after the fact and only if the contents of uninitialized memory leak into the output. I.e. this will help you notice security vulnerabilities, but will not really aid in debugging.

It currently does not reliably detect reads from uninitialized memory in multi-threaded programs. Pull requests switching the global counter to atomic type are welcome. For now you can work around this by applying the same hack as for black-box binaries.

This may miss single-byte uninitialized reads because the counter is u16; if you need to detect those, change it to u8, but be warned that it will be a bit more likely to miss uninitialized reads that way (one in 256 versus one in 65536).

Trophy case

List of previously unknown (i.e. zero-day) vulnerabilities found using this tool, to show that this whole idea is not completely bonkers:

  1. Memory disclosure in Claxon

See also

libdislocator, poor man's Address Sanitizer that also works with black-box binaries. libdiffuzz is based on libdislocator code.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK