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:
parent
60a99d2f81
commit
a025bda97a
@ -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"
|
||||
|
||||
213
scripts/binaryninja/manticore_viz/__init__.py
Normal file
213
scripts/binaryninja/manticore_viz/__init__.py
Normal 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)
|
||||
15
scripts/binaryninja/manticore_viz/plugin.json
Normal file
15
scripts/binaryninja/manticore_viz/plugin.json
Normal 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": ""
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user