Updated visualization plugin with coverage info & live update option (#444)

* plugin rearrangement and UI options

* Live update and clear options

* coverage stats

* added check for xref address

* coverage in separate option as BackgroundTaskThread

* license cleanup
This commit is contained in:
Theofilos Petsios 2017-08-23 17:47:23 -04:00 committed by GitHub
parent 60a99d2f81
commit a025bda97a
4 changed files with 238 additions and 64 deletions

View File

@ -5,19 +5,19 @@
Example:
```
$ ln -s $PWD/viz.py ~/Library/Application\ Support/Binary\ Ninja/plugins/
## Installation:
Copy and paste the `manticore_viz` directoru to the binary ninja [plugin folder](https://github.com/Vector35/binaryninja-api/tree/dev/python/examples#loading-plugins).
Alternatively, you can create a symbolic link to the respective directories.
E.g., in Mac OS X
```
cd ~/Library/Application\ Support/Binary\ Ninja
ln -s <manticore_root>/scripts/binaryninja/manticore_viz .
```
## Usage
- Run manticore on a binary
- Open the binary in Binary Ninja
- `import` it from the Binary Ninja Script Console, and call desired functions
Example:
```
>>> import viz
>>> viz.viz(bv, '/mnt/hgfs/code/manticore/examples/linux/mcore_1vCAKM')
```
- Open the same binary in Binary Ninja
- Select "Highlight Trace"

View File

@ -0,0 +1,213 @@
import fcntl
import os
import time
import threading
from binaryninja import (
PluginCommand, HighlightStandardColor, log, BackgroundTaskThread
)
from binaryninja.interaction import (
get_open_filename_input, get_directory_name_input, get_choice_input
)
import binaryninja.enums as enums
blue = HighlightStandardColor.BlueHighlightColor
black = HighlightStandardColor.BlackHighlightColor
white = HighlightStandardColor.WhiteHighlightColor
clear = HighlightStandardColor.NoHighlightColor
# renew interval
interval = 3
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class TraceVisualizer(object):
__metaclass__ = Singleton
def __init__(self, view, workspace, base=0x0, live=False):
self.view = view
self.workspace = workspace
self.base = base
# highlighted addresses
self.highlighted = set()
# covered basic blocks
self.cov_bb = set()
# comments inserted
self.cov_comments = set()
self.current_function = None
self.live_update = live
def visualize(self):
"""
Given a Manticore workspace, or trace file, highlight the basic blocks.
"""
if os.path.isfile(self.workspace):
t = threading.Thread(target=self.highlight_from_file,
args=(self.workspace,))
elif os.path.isdir(self.workspace):
t = threading.Thread(target=self.highlight_from_dir,
args=(self.workspace,))
t.start()
def highlight_from_file(self, tracefile):
while True:
self.process_trace(tracefile)
if not self.live_update:
break
time.sleep(interval)
def highlight_from_dir(self, workspace_dir):
while True:
for f in os.listdir(workspace_dir):
if f.endswith('trace'):
self.process_trace(os.path.join(workspace_dir, f))
if not self.live_update:
break
time.sleep(interval)
def process_trace(self, tracefile):
trace_addr = set()
with open(tracefile, "r") as f:
for line in f:
trace_addr.add(int(line.strip(), 0) - self.base)
for addr in trace_addr - self.highlighted:
self.highlight_addr(addr, blue)
def highlight_addr(self, addr, hl):
blocks = self.view.get_basic_blocks_at(addr)
if blocks:
blocks[0].set_user_highlight(hl)
if self.live_update and blocks[0].function != self.current_function:
self.current_function = blocks[0].function
self.view.file.navigate(self.view.file.view, blocks[0].start)
self.highlighted.add(addr)
self.cov_bb.add(blocks[0].start)
if self.live_update:
blocks[0].function.set_auto_instr_highlight(addr, white)
time.sleep(0.1)
blocks[0].function.set_auto_instr_highlight(addr, clear)
def highlight_block(self, addr, hl):
blocks = self.view.get_basic_blocks_at(addr)
for b in blocks:
b.set_user_highlight(hl)
def set_comment_at_xref(self, xref, comment):
try:
op = xref.function.get_lifted_il_at(xref.address).operation
except IndexError:
w = "ManticoreTrace: Could not lookup " + hex(xref.address)
w += " address for funciton " + str(xref.function)
log.log_warn(w)
return
if not (op == enums.LowLevelILOperation.LLIL_CALL or
op == enums.LowLevelILOperation.LLIL_JUMP or
op == enums.LowLevelILOperation.LLIL_JUMP_TO or
op == enums.LowLevelILOperation.LLIL_SYSCALL or
op == enums.LowLevelILOperation.LLIL_GOTO):
return
self.cov_comments.add((xref.function, xref.address))
xref.function.set_comment_at(xref.address, comment)
def clear_stats(self):
self.highlighted.clear()
self.cov_bb.clear()
for fun, addr in self.cov_comments:
fun.set_comment_at(addr, None)
class CoverageHelper(BackgroundTaskThread):
def __init__(self, view, tv):
self.tv = tv
self.view = view
BackgroundTaskThread.__init__(self, "Calculating Coverage", True)
def run(self):
# function cumulative bb coverage
# key: function address
# values: [total basic blocks covered, xrefs to function]
fun_cov = {f.start : [0, 0] for f in self.view.functions}
fun_xrefs = sorted([(f, self.view.get_code_refs(f.start))
for f in self.view.functions],
key=lambda x: len(x[1]))
for f, xrefs in fun_xrefs:
if not f.basic_blocks:
continue
cov = (len(
(set([b.start for b in f.basic_blocks])
.intersection(self.tv.cov_bb))
) / float(len(set(f.basic_blocks))))
fun_cov[f.start][0] += cov
for xref_f in xrefs:
fun_cov[xref_f.function.start][0] += cov
fun_cov[xref_f.function.start][1] += 1
for f, xrefs in fun_xrefs:
cov = str((fun_cov[f.start][0] * 100.0) / (fun_cov[f.start][1] + 1))
cov += "% cumulative BB coverage"
f.set_comment(f.start, "Function Stats: \n" + cov)
for xref in xrefs:
self.tv.set_comment_at_xref(xref, cov)
def get_workspace():
choice = get_choice_input("Select Trace Type",
"Input",
["Trace File", "Manticore Workspace"])
if choice == 0:
workspace = get_open_filename_input('Trace File')
else:
workspace = get_directory_name_input('Workspace Directory')
return workspace
def viz_trace(view):
"""
Given a Manticore trace file, highlight the basic blocks.
"""
tv = TraceVisualizer(view, None)
if tv.workspace is None:
tv.workspace = get_workspace()
tv.visualize()
def viz_live_trace(view):
"""
Given a Manticore trace file, highlight the basic blocks.
"""
tv = TraceVisualizer(view, None, live=True)
if tv.workspace is None:
tv.workspace = get_workspace()
# update due to singleton in case we are called after a clear
tv.live_update = True
tv.visualize()
def get_coverage(view):
tv = TraceVisualizer(view, None, live=False)
if tv.workspace is None:
tv.workspace = get_workspace()
tv.visualize()
c = CoverageHelper(view, tv)
c.start()
def clear_all(view):
tv = TraceVisualizer(view, None)
for addr in tv.highlighted:
tv.highlight_block(addr, clear)
tv.clear_stats()
tv.workspace = None
tv.live_update = False
PluginCommand.register("ManticoreTrace: Highlight",
"Highlight Manticore Execution Trace",
viz_trace)
PluginCommand.register("ManticoreTrace: BB Coverage",
"Compute cumulative BB coverage for each function ",
get_coverage)
PluginCommand.register("ManticoreTrace: Live Highlight",
"Highlight Manticore Execution Trace at Real-Time",
viz_live_trace)
PluginCommand.register("ManticoreTrace: Clear",
"Clear Manticore Trace Highlight",
clear_all)

View File

@ -0,0 +1,15 @@
{
"plugin": {
"name": "ManticoreTrace",
"type": ["ui"],
"api": "python2",
"description": "Manticore Trace Visualization.",
"longdescription": "This plugin visualizes Manticore execution traces.\n",
"license": {
"name": "",
"text": ""
},
"version": "0.0",
"author": ""
}
}

View File

@ -1,54 +0,0 @@
from binaryninja import *
import os.path
blue = HighlightStandardColor.BlueHighlightColor
black = HighlightStandardColor.BlackHighlightColor
white = HighlightStandardColor.WhiteHighlightColor
clear = HighlightStandardColor.NoHighlightColor
MBASE = 0x0007fffffdb7000
def viz(view, fname, base=0):
"""
Given a Manticore workspace, or trace file, highlight the basic blocks.
"""
fname = fname.replace('/mnt/hgfs', '~')
fname = os.path.expanduser(fname)
log_info('got: {}'.format(fname))
if os.path.isdir(fname):
for f in os.listdir(fname):
if f.endswith('trace'):
fullpath = os.path.join(fname, f)
viz_trace(view, fullpath, base)
else:
viz_trace(view, fname, base)
def viz_trace(view, fname, base=0):
"""
Given a Manticore trace file, highlight the basic blocks.
"""
with open(os.path.expanduser(fname)) as f:
addrs = [int(x.strip(), 0) for x in f.readlines()]
_viz_addrs(view, addrs, base, blue)
def clear_all(view, fname, base=0):
with open(os.path.expanduser(fname)) as f:
addrs = [int(x.strip(), 0) for x in f.readlines()]
_viz_addrs(view, addrs, base, white)
def _viz_addrs(view, addrs, base, hl):
for addr in addrs:
if base:
addr -= base
_highlight_block(view, addr, hl)
def _highlight_block(view, addr, hl):
blocks = view.get_basic_blocks_at(addr)
for b in blocks:
b.set_user_highlight(hl)