class EmulatorConfigsDialog(QDialog): def __init__(self, dwarf, parent=None): super(EmulatorConfigsDialog, self).__init__(parent) self.dwarf = dwarf self._prefs = Prefs() layout = QVBoxLayout(self) self.setMinimumWidth(500) layout.addWidget(QLabel('callbacks path')) callbacks_layout = QHBoxLayout() pick_path = QPushButton('choose') pick_path.clicked.connect(self.pick_callbacks_path) current_callbacks_path = self._prefs.get(prefs.EMULATOR_CALLBACKS_PATH) if current_callbacks_path == '': current_callbacks_path = 'none' self.callbacks_path_label = QLabel(current_callbacks_path) callbacks_layout.addWidget(pick_path) callbacks_layout.addWidget(self.callbacks_path_label) layout.addLayout(callbacks_layout) layout.addWidget(QLabel('delay between instructions')) self.instructions_delay = QLineEdit() self.instructions_delay.setText( str(self._prefs.get(prefs.EMULATOR_INSTRUCTIONS_DELAY, 0.5))) layout.addWidget(self.instructions_delay) buttons = QHBoxLayout() cancel = QPushButton('cancel') cancel.clicked.connect(self.close) buttons.addWidget(cancel) accept = QPushButton('accept') accept.clicked.connect(self.accept) buttons.addWidget(accept) layout.addLayout(buttons) def pick_callbacks_path(self): r = QFileDialog.getOpenFileName() if len(r) > 0 and len(r[0]) > 0: self._prefs.put(prefs.EMULATOR_CALLBACKS_PATH, r[0]) self.callbacks_path_label.setText(r[0]) @staticmethod def show_dialog(dwarf): dialog = EmulatorConfigsDialog(dwarf) result = dialog.exec_() if result == QDialog.Accepted: try: dialog._prefs.put(prefs.EMULATOR_INSTRUCTIONS_DELAY, float(dialog.instructions_delay.text())) except: pass
def __init__(self, parent=None, search_enabled=True): super(DwarfListView, self).__init__(parent=parent) self._search_enabled = search_enabled self._current_search = '' self._uppercase_hex = True _prefs = Prefs() self.rows_dualcolor = _prefs.get('dwarf_ui_alternaterowcolors', False) self.uppercase_hex = _prefs.get('dwarf_ui_hexstyle', 'upper').lower() == 'upper' self.setEditTriggers(self.NoEditTriggers) self.setHeaderHidden(False) self.setAutoFillBackground(True) self.setRootIsDecorated(False) # TODO: use filter self._proxy_model = QSortFilterProxyModel(self) self._proxy_model.setSourceModel(self.model())
def copy_hex_to_clipboard(hex_str): """ Helper for copying hexstr in prefered style """ _prefs = Prefs() uppercase = (_prefs.get('dwarf_ui_hexstyle', 'upper').lower() == 'upper') if isinstance(hex_str, str): if hex_str.startswith('0x'): if uppercase: hex_str = hex_str.upper().replace('0X', '0x') else: hex_str = hex_str.lower() pyperclip.copy(hex_str) elif isinstance(hex_str, int): str_fmt = '0x{0:x}' if uppercase: str_fmt = '0x{0:X}' pyperclip.copy(str_fmt.format(hex_str))
class AppWindow(QMainWindow): onRestart = pyqtSignal(name='onRestart') onSystemUIElementCreated = pyqtSignal(str, QWidget, name='onSystemUIElementCreated') onSystemUIElementRemoved = pyqtSignal(str, name='onSystemUIElementRemoved') def __init__(self, dwarf_args, flags=None): super(AppWindow, self).__init__(flags) self.dwarf_args = dwarf_args self.session_manager = SessionManager(self) self.session_manager.sessionCreated.connect(self.session_created) self.session_manager.sessionStopped.connect(self.session_stopped) self.session_manager.sessionClosed.connect(self.session_closed) self._tab_order = [ 'debug', 'modules', 'ranges', 'jvm-inspector', 'jvm-debugger' ] self._is_newer_dwarf = False self.q_settings = QSettings("dwarf_window_pos.ini", QSettings.IniFormat) self.menu = self.menuBar() self.view_menu = None self._initialize_ui_elements() self.setWindowTitle( 'Dwarf - A debugger for reverse engineers, crackers and security analyst' ) # load external assets _app = QApplication.instance() self.remove_tmp_dir() # themes self.prefs = Prefs() utils.set_theme(self.prefs.get('dwarf_ui_theme', 'black'), self.prefs) # load font if os.path.exists(utils.resource_path('assets/Anton.ttf')): QFontDatabase.addApplicationFont( utils.resource_path('assets/Anton.ttf')) if os.path.exists(utils.resource_path('assets/OpenSans-Regular.ttf')): QFontDatabase.addApplicationFont( utils.resource_path('assets/OpenSans-Regular.ttf')) font = QFont("OpenSans", 9, QFont.Normal) # TODO: add settingsdlg font_size = self.prefs.get('dwarf_ui_font_size', 12) font.setPixelSize(font_size) _app.setFont(font) if os.path.exists(utils.resource_path('assets/OpenSans-Bold.ttf')): QFontDatabase.addApplicationFont( utils.resource_path('assets/OpenSans-Bold.ttf')) # mainwindow statusbar self.progressbar = QProgressBar() self.progressbar.setRange(0, 0) self.progressbar.setVisible(False) self.progressbar.setFixedHeight(15) self.progressbar.setFixedWidth(100) self.progressbar.setTextVisible(False) self.progressbar.setValue(30) self.statusbar = QStatusBar(self) self.statusbar.setAutoFillBackground(False) self.statusbar.addPermanentWidget(self.progressbar) self.statusbar.setObjectName("statusbar") self.setStatusBar(self.statusbar) self.main_tabs = QTabWidget(self) self.main_tabs.setMovable(False) self.main_tabs.setTabsClosable(True) self.main_tabs.setAutoFillBackground(True) self.main_tabs.tabCloseRequested.connect(self._on_close_tab) self.setCentralWidget(self.main_tabs) # pluginmanager self.plugin_manager = PluginManager(self) self.plugin_manager.reload_plugins() if dwarf_args.any == '': self.welcome_window = WelcomeDialog(self) self.welcome_window.setModal(True) self.welcome_window.onIsNewerVersion.connect( self._enable_update_menu) self.welcome_window.onUpdateComplete.connect( self._on_dwarf_updated) self.welcome_window.setWindowTitle( 'Welcome to Dwarf - A debugger for reverse engineers, crackers and security analyst' ) self.welcome_window.onSessionSelected.connect(self._start_session) # wait for welcome screen self.hide() self.welcome_window.show() else: print('* Starting new Session') self._start_session(dwarf_args.target) def _initialize_ui_elements(self): # dockwidgets self.watchers_dwidget = None self.hooks_dwiget = None self.bookmarks_dwiget = None self.registers_dock = None self.console_dock = None self.backtrace_dock = None self.threads_dock = None # panels self.asm_panel = None self.backtrace_panel = None self.bookmarks_panel = None self.console_panel = None self.context_panel = None self.debug_panel = None self.contexts_list_panel = None self.data_panel = None self.ftrace_panel = None self.hooks_panel = None self.java_inspector_panel = None self.java_explorer_panel = None self.java_trace_panel = None self.modules_panel = None self.ranges_panel = None self.search_panel = None self.smali_panel = None self.watchers_panel = None self.welcome_window = None self._ui_elems = [] def _setup_main_menu(self): self.menu = self.menuBar() dwarf_menu = QMenu('Dwarf', self) theme = QMenu('Theme', dwarf_menu) theme.addAction('Black') theme.addAction('Dark') theme.addAction('Light') theme.triggered.connect(self._set_theme) dwarf_menu.addMenu(theme) dwarf_menu.addSeparator() if sys.platform == 'linux' or sys.platform == 'darwin': dwarf_bin_path = os.path.join( '/'.join(os.path.realpath(__file__).split('/')[:-2]), 'bin/dwarf') if not os.path.exists(dwarf_bin_path): dwarf_menu.addAction('Create launcher', utils.create_launcher) dwarf_menu.addSeparator() if self._is_newer_dwarf: dwarf_menu.addAction('Update', self._update_dwarf) dwarf_menu.addAction('Close', self.session_manager.session.stop) self.menu.addMenu(dwarf_menu) session = self.session_manager.session if session is not None: session_menu = session.main_menu if isinstance(session_menu, list): for menu in session_menu: self.menu.addMenu(menu) else: self.menu.addMenu(session_menu) # plugins if self.plugin_manager.plugins: self.plugin_menu = QMenu('Plugins', self) for plugin in self.plugin_manager.plugins: plugin_instance = self.plugin_manager.plugins[plugin] plugin_sub_menu = self.plugin_menu.addMenu( plugin_instance.name) try: actions = plugin_instance.__get_top_menu_actions__() for action in actions: plugin_sub_menu.addAction(action) except: pass if not plugin_sub_menu.isEmpty(): plugin_sub_menu.addSeparator() about = plugin_sub_menu.addAction('About') about.triggered.connect( lambda x, item=plugin: self._show_plugin_about(item)) if not self.plugin_menu.isEmpty(): self.menu.addMenu(self.plugin_menu) self.view_menu = QMenu('View', self) self.panels_menu = QMenu('Panels', self.view_menu) self.panels_menu.addAction('Search', lambda: self.show_main_tab('search'), shortcut=QKeySequence(Qt.CTRL + Qt.Key_F3)) self.view_menu.addMenu(self.panels_menu) self.debug_view_menu = self.view_menu.addMenu('Debug') self.view_menu.addSeparator() self.view_menu.addAction('Hide all', self._hide_all_widgets, shortcut=QKeySequence(Qt.CTRL + Qt.Key_F1)) self.view_menu.addAction('Show all', self._show_all_widgets, shortcut=QKeySequence(Qt.CTRL + Qt.Key_F2)) self.view_menu.addSeparator() self.menu.addMenu(self.view_menu) if self.dwarf_args.debug_script: debug_menu = QMenu('Debug', self) debug_menu.addAction('Reload core', self._menu_reload_core) debug_menu.addAction('Debug dwarf js core', self._menu_debug_dwarf_js) self.menu.addMenu(debug_menu) # tools _tools = self.prefs.get('tools') if _tools: tools_menu = QMenu('Tools', self) for _tool in _tools: if _tool and _tool['name']: if _tool['name'] == 'sep': tools_menu.addSeparator() continue _cmd = _tool['cmd'] tools_menu.addAction(_tool['name']) if not tools_menu.isEmpty(): tools_menu.triggered.connect(self._execute_tool) self.menu.addMenu(tools_menu) about_menu = QMenu('About', self) about_menu.addAction('Dwarf on GitHub', self._menu_github) about_menu.addAction('Documention', self._menu_documentation) about_menu.addAction('Api', self._menu_api) about_menu.addAction('Slack', self._menu_slack) about_menu.addSeparator() about_menu.addAction('Info', self._show_about_dlg) self.menu.addMenu(about_menu) def _show_plugin_about(self, plugin): plugin = self.plugin_manager.plugins[plugin] if plugin: info = plugin.__get_plugin_info__() version = utils.safe_read_map(info, 'version', '') description = utils.safe_read_map(info, 'description', '') author = utils.safe_read_map(info, 'author', '') homepage = utils.safe_read_map(info, 'homepage', '') license_ = utils.safe_read_map(info, 'license', '') utils.show_message_box( 'Name: {0}\nVersion: {1}\nDescription: {2}\nAuthor: {3}\nHomepage: {4}\nLicense: {5}' .format(plugin.name, version, description, author, homepage, license_)) def _enable_update_menu(self): self._is_newer_dwarf = True def _update_dwarf(self): if self.welcome_window: self.welcome_window._update_dwarf() def _on_close_tab(self, index): tab_text = self.main_tabs.tabText(index) if tab_text: tab_text = tab_text.lower().replace(' ', '-') if tab_text in self.session_manager.session.non_closable: return try: self._ui_elems.remove(tab_text) except ValueError: # recheck ValueError: list.remove(x): x not in list pass self.main_tabs.removeTab(index) self.onSystemUIElementRemoved.emit(tab_text) def _handle_tab_change(self): for index in range(self.main_tabs.count()): tab_name = self.main_tabs.tabText(index).lower().replace(' ', '-') if tab_name in self.session_manager.session.non_closable: self.main_tabs.tabBar().setTabButton(index, QTabBar.RightSide, None) if tab_name in self._tab_order: should_index = self._tab_order.index(tab_name) if index != should_index: self.main_tabs.tabBar().moveTab(index, should_index) def _on_dwarf_updated(self): self.onRestart.emit() def remove_tmp_dir(self): if os.path.exists('.tmp'): shutil.rmtree('.tmp', ignore_errors=True) def _execute_tool(self, qaction): if qaction: _tools = self.prefs.get('tools') if _tools: for _tool in _tools: if _tool and _tool['name'] and _tool['name'] != 'sep': if qaction.text() == _tool['name']: try: import subprocess subprocess.Popen(_tool['cmd'], creationflags=subprocess. CREATE_NEW_CONSOLE) except: pass break def _set_theme(self, qaction): if qaction: utils.set_theme(qaction.text(), self.prefs) def _hide_all_widgets(self): self.watchers_dwidget.hide() self.hooks_dwiget.hide() self.bookmarks_dwiget.hide() self.registers_dock.hide() self.console_dock.hide() self.backtrace_dock.hide() self.threads_dock.hide() def _show_all_widgets(self): self.watchers_dwidget.show() self.hooks_dwiget.show() self.bookmarks_dwiget.show() self.registers_dock.show() self.console_dock.show() self.backtrace_dock.show() self.threads_dock.show() def _menu_reload_core(self): self.dwarf.script.exports.reload() def _menu_debug_dwarf_js(self): you_know_what_to_do = json.loads( self.dwarf.script.exports.debugdwarfjs()) return you_know_what_to_do def show_main_tab(self, name): name = name.lower() # elem doesnt exists? create it if name not in self._ui_elems: self._create_ui_elem(name) index = 0 name = name.join(name.split()).lower() if name == 'ranges': index = self.main_tabs.indexOf(self.ranges_panel) elif name == 'search': index = self.main_tabs.indexOf(self.search_panel) elif name == 'modules': index = self.main_tabs.indexOf(self.modules_panel) elif name == 'data': index = self.main_tabs.indexOf(self.data_panel) elif name == 'jvm-tracer': index = self.main_tabs.indexOf(self.java_trace_panel) elif name == 'jvm-inspector': index = self.main_tabs.indexOf(self.java_inspector_panel) elif name == 'jvm-debugger': index = self.main_tabs.indexOf(self.java_explorer_panel) elif name == 'smali': index = self.main_tabs.indexOf(self.smali_panel) self.main_tabs.setCurrentIndex(index) def jump_to_address(self, ptr, view=0, show_panel=True): if show_panel: self.show_main_tab('debug') self.debug_panel.jump_to_address(ptr, view=view) @pyqtSlot(name='mainMenuGitHub') def _menu_github(self): QDesktopServices.openUrl(QUrl('https://github.com/iGio90/Dwarf')) @pyqtSlot(name='mainMenuApi') def _menu_api(self): QDesktopServices.openUrl( QUrl('http://www.giovanni-rocca.com/dwarf/javascript/')) @pyqtSlot(name='mainMenuDocumentation') def _menu_documentation(self): QDesktopServices.openUrl(QUrl('http://www.giovanni-rocca.com/dwarf/')) @pyqtSlot(name='mainMenuSlack') def _menu_slack(self): QDesktopServices.openUrl( QUrl('https://join.slack.com/t/resecret/shared_invite' '/enQtMzc1NTg4MzE3NjA1LTlkNzYxNTIwYTc2ZTYyOWY1MT' 'Q1NzBiN2ZhYjQwYmY0ZmRhODQ0NDE3NmRmZjFiMmE1MDYwN' 'WJlNDVjZDcwNGE')) def _show_about_dlg(self): about_dlg = AboutDialog(self) about_dlg.show() def _create_ui_elem(self, elem): elem = elem.lower() if not isinstance(elem, str): return if elem not in self._ui_elems: self._ui_elems.append(elem) elem_wiget = None if elem == 'watchers': from ui.session_widgets.watchers import WatchersWidget self.watchers_dwidget = QDockWidget('Watchers', self) self.watchers_panel = WatchersWidget(self) # dont respond to dblclick mem cant be shown # self.watchers_panel.onItemDoubleClicked.connect( # self._on_watcher_clicked) self.watchers_panel.onItemRemoved.connect( self._on_watcher_removeditem) self.watchers_panel.onItemAdded.connect(self._on_watcher_added) self.watchers_dwidget.setWidget(self.watchers_panel) self.watchers_dwidget.setObjectName('WatchersWidget') self.addDockWidget(Qt.LeftDockWidgetArea, self.watchers_dwidget) self.view_menu.addAction(self.watchers_dwidget.toggleViewAction()) elem_wiget = self.watchers_panel elif elem == 'hooks': from ui.session_widgets.hooks import HooksWidget self.hooks_dwiget = QDockWidget('Breakpoints', self) self.hooks_panel = HooksWidget(self) self.hooks_panel.onHookRemoved.connect(self._on_hook_removed) self.hooks_dwiget.setWidget(self.hooks_panel) self.hooks_dwiget.setObjectName('HooksWidget') self.addDockWidget(Qt.LeftDockWidgetArea, self.hooks_dwiget) self.view_menu.addAction(self.hooks_dwiget.toggleViewAction()) elem_wiget = self.hooks_panel elif elem == 'bookmarks': from ui.session_widgets.bookmarks import BookmarksWidget self.bookmarks_dwiget = QDockWidget('Boomarks', self) self.bookmarks_panel = BookmarksWidget(self) self.bookmarks_dwiget.setWidget(self.bookmarks_panel) self.bookmarks_dwiget.setObjectName('BookmarksWidget') self.addDockWidget(Qt.LeftDockWidgetArea, self.bookmarks_dwiget) self.view_menu.addAction(self.bookmarks_dwiget.toggleViewAction()) elem_wiget = self.bookmarks_panel elif elem == 'registers': from ui.session_widgets.context import ContextWidget self.registers_dock = QDockWidget('Context', self) self.context_panel = ContextWidget(self) self.registers_dock.setWidget(self.context_panel) self.registers_dock.setObjectName('ContextWidget') self.addDockWidget(Qt.RightDockWidgetArea, self.registers_dock) self.view_menu.addAction(self.registers_dock.toggleViewAction()) elem_wiget = self.context_panel elif elem == 'debug': from ui.panels.panel_debug import QDebugPanel self.debug_panel = QDebugPanel(self) self.main_tabs.addTab(self.debug_panel, 'Debug') elem_wiget = self.debug_panel elif elem == 'jvm-debugger': from ui.panels.panel_java_explorer import JavaExplorerPanel self.java_explorer_panel = JavaExplorerPanel(self) self.main_tabs.addTab(self.java_explorer_panel, 'JVM debugger') self.main_tabs.tabBar().moveTab( self.main_tabs.indexOf(self.java_explorer_panel), 1) elem_wiget = self.java_explorer_panel elif elem == 'jvm-inspector': from ui.panels.panel_java_inspector import JavaInspector self.java_inspector_panel = JavaInspector(self) self.main_tabs.addTab(self.java_inspector_panel, 'JVM inspector') elem_wiget = self.java_inspector_panel elif elem == 'console': from ui.session_widgets.console import ConsoleWidget self.console_dock = QDockWidget('Console', self) self.console_panel = ConsoleWidget(self) if self.dwarf_args.script and len( self.dwarf_args.script) > 0 and os.path.exists( self.dwarf_args.script): with open(self.dwarf_args.script, 'r') as f: self.console_panel.get_js_console( ).script_file = self.dwarf_args.script self.console_panel.get_js_console( ).function_content = f.read() self.dwarf.onLogToConsole.connect(self._log_js_output) self.dwarf.onLogEvent.connect(self._log_event) self.console_dock.setWidget(self.console_panel) self.console_dock.setObjectName('ConsoleWidget') self.addDockWidget(Qt.BottomDockWidgetArea, self.console_dock) self.view_menu.addAction(self.console_dock.toggleViewAction()) elem_wiget = self.console_panel elif elem == 'backtrace': from ui.session_widgets.backtrace import BacktraceWidget self.backtrace_dock = QDockWidget('Backtrace', self) self.backtrace_panel = BacktraceWidget(self) self.backtrace_dock.setWidget(self.backtrace_panel) self.backtrace_dock.setObjectName('BacktraceWidget') self.backtrace_panel.onShowMemoryRequest.connect( self._on_showmemory_request) self.addDockWidget(Qt.RightDockWidgetArea, self.backtrace_dock) self.view_menu.addAction(self.backtrace_dock.toggleViewAction()) elem_wiget = self.backtrace_panel elif elem == 'threads': from ui.session_widgets.threads import ThreadsWidget self.threads_dock = QDockWidget('Threads', self) self.contexts_list_panel = ThreadsWidget(self) self.dwarf.onThreadResumed.connect( self.contexts_list_panel.resume_tid) self.contexts_list_panel.onItemDoubleClicked.connect( self._manually_apply_context) self.threads_dock.setWidget(self.contexts_list_panel) self.threads_dock.setObjectName('ThreadPanel') self.addDockWidget(Qt.RightDockWidgetArea, self.threads_dock) self.view_menu.addAction(self.threads_dock.toggleViewAction()) elem_wiget = self.contexts_list_panel elif elem == 'modules': from ui.panels.panel_modules import ModulesPanel self.modules_panel = ModulesPanel(self) self.modules_panel.onModuleSelected.connect( self._on_module_dblclicked) self.modules_panel.onModuleFuncSelected.connect( self._on_modulefunc_dblclicked) self.modules_panel.onAddHook.connect(self._on_addmodule_hook) self.modules_panel.onDumpBinary.connect(self._on_dumpmodule) self.main_tabs.addTab(self.modules_panel, 'Modules') elem_wiget = self.modules_panel elif elem == 'ranges': from ui.panels.panel_ranges import RangesPanel self.ranges_panel = RangesPanel(self) self.ranges_panel.onItemDoubleClicked.connect( self._range_dblclicked) self.ranges_panel.onDumpBinary.connect(self._on_dumpmodule) # connect to watcherpanel func self.ranges_panel.onAddWatcher.connect( self.watchers_panel.do_addwatcher_dlg) self.main_tabs.addTab(self.ranges_panel, 'Ranges') elem_wiget = self.ranges_panel elif elem == 'search': from ui.panels.panel_search import SearchPanel self.search_panel = SearchPanel(self) self.main_tabs.addTab(self.search_panel, 'Search') elem_wiget = self.search_panel elif elem == 'data': from ui.panels.panel_data import DataPanel self.data_panel = DataPanel(self) self.main_tabs.addTab(self.data_panel, 'Data') elem_wiget = self.data_panel elif elem == 'jvm-tracer': from ui.panels.panel_java_trace import JavaTracePanel self.java_trace_panel = JavaTracePanel(self) self.main_tabs.addTab(self.java_trace_panel, 'JVM tracer') elem_wiget = self.java_trace_panel elif elem == 'smali': from ui.panels.panel_smali import SmaliPanel self.smali_panel = SmaliPanel() self.main_tabs.addTab(self.smali_panel, 'Smali') elem_wiget = self.smali_panel else: print('no handler for elem: ' + elem) # make tabs unclosable and sort self._handle_tab_change() if elem_wiget is not None: self.onSystemUIElementCreated.emit(elem, elem_wiget) # TODO: remove add @2x for item in self.findChildren(QDockWidget): if item: if 'darwin' in sys.platform: item.setStyleSheet( 'QDockWidget::title { padding-left:-30px; }') def set_status_text(self, txt): self.statusbar.showMessage(txt) # ************************************************************************ # **************************** Properties ******************************** # ************************************************************************ @property def disassembly(self): return self.asm_panel @property def backtrace(self): return self.backtrace_panel @property def console(self): return self.console_panel @property def context(self): return self.context_panel @property def threads(self): return self.contexts_list_panel @property def ftrace(self): return self.ftrace_panel @property def hooks(self): return self.hooks_panel @property def java_inspector(self): return self.java_inspector_panel @property def java_explorer(self): return self.java_explorer_panel @property def modules(self): return self.modules_panel @property def ranges(self): return self.ranges_panel @property def watchers(self): return self.watchers_panel @property def dwarf(self): if self.session_manager.session is not None: return self.session_manager.session.dwarf else: return None @property def ui_elements(self): return self._ui_elems # ************************************************************************ # **************************** Handlers ********************************** # ************************************************************************ # session handlers def _start_session(self, session_type, session_data=None): if self.welcome_window is not None: self.welcome_window.close() self.session_manager.create_session(session_type, session_data=session_data) def _restore_session(self, session_data): if 'session' in session_data: session_type = session_data['session'] self.dwarf_args.any = session_data['package'] self._start_session(session_type, session_data=session_data) def session_created(self): # session init done create ui for it session = self.session_manager.session self._setup_main_menu() for ui_elem in session.session_ui_sections: ui_elem = ui_elem.join(ui_elem.split()).lower() self._create_ui_elem(ui_elem) self.dwarf.onProcessAttached.connect(self._on_attached) self.dwarf.onProcessDetached.connect(self._on_detached) self.dwarf.onScriptLoaded.connect(self._on_script_loaded) # hookup self.dwarf.onSetRanges.connect(self._on_setranges) self.dwarf.onSetModules.connect(self._on_setmodules) self.dwarf.onAddNativeHook.connect(self._on_add_hook) self.dwarf.onApplyContext.connect(self._apply_context) self.dwarf.onThreadResumed.connect(self.on_tid_resumed) self.dwarf.onSetData.connect(self._on_set_data) self.session_manager.start_session(self.dwarf_args) ui_state = self.q_settings.value('dwarf_ui_state') if ui_state: self.restoreGeometry(ui_state) window_state = self.q_settings.value('dwarf_ui_window', self.saveState()) if window_state: self.restoreState(window_state) self.showMaximized() def session_stopped(self): self.remove_tmp_dir() self.menu.clear() self.main_tabs.clear() # actually we need to kill this. needs a refactor if self.java_trace_panel is not None: self.java_trace_panel = None for elem in self._ui_elems: if elem == 'watchers': self.watchers_panel.clear_list() self.watchers_panel.close() self.watchers_panel = None self.removeDockWidget(self.watchers_dwidget) self.watchers_dwidget = None elif elem == 'hooks': self.hooks_panel.close() self.hooks_panel = None self.removeDockWidget(self.hooks_dwiget) self.hooks_dwiget = None elif elem == 'registers': self.context_panel.close() self.context_panel = None self.removeDockWidget(self.registers_dock) self.registers_dock = None elif elem == 'debug': self.debug_panel.close() self.debug_panel = None self.main_tabs.removeTab(0) # self.main_tabs elif elem == 'jvm-debugger': self.java_explorer_panel.close() self.java_explorer_panel = None self.removeDockWidget(self.watchers_dwidget) elif elem == 'console': self.console_panel.close() self.console_panel = None self.removeDockWidget(self.console_dock) self.console_dock = None elif elem == 'backtrace': self.backtrace_panel.close() self.backtrace_panel = None self.removeDockWidget(self.backtrace_dock) elif elem == 'threads': self.contexts_list_panel.close() self.contexts_list_panel = None self.removeDockWidget(self.threads_dock) self.threads_dock = None elif elem == 'bookmarks': self.bookmarks_panel.close() self.bookmarks_panel = None self.removeDockWidget(self.bookmarks_dwiget) self.bookmarks_dwiget = None self._initialize_ui_elements() def session_closed(self): self._initialize_ui_elements() self.hide() if self.welcome_window is not None: self.welcome_window.exec() # close if it was a commandline session if self.welcome_window is None: if self.dwarf_args.any != '': self.close() # ui handler def closeEvent(self, event): """ Window closed save stuff or whatever at exit detaches dwarf """ # save windowstuff self.q_settings.setValue('dwarf_ui_state', self.saveGeometry()) self.q_settings.setValue('dwarf_ui_window', self.saveState()) if self.dwarf: try: self.dwarf.detach() except: pass super().closeEvent(event) def _on_watcher_clicked(self, ptr): """ Address in Watcher/Hookpanel was clicked show Memory """ if '.' in ptr: # java_hook file_path = ptr.replace('.', os.path.sep) if os.path.exists('.tmp/smali/' + file_path + '.smali'): if self.smali_panel is None: self._create_ui_elem('smali') self.smali_panel.set_file('.tmp/smali/' + file_path + '.smali') self.show_main_tab('smali') else: self.jump_to_address(ptr) def _on_watcher_added(self, ptr): """ Watcher Entry was added """ try: # set highlight self.debug_panel.memory_panel.add_highlight( HighLight('watcher', ptr, self.dwarf.pointer_size)) except HighlightExistsError: pass def _on_watcher_removeditem(self, ptr): """ Watcher Entry was removed remove highlight too """ self.debug_panel.memory_panel.remove_highlight(ptr) def _on_module_dblclicked(self, data): """ Module in ModulePanel was doubleclicked """ addr, size = data addr = utils.parse_ptr(addr) self.jump_to_address(addr) def _on_modulefunc_dblclicked(self, ptr): """ Function in ModulePanel was doubleclicked """ ptr = utils.parse_ptr(ptr) self.jump_to_address(ptr) def _on_dumpmodule(self, data): """ DumpBinary MenuItem in ModulePanel was selected """ ptr, size = data ptr = utils.parse_ptr(ptr) size = int(size, 10) self.dwarf.dump_memory(ptr=ptr, length=size) def _disassemble_range(self, dwarf_range): """ Disassemble MenuItem in Hexview was selected """ if dwarf_range: if self.asm_panel is None: self._create_ui_elem('disassembly') self.asm_panel.disassemble(dwarf_range) self.show_main_tab('disassembly') def _range_dblclicked(self, ptr): """ Range in RangesPanel was doubleclicked """ ptr = utils.parse_ptr(ptr) self.jump_to_address(ptr) # dwarf handlers def _log_js_output(self, output): if self.console_panel is not None: time_prefix = True if len(output.split('\n')) > 1 or len(output.split('<br />')) > 1: time_prefix = False self.console_panel.get_js_console().log(output, time_prefix=time_prefix) def _log_event(self, output): if self.console_panel is not None: self.console_panel.get_events_console().log(output) def _on_setranges(self, ranges): """ Dwarf wants to set Ranges only hooked up to switch tab or create ui its connected in panel after creation """ if self.ranges_panel is None: self.show_main_tab('ranges') # forward only now to panel it connects after creation self.ranges_panel.set_ranges(ranges) def _on_setmodules(self, modules): """ Dwarf wants to set Modules only hooked up to switch tab or create ui its connected in panel after creation """ if self.modules_panel is None: self._create_ui_elem('modules') self.modules_panel.set_modules(modules) if self.modules_panel is not None: self.show_main_tab('modules') def _manually_apply_context(self, context): """ perform additional operation if the context has been manually applied from the context list """ self._apply_context(context, manual=True) 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 reason = context['reason'] is_initial_setup = reason == -1 if manual or (self.dwarf.context_tid and not is_initial_setup): self.dwarf.context_tid = context['tid'] if is_initial_setup: self.debug_panel.on_context_setup() 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 reason == 2: # native on load if self.debug_panel.memory_panel_range is None: base = context['moduleBase'] self.jump_to_address(base) else: if 'pc' in context['context']: if self.debug_panel.disassembly_panel_range is None or manual: self.jump_to_address( context['context']['pc']['value'], view=1) if 'backtrace' in context: self.backtrace_panel.set_backtrace(context['backtrace']) def _on_add_hook(self, hook): try: # set highlight ptr = hook.get_ptr() ptr = utils.parse_ptr(ptr) self.debug_panel.memory_panel.add_highlight( HighLight('hook', ptr, self.dwarf.pointer_size)) except HighlightExistsError: pass def _on_hook_removed(self, ptr): ptr = utils.parse_ptr(ptr) self.debug_panel.memory_panel.remove_highlight(ptr) def _on_addmodule_hook(self, data): ptr, name = data self.dwarf.hook_native(ptr, own_input=name) def on_tid_resumed(self, tid): if self.dwarf: if self.dwarf.context_tid == tid: # clear backtrace if 'backtrace' in self._ui_elems: if self.backtrace_panel is not None: self.backtrace_panel.clear() # remove thread if 'threads' in self._ui_elems: if self.contexts_list_panel is not None: self.contexts_list_panel.resume_tid(tid) # clear registers if 'registers' in self._ui_elems: if self.context_panel is not None: self.context_panel.clear() # clear jvm explorer if 'jvm-debugger' in self._ui_elems: if self.java_explorer_panel is not None: self.java_explorer_panel.clear_panel() # invalidate dwarf context tid self.dwarf.context_tid = 0 def _on_set_data(self, data): if not isinstance(data, list): return if self.data_panel is None: self.show_main_tab('data') if self.data_panel is not None: self.data_panel.append_data(data[0], data[1], data[2]) def show_progress(self, text): self.progressbar.setVisible(True) self.set_status_text(text) def hide_progress(self): self.progressbar.setVisible(False) self.set_status_text('') def _on_attached(self, data): self.setWindowTitle('Dwarf - Attached to %s (%s)' % (data[1], data[0])) def _on_detached(self, data): reason = data[1] if reason == 'application-requested': self.session_manager.session.stop() return 0 if self.dwarf is not None: ret = QDialogDetached.show_dialog(self.dwarf, data[0], data[1], data[2]) if ret == 0: self.dwarf.restart_proc() elif ret == 1: self.session_manager.session.stop() return 0 def _on_script_loaded(self): # restore the loaded session if any self.session_manager.restore_session() def on_add_bookmark(self, ptr): """ provide ptr as int """ if self.bookmarks_panel is not None: self.bookmarks_panel._create_bookmark(ptr=hex(ptr)) def _on_showmemory_request(self, ptr): # its simple ptr show in memorypanel if isinstance(ptr, str): ptr = utils.parse_ptr(ptr) self.jump_to_address(ptr, 0) elif isinstance(ptr, list): # TODO: extend caller, ptr = ptr ptr = utils.parse_ptr(ptr) if caller == 'backtrace' or caller == 'bt': # jumpto in disasm self.jump_to_address(ptr, 1)
class AppWindow(QMainWindow): onRestart = pyqtSignal(name='onRestart') def __init__(self, dwarf_args, flags=None): super(AppWindow, self).__init__(flags) self.dwarf_args = dwarf_args self.session_manager = SessionManager(self) self.session_manager.sessionCreated.connect(self.session_created) self.session_manager.sessionStopped.connect(self.session_stopped) self.session_manager.sessionClosed.connect(self.session_closed) self._tab_order = [ 'memory', 'modules', 'ranges', 'jvm-inspector', 'jvm-debugger' ] self.menu = self.menuBar() self._is_newer_dwarf = False self.view_menu = None #dockwidgets self.watchers_dwidget = None self.hooks_dwiget = None self.bookmarks_dwiget = None self.registers_dock = None self.console_dock = None self.backtrace_dock = None self.threads_dock = None #panels self.asm_panel = None self.console_panel = None self.context_panel = None self.backtrace_panel = None self.contexts_list_panel = None self.data_panel = None self.emulator_panel = None self.ftrace_panel = None self.hooks_panel = None self.bookmarks_panel = None self.smali_panel = None self.java_inspector_panel = None self.java_explorer_panel = None self.java_trace_panel = None self.memory_panel = None self.modules_panel = None self.ranges_panel = None self.search_panel = None self.trace_panel = None self.watchers_panel = None self.welcome_window = None self._ui_elems = [] self.setWindowTitle( 'Dwarf - A debugger for reverse engineers, crackers and security analyst' ) # load external assets _app = QApplication.instance() self.remove_tmp_dir() # themes self.prefs = Prefs() self.set_theme(self.prefs.get('dwarf_ui_theme', 'black')) # load font if os.path.exists(utils.resource_path('assets/Anton.ttf')): QFontDatabase.addApplicationFont( utils.resource_path('assets/Anton.ttf')) if os.path.exists(utils.resource_path('assets/OpenSans-Regular.ttf')): QFontDatabase.addApplicationFont( utils.resource_path('assets/OpenSans-Regular.ttf')) _app.setFont(QFont("OpenSans", 9, QFont.Normal)) if os.path.exists(utils.resource_path('assets/OpenSans-Bold.ttf')): QFontDatabase.addApplicationFont( utils.resource_path('assets/OpenSans-Bold.ttf')) # mainwindow statusbar self.progressbar = QProgressBar() self.progressbar.setRange(0, 0) self.progressbar.setVisible(False) self.progressbar.setFixedHeight(15) self.progressbar.setFixedWidth(100) self.progressbar.setTextVisible(False) self.progressbar.setValue(30) self.statusbar = QStatusBar(self) self.statusbar.setAutoFillBackground(False) self.statusbar.addPermanentWidget(self.progressbar) self.statusbar.setObjectName("statusbar") self.setStatusBar(self.statusbar) self.main_tabs = QTabWidget(self) self.main_tabs.setMovable(False) self.main_tabs.setTabsClosable(True) self.main_tabs.setAutoFillBackground(True) self.main_tabs.tabCloseRequested.connect(self._on_close_tab) self.setCentralWidget(self.main_tabs) if self.dwarf_args.package is None: self.welcome_window = WelcomeDialog(self) self.welcome_window.setModal(True) self.welcome_window.onIsNewerVersion.connect( self._enable_update_menu) self.welcome_window.onUpdateComplete.connect( self._on_dwarf_updated) self.welcome_window.setWindowTitle( 'Welcome to Dwarf - A debugger for reverse engineers, crackers and security analyst' ) self.welcome_window.onSessionSelected.connect(self._start_session) self.welcome_window.onSessionRestore.connect(self._restore_session) # wait for welcome screen self.hide() self.welcome_window.show() else: if dwarf_args.package is not None: if dwarf_args.type is None: # no device given check if package is local path if os.path.exists(dwarf_args.package): print('* Starting new LocalSession') self._start_session('local') else: print('use -t to set sessiontype') exit(0) else: print('* Starting new Session') self._start_session(dwarf_args.type) def _setup_main_menu(self): self.menu = self.menuBar() dwarf_menu = QMenu('Dwarf', self) theme = QMenu('Theme', dwarf_menu) theme.addAction('Black') theme.addAction('Dark') theme.addAction('Light') theme.triggered.connect(self._set_theme) dwarf_menu.addMenu(theme) dwarf_menu.addSeparator() if self._is_newer_dwarf: dwarf_menu.addAction('Update', self._update_dwarf) dwarf_menu.addAction('Close', self.session_manager.session.stop) self.menu.addMenu(dwarf_menu) session = self.session_manager.session if session is not None: session_menu = session.main_menu if isinstance(session_menu, list): for menu in session_menu: self.menu.addMenu(menu) else: self.menu.addMenu(session_menu) self.view_menu = QMenu('View', self) subview_menu = QMenu('Subview', self.view_menu) subview_menu.addAction('Search', lambda: self.show_main_tab('search'), shortcut=QKeySequence(Qt.CTRL + Qt.Key_F3)) subview_menu.addAction('Emulator', lambda: self.show_main_tab('emulator'), shortcut=QKeySequence(Qt.CTRL + Qt.Key_F2)) subview_menu.addAction('Disassembly', lambda: self.show_main_tab('disassembly'), shortcut=QKeySequence(Qt.CTRL + Qt.Key_F5)) self.view_menu.addMenu(subview_menu) self.view_menu.addSeparator() self.menu.addMenu(self.view_menu) if self.dwarf_args.debug_script: debug_menu = QMenu('Debug', self) debug_menu.addAction('Reload core', self._menu_reload_core) debug_menu.addAction('Debug dwarf js core', self._menu_debug_dwarf_js) self.menu.addMenu(debug_menu) about_menu = QMenu('About', self) about_menu.addAction('Dwarf on GitHub', self._menu_github) about_menu.addAction('Documention', self._menu_documentation) about_menu.addAction('Api', self._menu_api) about_menu.addAction('Slack', self._menu_slack) about_menu.addSeparator() about_menu.addAction('Info', self._show_about_dlg) self.menu.addMenu(about_menu) def _enable_update_menu(self): self._is_newer_dwarf = True def _update_dwarf(self): if self.welcome_window: self.welcome_window._update_dwarf() def _on_close_tab(self, index): tab_text = self.main_tabs.tabText(index) if tab_text: if tab_text.lower() in self.session_manager.session.non_closable: return try: self._ui_elems.remove(tab_text.lower()) except ValueError: # recheck ValueError: list.remove(x): x not in list pass self.main_tabs.removeTab(index) def _handle_tab_change(self): for index in range(self.main_tabs.count()): tab_name = self.main_tabs.tabText(index).lower().replace(' ', '-') if tab_name in self.session_manager.session.non_closable: self.main_tabs.tabBar().setTabButton(index, QTabBar.RightSide, None) if tab_name in self._tab_order: should_index = self._tab_order.index(tab_name) if index != should_index: self.main_tabs.tabBar().moveTab(index, should_index) def _on_dwarf_updated(self): self.onRestart.emit() def remove_tmp_dir(self): if os.path.exists('.tmp'): shutil.rmtree('.tmp', ignore_errors=True) def _set_theme(self, qaction): if qaction: self.set_theme(qaction.text()) def _menu_reload_core(self): self.dwarf.load_script() def _menu_debug_dwarf_js(self): you_know_what_to_do = json.loads( self.dwarf._script.exports.debugdwarfjs()) return you_know_what_to_do def show_main_tab(self, name): # elem doesnt exists? create it if name not in self._ui_elems: self._create_ui_elem(name) index = 0 name = name.join(name.split()).lower() if name == 'memory': index = self.main_tabs.indexOf(self.memory_panel) elif name == 'ranges': index = self.main_tabs.indexOf(self.ranges_panel) elif name == 'search': index = self.main_tabs.indexOf(self.search_panel) elif name == 'modules': index = self.main_tabs.indexOf(self.modules_panel) elif name == 'disassembly': index = self.main_tabs.indexOf(self.asm_panel) elif name == 'trace': index = self.main_tabs.indexOf(self.trace_panel) elif name == 'data': index = self.main_tabs.indexOf(self.data_panel) elif name == 'emulator': index = self.main_tabs.indexOf(self.emulator_panel) elif name == 'java-trace': index = self.main_tabs.indexOf(self.java_trace_panel) elif name == 'jvm-inspector': index = self.main_tabs.indexOf(self.java_inspector_panel) elif name == 'jvm-debugger': index = self.main_tabs.indexOf(self.java_explorer_panel) elif name == 'smali': index = self.main_tabs.indexOf(self.smali_panel) self.main_tabs.setCurrentIndex(index) def jump_to_address(self, ptr, show_panel=True): if self.memory_panel is not None: if show_panel: self.show_main_tab('memory') self.memory_panel.read_memory(ptr) @pyqtSlot(name='mainMenuGitHub') def _menu_github(self): QDesktopServices.openUrl(QUrl('https://github.com/iGio90/Dwarf')) @pyqtSlot(name='mainMenuDocumentation') def _menu_api(self): QDesktopServices.openUrl(QUrl('https://igio90.github.io/Dwarf/')) @pyqtSlot(name='mainMenuApi') def _menu_documentation(self): QDesktopServices.openUrl(QUrl('https://igio90.github.io/Dwarf/api')) @pyqtSlot(name='mainMenuSlack') def _menu_slack(self): QDesktopServices.openUrl( QUrl('https://join.slack.com/t/resecret/shared_invite' '/enQtMzc1NTg4MzE3NjA1LTlkNzYxNTIwYTc2ZTYyOWY1MT' 'Q1NzBiN2ZhYjQwYmY0ZmRhODQ0NDE3NmRmZjFiMmE1MDYwN' 'WJlNDVjZDcwNGE')) def _show_about_dlg(self): about_dlg = AboutDialog(self) about_dlg.show() def _create_ui_elem(self, elem): if not isinstance(elem, str): return if elem not in self._ui_elems: self._ui_elems.append(elem) if elem == 'watchers': from ui.panel_watchers import WatchersPanel self.watchers_dwidget = QDockWidget('Watchers', self) self.watchers_panel = WatchersPanel(self) # dont respond to dblclick mem cant be shown # self.watchers_panel.onItemDoubleClicked.connect( # self._on_watcher_clicked) self.watchers_panel.onItemRemoved.connect( self._on_watcher_removeditem) self.watchers_panel.onItemAdded.connect(self._on_watcher_added) self.watchers_dwidget.setWidget(self.watchers_panel) self.watchers_dwidget.setObjectName('WatchersPanel') self.addDockWidget(Qt.LeftDockWidgetArea, self.watchers_dwidget) self.view_menu.addAction(self.watchers_dwidget.toggleViewAction()) elif elem == 'hooks': from ui.panel_hooks import HooksPanel self.hooks_dwiget = QDockWidget('Breakpoints', self) self.hooks_panel = HooksPanel(self) self.hooks_panel.onShowMemoryRequest.connect( self._on_watcher_clicked) self.hooks_panel.onHookRemoved.connect(self._on_hook_removed) self.hooks_dwiget.setWidget(self.hooks_panel) self.hooks_dwiget.setObjectName('HooksPanel') self.addDockWidget(Qt.LeftDockWidgetArea, self.hooks_dwiget) self.view_menu.addAction(self.hooks_dwiget.toggleViewAction()) elif elem == 'bookmarks': from ui.panel_bookmarks import BookmarksPanel self.bookmarks_dwiget = QDockWidget('Boomarks', self) self.bookmarks_panel = BookmarksPanel(self) self.bookmarks_panel.onShowMemoryRequest.connect( self._on_watcher_clicked) self.bookmarks_dwiget.setWidget(self.bookmarks_panel) self.bookmarks_dwiget.setObjectName('BookmarksPanel') self.addDockWidget(Qt.LeftDockWidgetArea, self.bookmarks_dwiget) self.view_menu.addAction(self.bookmarks_dwiget.toggleViewAction()) elif elem == 'registers': from ui.panel_context import ContextPanel self.registers_dock = QDockWidget('Context', self) self.context_panel = ContextPanel(self) self.registers_dock.setWidget(self.context_panel) self.registers_dock.setObjectName('ContextsPanel') self.addDockWidget(Qt.RightDockWidgetArea, self.registers_dock) self.view_menu.addAction(self.registers_dock.toggleViewAction()) elif elem == 'memory': from ui.panel_memory import MemoryPanel self.memory_panel = MemoryPanel(self) self.memory_panel.onShowDisassembly.connect( self._disassemble_range) self.memory_panel.dataChanged.connect(self._on_memory_modified) self.memory_panel.statusChanged.connect(self.set_status_text) self.main_tabs.addTab(self.memory_panel, 'Memory') elif elem == 'jvm-debugger': from ui.panel_java_explorer import JavaExplorerPanel self.java_explorer_panel = JavaExplorerPanel(self) self.main_tabs.addTab(self.java_explorer_panel, 'JVM debugger') self.main_tabs.tabBar().moveTab( self.main_tabs.indexOf(self.java_explorer_panel), 1) elif elem == 'jvm-inspector': from ui.panel_java_inspector import JavaInspector self.java_inspector_panel = JavaInspector(self) self.main_tabs.addTab(self.java_inspector_panel, 'JVM inspector') elif elem == 'console': from ui.panel_console import ConsolePanel self.console_dock = QDockWidget('Console', self) self.console_panel = ConsolePanel(self) self.dwarf.onLogToConsole.connect(self._log_js_output) self.console_dock.setWidget(self.console_panel) self.console_dock.setObjectName('ConsolePanel') self.addDockWidget(Qt.BottomDockWidgetArea, self.console_dock) self.view_menu.addAction(self.console_dock.toggleViewAction()) elif elem == 'backtrace': from ui.panel_backtrace import BacktracePanel self.backtrace_dock = QDockWidget('Backtrace', self) self.backtrace_panel = BacktracePanel(self) self.backtrace_dock.setWidget(self.backtrace_panel) self.backtrace_dock.setObjectName('BacktracePanel') self.backtrace_panel.onShowMemoryRequest.connect( self._on_watcher_clicked) self.addDockWidget(Qt.RightDockWidgetArea, self.backtrace_dock) self.view_menu.addAction(self.backtrace_dock.toggleViewAction()) elif elem == 'threads': from ui.panel_contexts_list import ContextsListPanel self.threads_dock = QDockWidget('Threads', self) self.contexts_list_panel = ContextsListPanel(self) self.dwarf.onThreadResumed.connect( self.contexts_list_panel.resume_tid) self.contexts_list_panel.onItemDoubleClicked.connect( self._manually_apply_context) self.threads_dock.setWidget(self.contexts_list_panel) self.threads_dock.setObjectName('ThreadPanel') self.addDockWidget(Qt.RightDockWidgetArea, self.threads_dock) self.view_menu.addAction(self.threads_dock.toggleViewAction()) elif elem == 'modules': from ui.panel_modules import ModulesPanel self.modules_panel = ModulesPanel(self) self.modules_panel.onModuleSelected.connect( self._on_module_dblclicked) self.modules_panel.onModuleFuncSelected.connect( self._on_modulefunc_dblclicked) self.modules_panel.onAddHook.connect(self._on_addmodule_hook) self.modules_panel.onDumpBinary.connect(self._on_dumpmodule) self.main_tabs.addTab(self.modules_panel, 'Modules') elif elem == 'ranges': from ui.panel_ranges import RangesPanel self.ranges_panel = RangesPanel(self) self.ranges_panel.onItemDoubleClicked.connect( self._range_dblclicked) self.ranges_panel.onDumpBinary.connect(self._on_dumpmodule) # connect to watcherpanel func self.ranges_panel.onAddWatcher.connect( self.watchers_panel.do_addwatcher_dlg) self.main_tabs.addTab(self.ranges_panel, 'Ranges') elif elem == 'search': from ui.panel_search import SearchPanel self.search_panel = SearchPanel(self) self.search_panel.onShowMemoryRequest.connect( self._on_watcher_clicked) self.main_tabs.addTab(self.search_panel, 'Search') elif elem == 'data': from ui.panel_data import DataPanel self.data_panel = DataPanel(self) self.main_tabs.addTab(self.data_panel, 'Data') elif elem == 'trace': from ui.panel_trace import TracePanel self.trace_panel = TracePanel(self) self.main_tabs.addTab(self.trace_panel, 'Trace') elif elem == 'disassembly': from ui.widgets.disasm_view import DisassemblyView self.asm_panel = DisassemblyView(self) self.asm_panel.onShowMemoryRequest.connect(self._on_disasm_showmem) self.main_tabs.addTab(self.asm_panel, 'Disassembly') elif elem == 'emulator': from ui.panel_emulator import EmulatorPanel self.emulator_panel = EmulatorPanel(self) self.main_tabs.addTab(self.emulator_panel, 'Emulator') elif elem == 'java-trace': from ui.panel_java_trace import JavaTracePanel self.java_trace_panel = JavaTracePanel(self) self.main_tabs.addTab(self.java_trace_panel, 'JVM tracer') elif elem == 'smali': from ui.panel_smali import SmaliPanel self.smali_panel = SmaliPanel() self.main_tabs.addTab(self.smali_panel, 'Smali') else: print('no handler for elem: ' + elem) # make tabs unclosable and sort self._handle_tab_change() # TODO: remove add @2x for item in self.findChildren(QDockWidget): if item: if 'darwin' in sys.platform: item.setStyleSheet( 'QDockWidget::title { padding-left:-30px; } QDockWidget::close-button, QDockWidget::float-button { width: 10px; height:10px }' ) def set_theme(self, theme): if theme: theme = theme.replace(os.pardir, '').replace('.', '') theme = theme.join(theme.split()).lower() theme_style = 'assets/' + theme + '_style.qss' if not os.path.exists(utils.resource_path(theme_style)): return self.prefs.put('dwarf_ui_theme', theme) try: _app = QApplication.instance() with open(theme_style) as stylesheet: _app.setStyleSheet(_app.styleSheet() + '\n' + stylesheet.read()) except Exception as e: pass # err = self.dwarf.spawn(dwarf_args.package, dwarf_args.script) def set_status_text(self, txt): self.statusbar.showMessage(txt) # ************************************************************************ # **************************** Properties ******************************** # ************************************************************************ @property def disassembly(self): return self.asm_panel @property def backtrace(self): return self.backtrace_panel @property def console(self): return self.console_panel @property def context(self): return self.context_panel @property def threads(self): return self.contexts_list_panel @property def emulator(self): return self.emulator_panel @property def ftrace(self): return self.ftrace_panel @property def hooks(self): return self.hooks_panel @property def java_inspector(self): return self.java_inspector_panel @property def java_explorer(self): return self.java_explorer_panel @property def memory(self): return self.memory_panel @property def modules(self): return self.memory_panel @property def ranges(self): return self.ranges_panel @property def trace(self): return self.trace_panel @property def watchers(self): return self.watchers_panel @property def dwarf(self): if self.session_manager.session is not None: return self.session_manager.session.dwarf else: return None # ************************************************************************ # **************************** Handlers ********************************** # ************************************************************************ # session handlers def _start_session(self, session_type, session_data=None): if self.welcome_window is not None: self.welcome_window.close() self.session_manager.create_session(session_type, session_data=session_data) def _restore_session(self, session_data): if 'session' in session_data: session_type = session_data['session'] self._start_session(session_type, session_data=session_data) def session_created(self): # session init done create ui for it session = self.session_manager.session self._setup_main_menu() for ui_elem in session.session_ui_sections: ui_elem = ui_elem.join(ui_elem.split()).lower() self._create_ui_elem(ui_elem) self.dwarf.onAttached.connect(self._on_attached) self.dwarf.onScriptLoaded.connect(self._on_script_loaded) # hookup self.dwarf.onSetRanges.connect(self._on_setranges) self.dwarf.onSetModules.connect(self._on_setmodules) self.dwarf.onAddNativeHook.connect(self._on_add_hook) self.dwarf.onApplyContext.connect(self._apply_context) self.dwarf.onThreadResumed.connect(self.on_tid_resumed) self.dwarf.onTraceData.connect(self._on_tracer_data) self.dwarf.onSetData.connect(self._on_set_data) self.session_manager.start_session(self.dwarf_args) q_settings = QSettings("dwarf_window_pos.ini", QSettings.IniFormat) ui_state = q_settings.value('dwarf_ui_state') if ui_state: self.restoreGeometry(ui_state) window_state = q_settings.value('dwarf_ui_window', self.saveState()) if window_state: self.restoreState(window_state) self.showMaximized() def session_stopped(self): self.remove_tmp_dir() self.menu.clear() self.main_tabs.clear() # actually we need to kill this. needs a refactor if self.java_trace_panel is not None: self.java_trace_panel = None for elem in self._ui_elems: if elem == 'watchers': self.watchers_panel.clear_list() self.watchers_panel.close() self.watchers_panel = None self.removeDockWidget(self.watchers_dwidget) self.watchers_dwidget = None elif elem == 'hooks': self.hooks_panel.close() self.hooks_panel = None self.removeDockWidget(self.hooks_dwiget) self.hooks_dwiget = None elif elem == 'registers': self.context_panel.close() self.context_panel = None self.removeDockWidget(self.registers_dock) self.registers_dock = None elif elem == 'memory': self.memory_panel.close() self.memory_panel = None self.main_tabs.removeTab(0) # self.main_tabs elif elem == 'jvm-debugger': self.java_explorer_panel.close() self.java_explorer_panel = None self.removeDockWidget(self.watchers_dwidget) elif elem == 'console': self.console_panel.close() self.console_panel = None self.removeDockWidget(self.console_dock) self.console_dock = None elif elem == 'backtrace': self.backtrace_panel.close() self.backtrace_panel = None self.removeDockWidget(self.backtrace_dock) elif elem == 'threads': self.contexts_list_panel.close() self.contexts_list_panel = None self.removeDockWidget(self.threads_dock) self.threads_dock = None elif elem == 'bookmarks': self.bookmarks_panel.close() self.bookmarks_panel = None self.removeDockWidget(self.bookmarks_dwiget) self.bookmarks_dwiget = None def session_closed(self): self._ui_elems = [] self.hide() if self.welcome_window is not None: self.welcome_window.exec() # close if it was a commandline session if self.welcome_window is None: if self.dwarf_args.package: self.close() # ui handler def closeEvent(self, event): """ Window closed save stuff or whatever at exit detaches dwarf """ # save windowstuff q_settings = QSettings("dwarf_window_pos.ini", QSettings.IniFormat) q_settings.setValue('dwarf_ui_state', self.saveGeometry()) q_settings.setValue('dwarf_ui_window', self.saveState()) if self.dwarf: self.dwarf.detach() super().closeEvent(event) def _on_watcher_clicked(self, ptr): """ Address in Watcher/Hookpanel was clicked show Memory """ if '.' in ptr: # java_hook file_path = ptr.replace('.', os.path.sep) if os.path.exists('.tmp/smali/' + file_path + '.smali'): if self.smali_panel is None: self._create_ui_elem('smali') self.smali_panel.set_file('.tmp/smali/' + file_path + '.smali') self.show_main_tab('smali') else: self.memory_panel.read_memory(ptr=ptr) self.show_main_tab('memory') def _on_disasm_showmem(self, ptr, length): """ Address in Disasm was clicked adds temphighlight for bytes from current instruction """ self.memory_panel.read_memory(ptr) self.memory_panel.add_highlight( HighLight('attention', utils.parse_ptr(ptr), length)) self.show_main_tab('memory') def _on_watcher_added(self, ptr): """ Watcher Entry was added """ try: # set highlight self.memory_panel.add_highlight( HighLight('watcher', ptr, self.dwarf.pointer_size)) except HighlightExistsError: pass def _on_watcher_removeditem(self, ptr): """ Watcher Entry was removed remove highlight too """ self.memory_panel.remove_highlight(ptr) def _on_module_dblclicked(self, data): """ Module in ModulePanel was doubleclicked """ addr, size = data addr = utils.parse_ptr(addr) size = int(size, 10) self.memory_panel.read_memory(ptr=addr, length=size) self.show_main_tab('Memory') def _on_modulefunc_dblclicked(self, ptr): """ Function in ModulePanel was doubleclicked """ ptr = utils.parse_ptr(ptr) self.memory_panel.read_memory(ptr=ptr) self.show_main_tab('Memory') def _on_dumpmodule(self, data): """ DumpBinary MenuItem in ModulePanel was selected """ ptr, size = data ptr = utils.parse_ptr(ptr) size = int(size, 10) self.dwarf.dump_memory(ptr=ptr, length=size) def _disassemble_range(self, mem_range): """ Disassemble MenuItem in Hexview was selected """ if mem_range: if self.asm_panel is None: self._create_ui_elem('disassembly') if mem_range: self.asm_panel.disassemble(mem_range) self.show_main_tab('disassembly') def _range_dblclicked(self, ptr): """ Range in RangesPanel was doubleclicked """ ptr = utils.parse_ptr(ptr) self.memory_panel.read_memory(ptr=ptr) self.show_main_tab('Memory') # dwarf handlers def _log_js_output(self, output): if self.console_panel is not None: self.console_panel.get_js_console().log(output) def _on_setranges(self, ranges): """ Dwarf wants to set Ranges only hooked up to switch tab or create ui its connected in panel after creation """ if self.ranges_panel is None: self.show_main_tab('ranges') # forward only now to panel it connects after creation self.ranges_panel.set_ranges(ranges) def _on_setmodules(self, modules): """ Dwarf wants to set Modules only hooked up to switch tab or create ui its connected in panel after creation """ if self.modules_panel is None: self._create_ui_elem('modules') self.modules_panel.set_modules(modules) if self.modules_panel is not None: self.show_main_tab('modules') def _manually_apply_context(self, context): """ perform additional operation if the context has been manually applied from the context list """ self._apply_context(context, manual=True) def _apply_context(self, context, manual=False): # update current context tid # this should be on top as any further api from js needs to be executed on that thread is_initial_hook = context['reason'] >= 0 if manual or (self.dwarf.context_tid and not is_initial_hook): self.dwarf.context_tid = context['tid'] if 'context' in context: if not manual: self.threads.add_context(context) is_java = context['is_java'] if is_java: if self.java_explorer_panel is None: self._create_ui_elem('jvm-debugger') self.context_panel.set_context(context['ptr'], 1, context['context']) self.java_explorer_panel.set_handle_arg(-1) self.show_main_tab('jvm-debugger') else: self.context_panel.set_context(context['ptr'], 0, context['context']) if 'pc' in context['context']: if not 'disassembly' in self._ui_elems: from lib.range import Range _range = Range(Range.SOURCE_TARGET, self.dwarf) _range.init_with_address( int(context['context']['pc']['value'], 16)) self._disassemble_range(_range) if 'backtrace' in context: self.backtrace_panel.set_backtrace(context['backtrace']) def _on_add_hook(self, hook): try: # set highlight ptr = hook.get_ptr() ptr = utils.parse_ptr(ptr) self.memory_panel.add_highlight( HighLight('hook', ptr, self.dwarf.pointer_size)) except HighlightExistsError: pass def _on_hook_removed(self, ptr): ptr = utils.parse_ptr(ptr) self.memory_panel.remove_highlight(ptr) def _on_addmodule_hook(self, data): ptr, name = data self.dwarf.hook_native(ptr, own_input=name) def on_tid_resumed(self, tid): if self.dwarf: if self.dwarf.context_tid == tid: # clear backtrace if 'backtrace' in self._ui_elems: if self.backtrace_panel is not None: self.backtrace_panel.clear() # remove thread if 'threads' in self._ui_elems: if self.contexts_list_panel is not None: self.contexts_list_panel.resume_tid(tid) # clear registers if 'registers' in self._ui_elems: if self.context_panel is not None: self.context_panel.clear() # clear jvm explorer if 'jvm-debugger' in self._ui_elems: if self.java_explorer_panel is not None: self.java_explorer_panel.clear_panel() # invalidate dwarf context tid self.dwarf.context_tid = 0 def _on_tracer_data(self, data): if not data: return if self.trace_panel is None: self._create_ui_elem('trace') if self.trace_panel is not None: self.show_main_tab('Trace') self.trace_panel.start() trace_events_parts = data[1].split(',') while trace_events_parts: trace_event = TraceEvent(trace_events_parts.pop(0), trace_events_parts.pop(0), trace_events_parts.pop(0), trace_events_parts.pop(0)) self.trace_panel.event_queue.append(trace_event) def _on_set_data(self, data): if not isinstance(data, list): return if self.data_panel is None: self._create_ui_elem('data') if self.data_panel is not None: self.show_main_tab('Data') self.data_panel.append_data(data[0], data[1], data[2]) def show_progress(self, text): self.progressbar.setVisible(True) self.set_status_text(text) def hide_progress(self): self.progressbar.setVisible(False) self.set_status_text('') def _on_attached(self, data): self.setWindowTitle('Dwarf - Attached to %s (%s)' % (data[1], data[0])) def _on_script_loaded(self): # restore the loaded session if any self.session_manager.restore_session() def _on_memory_modified(self, pos, length): data_pos = self.memory_panel.base + pos data = self.memory_panel.data[pos:pos + length] data = [data[0]] # todo: strange js part if self.dwarf.dwarf_api('writeBytes', [data_pos, data]): pass else: utils.show_message_box('Failed to write Memory') def on_add_bookmark(self, ptr): """ provide ptr as int """ if self.bookmarks_panel is not None: self.bookmarks_panel._create_bookmark(ptr=hex(ptr))
class AppWindow(QMainWindow): onRestart = pyqtSignal(name='onRestart') def __init__(self, dwarf_args, flags=None): super(AppWindow, self).__init__(flags) self.dwarf_args = dwarf_args self.session_manager = SessionManager(self) self.session_manager.sessionCreated.connect(self.session_created) self.session_manager.sessionStopped.connect(self.session_stopped) self.session_manager.sessionClosed.connect(self.session_closed) self.menu = self.menuBar() self._is_newer_dwarf = False self.view_menu = None self.asm_panel = None self.console_panel = None self.context_panel = None self.backtrace_panel = None self.contexts_list_panel = None self.data_panel = None self.emulator_panel = None self.ftrace_panel = None self.hooks_panel = None self.smali_panel = None self.java_inspector_panel = None self.java_explorer_panel = None self.java_trace_panel = None self.memory_panel = None self.modules_panel = None self.ranges_panel = None self.search_panel = None self.trace_panel = None self.watchers_panel = None self.welcome_window = None self._ui_elems = [] self.setWindowTitle( 'Dwarf - A debugger for reverse engineers, crackers and security analyst' ) # load external assets _app = QApplication.instance() self.remove_tmp_dir() # themes self.prefs = Prefs() self.set_theme(self.prefs.get('dwarf_ui_theme', 'black')) # set icon if os.name == 'nt': # windows stuff import ctypes try: # write ini to show folder with dwarficon folder_stuff = "[.ShellClassInfo]\nIconResource=assets\dwarf.ico,0\n[ViewState]\nMode=\nVid=\nFolderType=Generic\n" try: with open('desktop.ini', 'w') as ini: ini.writelines(folder_stuff) FILE_ATTRIBUTE_HIDDEN = 0x02 FILE_ATTRIBUTE_SYSTEM = 0x04 # set fileattributes to hidden + systemfile ctypes.windll.kernel32.SetFileAttributesW( r'desktop.ini', FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM) except PermissionError: # its hidden+system already pass # fix for showing dwarf icon in windows taskbar instead of pythonicon _appid = u'iGio90.dwarf.debugger' ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( _appid) if os.path.exists(utils.resource_path('assets/dwarf.png')): _icon = QIcon(utils.resource_path('assets/dwarf.png')) _app.setWindowIcon(_icon) self.setWindowIcon(_icon) except: pass else: if os.path.exists(utils.resource_path('assets/dwarf.png')): _icon = QIcon(utils.resource_path('assets/dwarf.png')) _app.setWindowIcon(_icon) self.setWindowIcon(_icon) # load font if os.path.exists(utils.resource_path('assets/Anton.ttf')): QFontDatabase.addApplicationFont('assets/Anton.ttf') if os.path.exists(utils.resource_path('assets/OpenSans-Regular.ttf')): QFontDatabase.addApplicationFont('assets/OpenSans-Regular.ttf') _app.setFont(QFont("OpenSans", 9, QFont.Normal)) if os.path.exists(utils.resource_path('assets/OpenSans-Bold.ttf')): QFontDatabase.addApplicationFont('assets/OpenSans-Bold.ttf') # mainwindow statusbar self.progressbar = QProgressBar() self.progressbar.setRange(0, 0) self.progressbar.setVisible(False) self.progressbar.setFixedHeight(15) self.progressbar.setFixedWidth(100) self.progressbar.setTextVisible(False) self.progressbar.setValue(30) self.statusbar = QStatusBar(self) self.statusbar.setAutoFillBackground(False) self.statusbar.addPermanentWidget(self.progressbar) self.statusbar.setObjectName("statusbar") self.setStatusBar(self.statusbar) self.main_tabs = QTabWidget(self) self.main_tabs.setMovable(True) self.main_tabs.setAutoFillBackground(True) self.setCentralWidget(self.main_tabs) if self.dwarf_args.package is None: self.welcome_window = WelcomeDialog(self) self.welcome_window.setModal(True) self.welcome_window.onIsNewerVersion.connect( self._enable_update_menu) self.welcome_window.onUpdateComplete.connect( self._on_dwarf_updated) self.welcome_window.setWindowTitle( 'Welcome to Dwarf - A debugger for reverse engineers, crackers and security analyst' ) self.welcome_window.onSessionSelected.connect(self._start_session) # wait for welcome screen self.hide() self.welcome_window.show() else: if dwarf_args.package is not None: if dwarf_args.type is None: # no device given check if package is local path if os.path.exists(dwarf_args.package): print('* Starting new LocalSession') self._start_session('local') else: print('use -t to set sessiontype') exit(0) else: print('* Starting new Session') self._start_session(dwarf_args.type) def _setup_main_menu(self): self.menu = self.menuBar() dwarf_menu = QMenu('Dwarf', self) if self._is_newer_dwarf: dwarf_menu.addAction('Update', self._update_dwarf) dwarf_menu.addAction('Exit', self.session_closed) self.menu.addMenu(dwarf_menu) session = self.session_manager.session if session is not None: session_menu = session.main_menu if isinstance(session_menu, list): for menu in session_menu: self.menu.addMenu(menu) else: self.menu.addMenu(session_menu) self.view_menu = QMenu('View', self) theme = QMenu('Theme', self.view_menu) theme.addAction('Black') theme.addAction('Dark') theme.addAction('Light') theme.triggered.connect(self._set_theme) self.view_menu.addMenu(theme) self.view_menu.addSeparator() self.menu.addMenu(self.view_menu) if self.dwarf_args.debug_script: debug_menu = QMenu('Debug', self) debug_menu.addAction('Reload core', self._menu_reload_core) self.menu.addMenu(debug_menu) about_menu = QMenu('About', self) about_menu.addAction('Dwarf on GitHub', self._menu_github) about_menu.addAction('Documention', self._menu_documentation) about_menu.addAction('Api', self._menu_api) about_menu.addAction('Slack', self._menu_slack) about_menu.addSeparator() about_menu.addAction('Info') self.menu.addMenu(about_menu) def _enable_update_menu(self): self._is_newer_dwarf = True def _update_dwarf(self): if self.welcome_window: self.welcome_window._update_dwarf() def _on_dwarf_updated(self): self.onRestart.emit() def remove_tmp_dir(self): if os.path.exists('.tmp'): shutil.rmtree('.tmp', ignore_errors=True) def _set_theme(self, qaction): if qaction: self.set_theme(qaction.text()) def _menu_reload_core(self): self.dwarf.load_script() def show_main_tab(self, name): index = 0 name = name.join(name.split()).lower() if name == 'memory': index = self.main_tabs.indexOf(self.memory_panel) elif name == 'ranges': index = self.main_tabs.indexOf(self.ranges_panel) elif name == 'search': index = self.main_tabs.indexOf(self.search_panel) elif name == 'modules': index = self.main_tabs.indexOf(self.modules_panel) elif name == 'disassembly': index = self.main_tabs.indexOf(self.asm_panel) elif name == 'trace': index = self.main_tabs.indexOf(self.trace_panel) elif name == 'data': index = self.main_tabs.indexOf(self.data_panel) elif name == 'emulator': index = self.main_tabs.indexOf(self.emulator_panel) elif name == 'java-trace': index = self.main_tabs.indexOf(self.java_trace_panel) elif name == 'java-inspector': index = self.main_tabs.indexOf(self.java_inspector_panel) elif name == 'java-explorer': index = self.main_tabs.indexOf(self.java_explorer_panel) elif name == 'smali': index = self.main_tabs.indexOf(self.smali_panel) self.main_tabs.setCurrentIndex(index) def jump_to_address(self, ptr): if self.memory_panel is not None: self.show_main_tab('memory') self.memory_panel.read_memory(ptr) @pyqtSlot(name='mainMenuGitHub') def _menu_github(self): QDesktopServices.openUrl(QUrl('https://github.com/iGio90/Dwarf')) @pyqtSlot(name='mainMenuDocumentation') def _menu_api(self): QDesktopServices.openUrl(QUrl('https://igio90.github.io/Dwarf/')) @pyqtSlot(name='mainMenuApi') def _menu_documentation(self): QDesktopServices.openUrl(QUrl('https://igio90.github.io/Dwarf/api')) @pyqtSlot(name='mainMenuSlack') def _menu_slack(self): QDesktopServices.openUrl( QUrl('https://join.slack.com/t/resecret/shared_invite' '/enQtMzc1NTg4MzE3NjA1LTlkNzYxNTIwYTc2ZTYyOWY1MT' 'Q1NzBiN2ZhYjQwYmY0ZmRhODQ0NDE3NmRmZjFiMmE1MDYwN' 'WJlNDVjZDcwNGE')) def _create_ui_elem(self, elem): if not isinstance(elem, str): return if elem not in self._ui_elems: self._ui_elems.append(elem) if elem == 'watchers': from ui.panel_watchers import WatchersPanel self.watchers_dwidget = QDockWidget('Watchers', self) self.watchers_panel = WatchersPanel(self) # dont respond to dblclick mem cant be shown # self.watchers_panel.onItemDoubleClicked.connect( # self._on_watcher_clicked) self.watchers_panel.onItemRemoved.connect( self._on_watcher_removeditem) self.watchers_panel.onItemAdded.connect(self._on_watcher_added) self.watchers_dwidget.setWidget(self.watchers_panel) self.watchers_dwidget.setObjectName('WatchersPanel') self.addDockWidget(Qt.LeftDockWidgetArea, self.watchers_dwidget) self.view_menu.addAction(self.watchers_dwidget.toggleViewAction()) elif elem == 'hooks': from ui.panel_hooks import HooksPanel self.hooks_dwiget = QDockWidget('Hooks', self) self.hooks_panel = HooksPanel(self) self.hooks_panel.onShowMemoryRequest.connect( self._on_watcher_clicked) self.hooks_panel.onHookRemoved.connect(self._on_hook_removed) self.hooks_dwiget.setWidget(self.hooks_panel) self.hooks_dwiget.setObjectName('HooksPanel') self.addDockWidget(Qt.LeftDockWidgetArea, self.hooks_dwiget) self.view_menu.addAction(self.hooks_dwiget.toggleViewAction()) elif elem == 'registers': from ui.panel_context import ContextPanel self.registers_dock = QDockWidget('Context', self) self.context_panel = ContextPanel(self) self.registers_dock.setWidget(self.context_panel) self.registers_dock.setObjectName('ContextsPanel') self.addDockWidget(Qt.RightDockWidgetArea, self.registers_dock) self.view_menu.addAction(self.registers_dock.toggleViewAction()) elif elem == 'memory': from ui.panel_memory import MemoryPanel self.memory_panel = MemoryPanel(self) self.memory_panel.onShowDisassembly.connect( self._disassemble_range) self.memory_panel.dataChanged.connect(self._on_memory_modified) self.memory_panel.statusChanged.connect(self.set_status_text) self.main_tabs.addTab(self.memory_panel, 'Memory') elif elem == 'java-explorer': from ui.panel_java_explorer import JavaExplorerPanel self.java_explorer_panel = JavaExplorerPanel(self) self.main_tabs.addTab(self.java_explorer_panel, 'JVM debugger') elif elem == 'java-inspector': from ui.panel_java_inspector import JavaInspector self.java_inspector_panel = JavaInspector(self) self.main_tabs.addTab(self.java_inspector_panel, 'JVM inspector') elif elem == 'console': from ui.panel_console import ConsolePanel self.console_dock = QDockWidget('Console', self) self.console_panel = ConsolePanel(self) self.dwarf.onLogToConsole.connect(self._log_js_output) self.console_dock.setWidget(self.console_panel) self.console_dock.setObjectName('ConsolePanel') self.addDockWidget(Qt.BottomDockWidgetArea, self.console_dock) self.view_menu.addAction(self.console_dock.toggleViewAction()) elif elem == 'backtrace': from ui.panel_backtrace import BacktracePanel self.backtrace_dock = QDockWidget('Backtrace', self) self.backtrace_panel = BacktracePanel(self) self.backtrace_dock.setWidget(self.backtrace_panel) self.backtrace_dock.setObjectName('BacktracePanel') self.backtrace_panel.onShowMemoryRequest.connect( self._on_watcher_clicked) self.addDockWidget(Qt.RightDockWidgetArea, self.backtrace_dock) self.view_menu.addAction(self.backtrace_dock.toggleViewAction()) elif elem == 'threads': from ui.panel_contexts_list import ContextsListPanel self.threads_dock = QDockWidget('Threads', self) self.contexts_list_panel = ContextsListPanel(self) self.dwarf.onThreadResumed.connect( self.contexts_list_panel.resume_tid) self.contexts_list_panel.onItemDoubleClicked.connect( self._apply_context) self.threads_dock.setWidget(self.contexts_list_panel) self.threads_dock.setObjectName('ThreadPanel') self.addDockWidget(Qt.RightDockWidgetArea, self.threads_dock) self.view_menu.addAction(self.threads_dock.toggleViewAction()) elif elem == 'modules': from ui.panel_modules import ModulesPanel self.modules_panel = ModulesPanel(self) self.modules_panel.onModuleSelected.connect( self._on_module_dblclicked) self.modules_panel.onModuleFuncSelected.connect( self._on_modulefunc_dblclicked) self.modules_panel.onAddHook.connect(self._on_addmodule_hook) self.modules_panel.onDumpBinary.connect(self._on_dumpmodule) self.main_tabs.addTab(self.modules_panel, 'Modules') elif elem == 'ranges': from ui.panel_ranges import RangesPanel self.ranges_panel = RangesPanel(self) self.ranges_panel.onItemDoubleClicked.connect( self._range_dblclicked) self.ranges_panel.onDumpBinary.connect(self._on_dumpmodule) # connect to watcherpanel func self.ranges_panel.onAddWatcher.connect( self.watchers_panel.do_addwatcher_dlg) self.main_tabs.addTab(self.ranges_panel, 'Ranges') elif elem == 'search': from ui.panel_search import SearchPanel self.search_panel = SearchPanel(self) self.search_panel.onShowMemoryRequest.connect( self._on_watcher_clicked) self.main_tabs.addTab(self.search_panel, 'Search') elif elem == 'data': from ui.panel_data import DataPanel self.data_panel = DataPanel(self) self.main_tabs.addTab(self.data_panel, 'Data') elif elem == 'trace': from ui.panel_trace import TracePanel self.trace_panel = TracePanel(self) self.main_tabs.addTab(self.trace_panel, 'Trace') elif elem == 'disassembly': from ui.disasm_view import DisassemblyView self.asm_panel = DisassemblyView(self) self.asm_panel.onShowMemoryRequest.connect(self._on_disasm_showmem) self.main_tabs.addTab(self.asm_panel, 'Disassembly') elif elem == 'emulator': from ui.panel_emulator import EmulatorPanel self.emulator_panel = EmulatorPanel(self) self.main_tabs.addTab(self.emulator_panel, 'Emulator') elif elem == 'java-trace': from ui.panel_java_trace import JavaTracePanel self.java_trace_panel = JavaTracePanel(self) self.main_tabs.addTab(self.java_trace_panel, 'JVM tracer') elif elem == 'smali': from ui.panel_smali import SmaliPanel self.smali_panel = SmaliPanel() self.main_tabs.addTab(self.smali_panel, 'Smali') else: print('no handler for elem: ' + elem) def set_theme(self, theme): if theme: theme = theme.replace(os.pardir, '').replace('.', '') theme = theme.join(theme.split()).lower() theme_style = 'assets/' + theme + '_style.qss' if not os.path.exists(utils.resource_path(theme_style)): return self.prefs.put('dwarf_ui_theme', theme) try: _app = QApplication.instance() with open(theme_style) as stylesheet: _app.setStyleSheet(_app.styleSheet() + '\n' + stylesheet.read()) except Exception as e: pass #err = self.dwarf.spawn(dwarf_args.package, dwarf_args.script) def set_status_text(self, txt): self.statusbar.showMessage(txt) # ************************************************************************ # **************************** Properties ******************************** # ************************************************************************ @property def disassembly(self): return self.asm_panel @property def backtrace(self): return self.backtrace_panel @property def console(self): return self.console_panel @property def context(self): return self.context_panel @property def threads(self): return self.contexts_list_panel @property def emulator(self): return self.emulator_panel @property def ftrace(self): return self.ftrace_panel @property def hooks(self): return self.hooks_panel @property def java_inspector(self): return self.java_inspector_panel @property def java_explorer(self): return self.java_explorer_panel @property def memory(self): return self.memory_panel @property def modules(self): return self.memory_panel @property def ranges(self): return self.ranges_panel @property def trace(self): return self.trace_panel @property def watchers(self): return self.watchers_panel @property def dwarf(self): if self.session_manager.session is not None: return self.session_manager.session.dwarf else: return None # ************************************************************************ # **************************** Handlers ********************************** # ************************************************************************ # session handlers def _start_session(self, session_type): if self.welcome_window is not None: self.welcome_window.close() self.session_manager.create_session(session_type) def session_created(self): # session init done create ui for it session = self.session_manager.session self._setup_main_menu() for ui_elem in session.session_ui_sections: ui_elem = ui_elem.join(ui_elem.split()).lower() self._create_ui_elem(ui_elem) self.dwarf.onAttached.connect(self._on_attached) # hookup self.dwarf.onSetRanges.connect(self._on_setranges) self.dwarf.onSetModules.connect(self._on_setmodules) self.dwarf.onAddNativeHook.connect(self._on_add_hook) self.dwarf.onApplyContext.connect(self._apply_context) self.dwarf.onThreadResumed.connect(self.on_tid_resumed) self.dwarf.onTraceData.connect(self._on_tracer_data) self.dwarf.onSetData.connect(self._on_set_data) self.session_manager.start_session(self.dwarf_args) q_settings = QSettings("dwarf_window_pos.ini", QSettings.IniFormat) ui_state = q_settings.value('dwarf_ui_state') if ui_state: self.restoreGeometry(ui_state) window_state = q_settings.value('dwarf_ui_window', self.saveState()) if window_state: self.restoreState(window_state) self.showMaximized() def session_stopped(self): self.remove_tmp_dir() self.menu.clear() self.main_tabs.clear() for elem in self._ui_elems: if elem == 'watchers': self.watchers_panel.clear_list() self.watchers_panel.close() self.watchers_panel = None self.removeDockWidget(self.watchers_dwidget) self.watchers_dwidget = None elif elem == 'hooks': self.hooks_panel.close() self.hooks_panel = None self.removeDockWidget(self.hooks_dwiget) self.hooks_dwiget = None elif elem == 'registers': self.context_panel.close() self.context_panel = None self.removeDockWidget(self.registers_dock) self.registers_dock = None elif elem == 'memory': self.memory_panel.close() self.memory_panel = None self.main_tabs.removeTab(0) # self.main_tabs elif elem == 'java-explorer': self.java_explorer_panel.close() self.java_explorer_panel = None self.removeDockWidget(self.watchers_dwidget) elif elem == 'console': self.console_panel.close() self.console_panel = None self.removeDockWidget(self.console_dock) self.console_dock = None elif elem == 'backtrace': self.backtrace_panel.close() self.backtrace_panel = None self.removeDockWidget(self.backtrace_dock) elif elem == 'threads': self.contexts_list_panel.close() self.contexts_list_panel = None self.removeDockWidget(self.threads_dock) self.threads_dock = None def session_closed(self): self._ui_elems = [] self.hide() if self.welcome_window is not None: self.welcome_window.exec() # close if it was a commandline session if self.welcome_window is None: if self.dwarf_args.package: self.close() # ui handler def closeEvent(self, event): """ Window closed save stuff or whatever at exit detaches dwarf """ # save windowstuff q_settings = QSettings("dwarf_window_pos.ini", QSettings.IniFormat) q_settings.setValue('dwarf_ui_state', self.saveGeometry()) q_settings.setValue('dwarf_ui_window', self.saveState()) if self.dwarf: self.dwarf.detach() super().closeEvent(event) def _on_watcher_clicked(self, ptr): """ Address in Watcher/Hookpanel was clicked show Memory """ if '.' in ptr: # java_hook file_path = ptr.replace('.', os.path.sep) if os.path.exists('.tmp/smali/' + file_path + '.smali'): if self.smali_panel is None: self._create_ui_elem('smali') self.smali_panel.set_file('.tmp/smali/' + file_path + '.smali') self.show_main_tab('smali') else: self.memory_panel.read_memory(ptr=ptr) self.show_main_tab('memory') def _on_disasm_showmem(self, ptr, length): """ Address in Disasm was clicked adds temphighlight for bytes from current instruction """ self.memory_panel.read_memory(ptr) self.memory_panel.add_highlight( HighLight('attention', utils.parse_ptr(ptr), length)) self.show_main_tab('memory') def _on_watcher_added(self, ptr): """ Watcher Entry was added """ try: # set highlight self.memory_panel.add_highlight( HighLight('watcher', ptr, self.dwarf.pointer_size)) except HighlightExistsError: pass def _on_watcher_removeditem(self, ptr): """ Watcher Entry was removed remove highlight too """ self.memory_panel.remove_highlight(ptr) def _on_module_dblclicked(self, data): """ Module in ModulePanel was doubleclicked """ addr, size = data addr = utils.parse_ptr(addr) size = int(size, 10) self.memory_panel.read_memory(ptr=addr, length=size) self.show_main_tab('Memory') def _on_modulefunc_dblclicked(self, ptr): """ Function in ModulePanel was doubleclicked """ ptr = utils.parse_ptr(ptr) self.memory_panel.read_memory(ptr=ptr) self.show_main_tab('Memory') def _on_dumpmodule(self, data): """ DumpBinary MenuItem in ModulePanel was selected """ ptr, size = data ptr = utils.parse_ptr(ptr) size = int(size, 10) self.dwarf.dump_memory(ptr=ptr, length=size) def _disassemble_range(self, mem_range): """ Disassemble MenuItem in Hexview was selected """ if mem_range: if self.asm_panel is None: self._create_ui_elem('disassembly') if mem_range: self.asm_panel.disassemble(mem_range) self.show_main_tab('disassembly') def _range_dblclicked(self, ptr): """ Range in RangesPanel was doubleclicked """ ptr = utils.parse_ptr(ptr) self.memory_panel.read_memory(ptr=ptr) self.show_main_tab('Memory') # dwarf handlers def _log_js_output(self, output): if self.console_panel is not None: self.console_panel.get_js_console().log(output) def _on_setranges(self, ranges): """ Dwarf wants to set Ranges only hooked up to switch tab or create ui its connected in panel after creation """ if self.ranges_panel is None: self._create_ui_elem('ranges') # forward only now to panel it connects after creation self.ranges_panel.set_ranges(ranges) # once we got ranges in place from our target we can create the search panel as well if self.search_panel is None: self._create_ui_elem('search') self.search_panel.set_ranges(ranges) if self.ranges_panel is not None: self.show_main_tab('ranges') def _on_setmodules(self, modules): """ Dwarf wants to set Modules only hooked up to switch tab or create ui its connected in panel after creation """ if self.modules_panel is None: self._create_ui_elem('modules') self.modules_panel.set_modules(modules) if self.modules_panel is not None: self.show_main_tab('modules') def _apply_context(self, context): if 'context' in context: is_java = context['is_java'] if is_java: if self.java_explorer_panel is None: self._create_ui_elem('java-explorer') self.context_panel.set_context(context['ptr'], 1, context['context']) self.java_explorer_panel.set_handle_arg(-1) self.show_main_tab('java-explorer') else: self.context_panel.set_context(context['ptr'], 0, context['context']) if 'backtrace' in context: self.backtrace_panel.set_backtrace(context['backtrace']) # update current context tid self.dwarf.context_tid = context['tid'] def _on_add_hook(self, hook): try: # set highlight ptr = hook.get_ptr() ptr = utils.parse_ptr(ptr) self.memory_panel.add_highlight( HighLight('hook', ptr, self.dwarf.pointer_size)) except HighlightExistsError: pass def _on_hook_removed(self, ptr): ptr = utils.parse_ptr(ptr) self.memory_panel.remove_highlight(ptr) def _on_addmodule_hook(self, data): ptr, name = data self.dwarf.hook_native(ptr, own_input=name) def on_tid_resumed(self, tid): if self.dwarf: if self.dwarf.context_tid == tid: # clear backtrace if 'backtrace' in self._ui_elems: if self.backtrace_panel is not None: self.backtrace_panel.clear() # remove thread if 'threads' in self._ui_elems: if self.contexts_list_panel is not None: self.contexts_list_panel.resume_tid(tid) # clear registers if 'registers' in self._ui_elems: if self.context_panel is not None: self.context_panel.clear() # clear jvm explorer if 'java-explorer' in self._ui_elems: if self.java_explorer_panel is not None: self.java_explorer_panel.clear_panel() def _on_tracer_data(self, data): if not data: return if self.trace_panel is None: self._create_ui_elem('trace') if self.trace_panel is not None: self.show_main_tab('Trace') self.trace_panel.start() trace_events_parts = data[1].split(',') while trace_events_parts: trace_event = TraceEvent(trace_events_parts.pop(0), trace_events_parts.pop(0), trace_events_parts.pop(0), trace_events_parts.pop(0)) self.trace_panel.event_queue.append(trace_event) def _on_set_data(self, data): if not isinstance(data, list): return if self.data_panel is None: self._create_ui_elem('data') if self.data_panel is not None: self.show_main_tab('Data') self.data_panel.append_data(data[0], data[1], data[2]) def show_progress(self, text): self.progressbar.setVisible(True) self.set_status_text(text) def hide_progress(self): self.progressbar.setVisible(False) self.set_status_text('') def _on_attached(self, data): self.setWindowTitle('Dwarf - Attached to %s (%s)' % (data[1], data[0])) def _on_memory_modified(self, pos, length): data_pos = self.memory_panel.base + pos data = self.memory_panel.data[pos:pos + length] data = [data[0]] # todo: strange js part if self.dwarf.dwarf_api('writeBytes', [data_pos, data]): pass else: utils.show_message_box('Failed to write Memory')
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.debug_panel = None self.setAutoFillBackground(True) self._running_disasm = False # 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._longest_bytes = 0 self._longest_mnemonic = 0 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
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
class Emulator(QThread): class EmulatorSetupFailedError(Exception): """ Setup Failed """ class EmulatorAlreadyRunningError(Exception): """ isrunning """ onEmulatorSetup = pyqtSignal(list, name='onEmulatorSetup') onEmulatorStart = pyqtSignal(name='onEmulatorStart') onEmulatorStop = pyqtSignal(name='onEmulatorStop') onEmulatorStep = pyqtSignal(name='onEmulatorStep') onEmulatorHook = pyqtSignal(Instruction, name='onEmulatorHook') onEmulatorMemoryHook = pyqtSignal(list, name='onEmulatorMemoryHook') onEmulatorMemoryRangeMapped = pyqtSignal( list, name='onEmulatorMemoryRangeMapped') onEmulatorLog = pyqtSignal(str, name='onEmulatorLog') # setup errors ERR_INVALID_TID = 1 ERR_INVALID_CONTEXT = 2 ERR_SETUP_FAILED = 3 def __init__(self, dwarf): super(Emulator, self).__init__() self.setTerminationEnabled(True) self.dwarf = dwarf self._prefs = Prefs() self._setup_done = False self._blacklist_regs = [] self.cs = None self.uc = None self.context = None self.thumb = False self.end_ptr = 0 self.step_mode = STEP_MODE_NONE self.current_context = None self._current_instruction = 0 self._next_instruction = 0 self._current_cpu_mode = 0 self._request_stop = False # configurations self.callbacks_path = None self.callbacks = None self.instructions_delay = 0 self._start_address = 0 self._end_address = 0 # prevent emulator loop for any reason # i.e through custom callback # we don't want any UI freeze, so we just setup a n00b way to check if we are looping # inside the same instruction for N times. # notice that when an unmapped memory region is required during emulation, this will be taken from target proc # and mapped into unicorn context. Later, the code fallback to execute the same instruction once again self._anti_loop = 0 def setup_arm(self): self.thumb = self.context.pc.thumb if self.thumb: self._current_cpu_mode = unicorn.UC_MODE_THUMB self.cs = Cs(CS_ARCH_ARM, CS_MODE_THUMB) self.uc = unicorn.Uc(unicorn.UC_ARCH_ARM, unicorn.UC_MODE_THUMB) # Enable VFP instr self.uc.mem_map(0x1000, 1024) self.uc.mem_write(0x1000, binascii.unhexlify(VFP)) self.uc.emu_start(0x1000 | 1, 0x1000 + len(VFP)) self.uc.mem_unmap(0x1000, 1024) else: self.cs = Cs(CS_ARCH_ARM, CS_MODE_ARM) self.uc = unicorn.Uc(unicorn.UC_ARCH_ARM, unicorn.UC_MODE_ARM) self._current_cpu_mode = unicorn.UC_MODE_ARM def setup_arm64(self): self.uc = unicorn.Uc(unicorn.UC_ARCH_ARM64, unicorn.UC_MODE_LITTLE_ENDIAN) self.cs = Cs(CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN) self._current_cpu_mode = unicorn.UC_MODE_LITTLE_ENDIAN def setup_x86(self): self.uc = unicorn.Uc(unicorn.UC_ARCH_X86, unicorn.UC_MODE_32) self.cs = Cs(CS_ARCH_X86, CS_MODE_32) def setup_x64(self): self.uc = unicorn.Uc(unicorn.UC_ARCH_X86, unicorn.UC_MODE_64) self.cs = Cs(CS_ARCH_X86, CS_MODE_64) def _setup(self, user_arch=None, user_mode=None, cs_arch=None, cs_mode=None): if user_arch is not None and user_mode is not None: try: self.uc = unicorn.Uc(user_arch, user_mode) self.cs = Cs(cs_arch, cs_mode) self.thumb = user_mode == unicorn.UC_MODE_THUMB except: raise self.EmulatorSetupFailedError('Unsupported arch') else: if self.dwarf.arch == 'arm': self.setup_arm() elif self.dwarf.arch == 'arm64': self.setup_arm64() elif self.dwarf.arch == 'ia32': self.setup_x86() elif self.dwarf.arch == 'x64': self.setup_x64() else: # unsupported arch raise self.EmulatorSetupFailedError('Unsupported arch') if not self.uc or not self.cs: raise self.EmulatorSetupFailedError('Unicorn or Capstone missing') # enable capstone details if self.cs is not None: self.cs.detail = True if not self.context.is_native_context: raise self.EmulatorSetupFailedError( 'Cannot run emulator on non-native context') err = self.map_range(self.context.pc.value) if err: raise self.EmulatorSetupFailedError('Mapping failed') self.current_context = EmulatorContext(self.dwarf) for reg in self.current_context._unicorn_registers: if reg in self.context.__dict__: if reg not in self._blacklist_regs: self.uc.reg_write( self.current_context._unicorn_registers[reg], self.context.__dict__[reg].value) self.uc.hook_add(unicorn.UC_HOOK_CODE, self.hook_code) self.uc.hook_add(unicorn.UC_HOOK_MEM_WRITE | unicorn.UC_HOOK_MEM_READ, self.hook_mem_access) self.uc.hook_add( unicorn.UC_HOOK_MEM_FETCH_UNMAPPED | unicorn.UC_HOOK_MEM_WRITE_UNMAPPED | unicorn.UC_HOOK_MEM_READ_UNMAPPED, self.hook_unmapped) self.current_context.set_context(self.uc) return 0 def run(self): # dont call this func if not self._setup_done: return try: if self.thumb and self._start_address % 2 != 1: self._start_address += 1 self.uc.emu_start( self._start_address, 0xffffffffffffffff) # end is handled in hook_code except unicorn.UcError as e: self.log_to_ui('[*] error: ' + str(e)) except Exception as e: self.log_to_ui('[*] error: ' + str(e)) self._setup_done = False self.onEmulatorStop.emit() def api(self, parts): """ expose api to js side for allowing emulator interaction while scripting :param parts: arr -> cmd api split by ":::" :return: the result from the api """ cmd = parts[0] if cmd == 'clean': return self.clean() elif cmd == 'setup': custom_uc_arch = None custom_cs_arch = None custom_uc_mode = None custom_cs_mode = None if len(parts) > 3: try: arch = 'UC_ARCH_' + parts[2].upper() if arch in unicorn.__dict__: custom_uc_arch = unicorn.__dict__[arch] arch = 'CS_ARCH_' + parts[2].upper() custom_cs_arch = capstone.__dict__[arch] mode = 'UC_MODE_' + parts[3].upper() if mode in unicorn.__dict__: custom_uc_mode = unicorn.__dict__[mode] mode = 'CS_MODE_' + parts[3].upper() custom_cs_mode = capstone.__dict__[mode] except: custom_uc_arch = None custom_cs_arch = None custom_uc_mode = None custom_cs_mode = None if custom_uc_arch is not None and custom_uc_mode is not None: err = self.setup(parts[1], user_arch=custom_uc_arch, user_mode=custom_uc_mode, cs_arch=custom_cs_arch, cs_mode=custom_cs_mode) else: err = self.setup(parts[1]) if err > 0: self.context = None return err elif cmd == 'start': until = 0 if len(parts) > 1: try: until = int(parts[1]) except: pass return self.emulate(until=until) elif cmd == 'step': step_mode = STEP_MODE_SINGLE if len(parts) > 1: try: step_mode = int(parts[1]) except: pass return self.emulate(step_mode=step_mode) def clean(self): if self.isRunning(): raise self.EmulatorAlreadyRunningError() self._current_instruction = 0 self._next_instruction = 0 self._current_cpu_mode = 0 self.context = None return 0 def hook_code(self, uc, address, size, user_data): # QApplication.processEvents() if self._request_stop: self.log_to_ui('Error: Emulator stopped - reached end') self.stop() return # anti loop checks if self._anti_loop == address: self.log_to_ui('Error: Emulator stopped - looping') self.stop() return self._current_instruction = address # check if pc/eip is end_ptr pc = 0 # address should be pc too ??? if self.dwarf.arch == 'arm': pc = uc.reg_read(unicorn.arm_const.UC_ARM_REG_PC) elif self.dwarf.arch == 'arm64': pc = uc.reg_read(unicorn.arm64_const.UC_ARM64_REG_PC) elif self.dwarf.arch == 'ia32': pc = uc.reg_read(unicorn.x86_const.UC_X86_REG_EIP) elif self.dwarf.arch == 'x64': pc = uc.reg_read(unicorn.x86_const.UC_X86_REG_RIP) if self.thumb: pc = pc | 1 if pc == self._end_address: self._request_stop = True # set the current context self.current_context.set_context(uc) instruction = None try: try: data = bytes(uc.mem_read(address, size)) assembly = self.cs.disasm(data, address) except: self.log_to_ui('Error: Emulator stopped - disasm') self.stop() return for i in assembly: # QApplication.processEvents() instruction = Instruction(self.dwarf, i, context=self.current_context) self.onEmulatorHook.emit(instruction) if self.callbacks is not None: try: self.callbacks.hook_code(self, instruction, address, size) except: # hook code not implemented in callbacks pass if not instruction.is_jump and not instruction.is_call: self._next_instruction = address + i.size else: if instruction.is_call: self._next_instruction = instruction.call_address elif instruction.is_jump: self._next_instruction = instruction.jump_address if instruction.should_change_arm_instruction_set: if self.thumb: self._current_cpu_mode = unicorn.UC_MODE_ARM self.thumb = False else: self._current_cpu_mode = unicorn.UC_MODE_THUMB self.thumb = True self.cs.mode(self._current_cpu_mode) break # time.sleep(self.instructions_delay) except: self.log_to_ui('Error: Emulator stopped') self.stop() return if self.step_mode != STEP_MODE_NONE: if self.step_mode == STEP_MODE_SINGLE: self.stop() elif self.step_mode == STEP_MODE_FUNCTION: if instruction is not None and instruction.is_call: self.stop() elif self.step_mode == STEP_MODE_JUMP: if instruction is not None and instruction.is_jump: self.stop() def hook_mem_access(self, uc, access, address, size, value, user_data): v = value if access == unicorn.UC_MEM_READ: v = int.from_bytes(uc.mem_read(address, size), 'little') self.onEmulatorMemoryHook.emit([uc, access, address, v]) if self.callbacks is not None: try: self.callbacks.hook_memory_access(self, access, address, size, v) except: # hook code not implemented in callbacks pass def hook_unmapped(self, uc, access, address, size, value, user_data): self.log_to_ui( "[*] Trying to access an unmapped memory address at 0x%x" % address) err = self.map_range(address) if err > 0: self.log_to_ui('[*] Error %d mapping range at %s' % (err, hex(address))) return False return True def invalida_configurations(self): self.callbacks_path = self._prefs.get(prefs.EMULATOR_CALLBACKS_PATH, '') self.instructions_delay = self._prefs.get( prefs.EMULATOR_INSTRUCTIONS_DELAY, 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 def setup(self, tid=0, user_arch=None, user_mode=None, cs_arch=None, cs_mode=None): if tid == 0: # get current context tid if none provided tid = self.dwarf.context_tid # make sure it's int < pp: why make sure its int and then using str(tid) later?? # when calling from api its str if isinstance(tid, str): try: tid = int(tid) except ValueError: return self.ERR_INVALID_TID if not isinstance(tid, int): return self.ERR_INVALID_TID self.context = None if str(tid) in self.dwarf.contexts: self.context = self.dwarf.contexts[str(tid)] if tid == 0 or self.context is None or not self.context.is_native_context: # prevent emulation if out-of-context return self.ERR_INVALID_CONTEXT try: self._setup(user_arch=user_arch, user_mode=user_mode, cs_arch=cs_arch, cs_mode=cs_mode) self.onEmulatorSetup.emit([user_arch, user_mode]) except self.EmulatorSetupFailedError: return self.ERR_SETUP_FAILED return 0 def start(self, priority=QThread.HighPriority): # dont call this func if not self._setup_done: return return super().start(priority=priority) def emulate(self, until=0, step_mode=STEP_MODE_NONE, user_arch=None, user_mode=None, cs_arch=None, cs_mode=None): if self.isRunning(): raise self.EmulatorAlreadyRunningError() if isinstance(until, str): try: until = int(until, 16) except ValueError: until = 0 if until and isinstance(until, int): self.end_ptr = utils.parse_ptr(until) if self.end_ptr == 0: # invalid end pointer raise self.EmulatorSetupFailedError('Invalid EndPtr') if self.context is None: err = self.setup(user_arch=user_arch, user_mode=user_mode, cs_arch=cs_arch, cs_mode=cs_mode) if err > 0: # make sure context is None if setup failed for any reason. we want a clean setup later self.context = None err_msg = 'unhandled error' if err == self.ERR_INVALID_TID: err_msg = 'invalid thread id' elif err == self.ERR_INVALID_CONTEXT: err_msg = 'invalid context' raise self.EmulatorSetupFailedError('Setup failed: %s' % err_msg) # calculate the start address address = self._next_instruction if address == 0: if self.uc._arch == unicorn.UC_ARCH_ARM: address = self.uc.reg_read(unicorn.arm_const.UC_ARM_REG_PC) elif self.uc._arch == unicorn.UC_ARCH_ARM64: address = self.uc.reg_read(unicorn.arm64_const.UC_ARM64_REG_PC) elif self.uc._arch == unicorn.UC_ARCH_X86 and self.uc._mode == unicorn.UC_MODE_32: address = self.uc.reg_read(unicorn.x86_const.UC_X86_REG_EIP) elif self.uc._arch == unicorn.UC_ARCH_X86 and self.uc._mode == unicorn.UC_MODE_64: address = self.uc.reg_read(unicorn.x86_const.UC_X86_REG_RIP) else: raise self.EmulatorSetupFailedError('Unsupported arch') if until > 0: self.log_to_ui('[*] start emulation from %s to %s' % (hex(address), hex(self.end_ptr))) else: if step_mode == STEP_MODE_NONE or step_mode == STEP_MODE_SINGLE: self.log_to_ui('[*] stepping %s' % hex(address)) elif step_mode == STEP_MODE_FUNCTION: self.log_to_ui('[*] stepping to next function call') elif step_mode == STEP_MODE_JUMP: self.log_to_ui('[*] stepping to next jump') self.onEmulatorStart.emit() # invalidate prefs before start self.invalida_configurations() # load callbacks if needed if self.callbacks_path is not None and self.callbacks_path != '': try: spec = spec_from_loader( "callbacks", SourceFileLoader("callbacks", self.callbacks_path)) self.callbacks = module_from_spec(spec) spec.loader.exec_module(self.callbacks) except Exception as e: self.log_to_ui('[*] failed to load callbacks: %s' % str(e)) # reset callbacks path self._prefs.put(prefs.EMULATOR_CALLBACKS_PATH, '') self.callbacks_path = '' self.callbacks = None else: self.callbacks = None # until is 0 (i.e we are stepping) if until == 0 and step_mode == STEP_MODE_NONE: self.step_mode = STEP_MODE_SINGLE else: self.step_mode = step_mode self._start_address = address if self.thumb: if self._start_address % 2 == 0: self._start_address = self._start_address | 1 else: if self._start_address % 2 != 0: self._start_address -= 1 self._end_address = self.end_ptr self._setup_done = True self.start() def stop(self): if self.isRunning(): self.uc.emu_stop() def log_to_ui(self, what): self.onEmulatorLog.emit(what)
def run_dwarf(): """ fire it up """ #os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" os.environ["QT_SCALE_FACTOR"] = "1" os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "0" os.environ["QT_SCREEN_SCALE_FACTORS"] = "1" args = process_args() #_check_dependencies() # not enabled atm from lib import utils from lib.git import Git from lib.prefs import Prefs from ui.app import AppWindow _prefs = Prefs() local_update_disabled = _prefs.get('disable_local_frida_update', False) if not local_update_disabled: _git = Git() import frida remote_frida = _git.get_frida_version() local_frida = frida.__version__ if remote_frida and local_frida != remote_frida[0]['tag_name']: print('Updating local frida version to ' + remote_frida[0]['tag_name']) try: res = utils.do_shell_command('pip3 install frida --upgrade --user') if 'Successfully installed frida-' + remote_frida[0]['tag_name'] in res: _on_restart() elif 'Requirement already up-to-date' in res: if os.path.exists('.git_cache'): shutil.rmtree('.git_cache', ignore_errors=True) else: print('failed to update local frida') print(res) except Exception as e: # pylint: disable=broad-except, invalid-name print('failed to update local frida') print(str(e)) if os.name == 'nt': # windows stuff import ctypes try: # write ini to show folder with dwarficon folder_stuff = "[.ShellClassInfo]\n" folder_stuff += "IconResource=assets\\dwarf.ico,0\n" folder_stuff += "[ViewState]\n" folder_stuff += "Mode=\n" folder_stuff += "Vid=\n" folder_stuff += "FolderType=Generic\n" try: with open('desktop.ini', 'w') as ini: ini.writelines(folder_stuff) # set fileattributes to hidden + systemfile ctypes.windll.kernel32.SetFileAttributesW( r'desktop.ini', 0x02 | 0x04 ) # FILE_ATTRIBUTE_HIDDEN = 0x02 | FILE_ATTRIBUTE_SYSTEM = 0x04 except PermissionError: # its hidden+system already pass # fix for showing dwarf icon in windows taskbar instead of pythonicon _appid = u'iGio90.dwarf.debugger' ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( _appid) ctypes.windll.user32.SetProcessDPIAware() except Exception: # pylint: disable=broad-except pass from PyQt5.QtCore import Qt from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QApplication qapp = QApplication([]) qapp.setDesktopSettingsAware(True) qapp.setAttribute(Qt.AA_EnableHighDpiScaling) qapp.setAttribute(Qt.AA_UseHighDpiPixmaps) qapp.setLayoutDirection(Qt.LeftToRight) qapp.setOrganizationName("https://github.com/iGio90/Dwarf") qapp.setApplicationName("dwarf") # set icon if os.name == "nt" and os.path.exists( utils.resource_path('assets/dwarf.ico')): _icon = QIcon(utils.resource_path('assets/dwarf.ico')) qapp.setWindowIcon(_icon) else: if os.path.exists(utils.resource_path('assets/dwarf.png')): _icon = QIcon(utils.resource_path('assets/dwarf.png')) qapp.setWindowIcon(_icon) app_window = AppWindow(args) app_window.setWindowIcon(_icon) app_window.onRestart.connect(_on_restart) try: sys.exit(qapp.exec_()) except SystemExit as sys_err: if sys_err.code == 0: # thanks for using dwarf print('Thank\'s for using Dwarf\nHave a nice day...') else: # something was wrong print('sysexit with: %d' % sys_err.code)
class Emulator(QThread): class EmulatorSetupFailedError(Exception): """ Setup Failed """ class EmulatorAlreadyRunningError(Exception): """ isrunning """ onEmulatorStart = pyqtSignal(name='onEmulatorStart') onEmulatorStop = pyqtSignal(name='onEmulatorStop') onEmulatorStep = pyqtSignal(name='onEmulatorStep') onEmulatorHook = pyqtSignal(Instruction, name='onEmulatorHook') onEmulatorMemoryHook = pyqtSignal(list, name='onEmulatorMemoryHook') onEmulatorMemoryRangeMapped = pyqtSignal( list, name='onEmulatorMemoryRangeMapped') onEmulatorLog = pyqtSignal(str, name='onEmulatorLog') def __init__(self, dwarf): super(Emulator, self).__init__() self.setTerminationEnabled(True) self.dwarf = dwarf self._prefs = Prefs() self._setup_done = False self._blacklist_regs = [] self.cs = None self.uc = None self.context = None self.thumb = False self.end_ptr = 0 self.current_context = None self.stepping = [False, False] self._current_instruction = 0 self._current_cpu_mode = 0 self._request_stop = False # configurations self.callbacks_path = None self.callbacks = None self.instructions_delay = 0 self._start_address = 0 self._end_address = 0 def setup_arm(self): self.thumb = self.context.pc.thumb if self.thumb: self.cs = Cs(CS_ARCH_ARM, CS_MODE_THUMB) self.uc = unicorn.Uc(unicorn.UC_ARCH_ARM, unicorn.UC_MODE_THUMB) self._current_cpu_mode = unicorn.UC_MODE_THUMB # Enable VFP instr self.uc.mem_map(0x1000, 1024) self.uc.mem_write(0x1000, binascii.unhexlify(VFP)) self.uc.emu_start(0x1000 | 1, 0x1000 + len(VFP)) self.uc.mem_unmap(0x1000, 1024) else: self.cs = Cs(CS_ARCH_ARM, CS_MODE_ARM) self.uc = unicorn.Uc(unicorn.UC_ARCH_ARM, unicorn.UC_MODE_ARM) self._current_cpu_mode = unicorn.UC_MODE_ARM def setup_arm64(self): self.uc = unicorn.Uc(unicorn.UC_ARCH_ARM64, unicorn.UC_MODE_LITTLE_ENDIAN) self.cs = Cs(CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN) self._current_cpu_mode = unicorn.UC_MODE_LITTLE_ENDIAN def setup_x86(self): self.uc = unicorn.Uc(unicorn.UC_ARCH_X86, unicorn.UC_MODE_32) self.cs = Cs(CS_ARCH_X86, CS_MODE_32) def setup_x64(self): self.uc = unicorn.Uc(unicorn.UC_ARCH_X86, unicorn.UC_MODE_64) self.cs = Cs(CS_ARCH_X86, CS_MODE_64) def _setup(self): if self.dwarf.arch == 'arm': self.setup_arm() elif self.dwarf.arch == 'arm64': self.setup_arm64() elif self.dwarf.arch == 'ia32': self.setup_x86() elif self.dwarf.arch == 'x64': self.setup_x64() else: # unsupported arch raise self.EmulatorSetupFailedError('Unsupported arch') if not self.uc or not self.cs: raise self.EmulatorSetupFailedError('Unicorn or Capstone missing') # enable capstone details if self.cs is not None: self.cs.detail = True err = self.map_range(self.context.pc.value) if err: raise self.EmulatorSetupFailedError('Mapping failed') self.current_context = EmulatorContext(self.dwarf) for reg in self.current_context._unicorn_registers: if reg in self.context.__dict__: if reg not in self._blacklist_regs: self.uc.reg_write( self.current_context._unicorn_registers[reg], self.context.__dict__[reg].value) self.uc.hook_add(unicorn.UC_HOOK_CODE, self.hook_code) self.uc.hook_add(unicorn.UC_HOOK_MEM_WRITE | unicorn.UC_HOOK_MEM_READ, self.hook_mem_access) self.uc.hook_add( unicorn.UC_HOOK_MEM_FETCH_UNMAPPED | unicorn.UC_HOOK_MEM_WRITE_UNMAPPED | unicorn.UC_HOOK_MEM_READ_UNMAPPED, self.hook_unmapped) self.current_context.set_context(self.uc) return True def run(self): # dont call this func if not self._setup_done: return try: self.uc.emu_start( self._start_address, 0xffffffffffffffff) # end is handled in hook_code except unicorn.UcError as e: self.log_to_ui('[*] error: ' + str(e)) except Exception as e: self.log_to_ui('[*] error: ' + str(e)) self._setup_done = False self.onEmulatorStop.emit() def api(self, parts): """ expose api to js side for allowing emulator interaction while scripting :param parts: arr -> cmd api split by ":::" :return: the result from the api """ cmd = parts[0] if cmd == 'clean': return self.clean() elif cmd == 'setup': return self.setup(parts[1]) elif cmd == 'start': return self.emulate(parts[1]) def clean(self): if self.isRunning(): return False self.stepping = [False, False] self._current_instruction = 0 self._current_cpu_mode = 0 return self._setup() def hook_code(self, uc, address, size, user_data): # QApplication.processEvents() if self._request_stop: self.log_to_ui('Error: Emulator stopped - reached end') self.stop() return if self._current_instruction == address: # we should never be here or it is looping self.log_to_ui('Error: Emulator stopped - looping') self.stop() self._current_instruction = address # check if pc/eip is end_ptr pc = 0 # address should be pc too ??? if self.dwarf.arch == 'arm': pc = uc.reg_read(unicorn.arm_const.UC_ARM_REG_PC) elif self.dwarf.arch == 'arm64': pc = uc.reg_read(unicorn.arm64_const.UC_ARM64_REG_PC) elif self.dwarf.arch == 'ia32': pc = uc.reg_read(unicorn.x86_const.UC_X86_REG_EIP) elif self.dwarf.arch == 'x64': pc = uc.reg_read(unicorn.x86_const.UC_X86_REG_RIP) if pc == self._end_address: self._request_stop = True # set the current context self.current_context.set_context(uc) # if it's arm we query the cpu mode to detect switches between arm and thumb and set capstone mode if needed if self.cs.arch == CS_ARCH_ARM: mode = self.uc.query(unicorn.UC_QUERY_MODE) if self._current_cpu_mode != mode: self._current_cpu_mode = mode self.cs.mode = self._current_cpu_mode self.thumb = self._current_cpu_mode == unicorn.UC_MODE_THUMB if self.stepping[0]: if self.stepping[1]: uc.emu_stop() return else: self.stepping[1] = True try: try: assembly = self.cs.disasm(bytes(uc.mem_read(address, size)), address) except: self.log_to_ui('Error: Emulator stopped - disasm') self.stop() for i in assembly: # QApplication.processEvents() instruction = Instruction(self.dwarf, i) self.onEmulatorHook.emit(instruction) if self.callbacks is not None: try: self.callbacks.hook_code(self, instruction, address, size) except: # hook code not implemented in callbacks pass # time.sleep(self.instructions_delay) except: self.log_to_ui('Error: Emulator stopped') self.stop() def hook_mem_access(self, uc, access, address, size, value, user_data): v = value if access == unicorn.UC_MEM_READ: v = int.from_bytes(uc.mem_read(address, size), 'little') self.onEmulatorMemoryHook.emit([uc, access, address, v]) if self.callbacks is not None: try: self.callbacks.hook_memory_access(self, access, address, size, v) except: # hook code not implemented in callbacks pass def hook_unmapped(self, uc, access, address, size, value, user_data): self.log_to_ui( "[*] Trying to access an unmapped memory address at 0x%x" % address) err = self.map_range(address) if err > 0: self.log_to_ui('[*] Error %d mapping range at %s' % (err, hex(address))) return False return True def invalida_configurations(self): self.callbacks_path = self._prefs.get(prefs.EMULATOR_CALLBACKS_PATH, '') self.instructions_delay = self._prefs.get( prefs.EMULATOR_INSTRUCTIONS_DELAY, 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 def setup(self, tid=0): if tid == 0: # get current context tid if none provided tid = self.dwarf.context_tid # make sure it's int < pp: why make sure its int and then using str(tid) later?? # when calling from api its str if isinstance(tid, str): try: tid = int(tid) except ValueError: return False if not isinstance(tid, int): return False self.context = None if str(tid) in self.dwarf.contexts: self.context = self.dwarf.contexts[str(tid)] if self.context is None: return False try: self._setup() except self.EmulatorSetupFailedError: return False return True def start(self, priority=QThread.HighPriority): # dont call this func if not self._setup_done: return return super().start(priority=priority) def emulate(self, until=0): if self.isRunning(): raise self.EmulatorAlreadyRunningError() if isinstance(until, str): try: until = int(until, 16) except ValueError: until = 0 if until and isinstance(until, int): self.end_ptr = utils.parse_ptr(until) if self.end_ptr == 0: # invalid end pointer raise self.EmulatorSetupFailedError('Invalid EndPtr') if self.context is None: if not self.setup(): raise self.EmulatorSetupFailedError('Setup failed') # calculate the start address address = self._current_instruction if address == 0: if self.uc._arch == unicorn.UC_ARCH_ARM: address = self.uc.reg_read(unicorn.arm_const.UC_ARM_REG_PC) elif self.uc._arch == unicorn.UC_ARCH_ARM64: address = self.uc.reg_read(unicorn.arm64_const.UC_ARM64_REG_PC) elif self.uc._arch == unicorn.UC_ARCH_X86 and self.uc._mode == unicorn.UC_MODE_32: address = self.uc.reg_read(unicorn.x86_const.UC_X86_REG_EIP) elif self.uc._arch == unicorn.UC_ARCH_X86 and self.uc._mode == unicorn.UC_MODE_64: address = self.uc.reg_read(unicorn.x86_const.UC_X86_REG_RIP) else: raise self.EmulatorSetupFailedError('Unsupported arch') if until > 0: self.log_to_ui('[*] start emulation from %s to %s' % (hex(address), hex(self.end_ptr))) else: self.log_to_ui('[*] stepping %s' % hex(address)) self.onEmulatorStart.emit() if self.thumb: address = address | 1 # invalidate prefs before start self.invalida_configurations() # load callbacks if needed if self.callbacks_path is not None and self.callbacks_path != '': try: spec = spec_from_loader( "callbacks", SourceFileLoader("callbacks", self.callbacks_path)) self.callbacks = module_from_spec(spec) spec.loader.exec_module(self.callbacks) except Exception as e: self.log_to_ui('[*] failed to load callbacks: %s' % str(e)) # reset callbacks path self._prefs.put(prefs.EMULATOR_CALLBACKS_PATH, '') self.callbacks_path = '' self.callbacks = None else: self.callbacks = None # until is 0 (i.e we are stepping) if until == 0: self.stepping = [True, False] # self.end_ptr = address + (self.dwarf.pointer_size * 2) stupid else: self.stepping = [False, False] self._start_address = address self._end_address = self.end_ptr self._setup_done = True self.start() def stop(self): if self.isRunning(): self.uc.emu_stop() def log_to_ui(self, what): self.onEmulatorLog.emit(what)
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
class Plugin: # TODO: check the if not pipe createpipe when it fails why retrying on every disasm/apply_ctx def __get_plugin_info__(self): return { 'name': 'r2dwarf', 'description': 'r2frida in Dwarf', 'version': '1.0.0', 'author': 'iGio90', 'homepage': 'https://github.com/iGio90/Dwarf', 'license': 'https://www.gnu.org/licenses/gpl-3.0', } def __get_top_menu_actions__(self): if len(self.menu_items) > 0: return self.menu_items options = QAction('Options') options.triggered.connect( lambda: OptionsDialog.show_dialog(self._prefs)) self.menu_items.append(options) return self.menu_items def __get_agent__(self): with open( os.path.join(os.path.dirname(os.path.abspath(__file__)), 'agent.js'), 'r') as f: return f.read() def __init__(self, app): self.app = app self._prefs = Prefs() self.pipe = None self.js_api_pipe = None self.current_seek = '' self.with_r2dec = False self._working = False self.menu_items = [] self.app.session_manager.sessionCreated.connect( self._on_session_created) self.app.session_manager.sessionStopped.connect( self._on_session_stopped) self.app.onUIElementCreated.connect(self._on_ui_element_created) def _create_pipe(self): self.pipe = self._open_pipe() r2_decompilers = self.pipe.cmd('e cmd.pdc=?') r2_decompilers = r2_decompilers.split() if r2_decompilers and 'pdd' in r2_decompilers: self.with_r2dec = True self.pipe.cmd("e scr.color=2; e scr.html=1; e scr.utf8=true;") def _create_js_api_pipe(self): self.js_api_pipe = self._open_pipe() def _open_pipe(self): device = self.app.dwarf.device pipe = R2Pipe() pipe.onPipeBroken.connect(self._on_pipe_error) if device.type == 'usb': pipe.open('frida://attach/usb//%d' % self.app.dwarf.pid) elif device.type == 'local': pipe.open('frida://%d' % self.app.dwarf.pid) else: raise Exception('unsupported device type %s' % device.type) r2arch = self.app.dwarf.arch r2bits = 32 if r2arch == 'arm64': r2arch = 'arm' r2bits = 64 elif r2arch == 'x64': r2arch = 'x86' r2bits = 64 elif r2arch == 'ia32': r2arch = 'x86' pipe.cmd('e asm.arch=%s; e asm.bits=%d; e asm.os=%s' % (r2arch, r2bits, self.app.dwarf.platform)) return pipe def _on_apply_context(self, context_data): if self.pipe is None: self._create_pipe() is_java = 'is_java' in context_data and context_data['is_java'] if not is_java: if 'context' in context_data: native_context = context_data['context'] pc = native_context['pc']['value'] if self.current_seek != pc: self.current_seek = pc self.pipe.cmd('s %s' % self.current_seek) def _on_disassemble(self, dwarf_range): if self.pipe is None: self._create_pipe() if self.disassembly_view.decompilation_view is not None: self.disassembly_view.decompilation_view.setParent(None) self.disassembly_view.decompilation_view = None if self.disassembly_view.graph_view is not None: self.disassembly_view.graph_view.setParent(None) self.disassembly_view.graph_view = None start_address = hex(dwarf_range.start_address) if self.current_seek != start_address: self.current_seek = start_address self.pipe.cmd('s %s' % self.current_seek) self.app.show_progress('r2: analyzing function') self._working = True self.r2analysis = R2Analysis(self.pipe, dwarf_range) self.r2analysis.onR2AnalysisFinished.connect(self._on_finish_analysis) self.r2analysis.start() def _on_finish_analysis(self, data): self.app.hide_progress() self._working = False dwarf_range = data[0] function_info = data[1] num_instructions = 0 if 'offset' in function_info: dwarf_range.start_offset = function_info[ 'offset'] - dwarf_range.base num_instructions = int(self.pipe.cmd('pi~?')) self.disassembly_view.disasm_view.start_disassemble( dwarf_range, num_instructions=num_instructions) if 'callrefs' in function_info: for ref in function_info['callrefs']: self.call_refs_model.appendRow([ QStandardItem(hex(ref['addr'])), QStandardItem(hex(ref['at'])), QStandardItem(ref['type']) ]) if 'codexrefs' in function_info: for ref in function_info['codexrefs']: self.code_xrefs_model.appendRow([ QStandardItem(hex(ref['addr'])), QStandardItem(hex(ref['at'])), QStandardItem(ref['type']) ]) def _on_finish_graph(self, data): self.app.hide_progress() self._working = False graph_data = data[0] if self._prefs.get(KEY_WIDESCREEN_MODE, False): if self.disassembly_view.graph_view is None: self.disassembly_view.graph_view = R2ScrollArea() self.disassembly_view.addWidget( self.disassembly_view.graph_view) self.disassembly_view.graph_view.setText('<pre>' + graph_data + '</pre>') else: r2_graph_view = R2ScrollArea() r2_graph_view.setText('<pre>' + graph_data + '</pre>') self.app.main_tabs.addTab(r2_graph_view, 'graph view') index = self.app.main_tabs.indexOf(r2_graph_view) self.app.main_tabs.setCurrentIndex(index) def _on_finish_decompiler(self, data): self.app.hide_progress() self._working = False decompile_data = data[0] if self._prefs.get(KEY_WIDESCREEN_MODE, False): if self.disassembly_view.decompilation_view is None: self.disassembly_view.decompilation_view = R2ScrollArea() r2_decompiler_view = self.disassembly_view.decompilation_view self.disassembly_view.addWidget( self.disassembly_view.decompilation_view) else: r2_decompiler_view = QPlainTextEdit() r2_decompiler_view.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) r2_decompiler_view.setHorizontalScrollBarPolicy( Qt.ScrollBarAsNeeded) self.app.main_tabs.addTab(r2_decompiler_view, 'decompiler') index = self.app.main_tabs.indexOf(r2_decompiler_view) self.app.main_tabs.setCurrentIndex(index) if decompile_data is not None: r2_decompiler_view.setText('<pre>' + decompile_data + '</pre>') def _on_hook_menu(self, menu, address): menu.addSeparator() r2_menu = menu.addMenu('r2') graph = r2_menu.addAction('graph view', self.show_graph_view) decompile = r2_menu.addAction('decompile', self.show_decompiler_view) if address == -1: graph.setEnabled(False) decompile.setEnabled(False) def _on_pipe_error(self, reason): print('r2: pipe got broken\n%s' % reason) self._create_pipe() def _on_receive_cmd(self, args): message, data = args if 'payload' in message: payload = message['payload'] if payload.startswith('r2 '): if self.js_api_pipe is None: self._create_js_api_pipe() cmd = message['payload'][3:] try: result = self.js_api_pipe.cmd(cmd) self.app.dwarf._script.post({ "type": 'r2', "payload": result }) except: self.app.dwarf._script.post({ "type": 'r2', "payload": None }) def _on_session_created(self): self.app.dwarf.onReceiveCmd.connect(self._on_receive_cmd) self.app.dwarf.onApplyContext.connect(self._on_apply_context) self.console = DwarfConsoleWidget(self.app, input_placeholder='r2', completer=False) self.console.onCommandExecute.connect(self.on_r2_command) self.app.main_tabs.addTab(self.console, 'r2') def _on_session_stopped(self): # TODO: cleanup the stuff if self.pipe: self.pipe.close() def _on_ui_element_created(self, elem, widget): if elem == 'disassembly': self.disassembly_view = widget self.disassembly_view.graph_view = None self.disassembly_view.decompilation_view = None self.disassembly_view.disasm_view.run_default_disassembler = False self.disassembly_view.disasm_view.onDisassemble.connect( self._on_disassemble) r2_info = QSplitter() r2_info.setOrientation(Qt.Vertical) call_refs = DwarfListView() self.call_refs_model = QStandardItemModel(0, 3) self.call_refs_model.setHeaderData(0, Qt.Horizontal, 'call refs') self.call_refs_model.setHeaderData(1, Qt.Horizontal, '') self.call_refs_model.setHeaderData(2, Qt.Horizontal, '') call_refs.setModel(self.call_refs_model) code_xrefs = DwarfListView() self.code_xrefs_model = QStandardItemModel(0, 3) self.code_xrefs_model.setHeaderData(0, Qt.Horizontal, 'code xrefs') self.code_xrefs_model.setHeaderData(1, Qt.Horizontal, '') self.code_xrefs_model.setHeaderData(2, Qt.Horizontal, '') code_xrefs.setModel(self.code_xrefs_model) r2_info.addWidget(call_refs) r2_info.addWidget(code_xrefs) self.disassembly_view.insertWidget(0, r2_info) self.disassembly_view.setStretchFactor(0, 1) self.disassembly_view.setStretchFactor(1, 5) self.disassembly_view.disasm_view.menu_extra_menu_hooks.append( self._on_hook_menu) def show_decompiler_view(self): if self._working: utils.show_message_box('please wait for the other works to finish') else: self.app.show_progress('r2: decompiling function') self._working = True self.r2decompiler = R2Decompiler(self.pipe, self.with_r2dec) self.r2decompiler.onR2Decompiler.connect( self._on_finish_decompiler) self.r2decompiler.start() def show_graph_view(self): if self._working: utils.show_message_box('please wait for the other works to finish') else: self.app.show_progress('r2: building graph view') self._working = True self.r2graph = R2Graph(self.pipe) self.r2graph.onR2Graph.connect(self._on_finish_graph) self.r2graph.start() def on_r2_command(self, cmd): if cmd == 'clear' or cmd == 'clean': self.console.clear() else: if self._working: self.console.log('please wait for other works to finish', time_prefix=False) else: try: result = self.pipe.cmd(cmd) self.console.log(result, time_prefix=False) except BrokenPipeError: self.console.log('pipe is broken. recreating...', time_prefix=False) self._create_pipe() self.pipe.cmd('s %s' % self.current_seek)