Files
williamknows-CodeCoverageMo…/parse-drcov-identify-untouched.py
William Knowles d12cada479 Initial code
2020-01-07 23:25:37 +00:00

148 lines
6.1 KiB
Python

#!/usr/bin/python3
import csv
from prettytable import PrettyTable
import sys
if len(sys.argv) <= 1:
print("\nGrab code coverage data with DynamoRIO's drcov:\n")
print("bin64/drrun.exe -t drcov -dump_text -- notepad")
print("\nOutputs *.log which can be passed here as an argument.\n")
print("python3 parse-drcov-untouched.py file.log")
print("\nCode is terrible, and commonly takes 5+ minutes to run, so it requires a bit of patience.\n")
sys.exit(1)
with open(sys.argv[1], "r") as inputFileRaw:
inputFile = inputFileRaw.read().splitlines()
startCollectingModules = False
startCollectingExecution = False
modulesList = []
modulesDict = {}
modulesExecution = {}
moduleStartAddress = {}
modulePathLocation = {}
for line in inputFile:
if "Columns: id, containing_id, start, end, entry, offset, checksum, timestamp, path" in line:
modulesList.append(line[9:].replace(" ","").lstrip().rstrip())
startCollectingModules = True
continue
if startCollectingModules:
if "BB Table" in line:
startCollectingModules=False
break
modulesList.append(line.replace(" ","").lstrip().rstrip()) # note that this may mess up the path if it has spaces in it
modulesDict = csv.DictReader(modulesList)
print("Generating dict to track byte-by-byte execution in each module.")
for module in modulesDict:
modulesExecution[module["id"]] = {}
for i in range(0, int(module["end"], 0)-int(module["start"], 0)):
modulesExecution[module["id"]][str("{0:#0{1}x}".format(i,18)).lower()] = 0
moduleStartAddress[module["id"]] = module["start"]
modulePathLocation[module["id"]] = module["path"]
print("Analysing execution.")
lineCount=0
for line in inputFile:
lineCount+=1
if "module id, start, size:" in line:
startCollectingExecution = True
continue # skip to next line
if startCollectingExecution:
moduleNumber = line.split(":")[0].split("[")[1].split("]")[0].lstrip().rstrip()
executionLocation = line.split(":")[1].split(",")[0].lstrip().rstrip()
executionSize = line.split(":")[1].split(",")[1].lstrip().rstrip()
for i in range(int(executionLocation, 0), int(executionLocation, 0) + int(executionSize) + 1): # + 1 just to be sure
try:
addressWithBase = str("{0:#0{1}x}".format(i,18)).lower()
if addressWithBase in modulesExecution[moduleNumber]:
modulesExecution[moduleNumber][addressWithBase] = 1
except Exception as e:
print(f"Exception occurred when adding touched address to dict: {e}")
print(f"Input file line: {lineCount}")
print(f"addressWithBase: {addressWithBase}")
print(f"moduleNumber: {moduleNumber}")
x = PrettyTable()
x.field_names = ["Path", "Touched (Bytes)", "Untouched (Bytes)", "Coverage (%)", "Largest Untouched (Bytes)", "Largest Untouched (Offset Bytes)"]
for moduleKey,moduleValue in modulesExecution.items():
if "DynamoRIO".lower() in modulePathLocation[moduleKey].lower():
continue
try:
untouchedBytes=0
touchedBytes=0
for addressLocation,addressUsed in moduleValue.items():
if int(addressUsed) == 0:
untouchedBytes+=1
if int(addressUsed) == 1:
touchedBytes+=1
currentUntouchedCount=0
currentUntouchedBaseAddress="0x0000000000000000"
largestUntouchedCount=0
largestUntouchedBaseAddress="0x0000000000000000"
lastAddressWasOneOrFirst=True
for addressLocation,addressUsed in moduleValue.items():
if int(addressUsed) == 0:
currentUntouchedCount+=1
if lastAddressWasOneOrFirst:
currentUntouchedBaseAddress=addressLocation
lastAddressWasOneOrFirst=False
else:
if currentUntouchedCount > largestUntouchedCount:
largestUntouchedCount = currentUntouchedCount
largestUntouchedBaseAddress = currentUntouchedBaseAddress
currentUntouchedCount=0
lastAddressWasOneOrFirst=True
# handle situation where nothing is touched (== the entire binary)
if touchedBytes==0:
largestUntouchedCount=untouchedBytes
# OPTIONAL (can be removed): make sure the largest offset is at a 16 byte offset from the beginning of he file
amountAdded=0
largestUntouchedBaseAddress = int(largestUntouchedBaseAddress, 16)
while (largestUntouchedBaseAddress % 16 != 0):
largestUntouchedBaseAddress+=1
amountAdded+=1
largestUntouchedCount=largestUntouchedCount-amountAdded # removed what's added to reflect smaller untouched buffer
#largestUntouchedBaseAddressFull = str("{0:#0{1}x}".format(int(largestUntouchedBaseAddress, 16),18)).lower()
codeCoveragePercentage = format(touchedBytes/untouchedBytes*100, ".3f")
# Module-by-module output (non-tabular)
# print("************************")
# print(f"Module path location: {modulePathLocation[moduleKey]}")
# print(f"Memory addresses touched (bytes)): {str(touchedBytes)} ({str(size(touchedBytes))})")
# print(f"Memory addresses untouched (bytes)): {str(untouchedBytes)} ({str(size(untouchedBytes))})")
# print(f"Code coverage (%): {str(codeCoveragePercentage)}")
# print(f"Largest untouched space (bytes): {str(largestUntouchedCount)} ({str(size(largestUntouchedCount))})")
# print(f"Largest untouched space (base address): {str(largestUntouchedBaseAddressFull)}")
x.add_row([modulePathLocation[moduleKey], touchedBytes, untouchedBytes, codeCoveragePercentage, largestUntouchedCount, largestUntouchedBaseAddress])
except Exception as e:
print(f"Exception occurred when analysing module: {e}")
print(f"largestUntouchedBaseAddress: {largestUntouchedBaseAddress}")
print(f"largestUntouchedCount: {largestUntouchedCount}")
print(f"moduleNumber: {moduleKey}")
# print table output
x.sortby = "Largest Untouched (Bytes)"
x.reversesort = True
print(x)