mirror of
https://github.com/williamknows/CodeCoverageModuleStomping
synced 2026-06-08 18:18:52 +00:00
148 lines
6.1 KiB
Python
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)
|