we have rendered... something
This commit is contained in:
parent
42358166c1
commit
6be9ba0727
|
@ -0,0 +1 @@
|
|||
from .plugin import TypeTapper
|
|
@ -1,21 +1,21 @@
|
|||
from typing import Optional
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
|
||||
import angr
|
||||
from angr import Block
|
||||
from angr.analyses.cfg import CFGBase
|
||||
from angr.knowledge_plugins.cfg import CFGNode
|
||||
from angr.knowledge_plugins.cfg import CFGNode, CFGModel
|
||||
|
||||
from .engine import TypeTapperEngine
|
||||
from .knowledge import TypeTapperManager
|
||||
|
||||
l = logging.getLogger(__name__)
|
||||
|
||||
class TypeTapper(angr.Analysis):
|
||||
def __init__(self, cfg: CFGBase, tmp_atoms: bool=False):
|
||||
self._cfg = cfg
|
||||
class TypeTapperAnalysis(angr.Analysis):
|
||||
def __init__(self, cfg: Optional[CFGModel]=None, tmp_atoms: bool=False):
|
||||
self._cfg = cfg if cfg is not None else self.kb.cfgs.get_most_accurate()
|
||||
self.manager = self.kb.request_knowledge(TypeTapperManager)
|
||||
self.manager.cfg = cfg.model
|
||||
self.manager.cfg = cfg
|
||||
self._engine = TypeTapperEngine(self.project, self.manager, tmp_atoms=tmp_atoms)
|
||||
|
||||
if not self._cfg.normalized:
|
||||
|
@ -44,7 +44,7 @@ class TypeTapper(angr.Analysis):
|
|||
block_addr = next(iter(reversed(queue.keys())))
|
||||
queue.pop(block_addr)
|
||||
node_blockinfo = self.manager.block_info[block_addr]
|
||||
node = self._cfg.model.get_any_node(block_addr)
|
||||
node = self._cfg.get_any_node(block_addr)
|
||||
fakeret_addr = next((pred.addr for pred, attrs in self._cfg.graph.pred[node].items() if attrs['jumpkind'] == 'Ijk_FakeRet'), None)
|
||||
for pred, attrs in self._cfg.graph.pred[node].items():
|
||||
if attrs['jumpkind'] == 'Ijk_FakeRet':
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from typing import Tuple, Any, List, Set, Optional, Dict
|
||||
from collections import defaultdict, Counter
|
||||
from enum import Enum, auto
|
||||
from enum import IntEnum, auto
|
||||
from dataclasses import dataclass, field
|
||||
import copy
|
||||
|
||||
|
@ -127,7 +127,7 @@ def simplify_op_sequence(seq: List[Op]):
|
|||
|
||||
|
||||
# noinspection PyArgumentList
|
||||
class DataKind(Enum):
|
||||
class DataKind(IntEnum):
|
||||
Int = auto()
|
||||
Float = auto()
|
||||
Pointer = auto()
|
||||
|
|
|
@ -0,0 +1,325 @@
|
|||
from typing import Tuple, Optional
|
||||
from collections import defaultdict
|
||||
from math import sqrt, sin, cos, pi
|
||||
|
||||
from PySide6.QtCore import QRectF, QPointF
|
||||
from PySide6.QtGui import QColor, QFont, QPen, QBrush, QPainterPath
|
||||
from PySide6.QtWidgets import QGraphicsItem, QGraphicsRectItem, QGraphicsSimpleTextItem, QHBoxLayout, QGraphicsScene
|
||||
|
||||
import networkx
|
||||
|
||||
from angrmanagement.ui.views import BaseView
|
||||
from angrmanagement.ui.widgets.qgraph import QZoomableDraggableGraphicsView
|
||||
|
||||
from .hierarchy_graph import HierarchicalGraph, RelativeAtomGroup
|
||||
from .data import Prop, DataKind
|
||||
|
||||
TEXT_COLOR = QColor(0, 0, 0)
|
||||
TEXT_FONT = QFont("default", 10)
|
||||
BACKGROUND_COLOR = QColor(255, 255, 255)
|
||||
BOX_COLOR = QColor(0xff, 0xc0, 0x80)
|
||||
BOX_BORDER_COLOR = QColor(0x10, 0x10, 0x10)
|
||||
ARROW_COLOR = QColor(0, 0, 0)
|
||||
|
||||
def kind_to_color(kind: DataKind) -> QColor:
|
||||
if kind == DataKind.Pointer:
|
||||
return QColor(0xe0, 0x10, 0x10)
|
||||
elif kind == DataKind.Int:
|
||||
return QColor(0x20, 0x20, 0xf0)
|
||||
elif kind == DataKind.Float:
|
||||
return QColor(0x10, 0xe0, 0x10)
|
||||
else:
|
||||
raise ValueError(kind)
|
||||
|
||||
class HierarchicalGraphView(BaseView):
|
||||
def __init__(self, instance, default_docking_position, hg: HierarchicalGraph, *args, current_group: Optional[RelativeAtomGroup]=None, **kwargs):
|
||||
super().__init__("typetapper", instance, default_docking_position, *args, **kwargs)
|
||||
self.base_caption = "TypeTapper"
|
||||
|
||||
layout = QHBoxLayout(self)
|
||||
self.graph = HierarchicalGraphWidget(self, hg, current_group)
|
||||
layout.addWidget(self.graph)
|
||||
self.setLayout(layout)
|
||||
|
||||
class HierarchicalGraphWidget(QZoomableDraggableGraphicsView):
|
||||
def __init__(self, parent, hg: HierarchicalGraph, current_group: Optional[RelativeAtomGroup]=None):
|
||||
self.hg = hg
|
||||
self.current_group = hg.root_group if current_group is None else current_group
|
||||
super().__init__(parent)
|
||||
self.setScene(QGraphicsScene())
|
||||
self._layout()
|
||||
|
||||
def _layout(self):
|
||||
ng = self.hg.local_graph(self.current_group)
|
||||
ug = networkx.Graph(ng)
|
||||
lookup = list(ug.nodes())
|
||||
reverse = {x: i for i, x in enumerate(lookup)}
|
||||
charts = [PropChart(None, self.hg.prop(node)) for node in lookup]
|
||||
networkx.relabel_nodes(ug, reverse, copy=False)
|
||||
|
||||
ag = networkx.drawing.nx_agraph.to_agraph(ug)
|
||||
for node in ag.nodes_iter():
|
||||
idx = int(node)
|
||||
node.attr['width'] = charts[idx].width
|
||||
node.attr['height'] = charts[idx].height
|
||||
node.attr['shape'] = 'box'
|
||||
for _, _, attr in ag.edge_attr.iteritems():
|
||||
attr['len'] = 100
|
||||
ag._layout()
|
||||
|
||||
for node in ag.nodes_iter():
|
||||
idx = int(node)
|
||||
chart = charts[idx]
|
||||
posx, posy = node.attr['pos'].split(',')
|
||||
chart.setPos(QPointF(float(posx), float(posy)))
|
||||
self.scene().addItem(chart)
|
||||
|
||||
for u, v in ug.edges:
|
||||
towards_end = lookup[v] in ng.succ[lookup[u]]
|
||||
towards_start = lookup[u] in ng.succ[lookup[v]]
|
||||
arrow = PropArrow(None, charts[u], charts[v], towards_start, towards_end)
|
||||
self.scene().addItem(arrow)
|
||||
|
||||
|
||||
class PropChart(QGraphicsItem):
|
||||
def __init__(self, parent, prop: Prop, max_width=300., default_unit=10., byte_height=20., margin_x=15., margin_y=5., padding_left=5.):
|
||||
self.prop = prop
|
||||
self.max_width = max_width
|
||||
self.default_unit = default_unit
|
||||
self.unit = default_unit
|
||||
self.byte_height = byte_height
|
||||
self.margin_x = margin_x
|
||||
self.margin_y = margin_y
|
||||
self.padding_left = padding_left
|
||||
self.width = 0.
|
||||
self.height = 0.
|
||||
super().__init__(parent)
|
||||
|
||||
self.objects = []
|
||||
self._layout_marks()
|
||||
|
||||
def boundingRect(self):
|
||||
return QRectF(0, 0, self.width, self.height)
|
||||
|
||||
def paint(self, painter, option, widget=...):
|
||||
pass
|
||||
|
||||
@property
|
||||
def center(self) -> QPointF:
|
||||
pos = self.pos()
|
||||
pos.setX(pos.x() + self.width / 2)
|
||||
pos.setY(pos.y() + self.height / 2)
|
||||
return pos
|
||||
|
||||
def _layout_marks(self):
|
||||
data = [(offset, kind, size, count) for offset, v1 in self.prop.struct_data.items() for size, v2 in v1.items() for kind, count in v2.items()]
|
||||
data.sort()
|
||||
cols_allocated = defaultdict(int)
|
||||
row_allocated = 0
|
||||
offset_allocated = 0
|
||||
marks = []
|
||||
ticks = []
|
||||
for offset, kind, size, count in data:
|
||||
xpos = max(cols_allocated[suboffset] for suboffset in range(offset, offset + size))
|
||||
for suboffset in range(offset, offset + size):
|
||||
cols_allocated[suboffset] = xpos + count
|
||||
|
||||
if offset >= offset_allocated:
|
||||
ypos = row_allocated
|
||||
row_allocated += size
|
||||
offset_allocated = offset + size
|
||||
ticks.append((offset, ypos))
|
||||
else:
|
||||
ypos = row_allocated - (offset_allocated - offset)
|
||||
stretch = max(0, offset + size - offset_allocated)
|
||||
row_allocated += stretch
|
||||
offset_allocated += stretch
|
||||
|
||||
marks.append((offset, kind, size, count, xpos, ypos))
|
||||
|
||||
total_width = max(cols_allocated.values()) if cols_allocated else 0
|
||||
|
||||
if total_width * self.unit > self.max_width:
|
||||
self.unit = self.max_width / total_width
|
||||
|
||||
self.width = self.max_width + 2 * self.margin_x + self.padding_left
|
||||
self.height = row_allocated + 2 * self.margin_y
|
||||
box = QGraphicsRectItem(QRectF(0, 0, self.width, self.height), self)
|
||||
box.setBrush(BOX_COLOR)
|
||||
box.setPen(BOX_BORDER_COLOR)
|
||||
self.objects.append(box)
|
||||
|
||||
for offset, kind, size, count, xpos, ypos in marks:
|
||||
rect = QGraphicsRectItem(QRectF(self.margin_x + self.padding_left + xpos * self.unit, self.margin_y + ypos * self.byte_height, count * self.unit, size * self.byte_height), self)
|
||||
rect.setBrush(kind_to_color(kind))
|
||||
rect.setPen(QColor(0x10, 0x10, 0x10))
|
||||
self.objects.append(rect)
|
||||
|
||||
for offset, ypos in ticks:
|
||||
tick = QGraphicsSimpleTextItem(str(offset), self)
|
||||
tick.setBrush(TEXT_COLOR)
|
||||
tick.setFont(TEXT_FONT)
|
||||
tick.setPos(QPointF(self.margin_x - tick.boundingRect().width(), self.margin_y + ypos * self.byte_height))
|
||||
self.objects.append(tick)
|
||||
|
||||
class PropArrow(QGraphicsItem):
|
||||
def __init__(self, parent, start: PropChart, end: PropChart, toward_start: bool, toward_end: bool, stroke=2., arrow_size=4.):
|
||||
self.start = start
|
||||
self.end = end
|
||||
self.toward_start = toward_start
|
||||
self.toward_end = toward_end
|
||||
self.stroke = stroke
|
||||
self.arrow_size = arrow_size
|
||||
self._percentage = 1.
|
||||
|
||||
super().__init__(parent)
|
||||
self.layout()
|
||||
|
||||
@property
|
||||
def percentage(self):
|
||||
return self._percentage
|
||||
|
||||
@percentage.setter
|
||||
def percentage(self, value):
|
||||
self._percentage = value
|
||||
self.layout()
|
||||
|
||||
def layout(self):
|
||||
center_start = self.start.center
|
||||
center_end = self.end.center
|
||||
delta_x = center_end.x() - center_start.x()
|
||||
delta_y = center_end.y() - center_start.y()
|
||||
slope = delta_y / delta_x if delta_x else float('inf')
|
||||
|
||||
start_corner_slope = self.start.height / self.start.width
|
||||
end_corner_slope = self.end.height / self.end.width
|
||||
|
||||
start_horiz = start_corner_slope > slope > -start_corner_slope
|
||||
end_horiz = end_corner_slope > slope > -end_corner_slope
|
||||
|
||||
if start_horiz:
|
||||
start_right = center_end.x() > center_start.x()
|
||||
if start_right:
|
||||
start_ex = self.start.width / 2
|
||||
else:
|
||||
start_ex = -self.start.width / 2
|
||||
start_ey = start_ex * slope
|
||||
else:
|
||||
start_bottom = center_end.y() > center_start.y()
|
||||
if start_bottom:
|
||||
start_ey = self.start.height / 2
|
||||
else:
|
||||
start_ey = -self.start.height / 2
|
||||
start_ex = start_ey / slope
|
||||
|
||||
if end_horiz:
|
||||
end_right = center_start.x() > center_end.x()
|
||||
if end_right:
|
||||
end_ex = self.end.width / 2
|
||||
else:
|
||||
end_ex = -self.end.width / 2
|
||||
end_ey = end_ex * slope
|
||||
else:
|
||||
end_bottom = center_start.y() > center_end.y()
|
||||
if end_bottom:
|
||||
end_ey = self.end.height / 2
|
||||
else:
|
||||
end_ey = -self.end.height / 2
|
||||
end_ex = end_ey / slope
|
||||
|
||||
start_pt = QPointF(center_start.x() + start_ex, center_start.y() + start_ey)
|
||||
end_pt = QPointF(center_end.x() + end_ex, center_end.y() + end_ey)
|
||||
|
||||
if self.percentage != 1.:
|
||||
end_pt = start_pt + (end_pt - start_pt) * self.percentage
|
||||
|
||||
self.prepareGeometryChange()
|
||||
self.setPos(start_pt)
|
||||
self.rel_end = end_pt - start_pt
|
||||
|
||||
|
||||
def orient(self, start: PropChart):
|
||||
if start is self.start:
|
||||
pass
|
||||
elif start is self.end:
|
||||
self.start, self.end = self.end, self.start
|
||||
self.toward_start, self.toward_end = self.toward_end, self.toward_start
|
||||
self.layout()
|
||||
else:
|
||||
raise ValueError("Orienting from node which is not associated with edge")
|
||||
|
||||
def boundingRect(self):
|
||||
x = 0
|
||||
y = 0
|
||||
w = self.rel_end.x()
|
||||
h = self.rel_end.y()
|
||||
|
||||
if w < 0:
|
||||
x, w = w, -w
|
||||
if h < 0:
|
||||
y, h = h, -h
|
||||
|
||||
return QRectF(x - self.arrow_size, y - self.arrow_size, w + self.arrow_size * 2, h + self.arrow_size * 2)
|
||||
|
||||
def paint(self, painter, option, widget=...):
|
||||
brush = QBrush(ARROW_COLOR)
|
||||
path = self.shape()
|
||||
painter.fillPath(path, brush)
|
||||
|
||||
def shape(self):
|
||||
path = QPainterPath(QPointF())
|
||||
s_tip1, s_flare1, s_base1, s_base2, s_flare2, s_tip2 = arrowhead_vectors(self.rel_end, QPointF(), self.stroke, self.arrow_size)
|
||||
e_tip1, e_flare1, e_base1, e_base2, e_flare2, e_tip2 = arrowhead_vectors(QPointF(), self.rel_end, self.stroke, self.arrow_size)
|
||||
if self.toward_start:
|
||||
path.lineTo(s_flare1)
|
||||
path.lineTo(s_base1)
|
||||
else:
|
||||
path.lineTo(s_tip1)
|
||||
|
||||
if self.toward_end:
|
||||
path.lineTo(e_base2)
|
||||
path.lineTo(e_flare2)
|
||||
path.lineTo(self.rel_end)
|
||||
path.lineTo(e_flare1)
|
||||
path.lineTo(e_base1)
|
||||
else:
|
||||
path.lineTo(e_tip2)
|
||||
path.lineTo(e_tip1)
|
||||
|
||||
if self.toward_start:
|
||||
path.lineTo(s_base2)
|
||||
path.lineTo(s_flare2)
|
||||
else:
|
||||
path.lineTo(s_tip2)
|
||||
path.lineTo(QPointF())
|
||||
path.closeSubpath()
|
||||
return path
|
||||
|
||||
def arrowhead_vectors(start: QPointF, end: QPointF, stem_width: float, flare_width: float) -> Tuple[QPointF, QPointF, QPointF, QPointF, QPointF, QPointF]:
|
||||
direction = end - start
|
||||
norm = normalized(direction)
|
||||
norm_out = rotate(norm, pi / 2)
|
||||
norm_back = -norm
|
||||
|
||||
return (
|
||||
end + norm_out * stem_width,
|
||||
end + norm_back * flare_width + norm_out * flare_width,
|
||||
end + norm_back * flare_width + norm_out * stem_width,
|
||||
end + norm_back * flare_width - norm_out * stem_width,
|
||||
end + norm_back * flare_width - norm_out * flare_width,
|
||||
end + -norm_out * stem_width
|
||||
)
|
||||
|
||||
def normalized(pt: QPointF) -> QPointF:
|
||||
length = sqrt(pt.x() ** 2 + pt.y() ** 2)
|
||||
if length:
|
||||
return pt / length
|
||||
else:
|
||||
return QPointF()
|
||||
|
||||
def rotate(pt: QPointF, angle: float) -> QPointF:
|
||||
cost = cos(angle)
|
||||
sint = sin(angle)
|
||||
x = pt.x()
|
||||
y = pt.y()
|
||||
return QPointF(x * cost - y * sint, x * sint + y * cost)
|
|
@ -3,6 +3,8 @@ from collections import defaultdict
|
|||
import angr
|
||||
import networkx
|
||||
|
||||
from angr.sim_variable import SimVariable, SimRegisterVariable, SimMemoryVariable, SimStackVariable, \
|
||||
SimTemporaryVariable
|
||||
from .data import BlockInfo, RegisterAtom, MemoryAtom, TmpAtom, Atom
|
||||
from .hierarchy_graph import HierarchicalGraph
|
||||
|
||||
|
@ -62,5 +64,13 @@ class TypeTapperManager(angr.knowledge_plugins.plugin.KnowledgeBasePlugin):
|
|||
return atom
|
||||
raise LookupError("Cannot find tmp %d in instruction %#x. Are your temp numbers based on the .normalized_block?" % (tmp, addr))
|
||||
|
||||
def lookup_variable(self, addr: int, var: SimVariable):
|
||||
if isinstance(var, SimRegisterVariable):
|
||||
return self.lookup_reg(addr, self.kb._project.arch.translate_register_name(var.reg, var.size))
|
||||
elif isinstance(var, (SimMemoryVariable, SimStackVariable)):
|
||||
return self.lookup_mem(addr)
|
||||
else:
|
||||
raise LookupError("Cannot handle var of type %s" % type(var))
|
||||
|
||||
def session(self, atom: Atom) -> HierarchicalGraph:
|
||||
return HierarchicalGraph(self, [atom])
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
from typing import Optional
|
||||
|
||||
from angr.analyses.decompiler.structured_codegen.c import CVariable
|
||||
from angrmanagement.plugins import BasePlugin
|
||||
from angrmanagement.ui.views import CodeView
|
||||
|
||||
from .analysis import TypeTapperAnalysis
|
||||
from .data import Atom
|
||||
from .knowledge import TypeTapperManager
|
||||
from .hierarchy_graph_view import HierarchicalGraphView
|
||||
|
||||
|
||||
class TypeTapper(BasePlugin):
|
||||
def __init__(self, workspace):
|
||||
super().__init__(workspace)
|
||||
self.kp: Optional[TypeTapperManager] = None
|
||||
|
||||
MENU_BUTTONS = ["TypeTapper initial analysis"]
|
||||
|
||||
def handle_click_menu(self, idx):
|
||||
assert idx == 0
|
||||
cfg = self.workspace.main_instance.cfg
|
||||
tt = self.workspace.main_instance.project.analyses[TypeTapperAnalysis](cfg)
|
||||
self.kp = tt.manager
|
||||
|
||||
def build_context_menu_node(self, node):
|
||||
# this is bad lol
|
||||
code_view = self.workspace.view_manager.current_tab
|
||||
if not isinstance(code_view, CodeView):
|
||||
return
|
||||
addr = code_view._textedit.get_closest_insaddr(node)
|
||||
|
||||
if self.kp is not None and isinstance(node, CVariable):
|
||||
try:
|
||||
atom = self.kp.lookup_variable(addr, node.variable)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
yield 'Start TypeTapper', lambda: self._start(atom)
|
||||
|
||||
def _start(self, node: Atom):
|
||||
hg = self.kp.session(node)
|
||||
|
||||
view = HierarchicalGraphView(self.workspace.main_instance, "center", hg)
|
||||
self.workspace.add_view(view)
|
||||
self.workspace.raise_view(view)
|
Loading…
Reference in New Issue