Exemplo n.º 1
0
    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'])
Exemplo n.º 2
0
 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
Exemplo n.º 3
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
Exemplo n.º 4
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
Exemplo n.º 5
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
Exemplo n.º 6
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
Exemplo n.º 7
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)
Exemplo n.º 8
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.onEmulatorMemoryRangeMapped.emit([range_.base, range_.size])
        return 0
Exemplo n.º 9
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
Exemplo n.º 10
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)
Exemplo n.º 11
0
    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))
Exemplo n.º 12
0
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)
Exemplo n.º 13
0
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
Exemplo n.º 14
0
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
Exemplo n.º 15
0
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)