/ Hacking

Building a Feedback Driven Fuzzer - Dev Log 2 : Coverage

Hello again, so today we will talk about how I calculate coverage by counting the basic blocks that gets executed.

A very easy way to do it is to use DynamoRio, Dynamorio is a dynamic instrumentation framework, luckily for me dynamo rio comes with drcov which is a module to calculate the coverage or simply it can give you in detail the basic blocks that gets executed, it can print that in both text and binary format.

Using drcov is straight forward,To print coverage in text format we can use the following command:
drrun -t drcov -dump_text -- /home/fady/a.out
To print coverage in binary format we can use the following command:
drrun -t drcov -dump_binary -- /home/fady/a.out

Now, all what I need to do is to write a parser that can parse the files and it's actually pretty easy since the file format is straight forward.

What I did was to generate the coverage file in text format which looks like this:

DRCOV VERSION: 2
DRCOV FLAVOR: drcov-64
Module Table: version 4, count 11
Columns: id, containing_id, start, end, entry, offset, path
  0,   0, 0x00007fd768c30000, 0x00007fd768c32000, 0x00007fd768c30910, 0000000000000000,  /media/fady/Data/Work/Projects/SecurityTools/DrCovFuzzer/Utils/DynamoRIO-x86_64-Linux-7.0.17704-0/tools/lib64/release/libdrcov.so
  1,   0, 0x00007fd768e31000, 0x00007fd768e33000, 0x00007fd768c30910, 0000000000001e68,  /media/fady/Data/Work/Projects/SecurityTools/DrCovFuzzer/Utils/DynamoRIO-x86_64-Linux-7.0.17704-0/tools/lib64/release/libdrcov.so
  2,   2, 0x00007fd7e8a2e000, 0x00007fd7e8a2f000, 0x00007fd7e8a2e680, 0000000000000000,  /home/fady/CoverageTester/a.out
  3,   2, 0x00007fd7e8c2e000, 0x00007fd7e8c30000, 0x00007fd7e8a2e680, 0000000000000d98,  /home/fady/CoverageTester/a.out
  4,   4, 0x00007fd7eca06000, 0x00007fd7eca2d000, 0x00007fd7eca07090, 0000000000000000,  /lib/x86_64-linux-gnu/ld-2.27.so
  5,   4, 0x00007fd7ecc2d000, 0x00007fd7ecc30000, 0x00007fd7eca07090, 0000000000027680,  /lib/x86_64-linux-gnu/ld-2.27.so
  6,   6, 0x00007fd7ecc31000, 0x00007fd7ecd92000, 0x00007fd7ecd2e564, 0000000000000000,  /media/fady/Data/Work/Projects/SecurityTools/DrCovFuzzer/Utils/DynamoRIO-x86_64-Linux-7.0.17704-0/lib64/release/libdynamorio.so
  7,   6, 0x00007fd7ecf92000, 0x00007fd7ecffb000, 0x00007fd7ecd2e564, 0000000000161180,  /media/fady/Data/Work/Projects/SecurityTools/DrCovFuzzer/Utils/DynamoRIO-x86_64-Linux-7.0.17704-0/lib64/release/libdynamorio.so
  8,   8, 0x00007ffea559c000, 0x00007ffea559e000, 0x00007ffea559c970, 0000000000000000,  [vdso]
  9,   9, 0x00007fd7ec5f0000, 0x00007fd7ec7d7000, 0x00007fd7ec611cb0, 0000000000000000,  /lib/x86_64-linux-gnu/libc-2.27.so
 10,   9, 0x00007fd7ec9d7000, 0x00007fd7ec9e1000, 0x00007fd7ec611cb0, 00000000001e7620,  /lib/x86_64-linux-gnu/libc-2.27.so
BB Table: 2103 bbs
module id, start, size:
module[  4]: 0x0000000000001090,   8
module[  4]: 0x0000000000001ea0,  85
module[  4]: 0x0000000000001ef5,  65
module[  4]: 0x0000000000001f5b,   6
module[  4]: 0x0000000000001f4b,  16
module[  4]: 0x0000000000001f61,  12
module[  4]: 0x0000000000001f6d,  14

As we can see above the file is showing a list of loaded modules first then it prints a list of executed blocks by refering to the given module ID.

The binary file is identical with the text file except for the list of exeuted blocks which is written in binary as the following:
[Offset_32bit] [size_16bit] [module_16bit]

The offset is the RVA of the block and it's 32 bit, the size represents the size of the block which is 16bit, module represents the module ID relevant to the block.

Putting it all together we have the below code which first skips unneeded lines and then reads the modules list and then it reads the basic blocks list, the modules are stored in CoverageEntry which is a data holder that stores module name and the RVA of the block.

QSet<CoverageEntry> Helpers::parseCoverageFile(QString fileName) {
    //Open the file.
    QSet<CoverageEntry> set;
    QFile file(fileName);
    if (!file.open(QIODevice::ReadOnly)) {
            qDebug()<<"[x] Failed to open file returning empty set.";
            return set;
    }
    if(file.size() == 0){
        qDebug()<<"[x] File is of size zero.";
        return set;
    }
    //Reading : "DRCOV VERSION: 2\n".
    QByteArray version = file.readLine();

    //Reading "DRCOV FLAVOR: drcov-64\n".
    QByteArray flavor = file.readLine();

    //Reading "Module Table: version 4, count 19\n" and parsing count.
    QByteArray modTable = file.readLine();
    QString modTableText(modTable);

    QString countStr = modTableText.remove(0,modTableText.indexOf("count"));
    countStr.chop(1);
    countStr = countStr.split(" ").at(1);
    quint32 count = countStr.toInt();
    //Skipping column list.
    file.readLine();

    //Reading modules.
    QMap<quint64, QString> modules;
    quint32 i = 0;
    for (i=0;i<count;i++) {
        QString moduleLine = file.readLine();
        QString idStr = moduleLine.split(",")[0];
        QString moduleStr = moduleLine.split(",")[6];
        modules.insert(idStr.toInt(),moduleStr.trimmed());
    }
    //Reading the basic blocks.
    QString bbLine = file.readLine();
    QString bbCountStr =  bbLine.remove("BB Table: ");
    quint64 bbCount = bbCountStr.remove(" bbs\n").toInt();
    //qDebug()<<"Entries:"<<bbCount;
    while(!file.atEnd()) {
        CoverageEntry tempEntry;
        quint32 temp32;
        
        //Offset.
        file.read((char *) &temp32,4);
        tempEntry.setOffset(temp32);
        
        //Size.
        quint16 temp16;
        file.read((char *) &temp16,2);
        
        //Module.
        file.read((char *) &temp16,2);
         
        tempEntry.setModule(modules[temp16]);
        
        set.insert(tempEntry);
        
    }
    qDebug()<<"Found "<<set.size()<<" Enteries";
    return set;
}

The reason for using QSet is because they store uniq values and have some very useful functions that allows us to easily check if new blocks were covered.

For the file name the file name looks like this
drcov.a.out.11441.0000.proc.log

a.out is the executable name and 11441 is the PID of the process so I made a function that generates the file name so we can read relevant coverage information for each process when fuzzing.

In the next devlog I will talk about how I used radamsa as my mutator.

Please accept my apologies for any typos, I am writing this in a very limited time window ;).

For the updates you can follow me on twitter @fady_othman.

Buy Me a Coffee at ko-fi.com
Building a Feedback Driven Fuzzer - Dev Log 2 : Coverage
Share this

Subscribe to Fady's Technical Blog