def _apply_context(self, context, manual=False): # update current context tid # this should be on top as any further api from js needs to be executed on that thread is_initial_hook = context['reason'] >= 0 if manual or (self.dwarf.context_tid and not is_initial_hook): self.dwarf.context_tid = context['tid'] if 'context' in context: if not manual: self.threads.add_context(context) is_java = context['is_java'] if is_java: if self.java_explorer_panel is None: self._create_ui_elem('jvm-debugger') self.context_panel.set_context(context['ptr'], 1, context['context']) self.java_explorer_panel.set_handle_arg(-1) self.show_main_tab('jvm-debugger') else: self.context_panel.set_context(context['ptr'], 0, context['context']) if 'pc' in context['context']: if not 'disassembly' in self._ui_elems: from lib.range import Range _range = Range(Range.SOURCE_TARGET, self.dwarf) _range.init_with_address( int(context['context']['pc']['value'], 16)) self._disassemble_range(_range) if 'backtrace' in context: self.backtrace_panel.set_backtrace(context['backtrace'])
def read_memory(self, ptr, length=0): if self.range is None: self.range = Range(Range.SOURCE_TARGET, self.dwarf) init = self.range.init_with_address(ptr, length) if init > 0: return 1 self.disasm() return 0
def read_memory(self, ptr, length=0): if self.range is None: self.range = Range(self.app) init = self.range.init_with_address(ptr, length) if init > 0: return 1 self.disasm() return 0
def read_memory(self, ptr, length=0, base=0): if self.range is None: self.range = Range(self.app) self.app.get_session_ui().request_session_ui_focus() init = self.range.init_with_address(ptr, length, base) if init > 0: return 1 self._set_memory_view(init == 0) return 0
def read_memory(self, ptr, length=0): self._lines.clear() if self._range is None: self._range = Range(Range.SOURCE_TARGET, self.dwarf) init = self._range.init_with_address(ptr, length) if init > 0: return 1 self.disassemble(self._range) return 0
def read_memory(self, ptr, length=0, base=0): if self.range is None: self.range = Range(self.get_source_type(), self.app.get_dwarf()) if self.get_source_type() == Range.SOURCE_TARGET: self.app.get_session_ui().request_session_ui_focus() init = self.range.init_with_address(ptr, length, base) if init > 0: return 1 self._set_memory_view(init == 0) return 0
def __init__(self, app, *__args): super().__init__(0, 18) self.app = app self.controller = PanelController(self) self.range = Range(app) self.verticalHeader().hide() self.horizontalHeader().hide() self.resizeColumnsToContents() self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.show_menu) self.setShowGrid(False)
def map_range(self, address): range_ = Range(Range.SOURCE_TARGET, self.dwarf) if range_.init_with_address(address) > 0: return 300 try: self.uc.mem_map(range_.base, range_.size) except Exception as e: self.dwarf.log(e) return 301 try: self.uc.mem_write(range_.base, range_.data) except Exception as e: self.dwarf.log(e) return 302 self.log_to_ui("[*] Mapped %d at 0x%x" % (range_.size, range_.base)) self.onEmulatorMemoryRangeMapped.emit([range_.base, range_.size]) return 0
def map_range(self, address): range = Range(Range.SOURCE_TARGET, self.dwarf) if range.init_with_address(address) > 0: return 300 try: self.uc.mem_map(range.base, range.size) except Exception as e: self.dwarf.log(e) return 301 try: self.uc.mem_write(range.base, range.data) except Exception as e: self.dwarf.log(e) return 302 self.log_to_ui("[*] Mapped %d at 0x%x" % (range.size, range.base)) self.dwarf.get_bus().emit('emulator_memory_range_mapped', range.base, range.size) return 0
class MemoryPanel(QTableWidget): def __init__(self, app, *__args): super().__init__(0, 18) self.app = app self.controller = PanelController(self) self.range = None self.verticalHeader().hide() self.horizontalHeader().hide() self.setSelectionMode(QAbstractItemView.SingleSelection) self.resizeColumnsToContents() self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.show_menu) self.setShowGrid(False) def show_menu(self, pos): cell = self.itemAt(pos) menu = QMenu() if cell: if isinstance(cell, ByteWidget): address = QAction(hex(cell.get_ptr())) address.triggered.connect(self.trigger_copy_address) menu.addAction(address) menu.addSeparator() asm_view = menu.addAction("ASM\t(A)") asm_view.triggered.connect(self.show_asm_view) menu.addAction(asm_view) menu.addSeparator() if isinstance(cell, ByteWidget): # todo # data = menu.addAction("Show as data\t(D)") # menu.addAction(data) hook_address = QAction("Hook address") hook_address.triggered.connect(self.trigger_hook_address) menu.addAction(hook_address) follow = QAction("Follow pointer\t(F)") follow.triggered.connect(self.trigger_follow_pointer) menu.addAction(follow) menu.addSeparator() if cell: wb = QAction("Write bytes") wb.triggered.connect(self.trigger_write_bytes) menu.addAction(wb) ws = menu.addAction("Write string") ws.triggered.connect(self.trigger_write_string) menu.addSeparator() jump_to = QAction("Jump to\t(G)") jump_to.triggered.connect(self.trigger_jump_to) menu.addAction(jump_to) menu.exec_(self.mapToGlobal(pos)) def read_pointer(self, byte_widget): return self.app.dwarf_api('readPointer', byte_widget.get_ptr()) def _set_memory_view(self, should_clear_rows=True): if should_clear_rows: self.setRowCount(0) self.setRowCount(int(math.ceil(self.range.size / 16.0))) self.setColumnCount(18) self.horizontalHeader().show() h_labels = [ '' ] for i in range(0, 16): h_labels.append(hex(i)) h_labels.append('') self.setHorizontalHeaderLabels(h_labels) self.resizeColumnsToContents() self.setColumnWidth(0, 100) self.horizontalHeader().setStretchLastSection(True) start_row = int(math.ceil((self.range.start_address - self.range.base) / 16.0)) self.controller.start(start_row) self.setCurrentCell(start_row, 1) index = self.currentIndex() self.scrollTo(index, QAbstractItemView.PositionAtCenter) self.setCurrentCell(start_row, 1) def read_memory(self, ptr, length=0, base=0): if self.range is None: self.range = Range(self.app) self.app.get_session_ui().request_session_ui_focus() init = self.range.init_with_address(ptr, length, base) if init > 0: return 1 self._set_memory_view(init == 0) return 0 def show_asm_view(self): if len(self.selectedItems()) == 0: return item = self.selectedItems()[0] if isinstance(item, ByteWidget): if self.range.base < item.get_ptr() < self.range.tail: self.range.set_start_offset(item.get_offset()) self.app.get_session_ui().disasm(_range=self.range) else: self.app.get_session_ui().disasm(ptr=item.get_ptr()) def keyPressEvent(self, event): if event.key() == Qt.Key_G: self.trigger_jump_to() elif event.key() == Qt.Key_F: self.trigger_follow_pointer() elif event.key() == Qt.Key_A: self.show_asm_view() pass elif event.key() == Qt.Key_O: self.swap_arm_mode() else: # dispatch those to super super(MemoryPanel, self).keyPressEvent(event) def trigger_copy_address(self): item = self.selectedItems()[0] if item.column() == 0: item = self.item(item.row(), 1) if isinstance(item, ByteWidget): pyperclip.copy(hex(item.get_ptr())) def trigger_follow_pointer(self): if len(self.selectedItems()) > 0 and isinstance(self.selectedItems()[0], ByteWidget): self.read_memory(self.read_pointer(self.selectedItems()[0])) def trigger_hook_address(self): item = self.selectedItems()[0] if item.column() == 0: item = self.item(item.row(), 1) if isinstance(item, ByteWidget): self.app.get_hooks_panel().hook_native(hex(item.get_ptr())) def trigger_jump_to(self): ptr = InputDialog.input_pointer(self.app) if ptr > 0: self.read_memory(ptr) def trigger_write_bytes(self): item = self.selectedItems()[0] if item.column() == 0: item = self.item(item.row(), 1) if isinstance(item, ByteWidget): ptr = item.get_ptr() if ptr + 16 > self.data['end']: if self.read_memory(ptr) > 0: return mem = self.app.dwarf_api('readBytes', ptr, 16) mem = binascii.hexlify(mem).decode('utf8') mem = ' '.join(re.findall('.{1,2}', mem)) content = InputDialog.input( self.app, hint='write bytes @%s' % hex(ptr), input_content=mem) if content[0]: if self.app.dwarf_api('writeBytes', [ptr, content[1].replace(' ', '')]): self.range.invalidate() self.read_memory(ptr) def trigger_write_string(self): item = self.selectedItems()[0] if item.column() == 0: item = self.item(item.row(), 1) if isinstance(item, ByteWidget): ptr = item.get_ptr() accept, content = InputDialog.input( self.app, hint='write utf8 string @%s' % hex(ptr)) if accept: if self.app.dwarf_api('writeUtf8', [ptr, content]): self.range.invalidate() self.read_memory(ptr) def on_script_destroyed(self): self.range = None self.controller.work = False self.setRowCount(0) self.setColumnCount(0) self.resizeRowsToContents() self.horizontalHeader().setStretchLastSection(True)
def on_emulator_hook(self, instruction): # @PinkiePonkie why setting context here (which is triggered each instruction) and later, set it again # in emulator_stop? step = double hit in set_context, running emulation on more than 1 instruction # doesn't need spam of set_context # self.app.context_panel.set_context(0, 2, self.emulator.current_context) # check if the previous hook is waiting for a register result if self._require_register_result is not None: row = 1 if len(self._require_register_result) == 1: res = 'jump = %s' % (hex(self._require_register_result[0])) else: res = '%s = %s' % (self._require_register_result[1], hex( self.emulator.uc.reg_read( self._require_register_result[0]))) if len(self.assembly._lines) > 1: if self.assembly._lines[len(self.assembly._lines) - row] is None: row = 2 telescope = self.get_telescope( self.emulator.uc.reg_read( self._require_register_result[0])) if telescope is not None and telescope != 'None': res += ' (' + telescope + ')' self.assembly._lines[len(self.assembly._lines) - row].string = res # invalidate self._require_register_result = None # check if the code jumped self._last_instruction_address = instruction.address self.assembly.add_instruction(instruction) # add empty line if jump if instruction.is_jump or instruction.is_call: self.assembly.add_instruction(None) self._require_register_result = [instruction.jump_address] else: # implicit regs read are notified later through mem access if len(instruction.regs_read) == 0: if len(instruction.operands) > 0: for i in instruction.operands: if i.type == CS_OP_REG: self._require_register_result = [ i.value.reg, instruction.reg_name(i.value.reg) ] break self.assembly.verticalScrollBar().setValue(len(self.assembly._lines)) self.assembly.viewport().update() if instruction.is_call: range_ = Range(Range.SOURCE_TARGET, self.app.dwarf) if range_.init_with_address(instruction.address, require_data=False) > 0: if range_.base > instruction.call_address > range_.tail: if self.emulator.step_mode == STEP_MODE_NONE: self.emulator.stop() action = JumpOutsideTheBoxDialog.show_dialog( self.app.dwarf) if action == 0: # step to jump if self.emulator.step_mode != STEP_MODE_NONE: self.handle_step() else: self.emulator.emulate(self.until_address, user_arch=self._uc_user_arch, user_mode=self._uc_user_mode, cs_arch=self._cs_user_arch, cs_mode=self._cs_user_mode) if action == 1: # step to next jump if self.emulator.step_mode != STEP_MODE_NONE: self.handle_step_next_jump() else: self.emulator.emulate(self.until_address, user_arch=self._uc_user_arch, user_mode=self._uc_user_mode, cs_arch=self._cs_user_arch, cs_mode=self._cs_user_mode) elif action == 2: # hook lr hook_addr = instruction.address + instruction.size if instruction.thumb: hook_addr += 1 self.app.dwarf.hook_native(input_=hex(hook_addr))
class DisassemblyView(QAbstractScrollArea): onShowMemoryRequest = pyqtSignal(str, int, name='onShowMemoryRequest') def __init__(self, parent=None): super(DisassemblyView, self).__init__(parent=parent) _prefs = Prefs() self._uppercase_hex = (_prefs.get('dwarf_ui_hexstyle', 'upper').lower() == 'upper') self._app_window = parent self.setAutoFillBackground(True) # setting font self.font = utils.get_os_monospace_font() self.font.setFixedPitch(True) self.setFont(self.font) self._char_width = self.fontMetrics().width("2") self._char_height = self.fontMetrics().height() self._base_line = self.fontMetrics().ascent() self._history = [] self._lines = [] self._range = None self._max_instructions = 128 self._longest_bytes = 0 self._longest_mnemonic = 0 self.capstone_arch = 0 self.capstone_mode = 0 self.keystone_arch = 0 self.keystone_mode = 0 self.on_arch_changed() self._ctrl_colors = { 'background': QColor('#181818'), 'foreground': QColor('#666'), 'jump_arrows': QColor('#444'), 'jump_arrows_hover': QColor('#ef5350'), 'divider': QColor('#666'), 'line': QColor('#111'), 'selection_fg': QColor(Qt.white), 'selection_bg': QColor('#ef5350') } self._jump_color = QColor('#39a') self._header_height = 0 self._ver_spacing = 2 self._dash_pen = QPen(self._ctrl_colors['jump_arrows'], 2.0, Qt.DashLine) self._solid_pen = QPen(self._ctrl_colors['jump_arrows'], 2.0, Qt.SolidLine) self._line_pen = QPen(self._ctrl_colors['divider'], 0, Qt.SolidLine) self._breakpoint_linewidth = 5 self._jumps_width = 100 self.setMouseTracking(True) self.current_jump = -1 self._current_line = -1 self._display_jumps = True self._follow_jumps = True self.pos = 0 # ************************************************************************ # **************************** Properties ******************************** # ************************************************************************ @property def display_jumps(self): return self._display_jumps @display_jumps.setter def display_jumps(self, value): if isinstance(value, bool): self._display_jumps = value if self._display_jumps: self._jumps_width = 100 else: self._jumps_width = 0 @property def follow_jumps(self): return self._follow_jumps @follow_jumps.setter def follow_jumps(self, value): if isinstance(value, bool): self._follow_jumps = value @pyqtProperty('QColor', designable=True) def background(self): return self._ctrl_colors['background'] @background.setter def background(self, value): self._ctrl_colors['background'] = value @pyqtProperty('QColor', designable=True) def foreground(self): return self._ctrl_colors['foreground'] @foreground.setter def foreground(self, value): self._ctrl_colors['foreground'] = QColor(value) @pyqtProperty('QColor', designable=True) def divider(self): return self._ctrl_colors['divider'] @divider.setter def divider(self, value): self._ctrl_colors['divider'] = QColor(value) @pyqtProperty('QColor', designable=True) def jump_arrows(self): return self._ctrl_colors['jump_arrows'] @jump_arrows.setter def jump_arrows(self, value): self._ctrl_colors['jump_arrows'] = QColor(value) @pyqtProperty('QColor', designable=True) def jump_arrows_hover(self): return self._ctrl_colors['jump_arrows_hover'] @jump_arrows_hover.setter def jump_arrows_hover(self, value): self._ctrl_colors['jump_arrows_hover'] = QColor(value) @pyqtProperty('QColor', designable=True) def line(self): return self._ctrl_colors['line'] @line.setter def line(self, value): self._ctrl_colors['line'] = QColor(value) # ************************************************************************ # **************************** Functions ********************************* # ************************************************************************ def add_instruction(self, instruction): self._lines.append(instruction) self.adjust() def disassemble(self, dwarf_range, num_instructions=256, stop_on_ret=True): progress = QProgressDialog() progress.setFixedSize(300, 50) progress.setAutoFillBackground(True) progress.setWindowModality(Qt.WindowModal) progress.setWindowTitle('Please wait') progress.setLabelText('Disassembling...') progress.setSizeGripEnabled(False) progress.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) progress.setWindowFlag(Qt.WindowContextHelpButtonHint, False) progress.setWindowFlag(Qt.WindowCloseButtonHint, False) # progress.setModal(True) progress.setCancelButton(None) progress.setRange(0, 0) progress.setMinimumDuration(0) progress.show() QApplication.processEvents() self._lines.clear() if len(self._history) == 0 or self._history[len(self._history) - 1] != dwarf_range.start_address: self._history.append(dwarf_range.start_address) if len(self._history) > 25: self._history.pop(0) self._longest_bytes = 0 try: capstone = Cs(self.capstone_arch, self.capstone_mode) capstone.detail = True except CsError: print('[DisasmView] failed to initialize capstone with %d, %d' % (self.capstone_arch, self.capstone_mode)) return self._range = dwarf_range self._max_instructions = num_instructions _counter = 0 for cap_inst in capstone.disasm(dwarf_range.data[dwarf_range.start_offset:], dwarf_range.start_address): QApplication.processEvents() if _counter > self._max_instructions: break dwarf_instruction = Instruction(self._app_window.dwarf, cap_inst) self.add_instruction(dwarf_instruction) _counter += 1 if stop_on_ret and cap_inst.group(CS_GRP_RET): break self.adjust() progress.cancel() def resizeEvent(self, event): super().resizeEvent(event) self.adjust() def adjust(self): for line in self._lines: if line: if len(line.bytes) > self._longest_bytes: self._longest_bytes = len(line.bytes) if len(line.mnemonic) > self._longest_mnemonic: self._longest_mnemonic = len(line.mnemonic) self.verticalScrollBar().setRange(0, len(self._lines) - self.visible_lines() + 1) self.verticalScrollBar().setPageStep(self.visible_lines()) self.viewport().update() def visible_lines(self): """ returns number of lines that fits viewport """ height = self.viewport().height() height -= self._header_height + self._char_height + self._ver_spacing num_lines = int(ceil(height / (self._char_height + self._ver_spacing))) return num_lines + 1 def pixel_to_line(self, screen_x, screen_y): """ helper """ coord_x, coord_y = self.pixel_to_data(screen_x, screen_y) # pylint: disable=unused-variable return coord_y def pixel_to_data(self, screen_x, screen_y): """ pixel to data coords """ if screen_x < 0: screen_x = 0 top_gap = self._header_height + self._char_height + self._ver_spacing data_x = int(ceil(screen_x / self._char_width)) data_y = int( ceil((screen_y - top_gap) / (self._ver_spacing + self._char_height))) return (data_x, data_y) def _on_context_menu(self, event): """ build and show contextmenu """ context_menu = QMenu() menu_actions = {} copy_addr = None if self.capstone_arch == CS_ARCH_ARM: switch_mode = context_menu.addAction("&Switch to %s" % ( 'THUMB' if self.capstone_mode == CS_MODE_ARM else 'ARM')) menu_actions[switch_mode] = self._on_switch_mode if len(menu_actions) > 0: action = context_menu.exec_(QCursor.pos()) if action in menu_actions: if action != copy_addr: menu_actions[action]() def read_memory(self, ptr, length=0): self._lines.clear() if self._range is None: self._range = Range(Range.SOURCE_TARGET, self.dwarf) init = self._range.init_with_address(ptr, length) if init > 0: return 1 self.disassemble(self._range) return 0 # ************************************************************************ # **************************** Drawing *********************************** # ************************************************************************ def paint_jumps(self, painter): painter.setRenderHint(QPainter.HighQualityAntialiasing) jump_list = [x.address for x in self._lines[self.pos:self.pos + self.visible_lines()] if x.is_jump] jump_targets = [x.jump_address for x in self._lines[self.pos:self.pos + self.visible_lines()] if x.address in jump_list] drawing_pos_x = self._jumps_width - 10 for index, line in enumerate(self._lines[self.pos:self.pos + self.visible_lines()]): if line.address in jump_list: # or line.address in jump_targets: if line.address == self.current_jump: self._solid_pen.setColor(self._ctrl_colors['jump_arrows_hover']) self._dash_pen.setColor(self._ctrl_colors['jump_arrows_hover']) else: self._solid_pen.setColor(self._ctrl_colors['jump_arrows']) self._dash_pen.setColor(self._ctrl_colors['jump_arrows']) if line.id == X86_INS_JMP or line.id == X86_INS_CALL: painter.setPen(self._solid_pen) else: painter.setPen(self._dash_pen) drawing_pos_y = (index + 1) * (self._char_height + self._ver_spacing) drawing_pos_y -= self._base_line - (self._char_height * 0.5) skip = False if line.address not in jump_targets: painter.drawLine(drawing_pos_x + 2, drawing_pos_y, self._jumps_width, drawing_pos_y) if line.jump_address in jump_targets: entry1 = [x for x in self._lines if x.address == line.address] entry2 = [x for x in self._lines if x.address == line.jump_address] if entry1 and entry2: skip = True pos2 = (self._lines.index(entry1[0]) - self._lines.index(entry2[0])) * (self._char_height + self._ver_spacing) painter.drawLine(drawing_pos_x, drawing_pos_y - pos2, drawing_pos_x, drawing_pos_y) painter.drawLine(drawing_pos_x, drawing_pos_y - pos2, 100, drawing_pos_y - pos2) arrow = QPolygon() arrow.append(QPoint(100, drawing_pos_y - pos2)) arrow.append(QPoint(100 - 8, drawing_pos_y - pos2 - 4)) arrow.append(QPoint(100 - 8, drawing_pos_y - pos2 + 4)) if line.address == self.current_jump: painter.setBrush(self._ctrl_colors['jump_arrows_hover']) painter.setPen(self._ctrl_colors['jump_arrows_hover']) else: painter.setBrush(self._ctrl_colors['jump_arrows']) painter.setPen(self._ctrl_colors['jump_arrows']) painter.drawPolygon(arrow) else: skip = True if not skip: if line.address > line.jump_address: if line.id == X86_INS_JMP or line.id == X86_INS_CALL: painter.setPen(self._solid_pen) else: painter.setPen(self._dash_pen) painter.drawLine(drawing_pos_x, 5, drawing_pos_x, drawing_pos_y) arrow = QPolygon() arrow.append(QPoint(drawing_pos_x, 0)) arrow.append(QPoint(drawing_pos_x + 4, 8)) arrow.append(QPoint(drawing_pos_x - 4, 8)) if line.address == self.current_jump: painter.setBrush(self._ctrl_colors['jump_arrows_hover']) painter.setPen(Qt.NoPen) else: painter.setBrush(self._ctrl_colors['jump_arrows']) painter.setPen(Qt.NoPen) painter.drawPolygon(arrow) elif line.address < line.jump_address: if line.id == X86_INS_JMP or line.id == X86_INS_CALL: painter.setPen(self._solid_pen) else: painter.setPen(self._dash_pen) painter.drawLine(drawing_pos_x, drawing_pos_y, drawing_pos_x, self.viewport().height() - 5) arrow = QPolygon() arrow.append(QPoint(drawing_pos_x, self.viewport().height())) arrow.append(QPoint(drawing_pos_x + 4, self.viewport().height() - 8)) arrow.append(QPoint(drawing_pos_x - 4, self.viewport().height() - 8)) if line.address == self.current_jump: painter.setBrush(self._ctrl_colors['jump_arrows_hover']) painter.setPen(Qt.NoPen) else: painter.setBrush(self._ctrl_colors['jump_arrows']) painter.setPen(Qt.NoPen) painter.drawPolygon(arrow) drawing_pos_x -= 10 if drawing_pos_x < 0: break def paint_line(self, painter, num_line, line): painter.setPen(self._ctrl_colors['foreground']) drawing_pos_x = self._jumps_width + self._breakpoint_linewidth + self._char_width drawing_pos_y = num_line * (self._char_height + self._ver_spacing) drawing_pos_y += self._header_height if not line: # empty line from emu return num = self._app_window.dwarf.pointer_size * 2 str_fmt = '{0:08x}' if num > 8: str_fmt = '{0:016x}' if self._uppercase_hex: str_fmt = str_fmt.replace('x', 'X') painter.drawText(drawing_pos_x, drawing_pos_y, str_fmt.format(line.address)) is_watched = False is_hooked = False if self._app_window.dwarf.is_address_watched(line.address): is_watched = True if line.address in self._app_window.dwarf.hooks: is_hooked = True if is_watched or is_hooked: if is_watched: height = self._char_height y_pos = drawing_pos_y y_pos -= self._base_line - (self._char_height * 0.5) y_pos += (self._char_height * 0.5) if is_hooked: y_pos -= (self._char_height * 0.5) height *= 0.5 painter.fillRect(self._jumps_width, y_pos - height, self._breakpoint_linewidth, height, QColor('#4fc3f7')) if is_hooked: height = self._char_height y_pos = drawing_pos_y y_pos -= self._base_line - (self._char_height * 0.5) y_pos += (self._char_height * 0.5) if is_watched: height *= 0.5 painter.fillRect(self._jumps_width, y_pos - height, self._breakpoint_linewidth, height, QColor('#009688')) drawing_pos_x = self._jumps_width + self._breakpoint_linewidth + self._char_width + 1 + self._char_width drawing_pos_x += ((self._app_window.dwarf.pointer_size * 2) * self._char_width) painter.setPen(QColor('#444')) drawing_pos_x += self._char_width for byte in line.bytes: painter.drawText(drawing_pos_x, drawing_pos_y, '{0:02x}'.format(byte)) drawing_pos_x += self._char_width * 3 drawing_pos_x = self._jumps_width + self._breakpoint_linewidth + ((self._app_window.dwarf.pointer_size * 2) * self._char_width) + (self._longest_bytes + 2) * (self._char_width * 3) painter.setPen(QColor('#39c')) painter.drawText(drawing_pos_x, drawing_pos_y, line.mnemonic) if line.is_jump: painter.setPen(self._jump_color) else: painter.setPen(self._ctrl_colors['foreground']) drawing_pos_x += (self._longest_mnemonic + 1) * self._char_width if line.operands and not line.is_jump: ops_str = line.op_str.split(', ', len(line.operands) - 1) a = 0 for op in line.operands: if op.type == CS_OP_IMM: painter.setPen(QColor('#ff5500')) elif op.type == 1: painter.setPen(QColor('#82c300')) else: painter.setPen(self._ctrl_colors['foreground']) painter.drawText(drawing_pos_x, drawing_pos_y, ops_str[a]) drawing_pos_x += len(ops_str[a] * self._char_width) if len(line.operands) > 1 and a < len(line.operands) - 1: painter.setPen(self._ctrl_colors['foreground']) painter.drawText(drawing_pos_x, drawing_pos_y, ', ') drawing_pos_x += 2 * self._char_width # if ops_str[a].startswith('0x') and not line.string: # line.string = '{0:d}'.format(int(ops_str[a], 16)) #drawing_pos_x += (len(ops_str[a]) + 1) * self._char_width a += 1 else: if self._follow_jumps and line.is_jump: if line.jump_address < line.address: painter.drawText(drawing_pos_x, drawing_pos_y, line.op_str + ' ▲') elif line.jump_address > line.address: painter.drawText(drawing_pos_x, drawing_pos_y, line.op_str + ' ▼') drawing_pos_x += (len(line.op_str) + 3) * self._char_width else: painter.drawText(drawing_pos_x, drawing_pos_y, line.op_str) drawing_pos_x += (len(line.op_str) + 1) * self._char_width if line.symbol_name: painter.drawText(drawing_pos_x, drawing_pos_y, '(' + line.symbol_name + ')') drawing_pos_x += (len(line.symbol_name) + 1) * self._char_width if line.string and not line.is_jump: painter.setPen(QColor('#aaa')) painter.drawText(drawing_pos_x, drawing_pos_y, ' ; "' + line.string + '"') def paintEvent(self, event): if not self.isVisible(): return self.pos = self.verticalScrollBar().value() painter = QPainter(self.viewport()) # fill background painter.fillRect(0, 0, self.viewport().width(), self.viewport().height(), self._ctrl_colors['background']) if self._display_jumps: painter.setPen(self._ctrl_colors['foreground']) drawing_pos_x = self._jumps_width self.paint_jumps(painter) painter.fillRect(drawing_pos_x, 0, self._breakpoint_linewidth, self.viewport().height(), self._ctrl_colors['jump_arrows']) for i, line in enumerate(self._lines[self.pos:self.pos + self.visible_lines()]): if i > self.visible_lines(): break if i == self._current_line: y_pos = self._header_height + (i * (self._char_height + self._ver_spacing)) y_pos += (self._char_height * 0.5) y_pos -= self._ver_spacing painter.fillRect(self._jumps_width + self._breakpoint_linewidth, y_pos - 1, self.viewport().width(), self._char_height + 2, self._ctrl_colors['line']) self.paint_line(painter, i + 1, line) painter.setPen(self._line_pen) painter.setBrush(Qt.NoBrush) drawing_pos_x = self._jumps_width + self._breakpoint_linewidth + self._char_width + self._char_width drawing_pos_x += ((self._app_window.dwarf.pointer_size * 2) * self._char_width) painter.fillRect(drawing_pos_x, 0, 1, self.viewport().height(), self._ctrl_colors['divider']) # ************************************************************************ # **************************** Handlers ********************************** # ************************************************************************ def keyPressEvent(self, event): if event.key() == Qt.Key_Backspace: if len(self._history) > 1: self._history.pop(len(self._history) - 1) self.read_memory(self._history[len(self._history) - 1]) elif event.key() == Qt.Key_G and event.modifiers() & Qt.ControlModifier: # ctrl+g self._on_jump_to() elif event.key() == Qt.Key_M and event.modifiers() & Qt.ControlModifier: # ctrl+m self._on_switch_mode() elif event.key() == Qt.Key_P and event.modifiers() & Qt.ControlModifier: # ctrl+p pass # patch instruction elif event.key() == Qt.Key_B and event.modifiers() & Qt.ControlModifier: # ctrl+b pass # patch bytes else: # dispatch those to super super().keyPressEvent(event) if self.capstone_mode == CS_MODE_ARM: self.capstone_mode = CS_MODE_THUMB else: self.capstone_mode = CS_MODE_ARM self.disassemble(self._range) def on_arch_changed(self): if self._app_window.dwarf.arch == 'arm64': self.capstone_arch = CS_ARCH_ARM64 self.capstone_mode = CS_MODE_LITTLE_ENDIAN elif self._app_window.dwarf.arch == 'arm': self.capstone_arch = CS_ARCH_ARM self.capstone_mode = CS_MODE_ARM elif self._app_window.dwarf.arch == 'ia32': self.capstone_arch = CS_ARCH_X86 self.capstone_mode = CS_MODE_32 elif self._app_window.dwarf.arch == 'x64': self.capstone_arch = CS_ARCH_X86 self.capstone_mode = CS_MODE_64 if self._app_window.dwarf.keystone_installed: import keystone.keystone_const as ks if self._app_window.dwarf.arch == 'arm64': self.keystone_arch = ks.KS_ARCH_ARM64 self.keystone_mode = ks.KS_MODE_LITTLE_ENDIAN elif self._app_window.dwarf.arch == 'arm': self.keystone_arch = ks.KS_ARCH_ARM self.keystone_mode = ks.KS_MODE_ARM elif self._app_window.dwarf.arch == 'ia32': self.keystone_arch = ks.KS_ARCH_X86 self.keystone_mode = ks.KS_MODE_32 elif self._app_window.dwarf.arch == 'x64': self.keystone_arch = ks.KS_ARCH_X86 self.keystone_mode = ks.KS_MODE_64 def mouseDoubleClickEvent(self, event): loc_x = event.pos().x() loc_y = event.pos().y() index = self.pixel_to_line(loc_x, loc_y) left_side = self._breakpoint_linewidth + self._jumps_width addr_width = ((self._app_window.dwarf.pointer_size * 2) * self._char_width) if loc_x > left_side: if loc_x < left_side + addr_width: if isinstance(self._lines[index + self.pos], Instruction): self.onShowMemoryRequest.emit(hex(self._lines[index + self.pos].address), len(self._lines[index + self.pos].bytes)) if loc_x > left_side + addr_width: if isinstance(self._lines[index + self.pos], Instruction): if self._follow_jumps and self._lines[index + self.pos].is_jump: new_pos = self._lines[index + self.pos].jump_address self.read_memory(new_pos) # pylint: disable=C0103 def mouseMoveEvent(self, event): """ onmousemove """ loc_x = event.pos().x() loc_y = event.pos().y() if loc_x > self._breakpoint_linewidth + self._jumps_width: self.current_jump = -1 index = self.pixel_to_line(loc_x, loc_y) if 0 <= index < self.visible_lines(): self._current_line = index if index + self.pos < len(self._lines): if isinstance(self._lines[index + self.pos], Instruction): if self._lines[index + self.pos].is_jump: self.current_jump = self._lines[index + self.pos].address #self.viewport().update(0, 0, self._breakpoint_linewidth + self._jumps_width, self.viewport().height()) y_pos = self._header_height + (index * (self._char_height + self._ver_spacing)) y_pos += (self._char_height * 0.5) y_pos -= self._ver_spacing self.viewport().update(0, 0, self.viewport().width(), self.viewport().height()) def mousePressEvent(self, event): # context menu if event.button() == Qt.RightButton: if self._range is None: return self._on_context_menu(event) return def _on_context_menu(self, event): """ build and show contextmenu """ loc_x = event.pos().x() loc_y = event.pos().y() context_menu = QMenu() # allow modeswitch arm/thumb if self.capstone_arch == CS_ARCH_ARM: mode_str = 'ARM' if self.capstone_mode == CS_MODE_ARM: mode_str = 'THUMB' entry_str = '&Switch to {0} Mode'.format(mode_str) context_menu.addAction(entry_str, self._on_switch_mode) context_menu.addSeparator() index = self.pixel_to_line(loc_x, loc_y) if 0 <= index < self.visible_lines(): if index + self.pos < len(self._lines): if isinstance(self._lines[index + self.pos], Instruction): context_menu.addAction('Copy Address', lambda: utils.copy_hex_to_clipboard(self._lines[index + self.pos].address)) context_menu.addSeparator() if self._uppercase_hex: str_fmt = '0x{0:X}' else: str_fmt = '0x{0:x}' addr_str = str_fmt.format(self._lines[index + self.pos].address) if self._app_window.watchers_panel: if self._app_window.dwarf.is_address_watched(self._lines[index + self.pos].address): context_menu.addAction('Remove Watcher', lambda: self._app_window.watchers_panel.remove_address(addr_str)) else: context_menu.addAction('Watch Address', lambda: self._app_window.watchers_panel.do_addwatcher_dlg(addr_str)) if self._app_window.hooks_panel: if self._lines[index + self.pos].address in self._app_window.dwarf.hooks: context_menu.addAction('Remove Hook', lambda: self._app_window.dwarf.dwarf_api('deleteHook', addr_str)) else: context_menu.addAction('Hook Address', lambda: self._app_window.dwarf.hook_native(addr_str)) glbl_pt = self.mapToGlobal(event.pos()) context_menu.exec_(glbl_pt) def _on_switch_mode(self): if self._range is None: return if self._app_window.dwarf.arch == 'arm': self._lines.clear() if self.capstone_mode == CS_MODE_ARM: self.capstone_mode = CS_MODE_THUMB else: self.capstone_mode = CS_MODE_ARM self.disassemble(self._range)
class AsmPanel(QTableWidget): def __init__(self, app): super(AsmPanel, self).__init__(app) self.app = app self.dwarf = app.get_dwarf() self.range = None self.cs_arch = 0 self.cs_mode = 0 self.ks_arch = 0 self.ks_mode = 0 self.on_arch_changed() self.horizontalHeader().hide() self.verticalHeader().hide() self.setColumnCount(5) self.horizontalHeader().setSectionResizeMode( QHeaderView.ResizeToContents) self.setShowGrid(False) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.show_menu) self.itemDoubleClicked.connect(self.item_double_clicked) self.history = [] def show_menu(self, pos): menu = QMenu() cs_config = menu.addAction("Capstone") if self.cs_arch == CS_ARCH_ARM: if self.cs_mode == CS_MODE_ARM: mode = QAction("THUMB mode\t(O)") else: mode = QAction("ARM mode\t(O)") mode.triggered.connect(self.swap_arm_mode) menu.addAction(mode) menu.addSeparator() write_instr = menu.addAction("Patch instruction") menu.addSeparator() jump_to = menu.addAction("Jump to\t(G)") jump_to.triggered.connect(self.trigger_jump_to) action = menu.exec_(self.mapToGlobal(pos)) if action == cs_config: self.trigger_cs_configs() elif action == write_instr: self.trigger_write_instruction(self.itemAt(pos)) def keyPressEvent(self, event): if event.key() == Qt.Key_G: self.trigger_jump_to() elif event.key() == Qt.Key_O: self.swap_arm_mode() elif event.key() == Qt.Key_Escape: if len(self.history) > 1: self.history.pop(len(self.history) - 1) self.read_memory(self.history[len(self.history) - 1]) else: # dispatch those to super super(AsmPanel, self).keyPressEvent(event) def trigger_jump_to(self): ptr, input = InputDialog.input_pointer(self.app) if ptr > 0: self.read_memory(ptr) def item_double_clicked(self, item): if isinstance(item, MemoryAddressWidget): self.read_memory(item.get_address()) def read_memory(self, ptr, length=0): if self.range is None: self.range = Range(Range.SOURCE_TARGET, self.dwarf) init = self.range.init_with_address(ptr, length) if init > 0: return 1 self.disasm() return 0 def disasm(self, _range=None): self.setRowCount(0) if _range: self.range = _range if self.range is None: return 1 if len(self.history) == 0 or self.history[ len(self.history) - 1] != self.range.start_address: self.history.append(self.range.start_address) if len(self.history) > 25: self.history.pop(0) md = Cs(self.cs_arch, self.cs_mode) md.detail = True insts = 0 for i in md.disasm(self.range.data[self.range.start_offset:], self.range.start_address): if insts > 128: break instruction = Instruction(self.dwarf, i) row = self.rowCount() self.insertRow(row) w = MemoryAddressWidget('0x%x' % i.address) w.setFlags(Qt.NoItemFlags) w.setForeground(Qt.red) w.set_offset(self.range.base - i.address) self.setItem(row, 0, w) w = NotEditableTableWidgetItem( binascii.hexlify(instruction.bytes).decode('utf8')) w.setFlags(Qt.NoItemFlags) w.setForeground(Qt.darkYellow) self.setItem(row, 1, w) if instruction.is_jump and instruction.jump_address != 0: w = MemoryAddressWidget(instruction.op_str) w.set_address(instruction.jump_address) else: w = NotEditableTableWidgetItem(instruction.op_str) w.setFlags(Qt.NoItemFlags) w.setForeground(Qt.lightGray) self.setItem(row, 3, w) w = NotEditableTableWidgetItem(instruction.mnemonic.upper()) w.setFlags(Qt.NoItemFlags) w.setForeground(Qt.white) w.setTextAlignment(Qt.AlignCenter) w.setFont(QFont(None, 11, QFont.Bold)) self.setItem(row, 2, w) if instruction.symbol_name is not None: w = NotEditableTableWidgetItem( '%s (%s)' % (instruction.symbol_name, instruction.symbol_module)) w.setFlags(Qt.NoItemFlags) w.setForeground(Qt.lightGray) self.setItem(row, 4, w) insts += 1 self.scrollToTop() return 0 def clear(self): self.range = None def swap_arm_mode(self): if self.dwarf.arch == 'arm': if self.cs_mode == CS_MODE_ARM: self.cs_mode = CS_MODE_THUMB elif self.cs_mode == CS_MODE_THUMB: self.cs_mode = CS_ARCH_ARM self.disasm() def trigger_cs_configs(self): accept, arch, mode = CsConfigsDialog.show_dialog( self.cs_arch, self.cs_mode) if accept: self.cs_arch = arch self.cs_mode = mode self.disasm() def trigger_write_instruction(self, item): if not self.dwarf.keystone_installed: details = '' try: import keystone.keystone_const except Exception as e: details = str(e) utils.show_message_box( 'keystone-engine not found. Install it to enable instructions patching', details=details) return accept, inst, arch, mode = WriteInstructionDialog().show_dialog( input_content='%s %s' % (self.item(item.row(), 1).text(), self.item(item.row(), 2).text()), arch=self.ks_arch, mode=self.ks_mode) self.ks_arch = 'KS_ARCH_' + arch.upper() self.ks_mode = 'KS_MODE_' + mode.upper() if accept and len(inst) > 0: import keystone try: ks = keystone.Ks( getattr(keystone.keystone_const, self.ks_arch), getattr(keystone.keystone_const, self.ks_mode)) encoding, count = ks.asm(inst) asm_widget = self.item(item.row(), 0) offset = asm_widget.get_offset() if self.dwarf.dwarf_api('writeBytes', [asm_widget.get_address(), encoding]): new_data = bytearray(self.range.data) for i in range(0, len(encoding)): try: new_data[self.asm_data_start + offset + i] = encoding[i] except Exception as e: if isinstance(e, IndexError): break self.range.data = bytes(new_data) self.disa() except Exception as e: self.dwarf.log(e) def on_arch_changed(self): if self.dwarf.arch == 'arm64': self.cs_arch = CS_ARCH_ARM64 self.cs_mode = CS_MODE_LITTLE_ENDIAN elif self.dwarf.arch == 'arm': self.cs_arch = CS_ARCH_ARM self.cs_mode = CS_MODE_ARM elif self.dwarf.arch == 'ia32': self.cs_arch = CS_ARCH_X86 self.cs_mode = CS_MODE_32 elif self.cs_arch == 'x64': self.cs_arch = CS_ARCH_X86 self.cs_mode = CS_MODE_64 if self.dwarf.keystone_installed: import keystone.keystone_const as ks if self.dwarf.arch == 'arm64': self.ks_arch = ks.KS_ARCH_ARM64 self.ks_mode = ks.KS_MODE_LITTLE_ENDIAN elif self.dwarf.arch == 'arm': self.ks_arch = ks.KS_ARCH_ARM self.ks_mode = ks.KS_MODE_ARM elif self.dwarf.arch == 'ia32': self.ks_arch = ks.KS_ARCH_X86 self.ks_mode = ks.KS_MODE_32 elif self.cs_arch == 'x64': self.ks_arch = ks.KS_ARCH_X86 self.ks_mode = ks.KS_MODE_64
class AsmPanel(QTableWidget): def __init__(self, app): super(AsmPanel, self).__init__(app) self.app = app self.range = None self.cs_arch = 0 self.cs_mode = 0 self.ks_arch = 0 self.ks_mode = 0 self.on_arch_changed() self.horizontalHeader().hide() self.verticalHeader().hide() self.setColumnCount(5) self.setShowGrid(False) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.show_menu) self.itemDoubleClicked.connect(self.item_double_clicked) def show_menu(self, pos): menu = QMenu() if self.cs_arch == CS_ARCH_ARM: if self.cs_mode == CS_MODE_ARM: mode = QAction("THUMB mode\t(O)") else: mode = QAction("ARM mode\t(O)") mode.triggered.connect(self.swap_arm_mode) menu.addAction(mode) menu.addSeparator() write_instr = menu.addAction("Patch instruction") write_instr.triggered.connect(self.trigger_write_instruction) menu.addSeparator() jump_to = menu.addAction("Jump to\t(G)") jump_to.triggered.connect(self.trigger_jump_to) menu.exec_(self.mapToGlobal(pos)) def keyPressEvent(self, event): if event.key() == Qt.Key_G: self.trigger_jump_to() else: # dispatch those to super super(AsmPanel, self).keyPressEvent(event) def trigger_jump_to(self): accept, ptr = InputDialog.input(hint='insert pointer') if accept: ptr = int(self.app.dwarf_api('evaluatePtr', ptr), 16) self.read_memory(ptr) def item_double_clicked(self, item): if isinstance(item, MemoryAddressWidget): self.read_memory(item.get_address()) def read_memory(self, ptr): if self.range is None: self.range = Range(self.app) init = self.range.init_with_address(ptr) if init > 0: return 1 self.disasm() return 0 def disasm(self, _range=None): self.setRowCount(0) if _range: self.range = _range md = Cs(self.cs_arch, self.cs_mode) md.detail = True insts = 0 for i in md.disasm(self.range.data[self.range.start_offset:], self.range.start_address): if insts > 128: break row = self.rowCount() self.insertRow(row) if insts == 0: sym = self.app.dwarf_api('getSymbolByAddress', i.address) if sym: module = '' if 'moduleName' in sym: module = '- %s' % sym['moduleName'] w = NotEditableTableWidgetItem('%s %s' % (sym['name'], module)) w.setFlags(Qt.NoItemFlags) w.setForeground(Qt.lightGray) self.setItem(row, 4, w) w = MemoryAddressWidget('0x%x' % i.address) w.setFlags(Qt.NoItemFlags) w.setForeground(Qt.red) w.set_offset(self.range.base - i.address) self.setItem(row, 0, w) w = NotEditableTableWidgetItem( binascii.hexlify(i.bytes).decode('utf8')) w.setFlags(Qt.NoItemFlags) w.setForeground(Qt.darkYellow) self.setItem(row, 1, w) is_jmp = False op_imm_value = 0 if CS_GRP_JUMP in i.groups or CS_GRP_CALL in i.groups: is_jmp = False if len(i.operands) > 0: for op in i.operands: if op.type == CS_OP_IMM: if len(i.operands) == 1: is_jmp = True if is_jmp: op_imm_value = op.value.imm sym = self.app.dwarf_api('getSymbolByAddress', op_imm_value) module = '' if 'moduleName' in sym: module = '- %s' % sym['moduleName'] w = NotEditableTableWidgetItem( '%s %s' % (sym['name'], module)) w.setFlags(Qt.NoItemFlags) w.setForeground(Qt.lightGray) self.setItem(row, 4, w) if is_jmp: w = MemoryAddressWidget(i.op_str) else: w = NotEditableTableWidgetItem(i.op_str) w.setFlags(Qt.NoItemFlags) w.setForeground(Qt.lightGray) self.setItem(row, 3, w) w = NotEditableTableWidgetItem(i.mnemonic.upper()) w.setFlags(Qt.NoItemFlags) w.setForeground(Qt.white) w.setTextAlignment(Qt.AlignCenter) w.setFont(QFont(None, 11, QFont.Bold)) self.setItem(row, 2, w) insts += 1 self.resizeColumnsToContents() self.scrollToTop() def swap_arm_mode(self): if self.app.get_arch() == 'arm': if self.cs_mode == CS_MODE_ARM: self.cs_mode = CS_MODE_THUMB elif self.cs_mode == CS_MODE_THUMB: self.cs_mode = CS_ARCH_ARM self.disasm() def trigger_write_instruction(self): if len(self.selectedItems()) == 0: return item = self.selectedItems()[0] accept, inst, arch, mode = WriteInstructionDialog().show_dialog( input_content='%s %s' % (self.item(item.row(), 1).text(), self.item(item.row(), 2).text()), arch=self.ks_arch, mode=self.ks_mode) self.ks_arch = 'KS_ARCH_' + arch.upper() self.ks_mode = 'KS_MODE_' + mode.upper() if accept and len(inst) > 0: import keystone try: ks = keystone.Ks( getattr(keystone.keystone_const, self.ks_arch), getattr(keystone.keystone_const, self.ks_mode)) encoding, count = ks.asm(inst) asm_widget = self.item(item.row(), 0) offset = asm_widget.get_offset() if self.app.dwarf_api('writeBytes', [asm_widget.get_address(), encoding]): new_data = bytearray(self.range.data) for i in range(0, len(encoding)): try: new_data[self.asm_data_start + offset + i] = encoding[i] except Exception as e: if isinstance(e, IndexError): break self.range.data = bytes(new_data) self.disa() except Exception as e: self.app.get_log_panel().log(str(e)) def on_arch_changed(self): if self.app.get_arch() == 'arm64': self.cs_arch = CS_ARCH_ARM64 self.cs_mode = CS_MODE_LITTLE_ENDIAN self.ks_arch = KS_ARCH_ARM64 self.ks_mode = KS_MODE_LITTLE_ENDIAN else: self.cs_arch = CS_ARCH_ARM self.cs_mode = CS_MODE_ARM self.ks_arch = KS_ARCH_ARM self.ks_mode = KS_MODE_ARM
class DisassemblyView(QAbstractScrollArea): onDisassemble = pyqtSignal(object, name='onDisassemble') onShowMemoryRequest = pyqtSignal(str, int, name='onShowMemoryRequest') def __init__(self, parent=None): super(DisassemblyView, self).__init__(parent=parent) _prefs = Prefs() self._uppercase_hex = (_prefs.get('dwarf_ui_hexstyle', 'upper').lower() == 'upper') self._app_window = parent self.setAutoFillBackground(True) self._app_window.dwarf.onApplyContext.connect(self.on_arch_changed) # setting font self.font = utils.get_os_monospace_font() self.font.setFixedPitch(True) self.setFont(self.font) self._char_width = QFontMetricsF(self.font).width( '#') # self.fontMetrics().width("#") if (self._char_width % 1) < .5: self.font.setLetterSpacing(QFont.AbsoluteSpacing, -(self._char_width % 1.0)) self._char_width -= self._char_width % 1.0 else: self.font.setLetterSpacing(QFont.AbsoluteSpacing, 1.0 - (self._char_width % 1.0)) self._char_width += 1.0 - (self._char_width % 1.0) self._char_height = self.fontMetrics().height() self._base_line = self.fontMetrics().ascent() self._history = [] self._lines = [] self._range = None self._longest_bytes = 0 self._longest_mnemonic = 0 self._running_disasm = False self.capstone_arch = 0 self.capstone_mode = 0 self.keystone_arch = 0 self.keystone_mode = 0 self.on_arch_changed() self._ctrl_colors = { 'background': QColor('#181818'), 'foreground': QColor('#666'), 'jump_arrows': QColor('#444'), 'jump_arrows_hover': QColor('#ef5350'), 'divider': QColor('#666'), 'line': QColor('#111'), 'selection_fg': QColor(Qt.white), 'selection_bg': QColor('#ef5350') } self._jump_color = QColor('#39a') self._header_height = 0 self._ver_spacing = 2 self._dash_pen = QPen(self._ctrl_colors['jump_arrows'], 2.0, Qt.DashLine) self._solid_pen = QPen(self._ctrl_colors['jump_arrows'], 2.0, Qt.SolidLine) self._line_pen = QPen(self._ctrl_colors['divider'], 0, Qt.SolidLine) self._breakpoint_linewidth = 5 self._jumps_width = 100 self.setMouseTracking(True) self.current_jump = -1 self._current_line = -1 self._display_jumps = True self._follow_jumps = True self.pos = 0 # hacky way to let plugins hook this and inject menu actions self.menu_extra_menu_hooks = [] """ this is one more way for allowing plugin hooks and perform additional operation on the range object """ self.run_default_disassembler = True # ************************************************************************ # **************************** Properties ******************************** # ************************************************************************ @property def display_jumps(self): return self._display_jumps @display_jumps.setter def display_jumps(self, value): if isinstance(value, bool): self._display_jumps = value if self._display_jumps: self._jumps_width = 100 else: self._jumps_width = 0 @property def follow_jumps(self): return self._follow_jumps @follow_jumps.setter def follow_jumps(self, value): if isinstance(value, bool): self._follow_jumps = value @pyqtProperty('QColor', designable=True) def background(self): return self._ctrl_colors['background'] @background.setter def background(self, value): self._ctrl_colors['background'] = value @pyqtProperty('QColor', designable=True) def foreground(self): return self._ctrl_colors['foreground'] @foreground.setter def foreground(self, value): self._ctrl_colors['foreground'] = QColor(value) @pyqtProperty('QColor', designable=True) def divider(self): return self._ctrl_colors['divider'] @divider.setter def divider(self, value): self._ctrl_colors['divider'] = QColor(value) @pyqtProperty('QColor', designable=True) def jump_arrows(self): return self._ctrl_colors['jump_arrows'] @jump_arrows.setter def jump_arrows(self, value): self._ctrl_colors['jump_arrows'] = QColor(value) @pyqtProperty('QColor', designable=True) def jump_arrows_hover(self): return self._ctrl_colors['jump_arrows_hover'] @jump_arrows_hover.setter def jump_arrows_hover(self, value): self._ctrl_colors['jump_arrows_hover'] = QColor(value) @pyqtProperty('QColor', designable=True) def line(self): return self._ctrl_colors['line'] @line.setter def line(self, value): self._ctrl_colors['line'] = QColor(value) # ************************************************************************ # **************************** Functions ********************************* # ************************************************************************ def add_instruction(self, instruction): self._lines.append(instruction) self.adjust() def disassemble(self, dwarf_range, num_instructions=0): if self._running_disasm: return self.onDisassemble.emit(dwarf_range) if self.run_default_disassembler: self.start_disassemble(dwarf_range, num_instructions=num_instructions) def start_disassemble(self, dwarf_range, num_instructions=0): self._running_disasm = True self._app_window.show_progress('disassembling...') self._lines.clear() self.viewport().update() if len(self._history) == 0 or self._history[ len(self._history) - 1] != dwarf_range.start_address: self._history.append(dwarf_range.start_address) if len(self._history) > 25: self._history.pop(0) self._longest_bytes = 0 try: capstone = Cs(self.capstone_arch, self.capstone_mode) capstone.detail = True except CsError: self._running_disasm = False print('[DisasmView] failed to initialize capstone with %d, %d' % (self.capstone_arch, self.capstone_mode)) return self._range = dwarf_range self.disasm_thread = DisassembleThread(self._app_window) self.disasm_thread._num_instructions = num_instructions self.disasm_thread._range = self._range self.disasm_thread._dwarf = self._app_window.dwarf self.disasm_thread._capstone = capstone self.disasm_thread.onFinished.connect(self._on_disasm_finished) self.disasm_thread.start(QThread.HighestPriority) def _on_disasm_finished(self, instructions): if isinstance(instructions, list): self._lines = instructions self.adjust() self._running_disasm = False self._app_window.hide_progress() def resizeEvent(self, event): super().resizeEvent(event) self.adjust() def adjust(self): for line in self._lines: if line: if len(line.bytes) > self._longest_bytes: self._longest_bytes = len(line.bytes) if len(line.mnemonic) > self._longest_mnemonic: self._longest_mnemonic = len(line.mnemonic) self.verticalScrollBar().setRange( 0, len(self._lines) - self.visible_lines() + 1) self.verticalScrollBar().setPageStep(self.visible_lines()) self.viewport().update() def visible_lines(self): """ returns number of lines that fits viewport """ height = self.viewport().height() height -= self._header_height + self._char_height + self._ver_spacing num_lines = int(ceil(height / (self._char_height + self._ver_spacing))) return num_lines + 1 def pixel_to_line(self, screen_x, screen_y): """ helper """ coord_x, coord_y = self.pixel_to_data(screen_x, screen_y) # pylint: disable=unused-variable return coord_y def pixel_to_data(self, screen_x, screen_y): """ pixel to data coords """ if screen_x < 0: screen_x = 0 top_gap = self._header_height + self._char_height + self._ver_spacing data_x = int(ceil(screen_x / int(self._char_width))) data_y = int( ceil((screen_y - top_gap) / (self._ver_spacing + self._char_height))) return (data_x, data_y) def read_memory(self, ptr, length=0): # TODO: remove read_memory self._lines.clear() if self._range is None: self._range = Range(Range.SOURCE_TARGET, self._app_window.dwarf) init = self._range.init_with_address(ptr, length) if init > 0: return 1 self.disassemble(self._range) return 0 # ************************************************************************ # **************************** Drawing *********************************** # ************************************************************************ def paint_jumps(self, painter): # TODO: order by distance painter.setRenderHint(QPainter.HighQualityAntialiasing) jump_list = [ x.address for x in self._lines[self.pos:self.pos + self.visible_lines()] if x.is_jump ] jump_targets = [ x.jump_address for x in self._lines[self.pos:self.pos + self.visible_lines()] if x.address in jump_list ] drawing_pos_x = self._jumps_width - 10 for index, line in enumerate(self._lines[self.pos:self.pos + self.visible_lines()]): if line.address in jump_list: # or line.address in jump_targets: if line.address == self.current_jump: self._solid_pen.setColor( self._ctrl_colors['jump_arrows_hover']) self._dash_pen.setColor( self._ctrl_colors['jump_arrows_hover']) else: self._solid_pen.setColor(self._ctrl_colors['jump_arrows']) self._dash_pen.setColor(self._ctrl_colors['jump_arrows']) if line.id == X86_INS_JMP or line.id == X86_INS_CALL: painter.setPen(self._solid_pen) else: painter.setPen(self._dash_pen) drawing_pos_y = (index + 1) * (self._char_height + self._ver_spacing) drawing_pos_y -= self._base_line - (self._char_height * 0.5) skip = False if line.address not in jump_targets: painter.drawLine(drawing_pos_x + 2, drawing_pos_y, self._jumps_width, drawing_pos_y) if line.jump_address in jump_targets: entry1 = [ x for x in self._lines if x.address == line.address ] entry2 = [ x for x in self._lines if x.address == line.jump_address ] if entry1 and entry2: skip = True pos2 = (self._lines.index(entry1[0]) - self._lines.index(entry2[0])) * ( self._char_height + self._ver_spacing) painter.drawLine(drawing_pos_x, drawing_pos_y - pos2, drawing_pos_x, drawing_pos_y) painter.drawLine(drawing_pos_x, drawing_pos_y - pos2, 100, drawing_pos_y - pos2) arrow = QPolygon() arrow.append(QPoint(100, drawing_pos_y - pos2)) arrow.append( QPoint(100 - 8, drawing_pos_y - pos2 - 4)) arrow.append( QPoint(100 - 8, drawing_pos_y - pos2 + 4)) if line.address == self.current_jump: painter.setBrush( self._ctrl_colors['jump_arrows_hover']) painter.setPen( self._ctrl_colors['jump_arrows_hover']) else: painter.setBrush( self._ctrl_colors['jump_arrows']) painter.setPen( self._ctrl_colors['jump_arrows']) painter.drawPolygon(arrow) else: skip = True if not skip: if line.address > line.jump_address: if line.id == X86_INS_JMP or line.id == X86_INS_CALL: painter.setPen(self._solid_pen) else: painter.setPen(self._dash_pen) painter.drawLine(drawing_pos_x, 5, drawing_pos_x, drawing_pos_y) arrow = QPolygon() arrow.append(QPoint(drawing_pos_x, 0)) arrow.append(QPoint(drawing_pos_x + 4, 8)) arrow.append(QPoint(drawing_pos_x - 4, 8)) if line.address == self.current_jump: painter.setBrush( self._ctrl_colors['jump_arrows_hover']) painter.setPen(Qt.NoPen) else: painter.setBrush(self._ctrl_colors['jump_arrows']) painter.setPen(Qt.NoPen) painter.drawPolygon(arrow) elif line.address < line.jump_address: if line.id == X86_INS_JMP or line.id == X86_INS_CALL: painter.setPen(self._solid_pen) else: painter.setPen(self._dash_pen) painter.drawLine(drawing_pos_x, drawing_pos_y, drawing_pos_x, self.viewport().height() - 5) arrow = QPolygon() arrow.append( QPoint(drawing_pos_x, self.viewport().height())) arrow.append( QPoint(drawing_pos_x + 4, self.viewport().height() - 8)) arrow.append( QPoint(drawing_pos_x - 4, self.viewport().height() - 8)) if line.address == self.current_jump: painter.setBrush( self._ctrl_colors['jump_arrows_hover']) painter.setPen(Qt.NoPen) else: painter.setBrush(self._ctrl_colors['jump_arrows']) painter.setPen(Qt.NoPen) painter.drawPolygon(arrow) drawing_pos_x -= 10 if drawing_pos_x < 0: break def paint_line(self, painter, num_line, line): painter.setPen(self._ctrl_colors['foreground']) drawing_pos_x = self._jumps_width + self._breakpoint_linewidth + int( self._char_width) drawing_pos_y = num_line * (self._char_height + self._ver_spacing) drawing_pos_y += self._header_height if not line: # empty line from emu return num = self._app_window.dwarf.pointer_size * 2 str_fmt = '{0:08x}' if num > 8: str_fmt = '{0:016x}' if self._uppercase_hex: str_fmt = str_fmt.replace('x', 'X') painter.drawText(drawing_pos_x, drawing_pos_y, str_fmt.format(line.address)) is_watched = False is_hooked = False if self._app_window.dwarf.is_address_watched(line.address): is_watched = True if line.address in self._app_window.dwarf.hooks: is_hooked = True if is_watched or is_hooked: if is_watched: height = self._char_height y_pos = drawing_pos_y y_pos -= self._base_line - (self._char_height * 0.5) y_pos += (self._char_height * 0.5) if is_hooked: y_pos -= (self._char_height * 0.5) height *= 0.5 painter.fillRect(self._jumps_width, y_pos - height, self._breakpoint_linewidth, height, QColor('greenyellow')) if is_hooked: height = self._char_height y_pos = drawing_pos_y y_pos -= self._base_line - (self._char_height * 0.5) y_pos += (self._char_height * 0.5) if is_watched: height *= 0.5 painter.fillRect(self._jumps_width, y_pos - height, self._breakpoint_linewidth, height, QColor('crimson')) drawing_pos_x = self._jumps_width + self._breakpoint_linewidth + int( self._char_width) + 1 + int(self._char_width) drawing_pos_x += (len(str_fmt.format(line.address)) * int(self._char_width)) painter.setPen(QColor('#444')) drawing_pos_x += int(self._char_width) for byte in line.bytes: painter.drawText(drawing_pos_x, drawing_pos_y, '{0:02x}'.format(byte)) drawing_pos_x += int(self._char_width) * 3 drawing_pos_x = self._jumps_width + self._breakpoint_linewidth + ( (self._app_window.dwarf.pointer_size * 2) * int(self._char_width) ) + (self._longest_bytes + 2) * (int(self._char_width) * 3) painter.setPen(QColor('#39c')) painter.drawText(drawing_pos_x, drawing_pos_y, line.mnemonic) if line.is_jump: painter.setPen(self._jump_color) else: painter.setPen(self._ctrl_colors['foreground']) drawing_pos_x += (self._longest_mnemonic + 1) * int(self._char_width) if line.operands and not line.is_jump: ops_str = line.op_str.split(', ', len(line.operands) - 1) a = 0 for op in line.operands: if op.type == CS_OP_IMM: painter.setPen(QColor('#ff5500')) elif op.type == 1: painter.setPen(QColor('#82c300')) else: painter.setPen(self._ctrl_colors['foreground']) painter.drawText(drawing_pos_x, drawing_pos_y, ops_str[a]) drawing_pos_x += len(ops_str[a] * int(self._char_width)) if len(line.operands) > 1 and a < len(line.operands) - 1: painter.setPen(self._ctrl_colors['foreground']) painter.drawText(drawing_pos_x, drawing_pos_y, ', ') drawing_pos_x += 2 * int(self._char_width) # if ops_str[a].startswith('0x') and not line.string: # line.string = '{0:d}'.format(int(ops_str[a], 16)) # drawing_pos_x += (len(ops_str[a]) + 1) * self._char_width a += 1 else: if self._follow_jumps and line.is_jump: if line.jump_address < line.address: painter.drawText(drawing_pos_x, drawing_pos_y, line.op_str + ' ▲') elif line.jump_address > line.address: painter.drawText(drawing_pos_x, drawing_pos_y, line.op_str + ' ▼') drawing_pos_x += (len(line.op_str) + 3) * int(self._char_width) else: painter.drawText(drawing_pos_x, drawing_pos_y, line.op_str) drawing_pos_x += (len(line.op_str) + 1) * int(self._char_width) if line.symbol_name: painter.drawText(drawing_pos_x, drawing_pos_y, '(' + line.symbol_name + ')') drawing_pos_x += (len(line.symbol_name) + 1) * int( self._char_width) if line.string and not line.is_jump: painter.setPen(QColor('#aaa')) painter.drawText(drawing_pos_x, drawing_pos_y, ' ; "' + line.string + '"') def paint_wait(self, painter): """ paint wait popup """ brdr_col = QColor('#444') back_col = QColor('#222') painter.setPen(QColor('#888')) back_rect = self.viewport().rect() back_rect.setWidth(back_rect.width() * .2) back_rect.setHeight(back_rect.height() * .1) screen_x = self.viewport().width() * .5 screen_x -= back_rect.width() * .5 screen_y = self.viewport().height() * .5 screen_y -= back_rect.height() * .5 painter.fillRect(screen_x - 1, screen_y - 1, back_rect.width() + 2, back_rect.height() + 2, brdr_col) painter.fillRect(screen_x, screen_y, back_rect.width(), back_rect.height(), back_col) qtext_align = QTextOption(Qt.AlignVCenter | Qt.AlignHCenter) text_rect = QRectF(screen_x + 10, screen_y + 10, back_rect.width() - 20, back_rect.height() - 20) painter.drawText(text_rect, "Disassembling...", option=qtext_align) def paintEvent(self, event): if not self.isVisible(): return painter = QPainter(self.viewport()) if self._running_disasm: return self.paint_wait(painter) if not self._lines: return self.pos = self.verticalScrollBar().value() # fill background painter.fillRect(0, 0, self.viewport().width(), self.viewport().height(), self._ctrl_colors['background']) if self._display_jumps: painter.setPen(self._ctrl_colors['foreground']) drawing_pos_x = self._jumps_width self.paint_jumps(painter) painter.fillRect(drawing_pos_x, 0, self._breakpoint_linewidth, self.viewport().height(), self._ctrl_colors['jump_arrows']) for i, line in enumerate(self._lines[self.pos:self.pos + self.visible_lines()]): if i > self.visible_lines(): break if i == self._current_line: y_pos = self._header_height + ( i * (self._char_height + self._ver_spacing)) y_pos += (self._char_height * 0.5) y_pos -= self._ver_spacing painter.fillRect( self._jumps_width + self._breakpoint_linewidth, y_pos - 1, self.viewport().width(), self._char_height + 2, self._ctrl_colors['line']) self.paint_line(painter, i + 1, line) painter.setPen(self._line_pen) painter.setBrush(Qt.NoBrush) drawing_pos_x = self._jumps_width + self._breakpoint_linewidth + int( self._char_width) + int(self._char_width) drawing_pos_x += ((self._app_window.dwarf.pointer_size * 2) * int(self._char_width)) painter.fillRect(drawing_pos_x, 0, 1, self.viewport().height(), self._ctrl_colors['divider']) # ************************************************************************ # **************************** Handlers ********************************** # ************************************************************************ def keyPressEvent(self, event): if event.key() == Qt.Key_Backspace: if len(self._history) > 1: self._history.pop(len(self._history) - 1) self.read_memory(self._history[len(self._history) - 1]) elif event.key( ) == Qt.Key_G and event.modifiers() & Qt.ControlModifier: # ctrl+g self._on_cm_jump_to_address() elif event.key( ) == Qt.Key_M and event.modifiers() & Qt.ControlModifier: # ctrl+m self._on_switch_mode() elif event.key( ) == Qt.Key_P and event.modifiers() & Qt.ControlModifier: # ctrl+p pass # patch instruction elif event.key( ) == Qt.Key_B and event.modifiers() & Qt.ControlModifier: # ctrl+b pass # patch bytes else: # dispatch those to super super().keyPressEvent(event) def on_arch_changed(self, context=None): if self._app_window.dwarf.arch == 'arm64': self.capstone_arch = CS_ARCH_ARM64 self.capstone_mode = CS_MODE_LITTLE_ENDIAN elif self._app_window.dwarf.arch == 'arm': self.capstone_arch = CS_ARCH_ARM context = self._app_window.dwarf.current_context() self.capstone_mode = CS_MODE_ARM if context is not None and context.is_native_context: if context.pc.thumb: self.capstone_mode = CS_MODE_THUMB elif self._app_window.dwarf.arch == 'ia32': self.capstone_arch = CS_ARCH_X86 self.capstone_mode = CS_MODE_32 elif self._app_window.dwarf.arch == 'x64': self.capstone_arch = CS_ARCH_X86 self.capstone_mode = CS_MODE_64 if self._app_window.dwarf.keystone_installed: import keystone.keystone_const as ks if self._app_window.dwarf.arch == 'arm64': self.keystone_arch = ks.KS_ARCH_ARM64 self.keystone_mode = ks.KS_MODE_LITTLE_ENDIAN elif self._app_window.dwarf.arch == 'arm': self.keystone_arch = ks.KS_ARCH_ARM self.keystone_mode = ks.KS_MODE_ARM elif self._app_window.dwarf.arch == 'ia32': self.keystone_arch = ks.KS_ARCH_X86 self.keystone_mode = ks.KS_MODE_32 elif self._app_window.dwarf.arch == 'x64': self.keystone_arch = ks.KS_ARCH_X86 self.keystone_mode = ks.KS_MODE_64 def mouseDoubleClickEvent(self, event): loc_x = event.pos().x() loc_y = event.pos().y() index = self.pixel_to_line(loc_x, loc_y) if 0 <= index < self.visible_lines(): if index + self.pos >= len(self._lines): return left_side = self._breakpoint_linewidth + self._jumps_width addr_width = ((self._app_window.dwarf.pointer_size * 2) * int(self._char_width)) if loc_x > left_side: if loc_x < left_side + addr_width: if self._lines[index + self.pos] and isinstance( self._lines[index + self.pos], Instruction): self.onShowMemoryRequest.emit( hex(self._lines[index + self.pos].address), len(self._lines[index + self.pos].bytes)) if loc_x > left_side + addr_width: if self._lines[index + self.pos] and isinstance( self._lines[index + self.pos], Instruction): if self._follow_jumps and self._lines[ index + self.pos].is_jump: new_pos = self._lines[index + self.pos].jump_address self.read_memory(new_pos) # pylint: disable=C0103 def mouseMoveEvent(self, event): """ onmousemove """ loc_x = event.pos().x() loc_y = event.pos().y() if loc_x > self._breakpoint_linewidth + self._jumps_width: self.current_jump = -1 index = self.pixel_to_line(loc_x, loc_y) if 0 <= index < self.visible_lines(): self._current_line = index if index + self.pos < len(self._lines): if isinstance(self._lines[index + self.pos], Instruction): if self._lines[index + self.pos].is_jump: self.current_jump = self._lines[index + self.pos].address # self.viewport().update(0, 0, self._breakpoint_linewidth + self._jumps_width, self.viewport().height()) y_pos = self._header_height + ( index * (self._char_height + self._ver_spacing)) y_pos += (self._char_height * 0.5) y_pos -= self._ver_spacing self.viewport().update(0, 0, self.viewport().width(), self.viewport().height()) def mousePressEvent(self, event): # context menu if event.button() == Qt.RightButton: if self._running_disasm: return self._on_context_menu(event) def _on_context_menu(self, event): """ build and show contextmenu """ loc_x = event.pos().x() loc_y = event.pos().y() context_menu = QMenu() context_menu.addAction('Jump to address', self._on_cm_jump_to_address) # allow mode switch arm/thumb if self.capstone_arch == CS_ARCH_ARM: if self.capstone_mode == CS_MODE_THUMB: mode_str = 'ARM' else: mode_str = 'THUMB' entry_str = '&Switch to {0} mode'.format(mode_str) context_menu.addAction(entry_str, self._on_switch_mode) if not self._lines: # allow jumpto in empty panel glbl_pt = self.mapToGlobal(event.pos()) context_menu.exec_(glbl_pt) return context_menu.addSeparator() index = self.pixel_to_line(loc_x, loc_y) address = -1 if 0 <= index < self.visible_lines(): if index + self.pos < len(self._lines): if isinstance(self._lines[index + self.pos], Instruction): address = self._lines[index + self.pos].address context_menu.addAction( 'Copy address', lambda: utils.copy_hex_to_clipboard(address)) context_menu.addSeparator() if self._uppercase_hex: str_fmt = '0x{0:X}' else: str_fmt = '0x{0:x}' addr_str = str_fmt.format(address) if self._app_window.watchers_panel: if self._app_window.dwarf.is_address_watched(address): context_menu.addAction( 'Remove watcher', lambda: self._app_window. watchers_panel.remove_address(addr_str)) else: context_menu.addAction( 'Watch address', lambda: self._app_window. watchers_panel.do_addwatcher_dlg(addr_str)) if self._app_window.hooks_panel: if address in self._app_window.dwarf.hooks: context_menu.addAction( 'Remove hook', lambda: self._app_window.dwarf.dwarf_api( 'deleteHook', addr_str)) else: context_menu.addAction( 'Hook address', lambda: self._app_window.dwarf. hook_native(addr_str)) for fcn in self.menu_extra_menu_hooks: try: fcn(context_menu, address) except Exception as e: print('failed to add hook menu: %s' % str(e)) glbl_pt = self.mapToGlobal(event.pos()) context_menu.exec_(glbl_pt) def _on_switch_mode(self): if self._range is None: return if self._app_window.dwarf.arch == 'arm': self._lines.clear() if self.capstone_mode == CS_MODE_ARM: self.capstone_mode = CS_MODE_THUMB else: self.capstone_mode = CS_MODE_ARM self.disassemble(self._range) def _on_cm_jump_to_address(self): ptr, _ = InputDialog.input_pointer(self._app_window) if ptr > 0: self.read_memory(ptr)