class QProximityGraphStringBlock(QProximityGraphBlock): """ String Block """ def __init__(self, is_selected, proximity_view: 'ProximityView', node: StringProxiNode): self._text = None self._text_item: QGraphicsSimpleTextItem = None super().__init__(is_selected, proximity_view, node) def _init_widgets(self): self._text = '"' + self._node.content.decode("utf-8") + '"' self._text_item = QGraphicsSimpleTextItem(self._text, self) self._text_item.setBrush(self.STRING_NODE_TEXT_COLOR) self._text_item.setFont(Conf.symexec_font) self._text_item.setPos(self.HORIZONTAL_PADDING, self.VERTICAL_PADDING) def paint(self, painter, option, widget): self._paint_boundary(painter) def _update_size(self): width_candidates = [ self.HORIZONTAL_PADDING * 2 + self._text_item.boundingRect().width(), ] self._width = max(width_candidates) self._height = self.VERTICAL_PADDING * 2 + self._text_item.boundingRect( ).height() self._width = max(30, self._width) self._height = max(10, self._height) self.recalculate_size()
class QProximityGraphFunctionBlock(QProximityGraphBlock): """ Function Block """ def __init__(self, is_selected, proximity_view: 'ProximityView', node: FunctionProxiNode): self._text = None self._text_item: QGraphicsSimpleTextItem = None super().__init__(is_selected, proximity_view, node) def _init_widgets(self): self._text = f"Function {self._node.func.name}" self._text_item = QGraphicsSimpleTextItem(self._text, self) self._text_item.setFont(Conf.symexec_font) self._text_item.setBrush(self.FUNCTION_NODE_TEXT_COLOR) self._text_item.setPos(self.HORIZONTAL_PADDING, self.VERTICAL_PADDING) def mouseDoubleClickEvent(self, event): if event.button() == Qt.LeftButton and ( event.modifiers() & Qt.ControlModifier) == Qt.ControlModifier: # ctrl + double click to collapse a function call event.accept() self._proximity_view.collapse_function(self._node.func) return super().mouseDoubleClickEvent(event) def paint(self, painter, option, widget): self._paint_boundary(painter) def _update_size(self): width_candidates = [ self.HORIZONTAL_PADDING * 2 + self._text_item.boundingRect().width(), ] self._width = max(width_candidates) self._height = self.VERTICAL_PADDING * 2 + self._text_item.boundingRect( ).height() self._width = max(30, self._width) self._height = max(10, self._height) self.recalculate_size()
class QBlockCode(QCachedGraphicsItem): """ Top-level code widget for a selection of text. Will construct an AST using QBlockCodeObj, mirroring the structure associated with the target object. This text is then rendered using a QTextDocument, with appropriate styles applied to it. Interaction events will be propagated to corresponding objects. """ GRAPH_ADDR_SPACING = 20 addr: int _addr_str: str obj: QBlockCodeObj _config: ConfigurationManager disasm_view: 'QDisassemblyBaseControl' workspace: 'Workspace' infodock: InfoDock parent: Any def __init__(self, addr: int, obj: QBlockCodeObj, config: ConfigurationManager, disasm_view: 'QDisassemblyBaseControl', workspace: 'Workspace', infodock: InfoDock, parent: Any = None): super().__init__(parent=parent) self.addr = addr self._addr_str = "%08x" % self.addr self._addr_item: QGraphicsSimpleTextItem = None self.obj = obj self._width = 0 self._height = 0 self._config = config self.parent = parent self.workspace = workspace self.infodock = infodock self._disasm_view = disasm_view self._qtextdoc = QTextDocument() self._qtextdoc.setDefaultFont(self._config.disasm_font) self._qtextdoc.setDocumentMargin(0) self._addr_item = QGraphicsSimpleTextItem(self._addr_str, self) self._addr_item.setBrush(Conf.disasm_view_node_address_color) self._addr_item.setFont(Conf.disasm_font) self.update_document() self.setToolTip("Address: " + self._addr_str) self.refresh() def refresh(self): self._addr_item.setVisible(self._disasm_view.show_address) self._layout_items_and_update_size() def update_document(self): self._qtextdoc.clear() cur = QTextCursor(self._qtextdoc) self.obj.render_to_doc(cur) def paint(self, painter, option, widget): #pylint: disable=unused-argument self.update_document() painter.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform | QPainter.HighQualityAntialiasing) painter.setFont(self._config.disasm_font) if self.infodock.is_instruction_selected( self.addr) or self.obj.should_highlight_line: highlight_color = Conf.disasm_view_node_instruction_selected_background_color painter.setBrush(highlight_color) painter.setPen(highlight_color) painter.drawRect(0, 0, self.width, self.height) x = 0 if self._disasm_view.show_address: x += self._addr_item.boundingRect().width( ) + self.GRAPH_ADDR_SPACING painter.translate(QPointF(x, 0)) self._qtextdoc.drawContents(painter) # # Event handlers # def get_obj_for_mouse_event(self, event: QMouseEvent) -> QBlockCodeObj: p = event.pos() if self._disasm_view.show_address: offset = self._addr_item.boundingRect().width( ) + self.GRAPH_ADDR_SPACING p.setX(p.x() - offset) if p.x() >= 0: hitpos = self._qtextdoc.documentLayout().hitTest( p, Qt.HitTestAccuracy.ExactHit) if hitpos >= 0: return self.obj.get_hit_obj(hitpos) return None def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.infodock.select_instruction(self.addr) obj = self.get_obj_for_mouse_event(event) if obj is not None: obj.mousePressEvent(event) def mouseDoubleClickEvent(self, event): obj = self.get_obj_for_mouse_event(event) if obj is not None: obj.mouseDoubleClickEvent(event) # # Private methods # def _layout_items_and_update_size(self): self.update_document() x, y = 0, 0 if self._disasm_view.show_address: self._addr_item.setPos(x, y) x += self._addr_item.boundingRect().width( ) + self.GRAPH_ADDR_SPACING x += self._qtextdoc.size().width() y += self._qtextdoc.size().height() self._width = x self._height = y self.recalculate_size() def _boundingRect(self): return QRectF(0, 0, self._width, self._height)
class QOperand(QCachedGraphicsItem): BRANCH_TARGETS_SPACING = 5 LABEL_VARIABLE_SPACING = 5 VARIABLE_IDENT_SPACING = 5 def __init__(self, workspace, func_addr, disasm_view, disasm, infodock, insn, operand, operand_index, is_branch_target, is_indirect_branch, branch_targets, config, parent=None): super().__init__(parent=parent) self.workspace = workspace self.func_addr = func_addr self.disasm_view = disasm_view self.disasm = disasm self.infodock = infodock self.variable_manager = infodock.variable_manager self.insn = insn self.operand = operand self.operand_index = operand_index self.is_branch_target = is_branch_target self.is_indirect_branch = is_indirect_branch self.branch_targets = branch_targets self._branch_target: Optional[int] = None # the variable involved self.variable = None self._cachy = None self._config = config # "widgets" self._label = None self._label_item: Optional[QGraphicsSimpleTextItem] = None self._variable_label = None self._variable_label_item: Optional[QGraphicsSimpleTextItem] = None self._variable_ident = None self._variable_ident_item: Optional[QGraphicsSimpleTextItem] = None self._branch_targets = None self._branch_targets_text = None self._branch_targets_item: Optional[QGraphicsSimpleTextItem] = None self._is_target_func = None self._width = None self._height = None self._init_widgets() # # Properties # @property def text(self): return self._label @property def is_constant(self): return isinstance(self.operand, ConstantOperand) @property def constant_value(self): if self.is_constant: return self.operand.cs_operand.imm return None @property def is_constant_memory(self): return (isinstance(self.operand, MemoryOperand) and len(self.operand.values) == 1 and isinstance(self.operand.values[0], Value) and isinstance(self.operand.values[0].val, int)) @property def constant_memory_value(self): if self.is_constant_memory: return self.operand.values[0].val return None @property def selected(self): return self.infodock.is_operand_selected(self.insn.addr, self.operand_index) @property def operand_descriptor(self): return OperandDescriptor(self.text, None, func_addr=self.func_addr, variable_ident=self.variable.ident if self.variable is not None else None) # # Event handlers # def mousePressEvent(self, event): if event.button() == Qt.LeftButton: selected = self.infodock.toggle_operand_selection( self.insn.addr, self.operand_index, self.operand_descriptor, insn_pos=self.parentItem().scenePos(), unique=QApplication.keyboardModifiers() != Qt.ControlModifier) if selected: # select the current instruction, too self.infodock.select_instruction(self.insn.addr, insn_pos=QPointF( self.x(), self.y()), unique=True) else: super().mousePressEvent(event) def mouseDoubleClickEvent(self, event): button = event.button() if button == Qt.LeftButton: if self._branch_target is not None: self.disasm_view.jump_to(self._branch_target, src_ins_addr=self.insn.addr, use_animation=True) return if self.is_constant: self.disasm_view.jump_to(self.constant_value, src_ins_addr=self.insn.addr, use_animation=True) return if self.is_constant_memory: self.disasm_view.jump_to(self.constant_memory_value, src_ins_addr=self.insn.addr, use_animation=True) else: super().mouseDoubleClickEvent(event) # # Public methods # def refresh(self): self._layout_items_and_update_size() self.recalculate_size() def paint(self, painter, option, widget): #pylint: disable=unused-argument # Background if self.selected: painter.setPen(self._config.disasm_view_operand_select_color) painter.setBrush(self._config.disasm_view_operand_select_color) painter.drawRect(0, 0, self.width, self.height) else: for _, selected_operand_desc in self.infodock.selected_operands.items( ): if self._equals_for_highlighting_purposes( selected_operand_desc): painter.setBrush( self._config.disasm_view_operand_highlight_color) painter.setPen( self._config.disasm_view_operand_highlight_color) painter.drawRect(0, 0, self.width, self.height) break # # Private methods # def _branch_target_for_operand(self, operand, branch_targets): if not branch_targets: return None if len(branch_targets) == 1: return next(iter(branch_targets)) # there are more than one targets # we pick the one that complies with the operand's text # my solution is pretty hackish... imm = self.constant_value if imm is not None and imm in branch_targets: # problem solved return imm else: # umm why? pass # try to render it rendered = operand.render()[0] for t in branch_targets: if rendered in ("%x" % t, "%#x" % t): return t if t == rendered: return t # ouch not sure what to do l.warning( 'Cannot determine branch targets for operand "%s". Please report on GitHub.', rendered) # return a random one return next(iter(branch_targets)) @staticmethod def _first_n_branch_targets(branch_targets, n): if not branch_targets: return [] return list(branch_targets)[:n] def _init_widgets(self): if self.is_branch_target: # a branch instruction is_target_func = bool(self.branch_targets is not None and next( iter(self.branch_targets)) in self.disasm.kb.functions) self._label = self.operand.render()[0] self._is_target_func = is_target_func if self.is_indirect_branch: # indirect jump self._branch_targets = self.branch_targets first_n_targets = self._first_n_branch_targets( self._branch_targets, 3) if first_n_targets: targets = [] for t in first_n_targets: txt = None if is_target_func: # try to get a function try: target_func = self.disasm.kb.functions.get_by_addr( t) txt = target_func.demangled_name except KeyError: pass # is the address a label? if txt is None and t in self.disasm.kb.labels: txt = self.disasm.kb.labels[t] if txt is None: # use the hex text txt = "%#08x" % t targets.append(txt) self._branch_targets_text = "[ " + ", ".join( targets) + " ]" else: self._branch_targets_text = "[ unknown ]" if self._branch_targets and len(self._branch_targets) == 1: self._branch_target = next(iter(self._branch_targets)) else: self._branch_target = self._branch_target_for_operand( self.operand, self.branch_targets) else: # not a branch formatting = {} if isinstance(self.operand, MemoryOperand): variable_sort = 'memory' elif isinstance(self.operand, RegisterOperand): variable_sort = 'register' else: variable_sort = None # without displaying variable self._label = self.operand.render(formatting=formatting)[0] if variable_sort: # try find the corresponding variable variable_and_offsets = self.variable_manager[ self.func_addr].find_variables_by_insn( self.insn.addr, variable_sort) if variable_and_offsets: variable, offset = self._pick_variable( variable_and_offsets) if variable is not None: self.variable = variable self._variable_ident = "<%s>" % variable.ident if offset is None: offset = 0 variable_str = variable.name ident = (self.insn.addr, 'operand', self.operand_index) if 'custom_values_str' not in formatting: formatting['custom_values_str'] = {} if variable_sort == 'memory': if offset == 0: custom_value_str = variable_str else: custom_value_str = "%s[%d]" % (variable_str, offset) else: custom_value_str = '' ## # Hacks ## if self.infodock.induction_variable_analysis is not None: r = self.infodock.induction_variable_analysis.variables.get( variable.ident, None) if r is not None and r.expr.__class__.__name__ == "InductionExpr": custom_value_str = "i*%d+%d" % (r.expr.stride, r.expr.init) if r is not None and r.expr.__class__.__name__ == "Add" and r.expr.operands[ 0].__class__.__name__ == "InductionExpr": custom_value_str = "i*%d+%d" % ( r.expr.operands[0].stride, r.expr.operands[0].init + r.expr.operands[1].value) formatting['custom_values_str'][ ident] = custom_value_str if 'show_prefix' not in formatting: formatting['show_prefix'] = {} formatting['show_prefix'][ident] = 'False' if 'values_style' not in formatting: formatting['values_style'] = {} formatting['values_style'][ident] = 'curly' # with variable displayed if variable_sort == 'memory': self._variable_label = self.operand.render( formatting=formatting)[0] else: self._variable_label = '' if self._branch_target or self._branch_targets: if self._is_target_func: label_color = self._config.disasm_view_target_addr_color else: label_color = self._config.disasm_view_antitarget_addr_color else: if self.is_constant: label_color = self._config.disasm_view_operand_constant_color else: label_color = self._config.disasm_view_operand_color # label # [rax] self._label_item = QGraphicsSimpleTextItem(self._label, self) self._label_item.setFont(self._config.disasm_font) self._label_item.setBrush(label_color) # variable # {s_10} if self._variable_label: self._variable_label_item = QGraphicsSimpleTextItem( self._variable_label, self) self._variable_label_item.setFont(self._config.disasm_font) self._variable_label_item.setBrush( self._config.disasm_view_variable_label_color) # additional branch targets if self._branch_targets_text: self._branch_targets_item = QGraphicsSimpleTextItem( self._branch_targets_text, self) self._branch_targets_item.setFont(self._config.disasm_font) self._branch_targets_item.setBrush( Qt.darkYellow ) # TODO: Expose as a configuration entry in Config # variable identifier if self.variable is not None and self.disasm_view.show_variable_identifier: self._variable_ident_item = QGraphicsSimpleTextItem( self._variable_ident, self) self._variable_ident_item.setFont(self._config.disasm_font) self._variable_ident_item.setBrush( Qt.darkGreen ) # TODO: Expose as a configuration entry in Config self._layout_items_and_update_size() def _layout_items_and_update_size(self): x, y = 0, 0 # label self._label_item.setPos(x, y) x += self._label_item.boundingRect().width() # variable if self._variable_label_item is not None: if self.disasm_view.show_variable: x += self.LABEL_VARIABLE_SPACING self._variable_label_item.setPos(x, y) x += self._variable_label_item.boundingRect().width() self._variable_label_item.show() else: self._variable_label_item.hide() # additional branch targets if self._branch_targets_item is not None: x += self.BRANCH_TARGETS_SPACING self._branch_targets_item.setPos(x, y) x += self._branch_targets_item.boundingRect().width() # variable identifier if self._variable_ident_item is not None: x += self.VARIABLE_IDENT_SPACING self._variable_ident_item.setPos(x, y) x += self._variable_ident_item.boundingRect().width() self._width = x self._height = self._label_item.boundingRect().height() self.recalculate_size() def _boundingRect(self): return QRectF(0, 0, self._width, self._height) def _pick_variable(self, variable_and_offsets): """ Pick the corresponding variable for the current operand. :param list variable_and_offsets: A list of variables and the offsets into each variable. :return: A tuple of variable and the offset. :rtype: tuple """ if isinstance(self.operand, MemoryOperand): if len(variable_and_offsets) > 1: l.error( "Instruction %#x has two memory operands. Please report it on GitHub.", self.insn.addr) return variable_and_offsets[0] elif isinstance(self.operand, RegisterOperand): # there might be multiple register-type variables for an instruction. pick the right one is... not easy the_reg = self.operand.register if the_reg is None: # huh, it does not have a Register child return None, None reg_name = the_reg.reg arch = self.workspace.instance.project.arch if len(variable_and_offsets) == 1: # only one candidate... var, offset = variable_and_offsets[0] if arch.registers[reg_name][0] == var.reg: return var, offset return None, None if self.operand_index > 0: # this is the source operand # which variable is read here? for var, offset in variable_and_offsets: if arch.registers[reg_name][0] == var.reg: if self._variable_has_access(var, self.insn.addr, 'read'): return var, offset l.debug( 'Cannot find any source variable for operand %d at instruction %#x.', self.operand_index, self.insn.addr) return None, None # this is the destination operand # which variable is written here? for var, offset in variable_and_offsets: if arch.registers[reg_name][0] == var.reg: if self._variable_has_access(var, self.insn.addr, 'write'): return var, offset l.debug( 'Cannot find any destination variable for operand %d at instruction %#x.', self.operand_index, self.insn.addr) # just return the first one return None, None else: # what's this type? why am I here? l.error('_pick_variable: Unsupported operand type %s.', self.operand.__class__) return None, None def _variable_has_access(self, variable, ins_addr, access_type): if variable not in self.variable_manager[ self.func_addr]._variable_accesses: l.error('Variable %s does not have any accessing records.', variable) return False accesses = self.variable_manager[ self.func_addr]._variable_accesses[variable] for access in accesses: if access.location.ins_addr == ins_addr and access.access_type == access_type: return True return False def _equals_for_highlighting_purposes(self, other): """ :param OperandDescriptor other: The other operand to compare with. :return: """ if other is None: return False highlight_mode = self.infodock.highlight_mode if highlight_mode == OperandHighlightMode.SAME_TEXT or self.variable is None: # when there is no related variable, we highlight as long as they have the same text return other.text == self.text elif highlight_mode == OperandHighlightMode.SAME_IDENT: if self.variable is not None and other.variable_ident is not None: return self.func_addr == other.func_addr and self.variable.ident == other.variable_ident return False
class QMemoryDataBlock(QCachedGraphicsItem): ADDRESS_LABEL_OFFSET = 20 LINEAR_INSTRUCTION_OFFSET = 120 BYTE_AREA_SPACING = 15 def __init__(self, workspace, infodock, addr, memory_data, bytes_per_line=16, parent=None): super().__init__(parent=parent) self.workspace = workspace self.infodock = infodock self.addr = addr self.memory_data: MemoryData = memory_data self.bytes_per_line: int = bytes_per_line # TODO: Move it to Conf self._addr_text = None self._width = None self._height = None self._bytes = [ ] # widgets self._addr_item: QGraphicsSimpleTextItem = None self._label_item: Optional[QGraphicsSimpleTextItem] = None self._line_items: List[Tuple[int, QGraphicsSimpleTextItem, List[QGraphicsSimpleTextItem], List[QGraphicsSimpleTextItem]]] = None self._init_widgets() # # Public methods # @property def width(self): return self.boundingRect().width() @property def height(self): return self.boundingRect().height() def paint(self, painter, option, widget): should_highlight = self.infodock.is_label_selected(self.addr) highlight_color = Conf.disasm_view_label_highlight_color if should_highlight: painter.setBrush(highlight_color) painter.setPen(highlight_color) painter.drawRect(0, 0, self.width, self.height) # # Event handlers # def mousePressEvent(self, event): if event.button() == Qt.LeftButton: # unselect all other labels self.infodock.unselect_all_labels() # select this label self.infodock.select_label(self.addr) # # Private methods # def _init_widgets(self): self._addr_text = "%08x" % self.addr self._bytes = [ ] if self.memory_data.content: for byt in self.memory_data.content: self._bytes.append(byt) if len(self._bytes) < self.memory_data.size: # load more from mapped memory start_address = self.memory_data.addr + len(self._bytes) size = self.memory_data.size - len(self._bytes) try: mem_bytes = self.workspace.instance.project.loader.memory.load(start_address, size) except KeyError: mem_bytes = b"" self._bytes += [ b for b in mem_bytes ] + [ '??' ] * (size - len(mem_bytes)) # address self._addr_item = QGraphicsSimpleTextItem(self._addr_text, self) self._addr_item.setFont(Conf.disasm_font) self._addr_item.setBrush(Conf.disasm_view_node_address_color) # label self._init_label_item() # bytes self._init_bytes() self._layout_items_and_update_size() def _init_label_item(self): lbl_text = get_label_text(self.addr, self.workspace.instance.kb) if lbl_text: self._label_item = QGraphicsSimpleTextItem(lbl_text, self) self._label_item.setFont(Conf.code_font) self._label_item.setBrush(Conf.disasm_view_label_color) else: if self._label_item is not None: self._label_item.setParentItem(None) self._label_item = None def _init_bytes(self): if self._line_items: # remove existing items for line in self._line_items: for item in line: item.setParentItem(None) self._line_items = None addr = self.addr i = 0 self._line_items = [] while i < len(self._bytes): byte_offset = addr % self.bytes_per_line if byte_offset == 0: end_pos = i + self.bytes_per_line else: end_pos = self.bytes_per_line - byte_offset all_bytes = self._bytes[i : end_pos] # print("... print %#x, %d bytes, byte_offset %d" % (addr, len(all_bytes), byte_offset)) addr_item, bytes_list, character_list = self._init_line(addr, byte_offset, all_bytes) self._line_items.append((byte_offset, addr_item, bytes_list, character_list)) addr += end_pos - i i = end_pos def _init_line(self, addr, byte_offset, all_bytes): # colors printable_byte_color = Conf.disasm_view_printable_byte_color printable_char_color = Conf.disasm_view_printable_character_color unprintable_byte_color = Conf.disasm_view_unprintable_byte_color unprintable_char_color = Conf.disasm_view_unprintable_character_color unknown_byte_color = Conf.disasm_view_unknown_byte_color unknown_char_color = Conf.disasm_view_unknown_character_color # address addr_text = "%08x" % addr addr_item = QGraphicsSimpleTextItem(addr_text, self) addr_item.setBrush(Conf.disasm_view_node_address_color) addr_item.setFont(Conf.disasm_font) # draw each byte bytes_list = [ ] for idx, byt in enumerate(all_bytes): if type(byt) is int: if is_printable(byt): color = printable_byte_color else: color = unprintable_byte_color o = QGraphicsSimpleTextItem("%02x" % byt, self) o.setFont(Conf.disasm_font) o.setBrush(color) else: # str, usually because it is an unknown byte, in which case the str is "??" o = QGraphicsSimpleTextItem(byt, self) o.setBrush(unknown_byte_color) o.setFont(Conf.disasm_font) bytes_list.append(o) line_chars = byte_offset + idx + 1 # the number of existing characters on this line, including spaces if line_chars % 8 == 0 and line_chars != self.bytes_per_line: # print a deliminator o = QGraphicsSimpleTextItem("-", self) o.setBrush(Qt.black) o.setFont(Conf.disasm_font) bytes_list.append(o) # printable characters character_list = [ ] for byt in all_bytes: if type(byt) is int: if is_printable(byt): color = printable_char_color ch = chr(byt) else: color = unprintable_char_color ch = "." else: color = unknown_char_color ch = "?" o = QGraphicsSimpleTextItem(ch, self) o.setBrush(color) o.setFont(Conf.disasm_font) character_list.append(o) return addr_item, bytes_list, character_list def _layout_items_and_update_size(self): x, y = 0, 0 # # first line # # address self._addr_item.setPos(x, y) x += self._addr_item.boundingRect().width() # label if self._label_item: x += self.ADDRESS_LABEL_OFFSET self._label_item.setPos(x, y) # # the following lines: content # max_x = x x = 0 y += self._addr_item.boundingRect().height() for byte_offset, addr_item, bytes_line, characters_line in self._line_items: addr_item.setPos(x, y) x += addr_item.boundingRect().width() + self.LINEAR_INSTRUCTION_OFFSET # skip byte offset byte_width = bytes_line[0].boundingRect().width() byte_spacing = byte_width // 2 x += byte_offset * (byte_width + byte_spacing) all_bytes = 0 pos = 0 while pos < len(bytes_line): byte_ = bytes_line[pos] byte_.setPos(x, y) x += byte_width line_chars = byte_offset + all_bytes + 1 # the number of existing characters on this line, including spaces if line_chars % 8 == 0 and line_chars != self.bytes_per_line: # now we get a delimiter pos += 1 delimiter = bytes_line[pos] delimiter.setPos(x, y) x += byte_spacing pos += 1 all_bytes += 1 if (byte_offset + all_bytes) % self.bytes_per_line != 0: more_chars = self.bytes_per_line - (byte_offset + all_bytes % self.bytes_per_line) x += more_chars * (byte_width + byte_spacing) x += self.BYTE_AREA_SPACING # printable characters character_width = characters_line[0].boundingRect().width() x += byte_offset * character_width for o in characters_line: o.setPos(x, y) x += character_width max_x = max(x, max_x) # next line! x = 0 y += bytes_line[0].boundingRect().height() self._width = max_x self._height = y self.recalculate_size() def _boundingRect(self): return QRectF(0, 0, self._width, self._height)
def center_text_item(item: QGraphicsSimpleTextItem, x: float, y: float): bounds = item.boundingRect() x -= bounds.width() // 2 y -= bounds.height() // 2 item.setPos(x, y)
def _init_widgets(self): self._node: CallProxiNode self._func_name = self._node.callee.name if self._node.args is not None: self._args = [self._argument_text(arg) for arg in self._node.args] else: self._args = [] # func name self._func_name_item = QGraphicsSimpleTextItem(self._func_name, self) if self._node.callee.is_simprocedure: pen_color = self.CALL_NODE_TEXT_COLOR_SIMPROC elif self._node.callee.is_plt: pen_color = self.CALL_NODE_TEXT_COLOR_SIMPROC else: pen_color = self.CALL_NODE_TEXT_COLOR self._func_name_item.setBrush(pen_color) self._func_name_item.setFont(Conf.symexec_font) self._func_name_item.setPos(self.HORIZONTAL_PADDING, self.VERTICAL_PADDING) x = self.HORIZONTAL_PADDING + self._func_name_item.boundingRect( ).width() y = self.VERTICAL_PADDING # left parenthesis self._left_parenthesis_item = QGraphicsSimpleTextItem("(", self) self._left_parenthesis_item.setBrush(pen_color) self._left_parenthesis_item.setFont(Conf.symexec_font) self._left_parenthesis_item.setPos(x, y) x += self._left_parenthesis_item.boundingRect().width() # arguments self._args_list = [] for i, (type_, arg) in enumerate(self._args): if type_ is StringProxiNode: color = self.STRING_NODE_TEXT_COLOR elif type_ is IntegerProxiNode: color = self.INTEGER_NODE_TEXT_COLOR elif type_ is VariableProxiNode: color = self.VARIABLE_NODE_TEXT_COLOR elif type_ is UnknownProxiNode: color = self.UNKNOWN_NODE_TEXT_COLOR else: color = self.CALL_NODE_TEXT_COLOR o = QGraphicsSimpleTextItem(arg, self) o.setBrush(color) o.setFont(Conf.symexec_font) o.setPos(x, y) self._args_list.append(o) x += o.boundingRect().width() # comma if i != len(self._args) - 1: comma = QGraphicsSimpleTextItem(", ", self) comma.setBrush(pen_color) comma.setFont(Conf.symexec_font) comma.setPos(x, y) self._args_list.append(comma) x += comma.boundingRect().width() # right parenthesis self._right_parenthesis_item = QGraphicsSimpleTextItem(")", self) self._right_parenthesis_item.setBrush(pen_color) self._right_parenthesis_item.setFont(Conf.symexec_font) self._right_parenthesis_item.setPos(x, y)
class QProximityGraphCallBlock(QProximityGraphBlock): """ Call Block """ def __init__(self, is_selected, proximity_view: 'ProximityView', node: CallProxiNode): self._func_name: str = None self._args: List[Tuple[Type, str]] = None self._func_name_item: QGraphicsSimpleTextItem = None self._left_parenthesis_item: QGraphicsSimpleTextItem = None self._args_list: List[QGraphicsSimpleTextItem] = None self._right_parenthesis_item: QGraphicsSimpleTextItem = None super().__init__(is_selected, proximity_view, node) def _init_widgets(self): self._node: CallProxiNode self._func_name = self._node.callee.name if self._node.args is not None: self._args = [self._argument_text(arg) for arg in self._node.args] else: self._args = [] # func name self._func_name_item = QGraphicsSimpleTextItem(self._func_name, self) if self._node.callee.is_simprocedure: pen_color = self.CALL_NODE_TEXT_COLOR_SIMPROC elif self._node.callee.is_plt: pen_color = self.CALL_NODE_TEXT_COLOR_SIMPROC else: pen_color = self.CALL_NODE_TEXT_COLOR self._func_name_item.setBrush(pen_color) self._func_name_item.setFont(Conf.symexec_font) self._func_name_item.setPos(self.HORIZONTAL_PADDING, self.VERTICAL_PADDING) x = self.HORIZONTAL_PADDING + self._func_name_item.boundingRect( ).width() y = self.VERTICAL_PADDING # left parenthesis self._left_parenthesis_item = QGraphicsSimpleTextItem("(", self) self._left_parenthesis_item.setBrush(pen_color) self._left_parenthesis_item.setFont(Conf.symexec_font) self._left_parenthesis_item.setPos(x, y) x += self._left_parenthesis_item.boundingRect().width() # arguments self._args_list = [] for i, (type_, arg) in enumerate(self._args): if type_ is StringProxiNode: color = self.STRING_NODE_TEXT_COLOR elif type_ is IntegerProxiNode: color = self.INTEGER_NODE_TEXT_COLOR elif type_ is VariableProxiNode: color = self.VARIABLE_NODE_TEXT_COLOR elif type_ is UnknownProxiNode: color = self.UNKNOWN_NODE_TEXT_COLOR else: color = self.CALL_NODE_TEXT_COLOR o = QGraphicsSimpleTextItem(arg, self) o.setBrush(color) o.setFont(Conf.symexec_font) o.setPos(x, y) self._args_list.append(o) x += o.boundingRect().width() # comma if i != len(self._args) - 1: comma = QGraphicsSimpleTextItem(", ", self) comma.setBrush(pen_color) comma.setFont(Conf.symexec_font) comma.setPos(x, y) self._args_list.append(comma) x += comma.boundingRect().width() # right parenthesis self._right_parenthesis_item = QGraphicsSimpleTextItem(")", self) self._right_parenthesis_item.setBrush(pen_color) self._right_parenthesis_item.setFont(Conf.symexec_font) self._right_parenthesis_item.setPos(x, y) def _argument_text(self, arg) -> Tuple[Type, str]: # pylint: disable=no-self-use if isinstance(arg, StringProxiNode): return StringProxiNode, '"' + arg.content.decode("utf-8").replace( "\n", "\\n") + '"' elif isinstance(arg, IntegerProxiNode): return IntegerProxiNode, str(arg.value) elif isinstance(arg, VariableProxiNode): return VariableProxiNode, str(arg.name) elif isinstance(arg, UnknownProxiNode): return UnknownProxiNode, str(arg.dummy_value) return object, "Unknown" def mouseDoubleClickEvent(self, event): if event.button() == Qt.LeftButton and ( event.modifiers() & Qt.ControlModifier) == Qt.ControlModifier: # ctrl + double click to expand a function call event.accept() self._proximity_view.expand_function(self._node.callee) return super().mouseDoubleClickEvent(event) def paint(self, painter, option, widget): self._paint_boundary(painter) def _update_size(self): width_candidates = [ self.HORIZONTAL_PADDING * 2 + self._func_name_item.boundingRect().width() + self._left_parenthesis_item.boundingRect().width() + sum(map(lambda x: x.boundingRect().width(), self._args_list)) + self._right_parenthesis_item.boundingRect().width() ] self._width = max(width_candidates) self._height = self.VERTICAL_PADDING * 2 + self._func_name_item.boundingRect( ).height() self._width = max(30, self._width) self._height = max(10, self._height) self.recalculate_size()
class ProjectItemIcon(QGraphicsRectItem): """Base class for project item icons drawn in Design View.""" ITEM_EXTENT = 64 def __init__(self, toolbox, icon_file, icon_color, background_color): """ Args: toolbox (ToolboxUI): QMainWindow instance icon_file (str): Path to icon resource icon_color (QColor): Icon's color background_color (QColor): Background color """ super().__init__() self._toolbox = toolbox self.icon_file = icon_file self._moved_on_scene = False self.previous_pos = QPointF() self.current_pos = QPointF() self.icon_group = {self} self.renderer = QSvgRenderer() self.svg_item = QGraphicsSvgItem(self) self.colorizer = QGraphicsColorizeEffect() self.setRect( QRectF(-self.ITEM_EXTENT / 2, -self.ITEM_EXTENT / 2, self.ITEM_EXTENT, self.ITEM_EXTENT)) self.text_font_size = 10 # point size # Make item name graphics item. self._name = "" self.name_item = QGraphicsSimpleTextItem(self._name, self) self.set_name_attributes() # Set font, size, position, etc. # Make connector buttons self.connectors = dict( bottom=ConnectorButton(self, toolbox, position="bottom"), left=ConnectorButton(self, toolbox, position="left"), right=ConnectorButton(self, toolbox, position="right"), ) # Make exclamation and rank icons self.exclamation_icon = ExclamationIcon(self) self.execution_icon = ExecutionIcon(self) self.rank_icon = RankIcon(self) brush = QBrush(background_color) self._setup(brush, icon_file, icon_color) shadow_effect = QGraphicsDropShadowEffect() shadow_effect.setOffset(1) shadow_effect.setEnabled(False) self.setGraphicsEffect(shadow_effect) def finalize(self, name, x, y): """ Names the icon and moves it by given amount. Args: name (str): icon's name x (int): horizontal offset y (int): vertical offset """ self.update_name_item(name) self.moveBy(x, y) def _setup(self, brush, svg, svg_color): """Setup item's attributes. Args: brush (QBrush): Used in filling the background rectangle svg (str): Path to SVG icon file svg_color (QColor): Color of SVG icon """ self.setPen(QPen(Qt.black, 1, Qt.SolidLine)) self.setBrush(brush) self.colorizer.setColor(svg_color) # Load SVG loading_ok = self.renderer.load(svg) if not loading_ok: self._toolbox.msg_error.emit( "Loading SVG icon from resource:{0} failed".format(svg)) return size = self.renderer.defaultSize() self.svg_item.setSharedRenderer(self.renderer) self.svg_item.setElementId( "") # guess empty string loads the whole file dim_max = max(size.width(), size.height()) rect_w = self.rect().width() # Parent rect width margin = 32 self.svg_item.setScale((rect_w - margin) / dim_max) self.svg_item.setPos(self.rect().center() - self.svg_item.sceneBoundingRect().center()) self.svg_item.setGraphicsEffect(self.colorizer) self.setFlag(QGraphicsItem.ItemIsMovable, enabled=True) self.setFlag(QGraphicsItem.ItemIsSelectable, enabled=True) self.setFlag(QGraphicsItem.ItemIsFocusable, enabled=True) self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, enabled=True) self.setAcceptHoverEvents(True) self.setCursor(Qt.PointingHandCursor) # Set exclamation, execution_log, and rank icons position self.exclamation_icon.setPos( self.rect().topRight() - self.exclamation_icon.sceneBoundingRect().topRight()) self.execution_icon.setPos( self.rect().bottomRight() - 0.5 * self.execution_icon.sceneBoundingRect().bottomRight()) self.rank_icon.setPos(self.rect().topLeft()) def name(self): """Returns name of the item that is represented by this icon. Returns: str: icon's name """ return self._name def update_name_item(self, new_name): """Set a new text to name item. Args: new_name (str): icon's name """ self._name = new_name self.name_item.setText(new_name) self.set_name_attributes() def set_name_attributes(self): """Set name QGraphicsSimpleTextItem attributes (font, size, position, etc.)""" # Set font size and style font = self.name_item.font() font.setPointSize(self.text_font_size) font.setBold(True) self.name_item.setFont(font) # Set name item position (centered on top of the master icon) name_width = self.name_item.boundingRect().width() name_height = self.name_item.boundingRect().height() self.name_item.setPos( self.rect().x() + self.rect().width() / 2 - name_width / 2, self.rect().y() - name_height - 4) def conn_button(self, position="left"): """Returns item's connector button. Args: position (str): "left", "right" or "bottom" Returns: QWidget: connector button """ return self.connectors.get(position, self.connectors["left"]) def outgoing_links(self): """Collects outgoing links. Returns: list of LinkBase: outgoing links """ return [ l for conn in self.connectors.values() for l in conn.outgoing_links() ] def incoming_links(self): """Collects incoming links. Returns: list of LinkBase: outgoing links """ return [ l for conn in self.connectors.values() for l in conn.incoming_links() ] def run_execution_leave_animation(self, skipped): """ Starts the animation associated with execution leaving the icon. Args: skipped (bool): True if project item was not actually executed. """ animation_group = QParallelAnimationGroup(self._toolbox) for link in self.outgoing_links(): animation_group.addAnimation( link.make_execution_animation(skipped)) animation_group.start() def hoverEnterEvent(self, event): """Sets a drop shadow effect to icon when mouse enters its boundaries. Args: event (QGraphicsSceneMouseEvent): Event """ self.prepareGeometryChange() self.graphicsEffect().setEnabled(True) event.accept() def hoverLeaveEvent(self, event): """Disables the drop shadow when mouse leaves icon boundaries. Args: event (QGraphicsSceneMouseEvent): Event """ self.prepareGeometryChange() self.graphicsEffect().setEnabled(False) event.accept() def mousePressEvent(self, event): super().mousePressEvent(event) self.icon_group = set(x for x in self.scene().selectedItems() if isinstance(x, ProjectItemIcon)) | {self} for icon in self.icon_group: icon.previous_pos = icon.scenePos() def mouseMoveEvent(self, event): """Moves icon(s) while the mouse button is pressed. Update links that are connected to selected icons. Args: event (QGraphicsSceneMouseEvent): Event """ super().mouseMoveEvent(event) self.update_links_geometry() def moveBy(self, dx, dy): super().moveBy(dx, dy) self.update_links_geometry() def update_links_geometry(self): """Updates geometry of connected links to reflect this item's most recent position.""" links = set(link for icon in self.icon_group for conn in icon.connectors.values() for link in conn.links) for link in links: link.update_geometry() def mouseReleaseEvent(self, event): for icon in self.icon_group: icon.current_pos = icon.scenePos() # pylint: disable=undefined-variable if (self.current_pos - self.previous_pos ).manhattanLength() > qApp.startDragDistance(): self._toolbox.undo_stack.push( MoveIconCommand(self, self._toolbox.project())) super().mouseReleaseEvent(event) def notify_item_move(self): if self._moved_on_scene: self._moved_on_scene = False scene = self.scene() scene.item_move_finished.emit(self) def contextMenuEvent(self, event): """Show item context menu. Args: event (QGraphicsSceneMouseEvent): Mouse event """ event.accept() self.scene().clearSelection() self.setSelected(True) ind = self._toolbox.project_item_model.find_item(self.name()) self._toolbox.show_project_item_context_menu(event.screenPos(), ind) def itemChange(self, change, value): """ Reacts to item removal and position changes. In particular, destroys the drop shadow effect when the items is removed from a scene and keeps track of item's movements on the scene. Args: change (GraphicsItemChange): a flag signalling the type of the change value: a value related to the change Returns: Whatever super() does with the value parameter """ if change == QGraphicsItem.ItemScenePositionHasChanged: self._moved_on_scene = True elif change == QGraphicsItem.GraphicsItemChange.ItemSceneChange and value is None: self.prepareGeometryChange() self.setGraphicsEffect(None) return super().itemChange(change, value) def select_item(self): """Update GUI to show the details of the selected item.""" ind = self._toolbox.project_item_model.find_item(self.name()) self._toolbox.ui.treeView_project.setCurrentIndex(ind)
class QDepGraphBlock(QCachedGraphicsItem): HORIZONTAL_PADDING = 20 VERTICAL_PADDING = 20 LINE_MARGIN = 3 def __init__(self, is_selected, dep_view: 'DependencyView', definition: 'Definition' = None, atom: Atom = None, addr: int = None): super().__init__() self._dep_view = dep_view self._workspace = self._dep_view.workspace self._config = Conf self.selected = is_selected # widgets self._definition_str: str = None self._definition_item: QGraphicsSimpleTextItem = None self._instruction_str: str = None self._instruction_item: QGraphicsSimpleTextItem = None self._function_str: str = None self._function_item: QGraphicsSimpleTextItem = None self._text: Optional[str] = None self._text_item: Optional[QGraphicsSimpleTextItem] = None self.definition = definition self.atom = atom self.addr = addr self._init_widgets() self._update_size() self.setAcceptHoverEvents(True) def _init_widgets(self): # definition self._definition_str = "" if self.definition is not None: atom = self.definition.atom else: atom = self.atom addr_str = "unknown address" if self.addr is None else "%#x" % self.addr if isinstance(atom, Register): # convert it to a register name arch = self._workspace.instance.project.arch register_name = arch.translate_register_name(atom.reg_offset, size=atom.size) self._definition_str = "Register {} @ {}".format( register_name, addr_str) elif isinstance(atom, MemoryLocation): if isinstance(atom.addr, SpOffset): self._definition_str = "Stack sp%+#x @ %s" % (atom.addr.offset, addr_str) elif isinstance(atom.addr, int): self._definition_str = "Memory %#x @ %s" % (atom.addr, addr_str) if not self._definition_str: # fallback self._definition_str = repr(self.definition.atom) # function and instruction text if self.addr is None: self._function_str = "Unknown" self._instruction_str = "Unknown" else: # function string the_func = locate_function(self._workspace.instance, self.addr) if the_func is None: # is it a SimProcedure? if self._workspace.instance.project.is_hooked(self.addr): hooker = self._workspace.instance.project.hooked_by( self.addr) self._function_str = "SimProcedure " + hooker.__class__.__name__.split( '.')[-1] else: self._function_str = "Unknown" else: offset = self.addr - the_func.addr if not the_func.name: self._function_str = "%#x%+x" % (the_func.addr, offset) else: self._function_str = "%s%+x" % (the_func.name, offset) # instruction self._instruction_str = "%s: %s" % ( self._function_str, self._workspace.instance.get_instruction_text_at(self.addr)) # text self._text = get_string_for_display( self._workspace.instance.cfg, self.addr, self._workspace.instance.project, max_size=60, ) x = self.HORIZONTAL_PADDING y = self.VERTICAL_PADDING # definition self._definition_item = QGraphicsSimpleTextItem( self._definition_str, self) self._definition_item.setBrush(Qt.darkBlue) self._definition_item.setFont(Conf.symexec_font) self._definition_item.setPos(x, y) y += self._definition_item.boundingRect().height() + self.LINE_MARGIN # instruction self._instruction_item = QGraphicsSimpleTextItem( self._instruction_str, self) self._instruction_item.setBrush(Qt.black) self._instruction_item.setFont(Conf.symexec_font) self._instruction_item.setPos(x, y) x += self._instruction_item.boundingRect().width() # text if self._text: x += 10 self._text_item = QGraphicsSimpleTextItem(self._text, self) self._text_item.setFont(Conf.symexec_font) self._text_item.setBrush(Qt.gray) self._text_item.setPos(x, y) # y += self._instruction_item.boundingRect().height() # x = self.HORIZONTAL_PADDING def refresh(self): self._update_size() # # Event handlers # def mousePressEvent(self, event): #pylint: disable=no-self-use super().mousePressEvent(event) def mouseReleaseEvent(self, event): # _l.debug('QStateBlock received mouse release event') if event.button() == Qt.LeftButton: self.selected = not self.selected self._dep_view.redraw_graph() event.accept() super().mouseReleaseEvent(event) def mouseDoubleClickEvent(self, event): # _l.debug('QStateBlock received mouse double click event') if event.button() == Qt.LeftButton: self._workspace.viz(self.addr) event.accept() super().mouseDoubleClickEvent(event) def hoverEnterEvent(self, event: PySide2.QtWidgets.QGraphicsSceneHoverEvent): self._dep_view.hover_enter_block(self) def hoverLeaveEvent(self, event: PySide2.QtWidgets.QGraphicsSceneHoverEvent): self._dep_view.hover_leave_block(self) def paint(self, painter, option, widget): #pylint: disable=unused-argument """ Paint a state block on the scene. :param painter: :return: None """ painter.setFont(Conf.symexec_font) normal_background = QColor(0xfa, 0xfa, 0xfa) selected_background = QColor(0xcc, 0xcc, 0xcc) # The node background if self.selected: painter.setBrush(selected_background) else: painter.setBrush(normal_background) painter.setPen(QPen(QColor(0xf0, 0xf0, 0xf0), 1.5)) painter.drawRect(0, 0, self.width, self.height) def _boundingRect(self): return QRectF(0, 0, self._width, self._height) # # Private methods # def _update_size(self): width_candidates = [ # definition string self._definition_item.boundingRect().width(), # instruction & text self.HORIZONTAL_PADDING * 2 + self._instruction_item.boundingRect().width() + ((10 + self._text_item.boundingRect().width()) if self._text_item is not None else 0), ] self._width = max(width_candidates) self._height = self.VERTICAL_PADDING * 2 + ( self.LINE_MARGIN + self._definition_item.boundingRect().height()) * 2 self._width = max(100, self._width) self._height = max(50, self._height) self.recalculate_size()
class QInstruction(QCachedGraphicsItem): GRAPH_ADDR_SPACING = 20 GRAPH_MNEMONIC_SPACING = 10 GRAPH_OPERAND_SPACING = 2 GRAPH_COMMENT_STRING_SPACING = 10 INTERSPERSE_ARGS = ', ' LINEAR_INSTRUCTION_OFFSET = 120 COMMENT_PREFIX = "// " def __init__(self, workspace, func_addr, disasm_view, disasm, infodock, insn, out_branch, config, parent=None): super().__init__(parent=parent) # initialization self.workspace = workspace self.func_addr = func_addr self.disasm_view = disasm_view self.disasm = disasm self.infodock = infodock self.variable_manager = infodock.variable_manager self.insn = insn self.out_branch = out_branch self._config = config # all "widgets" self._addr = None self._mnemonic = None self._addr_item: QGraphicsSimpleTextItem = None self._mnemonic_item: QGraphicsSimpleTextItem = None self._operands: List[QOperand] = [] self._commas: List[QGraphicsSimpleTextItem] = [] self._string = None self._string_item: Optional[QGraphicsSimpleTextItem] = None self._comment = None self._comment_items: Optional[ List[QGraphicsSimpleTextItem]] = None # one comment per line self._legend = None self._width = 0 self._height = 0 self._init_widgets() def contextMenuEvent( self, event: PySide2.QtWidgets.QGraphicsSceneContextMenuEvent) -> None: pass def mousePressEvent(self, event: QGraphicsSceneMouseEvent): if self.workspace.plugins.handle_click_insn(self, event): # stop handling this event if the event has been handled by a plugin event.accept() elif event.button( ) == Qt.LeftButton and QApplication.keyboardModifiers() in ( Qt.NoModifier, Qt.ControlModifier): # toggle selection self.infodock.toggle_instruction_selection( self.addr, insn_pos=self.scenePos(), unique=QApplication.keyboardModifiers() != Qt.ControlModifier) event.accept() elif event.button( ) == Qt.RightButton and QApplication.keyboardModifiers( ) == Qt.NoModifier: if self.addr not in self.infodock.selected_insns: self.infodock.toggle_instruction_selection( self.addr, insn_pos=self.scenePos(), unique=True) self.disasm_view.instruction_context_menu(self.insn, QCursor.pos()) event.accept() else: super().mousePressEvent(event) @property def addr(self): return self.insn.addr def _calc_backcolor(self): # First we'll check for customizations color = self.workspace.plugins.color_insn(self.insn.addr, self.selected) if color is not None: return color if self.selected: return self._config.disasm_view_node_instruction_selected_background_color return None # None here means transparent, reusing the block color @property def selected(self): """ If this instruction is selected or not. :return: True if it is selected, False otherwise. :rtype: bool """ return self.infodock.is_instruction_selected(self.addr) def clear_cache(self): super().clear_cache() for obj in self._operands: obj.clear_cache() def refresh(self): self.load_comment() self._init_comments_or_string() for operand in self._operands: operand.refresh() self._layout_items_and_update_size() self.recalculate_size() def get_operand(self, operand_idx): if operand_idx < len(self._operands): return self._operands[operand_idx] return None def load_comment(self): self._comment = get_comment_for_display(self.workspace.instance.kb, self.insn.addr) def paint(self, painter, option, widget): # pylint: disable=unused-argument painter.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform | QPainter.HighQualityAntialiasing) # background color backcolor = self._calc_backcolor() if backcolor is not None: painter.setBrush(backcolor) painter.setPen(backcolor) painter.drawRect(0, 0, self.width, self.height) # any plugin instruction rendering passes self.workspace.plugins.draw_insn(self, painter) # # Private methods # def _init_widgets(self): self.load_comment() self._operands.clear() # address self._addr = "%08x" % self.insn.addr self._addr_item = QGraphicsSimpleTextItem(self) self._addr_item.setBrush( QBrush(self._config.disasm_view_node_address_color)) self._addr_item.setFont(self._config.disasm_font) self._addr_item.setText(self._addr) # mnemonic self._mnemonic = self.insn.mnemonic.render()[0] self._mnemonic_item = QGraphicsSimpleTextItem(self) self._mnemonic_item.setFont(self._config.disasm_font) self._mnemonic_item.setBrush( self._config.disasm_view_node_mnemonic_color) self._mnemonic_item.setText(self._mnemonic) # operands for i, operand in enumerate(self.insn.operands): is_branch_target = self.insn.type in ( 'branch', 'call') and i == self.insn.branch_target_operand is_indirect_branch = self.insn.branch_type == 'indirect' branch_targets = None if is_branch_target: if self.out_branch is not None: branch_targets = self.out_branch.targets else: # it does not create multiple branches. e.g., a call instruction if len(operand.children) == 1 and type( operand.children[0]) is Value: branch_targets = (operand.children[0].val, ) qoperand = QOperand(self.workspace, self.func_addr, self.disasm_view, self.disasm, self.infodock, self.insn, operand, i, is_branch_target, is_indirect_branch, branch_targets, self._config, parent=self) self._operands.append(qoperand) # all commas for _ in range(len(self._operands) - 1): comma = QGraphicsSimpleTextItem(self.INTERSPERSE_ARGS, self) comma.setFont(self._config.disasm_font) comma.setBrush(self._config.disasm_view_node_mnemonic_color) self._commas.append(comma) if should_display_string_label(self.workspace.instance.cfg, self.insn.addr, self.workspace.instance.project): # yes we should display a string label self._string = get_string_for_display( self.workspace.instance.cfg, self.insn.addr, self.workspace.instance.project) if self._string is None: self._string = "<Unknown>" self._init_comments_or_string() self._layout_items_and_update_size() def _init_comments_or_string(self): # remove existing comments or strings if self._comment_items: for comm in self._comment_items: comm: QGraphicsSimpleTextItem comm.setParentItem(None) self._comment_items = None elif self._string_item is not None: self._string_item.setParentItem(None) self._string_item = None # comment or string - comments have precedence if self._comment: self._string_item = None lines = self._comment.split('\n') self._comment_items = [] for line in lines: comment = QGraphicsSimpleTextItem(self.COMMENT_PREFIX + line, self) comment.setFont(self._config.disasm_font) comment.setBrush( Qt.darkGreen) # TODO: Expose it as a setting in Config self._comment_items.append(comment) elif self._string is not None: self._comment_items = None self._string_item = QGraphicsSimpleTextItem(self._string, self) self._string_item.setFont(self._config.disasm_font) self._string_item.setBrush( Qt.gray) # TODO: Expose it as a setting in Config def _layout_items_and_update_size(self): x, y = 0, 0 # address if self.disasm_view.show_address: self._addr_item.setVisible(True) self._addr_item.setPos(x, y) x += self._addr_item.boundingRect().width( ) + self.GRAPH_ADDR_SPACING else: self._addr_item.setVisible(False) # mnemonic self._mnemonic_item.setPos(x, y) x += self._mnemonic_item.boundingRect().width( ) + self.GRAPH_MNEMONIC_SPACING # operands and commas for operand, comma in zip(self._operands, self._commas): operand.setPos(x, y) x += operand.boundingRect().width() comma.setPos(x, y) x += comma.boundingRect().width() # the last operand if self._operands: self._operands[-1].setPos(x, y) x += self._operands[-1].boundingRect().width() # comments and strings if self._comment_items: x += self.GRAPH_COMMENT_STRING_SPACING max_comment_width = 0 for comment in self._comment_items: comment.setPos(x, y) max_comment_width = max(comment.boundingRect().width(), max_comment_width) y += comment.boundingRect().height() x += max_comment_width elif self._string_item is not None: x += self.GRAPH_COMMENT_STRING_SPACING self._string_item.setPos(x, y) x += self._string_item.boundingRect().width() y += self._string_item.boundingRect().height() else: y = self._mnemonic_item.boundingRect().height() # update size self._width = x self._height = y self.recalculate_size() def _boundingRect(self): return QRectF(0, 0, self._width, self._height)
class ProjectItemIcon(QGraphicsRectItem): ITEM_EXTENT = 64 def __init__(self, toolbox, x, y, project_item, icon_file, icon_color, background_color): """Base class for project item icons drawn in Design View. Args: toolbox (ToolBoxUI): QMainWindow instance x (float): Icon x coordinate y (float): Icon y coordinate project_item (ProjectItem): Item icon_file (str): Path to icon resource icon_color (QColor): Icon's color background_color (QColor): Background color """ super().__init__() self._toolbox = toolbox self._project_item = project_item self._moved_on_scene = False self._previous_pos = QPointF() self._current_pos = QPointF() self.icon_group = {self} self.renderer = QSvgRenderer() self.svg_item = QGraphicsSvgItem(self) self.colorizer = QGraphicsColorizeEffect() self.setRect( QRectF(x - self.ITEM_EXTENT / 2, y - self.ITEM_EXTENT / 2, self.ITEM_EXTENT, self.ITEM_EXTENT)) self.text_font_size = 10 # point size # Make item name graphics item. name = project_item.name if project_item else "" self.name_item = QGraphicsSimpleTextItem(name, self) self.set_name_attributes() # Set font, size, position, etc. # Make connector buttons self.connectors = dict( bottom=ConnectorButton(self, toolbox, position="bottom"), left=ConnectorButton(self, toolbox, position="left"), right=ConnectorButton(self, toolbox, position="right"), ) # Make exclamation and rank icons self.exclamation_icon = ExclamationIcon(self) self.rank_icon = RankIcon(self) brush = QBrush(background_color) self._setup(brush, icon_file, icon_color) self.activate() def activate(self): """Adds items to scene and setup graphics effect. Called in the constructor and when re-adding the item to the project in the context of undo/redo. """ scene = self._toolbox.ui.graphicsView.scene() scene.addItem(self) shadow_effect = QGraphicsDropShadowEffect() shadow_effect.setOffset(1) shadow_effect.setEnabled(False) self.setGraphicsEffect(shadow_effect) def _setup(self, brush, svg, svg_color): """Setup item's attributes. Args: brush (QBrush): Used in filling the background rectangle svg (str): Path to SVG icon file svg_color (QColor): Color of SVG icon """ self.setPen(QPen(Qt.black, 1, Qt.SolidLine)) self.setBrush(brush) self.colorizer.setColor(svg_color) # Load SVG loading_ok = self.renderer.load(svg) if not loading_ok: self._toolbox.msg_error.emit( "Loading SVG icon from resource:{0} failed".format(svg)) return size = self.renderer.defaultSize() self.svg_item.setSharedRenderer(self.renderer) self.svg_item.setElementId( "") # guess empty string loads the whole file dim_max = max(size.width(), size.height()) rect_w = self.rect().width() # Parent rect width margin = 32 self.svg_item.setScale((rect_w - margin) / dim_max) x_offset = (rect_w - self.svg_item.sceneBoundingRect().width()) / 2 y_offset = (rect_w - self.svg_item.sceneBoundingRect().height()) / 2 self.svg_item.setPos(self.rect().x() + x_offset, self.rect().y() + y_offset) self.svg_item.setGraphicsEffect(self.colorizer) self.setFlag(QGraphicsItem.ItemIsMovable, enabled=True) self.setFlag(QGraphicsItem.ItemIsSelectable, enabled=True) self.setFlag(QGraphicsItem.ItemIsFocusable, enabled=True) self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, enabled=True) self.setAcceptHoverEvents(True) self.setCursor(Qt.PointingHandCursor) # Set exclamation and rank icons position self.exclamation_icon.setPos( self.rect().topRight() - self.exclamation_icon.sceneBoundingRect().topRight()) self.rank_icon.setPos(self.rect().topLeft()) def name(self): """Returns name of the item that is represented by this icon.""" return self._project_item.name def update_name_item(self, new_name): """Set a new text to name item. Used when a project item is renamed.""" self.name_item.setText(new_name) self.set_name_attributes() def set_name_attributes(self): """Set name QGraphicsSimpleTextItem attributes (font, size, position, etc.)""" # Set font size and style font = self.name_item.font() font.setPointSize(self.text_font_size) font.setBold(True) self.name_item.setFont(font) # Set name item position (centered on top of the master icon) name_width = self.name_item.boundingRect().width() name_height = self.name_item.boundingRect().height() self.name_item.setPos( self.rect().x() + self.rect().width() / 2 - name_width / 2, self.rect().y() - name_height - 4) def conn_button(self, position="left"): """Returns items connector button (QWidget).""" return self.connectors.get(position, self.connectors["left"]) def outgoing_links(self): return [ l for conn in self.connectors.values() for l in conn.outgoing_links() ] def incoming_links(self): return [ l for conn in self.connectors.values() for l in conn.incoming_links() ] def hoverEnterEvent(self, event): """Sets a drop shadow effect to icon when mouse enters its boundaries. Args: event (QGraphicsSceneMouseEvent): Event """ self.prepareGeometryChange() self.graphicsEffect().setEnabled(True) event.accept() def hoverLeaveEvent(self, event): """Disables the drop shadow when mouse leaves icon boundaries. Args: event (QGraphicsSceneMouseEvent): Event """ self.prepareGeometryChange() self.graphicsEffect().setEnabled(False) event.accept() def mousePressEvent(self, event): super().mousePressEvent(event) self.icon_group = set(x for x in self.scene().selectedItems() if isinstance(x, ProjectItemIcon)) | {self} for icon in self.icon_group: icon._previous_pos = icon.scenePos() def mouseMoveEvent(self, event): """Moves icon(s) while the mouse button is pressed. Update links that are connected to selected icons. Args: event (QGraphicsSceneMouseEvent): Event """ super().mouseMoveEvent(event) self.update_links_geometry() def moveBy(self, dx, dy): super().moveBy(dx, dy) self.update_links_geometry() def update_links_geometry(self): """Updates geometry of connected links to reflect this item's most recent position.""" links = set(link for icon in self.icon_group for conn in icon.connectors.values() for link in conn.links) for link in links: link.update_geometry() def mouseReleaseEvent(self, event): for icon in self.icon_group: icon._current_pos = icon.scenePos() # pylint: disable=undefined-variable if (self._current_pos - self._previous_pos ).manhattanLength() > qApp.startDragDistance(): self._toolbox.undo_stack.push(MoveIconCommand(self)) super().mouseReleaseEvent(event) def notify_item_move(self): if self._moved_on_scene: self._moved_on_scene = False scene = self.scene() scene.item_move_finished.emit(self) def contextMenuEvent(self, event): """Show item context menu. Args: event (QGraphicsSceneMouseEvent): Mouse event """ self.scene().clearSelection() self.setSelected(True) self._toolbox.show_item_image_context_menu(event.screenPos(), self.name()) def keyPressEvent(self, event): """Handles deleting and rotating the selected item when dedicated keys are pressed. Args: event (QKeyEvent): Key event """ if event.key() == Qt.Key_Delete and self.isSelected(): self._project_item._project.remove_item(self.name()) event.accept() elif event.key() == Qt.Key_R and self.isSelected(): # TODO: # 1. Change name item text direction when rotating # 2. Save rotation into project file rect = self.mapToScene(self.boundingRect()).boundingRect() center = rect.center() t = QTransform() t.translate(center.x(), center.y()) t.rotate(90) t.translate(-center.x(), -center.y()) self.setPos(t.map(self.pos())) self.setRotation(self.rotation() + 90) links = set(lnk for conn in self.connectors.values() for lnk in conn.links) for link in links: link.update_geometry() event.accept() else: super().keyPressEvent(event) def itemChange(self, change, value): """ Reacts to item removal and position changes. In particular, destroys the drop shadow effect when the items is removed from a scene and keeps track of item's movements on the scene. Args: change (GraphicsItemChange): a flag signalling the type of the change value: a value related to the change Returns: Whatever super() does with the value parameter """ if change == QGraphicsItem.ItemScenePositionHasChanged: self._moved_on_scene = True elif change == QGraphicsItem.GraphicsItemChange.ItemSceneChange and value is None: self.prepareGeometryChange() self.setGraphicsEffect(None) return super().itemChange(change, value) def show_item_info(self): """Update GUI to show the details of the selected item.""" ind = self._toolbox.project_item_model.find_item(self.name()) self._toolbox.ui.treeView_project.setCurrentIndex(ind)
class QFunctionHeader(QCachedGraphicsItem): def __init__(self, addr, name, prototype, args, config, disasm_view, workspace, infodock, parent=None): super().__init__(parent=parent) self.workspace = workspace self.addr = addr self.name = name self.prototype = prototype # type: SimTypeFunction self.args = args self.infodock = infodock self._width = 0 self._height = 0 self._config = config self._disasm_view = disasm_view self._return_type_width = None self._arg_str_list = None self._args_str = None self._function_name_item: QGraphicsSimpleTextItem = None self._args_str_item: QGraphicsSimpleTextItem = None self._prototype_arg_item: Optional[QGraphicsSimpleTextItem] = None self._init_widgets() def refresh(self): pass def paint(self, painter, option, widget): painter.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform | QPainter.HighQualityAntialiasing) if self.infodock.is_label_selected(self.addr): highlight_color = Conf.disasm_view_label_highlight_color painter.setBrush(highlight_color) painter.setPen(highlight_color) painter.drawRect(0, 0, self.width, self.height) # # Event handlers # def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.infodock.select_label(self.addr) elif event.button( ) == Qt.RightButton and QApplication.keyboardModifiers( ) == Qt.NoModifier: if self.addr not in self.infodock.selected_labels: self.infodock.select_label(self.addr) self._disasm_view.label_context_menu(self.addr, QCursor.pos()) # # Private methods # def _init_widgets(self): if self.args is not None: self._arg_str_list = [] for arg in self.args: if isinstance(arg, SimRegArg): self._arg_str_list.append(arg.reg_name) else: self._arg_str_list.append(str(arg)) self._args_str = "Args: (%s)" % (", ".join(self._arg_str_list)) else: self._args_str = "" # # prototype # if self.prototype is None: # Just print the function name self._function_name_item = QGraphicsSimpleTextItem(self.name, self) self._function_name_item.setFont(self._config.code_font) self._function_name_item.setBrush( self._config.disasm_view_function_color) else: # print the prototype proto_str = "" # type of the return value rt = type2str(self.prototype.returnty) proto_str += rt # space proto_str += " " # function name proto_str += self.name # left parenthesis proto_str += "(" # arguments for i, arg_type in enumerate(self.prototype.args): type_str = type2str(arg_type) proto_str += type_str + " " if self.prototype.arg_names and i < len( self.prototype.arg_names): arg_name = self.prototype.arg_names[i] else: arg_name = "arg_%d" % i proto_str += arg_name if i < len(self.prototype.args) - 1: # splitter proto_str += ", " # right parenthesis proto_str += ")" self._prototype_arg_item = QGraphicsSimpleTextItem(proto_str, self) self._prototype_arg_item.setFont(self._config.code_font) self._prototype_arg_item.setBrush( self._config.disasm_view_function_color) # arguments if self._arg_str_list is not None: s = 'Args: (' + ", ".join(self._arg_str_list) + ")" self._args_str_item = QGraphicsSimpleTextItem(s, self) self._args_str_item.setFont(self._config.code_font) self._args_str_item.setBrush( self._config.disasm_view_function_color) self._layout_items_and_update_size() def _layout_items_and_update_size(self): x, y = 0, 0 if self._function_name_item is not None: # function anme self._function_name_item.setPos(x, y) x += self._function_name_item.boundingRect().width() height = self._function_name_item.boundingRect().height() elif self._prototype_arg_item is not None: # prototype self._prototype_arg_item.setPos(x, y) x += self._prototype_arg_item.boundingRect().width() height = self._prototype_arg_item.boundingRect().height() else: height = 0 # new line max_x = x x = 0 y += height # arguments if self._args_str_item is not None: self._args_str_item.setPos(x, y) x += self._args_str_item.boundingRect().width() y += self._args_str_item.boundingRect().height() max_x = max(x, max_x) self._width = max_x self._height = y self.recalculate_size() def _boundingRect(self): return QRectF(0, 0, self._width, self._height)
class QBlockLabel(QCachedGraphicsItem): def __init__(self, addr, text, config, disasm_view, workspace, infodock, parent=None): super().__init__(parent=parent) self.workspace = workspace self.addr = addr self.text = text self._width = 0 self._height = 0 self.infodock = infodock self._config = config self._disasm_view = disasm_view self._text_item: QGraphicsSimpleTextItem = None self._init_widgets() def paint(self, painter, option, widget): #pylint: disable=unused-argument painter.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform | QPainter.HighQualityAntialiasing) painter.setFont(self._config.code_font) # background if self.infodock.is_label_selected(self.addr): highlight_color = Conf.disasm_view_label_highlight_color painter.setBrush(highlight_color) painter.setPen(highlight_color) painter.drawRect(0, 0, self.width, self.height) # # Event handlers # def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.infodock.select_label(self.addr) # # Private methods # def _init_widgets(self): self._text_item = QGraphicsSimpleTextItem(self.text, self) self._text_item.setBrush(Conf.disasm_view_label_color) self._text_item.setFont(self._config.disasm_font) self._layout_items_and_update_size() def _layout_items_and_update_size(self): self._text_item.setPos(0, 0) self._width = self._text_item.boundingRect().width() self._height = self._text_item.boundingRect().height() self.recalculate_size() def _boundingRect(self): return QRectF(0, 0, self._width, self._height)
class QUnknownBlock(QCachedGraphicsItem): LINEAR_INSTRUCTION_OFFSET = 120 DEFAULT_TEXT = 'Unknown' def __init__(self, workspace, addr, bytes_, parent=None): super().__init__(parent=parent) self.workspace = workspace self.addr = addr self.bytes = bytes_ self._width = 0 self._height = 0 self._addr_text = None self._addr_item: QGraphicsSimpleTextItem = None self._byte_lines: List[QGraphicsSimpleTextItem] = None self._config = Conf self._init_widgets() # # Public methods # def paint(self, painter, option, widget): #pylint: disable=unused-argument # painter.setRenderHints( # QPainter.Antialiasing | QPainter.SmoothPixmapTransform | QPainter.HighQualityAntialiasing) # painter.setFont(self._config.disasm_font) pass def _boundingRect(self): return QRectF(0, 0, self._width, self._height) # # Private methods # def _init_widgets(self): # Address self._addr_text = "%08x" % self.addr self._addr_item = QGraphicsSimpleTextItem(self._addr_text, self) self._addr_item.setBrush(Conf.disasm_view_node_address_color) self._addr_item.setFont(Conf.disasm_font) # Bytes self._byte_lines = [] if self.bytes: line = "" for i, b in enumerate(self.bytes): line += "%02x " % b if i > 0 and (i + 1) % 16 == 0: o = QGraphicsSimpleTextItem(line, self) o.setFont(Conf.disasm_font) o.setBrush(Conf.disasm_view_unprintable_byte_color) self._byte_lines.append(o) line = "" if line: o = QGraphicsSimpleTextItem(line, self) o.setFont(Conf.disasm_font) o.setBrush(Conf.disasm_view_unprintable_byte_color) self._byte_lines.append(o) else: o = QGraphicsSimpleTextItem(QUnknownBlock.DEFAULT_TEXT, self) o.setBrush(Conf.disasm_view_unprintable_byte_color) o.setFont(Conf.disasm_font) self._byte_lines.append(o) self._layout_items_and_update_size() def _layout_items_and_update_size(self): x, y = 0, 0 # address self._addr_item.setPos(x, y) x += self._addr_item.boundingRect().width() x += self.LINEAR_INSTRUCTION_OFFSET # lines max_x = x for line in self._byte_lines: line.setPos(x, y) y += line.boundingRect().height() max_x = max(max_x, line.boundingRect().width()) self._width = max_x self._height = y self.recalculate_size()
class QVariable(QCachedGraphicsItem): IDENT_LEFT_PADDING = 5 OFFSET_LEFT_PADDING = 12 def __init__(self, workspace, disasm_view, variable, config, parent=None): super(QVariable, self).__init__(parent=parent) # initialization self.workspace = workspace self.disasm_view = disasm_view self.variable = variable self._config = config self._variable_name = None self._variable_name_item: QGraphicsSimpleTextItem = None self._variable_ident = None self._variable_ident_item: QGraphicsSimpleTextItem = None self._variable_offset = None self._variable_offset_item: QGraphicsSimpleTextItem = None self._init_widgets() # # Public methods # def paint(self, painter, option, widget): # pylint: disable=unused-argument pass def refresh(self): super(QVariable, self).refresh() if self._variable_ident_item is not None: self._variable_ident_item.setVisible( self.disasm_view.show_variable_identifier) self._layout_items_and_update_size() # # Private methods # def _init_widgets(self): # variable name self._variable_name = "" if not self.variable.name else self.variable.name self._variable_name_item = QGraphicsSimpleTextItem( self._variable_name, self) self._variable_name_item.setFont(self._config.disasm_font) self._variable_name_item.setBrush( Qt.darkGreen) # TODO: Expose it as a configuration entry in Config # variable ident self._variable_ident = "<%s>" % ("" if not self.variable.ident else self.variable.ident) self._variable_ident_item = QGraphicsSimpleTextItem( self._variable_ident, self) self._variable_ident_item.setFont(self._config.disasm_font) self._variable_ident_item.setBrush( Qt.blue) # TODO: Expose it as a configuration entry in Config self._variable_ident_item.setVisible( self.disasm_view.show_variable_identifier) # variable offset self._variable_offset = "%#x" % self.variable.offset self._variable_offset_item = QGraphicsSimpleTextItem( self._variable_offset, self) self._variable_offset_item.setFont(self._config.disasm_font) self._variable_offset_item.setBrush( Qt.darkYellow ) # TODO: Expose it as a configuration entry in Config self._layout_items_and_update_size() def _layout_items_and_update_size(self): x, y = 0, 0 # variable name self._variable_name_item.setPos(x, y) x += self._variable_name_item.boundingRect().width( ) + self.IDENT_LEFT_PADDING if self.disasm_view.show_variable_identifier: # identifier x += self.IDENT_LEFT_PADDING self._variable_ident_item.setPos(x, y) x += self._variable_ident_item.boundingRect().width() # variable offset x += self.OFFSET_LEFT_PADDING self._variable_offset_item.setPos(x, y) x += self._variable_offset_item.boundingRect().width() self._width = x self._height = self._variable_name_item.boundingRect().height() self.recalculate_size() def _boundingRect(self): return QRectF(0, 0, self._width, self._height)