class SonareWindow(QMainWindow): # TODO: HTML should use settings specified here FONT_NAME = 'Monospace' FONT_SIZE = 8 WINDOW_COLOR = QColor(0x3c, 0x3c, 0x3c) BG_COLOR = QColor(0x50, 0x50, 0x64) DEFAULT_TEXT_COLOR = QColor(0xD8, 0xD8, 0xD8) # TODO: HTML should use settings specified here ADDR_COLOR = QColor(0x7F, 0xEC, 0x91) SYMBOL_COLOR = QColor(0xD8, 0xD8, 0xD8) def __init__(self, path): QMainWindow.__init__(self) self.setMinimumSize(QSize(600, 400)) self.font = QFont(self.FONT_NAME, self.FONT_SIZE) self.fontMetrics = QFontMetricsF(self.font) palette = self.palette() # palette.setColor(QPalette.Window, self.WINDOW_COLOR) # palette.setColor(QPalette.WindowText, self.DEFAULT_TEXT_COLOR) palette.setColor(QPalette.Base, self.BG_COLOR) palette.setColor(QPalette.Text, self.DEFAULT_TEXT_COLOR) self.setPalette(palette) self._makeMenus() self.open(path) self._makeScene() self._makeFlagList() self.funcName = None self._updateWindowTitle() def viewGoto(self): addr = self.inputAddr('Sonare - Goto', 'Enter an address:') if addr is None: return self.gotoAddr(addr) def viewGraph(self): self.curView.setParent(None) if self.curView is self.textView: self.curView = self.graphView else: self.curView = self.textView self.setCentralWidget(self.curView) def _makeMenus(self): fileMenu = self.menuBar().addMenu(u"&File") quitAct = QAction(u"&Quit", self) quitAct.setShortcuts(QKeySequence.Quit) quitAct.triggered.connect(self.close) fileMenu.addAction(quitAct) viewMenu = self.menuBar().addMenu("&View") gotoAct = QAction(u"&Goto", self) gotoAct.setShortcuts(QKeySequence(u"Ctrl+G")) gotoAct.triggered.connect(self.viewGoto) viewMenu.addAction(gotoAct) # TODO: should probably be with a check mark textGraphAct = QAction(u"Switch text/g&raph view", self) textGraphAct.setShortcuts(QKeySequence(u"Ctrl+R")) textGraphAct.triggered.connect(self.viewGraph) viewMenu.addAction(textGraphAct) def _makeScene(self): self.textView = textview.SonareTextView(self) self.graphScene = graph.SonareGraphScene(self) self.graphView = QGraphicsView(self.graphScene) self.graphView.setRenderHints( QPainter.Antialiasing | QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform | QPainter.HighQualityAntialiasing) self.curView = self.textView self.setCentralWidget(self.curView) def _makeFlagList(self): model = FlagListModel(self) ftdock = FilteredTreeDock(self, "Flags", model, self) ftdock.setFilterKeyColumn(1) # filter by name ftdock.sortByColumn(0, Qt.AscendingOrder) # sort by address self.addDockWidget(Qt.LeftDockWidgetArea, ftdock) def onDblClick(modelIdx): addrItemIdx = modelIdx.sibling(modelIdx.row(), 0) # this is actually the proxy model model = ftdock.treeView.model() addr = model.data(addrItemIdx, Qt.UserRole + 1) self.gotoAddr(addr) ftdock.treeView.doubleClicked.connect(onDblClick) def _updateWindowTitle(self): self.setWindowTitle('Sonare - {} ({})' .format(self.funcName or '?', os.path.basename(self.filePath))) def open(self, path): self.r2core = RCore() self.r2core.flags.space_set(b'symbols') self.r2core.file_open(path.encode('ascii'), False, 0) self.r2core.bin_load("", 0) self.r2core.anal_all() print() # anal_all is noisy # clean up function overlaps self.r2core.cmd_str('aff') self.core = Core(self.r2core) self.filePath = path arch = self.r2core.config.get('asm.arch') if arch == 'x86': self.asmFormatter = X86AsmFormatter(self) elif arch == 'mips': self.asmFormatter = MipsAsmFormatter(self) else: raise NotImplementedError("asm formatting for {}".format(arch)) def inputAddr(self, title, prompt): s, ok = QInputDialog.getText(self, title, prompt) if not ok: return None try: return self.getAddr(s) except ValueError: # TODO: message box return None def getAddr(self, addrName): try: return int(addrName, 16) except ValueError: pass if isinstance(addrName, unicode): addrName = addrName.encode('ascii') return self.r2core.num.get(addrName) def gotoFunc(self, funcName): funcAddr = self.getAddr(funcName) if funcAddr is None: raise ValueError("Unknown func '{}'".format(funcName)) self.gotoAddr(funcAddr) def gotoAddr(self, funcAddr): func = self.r2core.anal.get_fcn_at(funcAddr, 1) # R_ANAL_FCN_TYPE_FCN if func is None: self.funcName = '?' else: self.funcName = func.name funcAddr = func.addr self.textView.gotoAddr(funcAddr) self.graphScene.loadFunc(funcAddr) if self.graphScene.myBlocks: firstBlock = self.graphScene.myBlocks[0] r = self.graphScene.getBlockRect(firstBlock.addr) self.graphView.centerOn(r.center().x(), r.top()) self._updateWindowTitle() def getAddrName(self, addr): flag = self.r2core.flags.get_i(int(addr) & 0xffffffffffffffff) if flag is None: return None else: return flag.name @property def isBigEndian(self): return self.r2core.config.get('cfg.bigendian') == 'true' def getBytes(self, addr, size): hexStr = self.r2core.cmd_str('p8 {}@{:#x}'.format(size, addr)).strip() return unhexlify(hexStr) def getWord(self, addr): # TODO: use r2 api buf = self.getBytes(addr, 4) fmt = '>L' if self.isBigEndian else '<L' return unpack(fmt, buf)[0] def fmtNum(self, val): if abs(val) < 10: return str(val) if abs(val) <= 0xffff: return format(val, '#x') else: hexStr = format(val, '08x') # split off last 2 bytes return hexStr[:-4] + ':' + hexStr[-4:]
class SonareWindow(QMainWindow): # TODO: HTML should use settings specified here FONT_NAME = 'Monospace' FONT_SIZE = 8 WINDOW_COLOR = QColor(0x3c, 0x3c, 0x3c) BG_COLOR = QColor(0x50, 0x50, 0x64) DEFAULT_TEXT_COLOR = QColor(0xD8, 0xD8, 0xD8) # TODO: HTML should use settings specified here ADDR_COLOR = QColor(0x7F, 0xEC, 0x91) SYMBOL_COLOR = QColor(0xD8, 0xD8, 0xD8) def __init__(self, path): QMainWindow.__init__(self) self.setMinimumSize(QSize(600, 400)) self.font = QFont(self.FONT_NAME, self.FONT_SIZE) self.fontMetrics = QFontMetricsF(self.font) palette = self.palette() # palette.setColor(QPalette.Window, self.WINDOW_COLOR) # palette.setColor(QPalette.WindowText, self.DEFAULT_TEXT_COLOR) palette.setColor(QPalette.Base, self.BG_COLOR) palette.setColor(QPalette.Text, self.DEFAULT_TEXT_COLOR) self.setPalette(palette) self.open(path) self._makeScene() self._makeFlagList() self.funcName = None self._updateWindowTitle() def _makeScene(self): self.scene = graph.SonareGraphScene(self) self.view = QGraphicsView(self.scene) self.view.setRenderHints( QPainter.Antialiasing | QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform | QPainter.HighQualityAntialiasing) self.setCentralWidget(self.view) def _makeFlagList(self): model = FlagListModel(self) ftdock = FilteredTreeDock(self, "Flags", model, self) ftdock.setFilterKeyColumn(1) # filter by name ftdock.sortByColumn(0, Qt.AscendingOrder) # sort by address self.addDockWidget(Qt.LeftDockWidgetArea, ftdock) def onDblClick(modelIdx): addrItemIdx = modelIdx.sibling(modelIdx.row(), 0) # this is actually the proxy model model = ftdock.treeView.model() addr = model.data(addrItemIdx, Qt.UserRole + 1) self.gotoAddr(addr) ftdock.treeView.doubleClicked.connect(onDblClick) def _updateWindowTitle(self): self.setWindowTitle('Sonare - {} ({})' .format(self.funcName or '?', os.path.basename(self.filePath))) def open(self, path): self.r2core = RCore() self.r2core.flags.space_set(b'symbols') self.r2core.file_open(path.encode('ascii'), False, BASE_ADDR) self.r2core.bin_load("", 0) self.r2core.anal_all() print() # anal_all is noisy # clean up function overlaps self.r2core.cmd_str('aff') self.filePath = path arch = self.r2core.config.get('asm.arch') if arch == 'x86': self.asmFormatter = X86AsmFormatter(self) elif arch == 'mips': self.asmFormatter = MipsAsmFormatter(self) else: raise NotImplementedError("asm formatting for {}".format(arch)) def getAddr(self, addrName): if isinstance(addrName, unicode): addrName = addrName.encode('ascii') return self.r2core.num.get(addrName) def gotoFunc(self, funcName): funcAddr = self.getAddr(funcName) if funcAddr is None: raise ValueError("Unknown func '{}'".format(funcName)) self.gotoAddr(funcAddr) def gotoAddr(self, funcAddr): func = self.r2core.anal.get_fcn_at(funcAddr, 1) # R_ANAL_FCN_TYPE_FCN if func is None: self.funcName = '?' else: self.funcName = func.name funcAddr = func.addr self.scene.loadFunc(funcAddr) firstBlock = self.scene.myBlocks[0] r = self.scene.getBlockRect(firstBlock.addr) self.view.centerOn(r.center().x(), r.top()) self._updateWindowTitle() def getAddrName(self, addr): flag = self.r2core.flags.get_i(int(addr) & 0xffffffffffffffff) if flag is None: return None else: return flag.name @property def isBigEndian(self): return self.r2core.config.get('cfg.bigendian') == 'true' def getWord(self, addr): # TODO: use r2 api hexStr = self.r2core.cmd_str('p8 4@{:#x}'.format(addr)).strip() buf = unhexlify(hexStr) fmt = '>L' if self.isBigEndian else '<L' return unpack(fmt, buf)[0] def fmtNum(self, val): if abs(val) < 10: return str(val) if abs(val) <= 0xffff: return format(val, '#x') else: hexStr = format(val, '08x') # split off last 2 bytes return hexStr[:-4] + ':' + hexStr[-4:]