Trace symbol coverage

Purpose

Display a list of executed binaries and symbols in a Reven scenario, indicating how many transitions where spent in each symbol/binary.

How to use

usage: trace_coverage.py [-h] [--host HOST] [-p PORT] [-m MAX_TRANSITION]

optional arguments:
  -h, --help            show this help message and exit
  --host HOST           Reven host, as a string (default: "localhost")
  -p PORT, --port PORT  Reven port, as an int (default: 13370)
  -m MAX_TRANSITION, --max-transition MAX_TRANSITION
                        Maximum number of transitions

Known limitations

N/A

Supported versions

Reven 2.2+

Supported perimeter

Any Reven scenario.

Dependencies

None.

Source

import argparse

import reven2 as reven

"""
# Trace symbol coverage

## Purpose

Display a list of executed binaries and symbols in a Reven scenario,
indicating how many transitions where spent in each symbol/binary.

## How to use

```bash
usage: trace_coverage.py [-h] [--host HOST] [-p PORT] [-m MAX_TRANSITION]

optional arguments:
  -h, --help            show this help message and exit
  --host HOST           Reven host, as a string (default: "localhost")
  -p PORT, --port PORT  Reven port, as an int (default: 13370)
  -m MAX_TRANSITION, --max-transition MAX_TRANSITION
                        Maximum number of transitions
```

# Known limitations

N/A

## Supported versions

Reven 2.2+

## Supported perimeter

Any Reven scenario.

## Dependencies

None.
"""


def trace_coverage(reven_server, max_transition=None):
    if max_transition is None:
        max_transition = reven_server.trace.transition_count
    else:
        max_transition = min(max_transition, reven_server.trace.transition_count)

    transition_id = 0
    coverages = {}
    while transition_id < max_transition:
        ctx = reven_server.trace.context_before(transition_id)
        transition_id += 1
        asid = ctx.read(reven.arch.x64.cr3)
        loc = ctx.ossi.location()
        unknown = True if loc is None else False
        binary = "unknown" if unknown else loc.binary.path
        symbol = "unknown" if unknown or loc.symbol is None else loc.symbol.name

        try:
            asid_coverage = coverages[asid]
        except KeyError:
            coverages[asid] = {}
            asid_coverage = coverages[asid]

        try:
            binary_coverage = asid_coverage[binary]
            binary_coverage[0] += 1
            if binary == "unknown":
                continue
        except KeyError:
            if binary == "unknown":
                asid_coverage[binary] = [1, None]
                continue
            asid_coverage[binary] = [1, {}]
            binary_coverage = asid_coverage[binary]

        try:
            binary_coverage[1][symbol] += 1
        except KeyError:
            binary_coverage[1][symbol] = 1
    return coverages


def print_coverages(coverages):
    for asid, asid_coverage in coverages.items():
        print("***** Coverage for CR3 = {:#x} *****\n".format(asid))
        for binary, binary_coverage in asid_coverage.items():
            print("- {}: {}".format(binary, binary_coverage[0]))
            if binary_coverage[1] is None:
                continue
            for symbol, symbol_coverage in binary_coverage[1].items():
                print("    - {}: {}".format(symbol, symbol_coverage))
            print("\n")


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--host", type=str, default="localhost", help='Reven host, as a string (default: "localhost")')
    parser.add_argument("-p", "--port", type=int, default="13370", help="Reven port, as an int (default: 13370)")
    parser.add_argument("-m", "--max-transition", type=int, help="Maximum number of transitions")
    args = parser.parse_args()

    reven_server = reven.RevenServer(args.host, args.port)
    coverages = trace_coverage(reven_server, args.max_transition)
    print_coverages(coverages)