DrSancov is a small DBI module based on DynamoRIO, which aims to combine the basic functionality of AddressSanitizer and SanitizerCoverage, while being similar to DrCov (hence the name). In other words, it mimics the behavior of the two compiler instrumentations, by printing out ASAN-like reports upon SIGSEGV and similar program crashes, and saving code coverage information to .sancov
files compatible with those generated by SanitizerCoverage. The purpose of the tool is to make it easier to test closed-source software using existing fuzzing pipelines which expect the target to be compiled with ASAN/SanitizerCoverage. It works with x86 and x86-64 applications.
Please note that the project does not improve memory error detection; it only produces ASAN compatible reports for conditions that would result in a hard crash anyway. For better sanitization of heap memory accesses in compiled binaries, check e.g. AFL's libdislocator.
The code coverage information is collected with a basic block granularity.
A Makefile for GNU/Linux is provided, and the end result is a libdrsancov.so
library. The Makefile assumes that DynamoRIO resides in a ../dynamorio
directory relative to itself, for example:
.
|-- code
| |-- common.cc
| |-- common.h
| |-- drsancov.cc
| |-- Makefile
| |-- tokenizer.cc
| `-- tokenizer.h
|-- dynamorio
| |-- ACKNOWLEDGEMENTS
| |-- bin32
| | |-- drconfig
| | |-- drconfig.debug
[...]
Then, compilation should run smoothly as follows:
$ cd code
$ make
clang++ -c -o drsancov.o drsancov.cc -O2 -fPIC -I../dynamorio/include -I../dynamorio/ext/include -DX86_64 -DLINUX -fno-stack-protector
clang++ -c -o common.o common.cc -O2 -fPIC -I../dynamorio/include -I../dynamorio/ext/include -DX86_64 -DLINUX -fno-stack-protector
clang++ -c -o tokenizer.o tokenizer.cc -O2 -fPIC -I../dynamorio/include -I../dynamorio/ext/include -DX86_64 -DLINUX -fno-stack-protector
clang++ -shared -o libdrsancov.so drsancov.o common.o tokenizer.o -Wl,-hash-style=both ../dynamorio/lib64/debug/libdynamorio.so ../dynamorio/ext/lib64/debug/libdrsyms.so ../dynamorio/ext/lib64/debug/libdrmgr.so
$
In order to use DrSancov, prepend the usual target command line with drrun -c libdrsancov.so --
, which will run the program under our instrumentation. Let's consider the following program:
$ cat -n tests/test.cc
1 #include <cstdio>
2 #include <cstdlib>
3
4 int main(int argc, char **argv) {
5 if (argc >= 2 && argv[1][0] == 'F' && argv[1][1] == 'U' && argv[1][2] == 'Z' && argv[1][3] == 'Z') {
6 int *x = (int *)0x12345678;
7 *x = 0x41414141;
8 }
9
10 return 0;
11 }
$ g++ tests/test.cc -o tests/test
$
For argument "FUZZ", we'd typically see a standard "Segmentation fault" message:
$ tests/test FUZZ
Segmentation fault
$
With DrSancov enabled, we get a familiar report:
$ dynamorio/bin64/drrun -c code/libdrsancov.so -- tests/test FUZZ
ASAN:SIGSEGV
=================================================================
==7548==ERROR: AddressSanitizer: SEGV on unknown address 0x12345678 (pc 0x7f114701a196 sp 0x7ffe0837a830 bp 0x7ffe0837a830 T0)
#0 0x7f114701a196 in test+1196
==7548==CONTEXT
rax=0000000012345678 rbx=0000000000000000 rcx=0000000000000080 rdx=00007ffe0837a930
rsi=00007ffe0837a918 rdi=0000000000000002 rsp=00007ffe0837a830 rbp=00007ffe0837a830
r8=00007f1149d8ad80 r9=0000000000000000 r10=00007f114701f010 r11=0000000000000000
r12=00007f114701a040 r13=00007ffe0837a910 r14=0000000000000000 r15=0000000000000000
rip=00007f1137b63a18 rflags=0000000000010246
Accessed address: 0x12345678
Faulting code:
0x00007f1137b63a18 mov dword ptr [rax], 0x41414141
==7548==ABORTING
$
In addition to the somewhat standarized ASAN header, there is also extra information printed out in the "context" section.
Similarly to ASAN, DrSancov can be controlled with the ASAN_OPTIONS
environment variable. The currently supported switches are coverage
, exitcode
, log_path
and coverage_dir
. Let's try to obtain some code coverage for two inputs:
$ ASAN_OPTIONS=coverage=1 dynamorio/bin64/drrun -c code/libdrsancov.so -- tests/test FU
DrSanitizerCoverage: ./test.20345.sancov: 25 PCs written
$ ASAN_OPTIONS=coverage=1 dynamorio/bin64/drrun -c code/libdrsancov.so -- tests/test FUZ
DrSanitizerCoverage: ./test.20361.sancov: 26 PCs written
$ xxd -e -g8 -c8 test.20345.sancov | awk '{print $2}' >cov1
$ xxd -e -g8 -c8 test.20361.sancov | awk '{print $2}' >cov2
$ diff cov1 cov2
19a20
> 0000000000001174
$
Here, we can see that the difference in coverage between "FU" and "FUZ" is a single basic block at offset 0x1174, which turns out to be the verification of the final letter of the parameter:
$ objdump -d tests/test
[...]
1174: 48 8b 45 e0 mov -0x20(%rbp),%rax
1178: 48 83 c0 08 add $0x8,%rax
117c: 48 8b 00 mov (%rax),%rax
117f: 48 83 c0 03 add $0x3,%rax
1183: 0f b6 00 movzbl (%rax),%eax
1186: 3c 5a cmp $0x5a,%al
1188: 75 12 jne 119c <main+0x77>
[...]
This demonstrates basic output compatibility between the DBI and the Sanitizer-family instrumentations.