summaryrefslogtreecommitdiff
path: root/python/examples
diff options
context:
space:
mode:
Diffstat (limited to 'python/examples')
-rw-r--r--python/examples/triage/__init__.py2
-rw-r--r--python/examples/triage/byte.py792
-rw-r--r--python/examples/triage/entropy.py40
-rw-r--r--python/examples/triage/exports.py123
-rw-r--r--python/examples/triage/files.py140
-rw-r--r--python/examples/triage/headers.py91
-rw-r--r--python/examples/triage/imports.py37
-rw-r--r--python/examples/triage/sections.py147
-rw-r--r--python/examples/triage/view.py134
9 files changed, 1438 insertions, 68 deletions
diff --git a/python/examples/triage/__init__.py b/python/examples/triage/__init__.py
index 90cdbd77..d43379da 100644
--- a/python/examples/triage/__init__.py
+++ b/python/examples/triage/__init__.py
@@ -1 +1,3 @@
from . import view
+from . import byte
+from . import files
diff --git a/python/examples/triage/byte.py b/python/examples/triage/byte.py
new file mode 100644
index 00000000..23302033
--- /dev/null
+++ b/python/examples/triage/byte.py
@@ -0,0 +1,792 @@
+# coding: utf8
+
+from PySide2.QtWidgets import QAbstractScrollArea, QAbstractSlider
+from PySide2.QtGui import QPainter, QPalette, QFont
+from PySide2.QtCore import Qt, QTimer, QRect
+import binaryninjaui
+from binaryninjaui import View, ViewType, RenderContext, UIContext, ThemeColor, UIAction
+from binaryninja.enums import LinearDisassemblyLineType
+from binaryninja.binaryview import AddressRange
+
+
+class ByteViewLine(object):
+ def __init__(self, addr, length, text, separator):
+ self.address = addr
+ self.length = length
+ self.text = text
+ self.separator = separator
+
+
+class ByteView(QAbstractScrollArea, View):
+ def __init__(self, parent, data):
+ QAbstractScrollArea.__init__(self, parent)
+ View.__init__(self)
+ self.setupView(self)
+ self.data = data
+ self.byte_mapping = [
+ u' ', u'☺', u'☻', u'♥', u'♦', u'♣', u'♠', u'•', u'◘', u'○', u'◙', u'♂', u'♀', u'♪', u'♫', u'☼',
+ u'▸', u'◂', u'↕', u'‼', u'¶', u'§', u'▬', u'↨', u'↑', u'↓', u'→', u'←', u'∟', u'↔', u'▴', u'▾',
+ u' ', u'!', u'"', u'#', u'$', u'%', u'&', u'\'', u'(', u')', u'*', u'+', u',', u'-', u'.', u'/',
+ u'0', u'1', u'2', u'3', u'4', u'5', u'6', u'7', u'8', u'9', u':', u';', u'<', u'=', u'>', u'?',
+ u'@', u'A', u'B', u'C', u'D', u'E', u'F', u'G', u'H', u'I', u'J', u'K', u'L', u'M', u'N', u'O',
+ u'P', u'Q', u'R', u'S', u'T', u'U', u'V', u'W', u'X', u'Y', u'Z', u'[', u'\\', u']', u'^', u'_',
+ u'`', u'a', u'b', u'c', u'd', u'e', u'f', u'g', u'h', u'i', u'j', u'k', u'l', u'm', u'n', u'o',
+ u'p', u'q', u'r', u's', u't', u'u', u'v', u'w', u'x', u'y', u'z', u'{', u'|', u'}', u'~', u'⌂',
+ u'Ç', u'ü', u'é', u'â', u'ä', u'à', u'å', u'ç', u'ê', u'ë', u'è', u'ï', u'î', u'ì', u'Ä', u'Å',
+ u'É', u'æ', u'Æ', u'ô', u'ö', u'ò', u'û', u'ù', u'ÿ', u'Ö', u'Ü', u'¢', u'£', u'¥', u'₧', u'ƒ',
+ u'á', u'í', u'ó', u'ú', u'ñ', u'Ñ', u'ª', u'º', u'¿', u'⌐', u'¬', u'½', u'¼', u'¡', u'«', u'»',
+ u'░', u'▒', u'▓', u'│', u'┤', u'╡', u'╢', u'╖', u'╕', u'╣', u'║', u'╗', u'╝', u'╜', u'╛', u'┐',
+ u'└', u'┴', u'┬', u'├', u'─', u'┼', u'╞', u'╟', u'╚', u'╔', u'╩', u'╦', u'╠', u'═', u'╬', u'╧',
+ u'╨', u'╤', u'╥', u'╙', u'╘', u'╒', u'╓', u'╫', u'╪', u'┘', u'┌', u'█', u'▄', u'▌', u'▐', u'▀',
+ u'α', u'ß', u'Γ', u'π', u'Σ', u'σ', u'µ', u'τ', u'Φ', u'Θ', u'Ω', u'δ', u'∞', u'φ', u'ε', u'∩',
+ u'≡', u'±', u'≥', u'≤', u'⌠', u'⌡', u'÷', u'≈', u'°', u'∙', u'·', u'√', u'ⁿ', u'²', u'■', u' '
+ ]
+
+ self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
+ self.setFocusPolicy(Qt.StrongFocus)
+
+ self.cursorAddr = self.data.start
+ self.prevCursorAddr = self.cursorAddr
+ self.selectionStartAddr = self.cursorAddr
+ self.topAddr = self.cursorAddr
+ self.topLine = 0
+ self.selectionVisible = False
+ self.caretVisible = False
+ self.caretBlink = True
+ self.leftButtonDown = False
+ self.cols = 128
+ self.updatesRequired = False
+ self.visibleRows = 1
+ self.lines = []
+
+ self.updateRanges()
+
+ areaSize = self.viewport().size()
+ self.adjustSize(areaSize.width(), areaSize.height())
+
+ if self.allocatedLength > 0x7fffffff:
+ self.scrollBarMultiplier = (self.allocatedLength // 0x7fffffff) + 1
+ else:
+ self.scrollBarMultiplier = 1
+ self.wheelDelta = 0
+ self.updatingScrollBar = False
+ self.verticalScrollBar().setRange(0, (self.allocatedLength - 1) // self.scrollBarMultiplier)
+ self.verticalScrollBar().sliderMoved.connect(self.scrollBarMoved)
+ self.verticalScrollBar().actionTriggered.connect(self.scrollBarAction)
+
+ self.cursorTimer = QTimer(self)
+ self.cursorTimer.setInterval(500)
+ self.cursorTimer.setSingleShot(False)
+ self.cursorTimer.timeout.connect(self.cursorTimerEvent)
+ self.cursorTimer.start()
+
+ self.updateTimer = QTimer(self)
+ self.updateTimer.setInterval(200)
+ self.updateTimer.setSingleShot(False)
+ #self.updateTimer.timeout.connect(self.updateTimerEvent)
+
+ self.actionHandler().bindAction("Move Cursor Up", UIAction(lambda ctxt: self.up(False)))
+ self.actionHandler().bindAction("Move Cursor Down", UIAction(lambda ctxt: self.down(False)))
+ self.actionHandler().bindAction("Move Cursor Left", UIAction(lambda ctxt: self.left(1, False)))
+ self.actionHandler().bindAction("Move Cursor Right", UIAction(lambda ctxt: self.right(1, False)))
+ self.actionHandler().bindAction("Move Cursor Word Left", UIAction(lambda ctxt: self.left(8, False)))
+ self.actionHandler().bindAction("Move Cursor Word Right", UIAction(lambda ctxt: self.right(8, False)))
+ self.actionHandler().bindAction("Extend Selection Up", UIAction(lambda ctxt: self.up(True)))
+ self.actionHandler().bindAction("Extend Selection Down", UIAction(lambda ctxt: self.down(True)))
+ self.actionHandler().bindAction("Extend Selection Left", UIAction(lambda ctxt: self.left(1, True)))
+ self.actionHandler().bindAction("Extend Selection Right", UIAction(lambda ctxt: self.right(1, True)))
+ self.actionHandler().bindAction("Extend Selection Word Left", UIAction(lambda ctxt: self.left(8, True)))
+ self.actionHandler().bindAction("Extend Selection Word Right", UIAction(lambda ctxt: self.right(8, True)))
+ self.actionHandler().bindAction("Page Up", UIAction(lambda ctxt: self.pageUp(False)))
+ self.actionHandler().bindAction("Page Down", UIAction(lambda ctxt: self.pageDown(False)))
+ self.actionHandler().bindAction("Extend Selection Page Up", UIAction(lambda ctxt: self.pageUp(True)))
+ self.actionHandler().bindAction("Extend Selection Page Down", UIAction(lambda ctxt: self.pageDown(True)))
+ self.actionHandler().bindAction("Move Cursor to Start Of Line", UIAction(lambda ctxt: self.moveToStartOfLine(False)))
+ self.actionHandler().bindAction("Move Cursor to End Of Line", UIAction(lambda ctxt: self.moveToEndOfLine(False)))
+ self.actionHandler().bindAction("Move Cursor to Start Of View", UIAction(lambda ctxt: self.moveToStartOfView(False)))
+ self.actionHandler().bindAction("Move Cursor to End Of View", UIAction(lambda ctxt: self.moveToEndOfView(False)))
+ self.actionHandler().bindAction("Extend Selection to Start Of Line", UIAction(lambda ctxt: self.moveToStartOfLine(True)))
+ self.actionHandler().bindAction("Extend Selection to End Of Line", UIAction(lambda ctxt: self.moveToEndOfLine(True)))
+ self.actionHandler().bindAction("Extend Selection to Start Of View", UIAction(lambda ctxt: self.moveToStartOfView(True)))
+ self.actionHandler().bindAction("Extend Selection to End Of View", UIAction(lambda ctxt: self.moveToEndOfView(True)))
+
+ def getData(self):
+ return self.data
+
+ def getStart(self):
+ return self.data.start
+
+ def getEnd(self):
+ return self.data.end
+
+ def getLength(self):
+ return self.getEnd() - self.getStart()
+
+ def getCurrentOffset(self):
+ return self.cursorAddr
+
+ def getSelectionOffsets(self):
+ start = self.selectionStartAddr
+ end = self.cursorAddr
+ if end < start:
+ t = start
+ start = end
+ end = t
+ return (start, end)
+
+ def updateRanges(self):
+ self.ranges = self.data.allocated_ranges
+ # Remove regions not backed by the file
+ for i in self.data.segments:
+ if i.data_length < len(i):
+ self.removeRange(i.start + i.data_length, i.end)
+ self.allocatedLength = 0
+ for i in self.ranges:
+ self.allocatedLength += i.end - i.start
+
+ def removeRange(self, begin, end):
+ newRanges = []
+ for i in self.ranges:
+ if (end <= i.start) or (begin >= i.end):
+ newRanges.append(i)
+ elif (begin <= i.start) and (end >= i.end):
+ continue
+ elif (begin <= i.start) and (end < i.end):
+ newRanges.append(AddressRange(end, i.end))
+ elif (begin > i.start) and (end >= i.end):
+ newRanges.append(AddressRange(i.start, begin))
+ else:
+ newRanges.append(AddressRange(i.start, begin))
+ newRanges.append(AddressRange(end, i.end))
+ self.ranges = newRanges
+
+ def setTopToAddress(self, addr):
+ for i in self.ranges:
+ if (addr >= i.start) and (addr <= i.end):
+ self.topAddr = addr - ((addr - i.start) % self.cols)
+ if self.topAddr < i.start:
+ self.topAddr = i.start
+ return
+ if i.start > addr:
+ self.topAddr = i.start
+ return
+ self.topAddr = self.data.end
+
+ def navigate(self, addr):
+ if addr < self.getStart():
+ return False
+ if addr > self.getEnd():
+ return False
+ self.cursorAddr = self.getStart()
+ for i in self.ranges:
+ if i.start > addr:
+ break
+ if addr > i.end:
+ self.cursorAddr = i.end
+ elif addr >= i.start:
+ self.cursorAddr = addr
+ else:
+ self.cursorAddr = i.start
+ self.setTopToAddress(self.cursorAddr)
+ self.refreshLines()
+ self.showContextAroundTop()
+ self.selectNone()
+ self.repositionCaret()
+ return True
+
+ def updateFonts(self):
+ areaSize = self.viewport().size()
+ self.adjustSize(areaSize.width(), areaSize.height())
+
+ def createRenderContext(self):
+ render = RenderContext(self)
+ userFont = binaryninjaui.getMonospaceFont(self)
+ # Some fonts aren't fixed width across all characters, use a known good one
+ font = QFont("Menlo", userFont.pointSize())
+ font.setKerning(False)
+ render.setFont(font)
+ return render
+
+ def adjustSize(self, width, height):
+ self.addrWidth = max(len("%x" % self.data.end), 8)
+ render = self.createRenderContext()
+ cols = ((width - 4) // render.getFontWidth()) - (self.addrWidth + 2)
+ if cols != self.cols:
+ self.cols = cols
+ if self.topLine < len(self.lines):
+ self.setTopToAddress(self.lines[self.topLine].address)
+ else:
+ self.setTopToAddress(self.cursorAddr)
+ self.refreshLines()
+ self.visibleRows = (height - 4) // render.getFontHeight()
+ self.verticalScrollBar().setPageStep(self.visibleRows * self.cols // self.scrollBarMultiplier)
+ self.refreshAtCurrentLocation()
+ self.viewport().update()
+
+ def getContiguousOffsetForAddress(self, addr):
+ offset = 0
+ for i in self.ranges:
+ if (addr >= i.start) and (addr <= i.end):
+ offset += addr - i.start
+ break
+ offset += i.end - i.start
+ return offset
+
+ def getAddressForContiguousOffset(self, offset):
+ cur = 0
+ for i in self.ranges:
+ if offset < (cur + (i.end - i.start)):
+ return i.start + (offset - cur)
+ cur += i.end - i.start
+ return self.data.end
+
+ def refreshLines(self):
+ addr = self.topAddr
+ self.lines = []
+ self.topLine = 0
+ self.bottomAddr = self.topAddr
+ self.updateRanges()
+ if self.allocatedLength > 0x7fffffff:
+ self.scrollBarMultiplier = (self.allocatedLength // 0x7fffffff) + 1
+ else:
+ self.scrollBarMultiplier = 1
+ self.updatingScrollBar = True
+ self.verticalScrollBar().setRange(0, (self.allocatedLength - 1) // self.scrollBarMultiplier)
+ self.verticalScrollBar().setValue(self.getContiguousOffsetForAddress(addr) // self.scrollBarMultiplier)
+ self.updatingScrollBar = False
+ self.updateCache()
+ self.viewport().update()
+ UIContext.updateStatus()
+
+ def refreshAtCurrentLocation(self):
+ if self.topLine < len(self.lines):
+ self.topAddr = self.lines[self.topLine].address
+ self.refreshLines()
+
+ def createLine(self, addr, length, separator):
+ if separator:
+ return ByteViewLine(addr, length, u'', True)
+ else:
+ data = self.data.read(addr, length)
+ text = u''.join([self.byte_mapping[value] for value in data])
+ return ByteViewLine(addr, length, text, False)
+
+ def cachePreviousLines(self):
+ prevEnd = None
+ for i in self.ranges:
+ if (self.topAddr > i.start) and (self.topAddr <= i.end):
+ startLine = self.topAddr - ((self.topAddr - i.start) % self.cols)
+ if startLine == self.topAddr:
+ startLine -= self.cols
+ if startLine < i.start:
+ startLine = i.start
+ line = self.createLine(startLine, self.topAddr - startLine, False)
+ self.lines.insert(0, line)
+ self.topLine += 1
+ self.topAddr = startLine
+ return True
+ elif i.start >= self.topAddr:
+ if prevEnd is None:
+ return False
+ line = self.createLine(prevEnd, i.start - prevEnd, True)
+ self.lines.insert(0, line)
+ self.topLine += 1
+ self.topAddr = prevEnd
+ return True
+ prevEnd = i.end
+ if prevEnd is None:
+ return False
+ line = self.createLine(prevEnd, self.topAddr - prevEnd, True)
+ self.lines.insert(0, line)
+ self.topLine += 1
+ self.topAddr = prevEnd
+
+ def cacheNextLines(self):
+ lastAddr = self.data.start
+ for i in self.ranges:
+ if (self.bottomAddr >= i.start) and (self.bottomAddr < i.end):
+ endLine = self.bottomAddr + self.cols
+ if endLine > i.end:
+ endLine = i.end
+ line = self.createLine(self.bottomAddr, endLine - self.bottomAddr, False)
+ self.lines.append(line)
+ self.bottomAddr = endLine
+ return True
+ elif i.start > self.bottomAddr:
+ line = self.createLine(self.bottomAddr, i.start - self.bottomAddr, True)
+ self.lines.append(line)
+ self.bottomAddr = i.start
+ return True
+ lastAddr = i.end
+ if self.bottomAddr == lastAddr:
+ # Ensure there is a place for the cursor at the end of the file
+ if (len(self.lines) > 0) and (self.lines[-1].length != self.cols):
+ return False
+ line = self.createLine(lastAddr, 0, False)
+ self.lines.append(line)
+ self.bottomAddr += 1
+ return True
+ return False
+
+ def updateCache(self):
+ # Cache enough for the current page and the next page
+ while (len(self.lines) - self.topLine) <= (self.visibleRows * 2):
+ if not self.cacheNextLines():
+ break
+ # Cache enough for the previous page
+ while self.topLine <= self.visibleRows:
+ if not self.cachePreviousLines():
+ break
+ # Trim cache
+ if self.topLine > (self.visibleRows * 4):
+ self.lines = self.lines[self.topLine - (self.visibleRows * 4):]
+ self.topLine = self.visibleRows * 4
+ self.topAddr = self.lines[0].address
+ if (len(self.lines) - self.topLine) > (self.visibleRows * 5):
+ self.bottomAddr = self.lines[self.topLine + (self.visibleRows * 5)].address
+ self.lines = self.lines[0:self.topLine + (self.visibleRows * 5)]
+
+ def scrollLines(self, count):
+ newOffset = self.topLine + count
+ if newOffset < 0:
+ self.topLine = 0
+ elif newOffset >= len(self.lines):
+ self.topLine = len(self.lines) - 1
+ else:
+ self.topLine = newOffset
+ self.updateCache()
+ self.viewport().update()
+ if self.topLine < len(self.lines):
+ self.updatingScrollBar = True
+ addr = self.lines[self.topLine].address
+ self.verticalScrollBar().setValue(self.getContiguousOffsetForAddress(addr) // self.scrollBarMultiplier)
+ self.updatingScrollBar = False
+
+ def showContextAroundTop(self):
+ scroll = self.visibleRows // 4
+ if scroll > self.topLine:
+ self.topLine = 0
+ else:
+ self.topLine -= scroll
+ if self.topLine < len(self.lines):
+ self.updatingScrollBar = True
+ addr = self.lines[self.topLine].address
+ self.verticalScrollBar().setValue(self.getContiguousOffsetForAddress(addr) // self.scrollBarMultiplier)
+ self.updatingScrollBar = False
+ self.updateCache()
+
+ def repositionCaret(self):
+ self.updateCache()
+ found = False
+ for i in range(0, len(self.lines)):
+ if (((self.cursorAddr >= self.lines[i].address) and (self.cursorAddr < (self.lines[i].address + self.lines[i].length))) or
+ (((i + 1) == len(self.lines)) and (self.cursorAddr == (self.lines[i].address + self.lines[i].length)))):
+ if i < self.topLine:
+ self.topLine = i
+ elif i > (self.topLine + self.visibleRows - 1):
+ self.topLine = i - (self.visibleRows - 1)
+ self.updatingScrollBar = True
+ addr = self.lines[self.topLine].address
+ self.verticalScrollBar().setValue(self.getContiguousOffsetForAddress(addr) // self.scrollBarMultiplier)
+ self.updatingScrollBar = False
+ self.updateCache()
+ self.viewport().update()
+ found = True
+ break
+ if not found:
+ self.setTopToAddress(self.cursorAddr)
+ self.refreshLines()
+ self.showContextAroundTop()
+ # Force caret to be visible and repaint
+ self.caretBlink = True
+ self.cursorTimer.stop()
+ self.cursorTimer.start()
+ self.updateCaret()
+ UIContext.updateStatus()
+
+ def updateCaret(self):
+ # Rerender both the old caret position and the new caret position
+ render = self.createRenderContext()
+ for i in range(self.topLine, min(len(self.lines), self.topLine + self.visibleRows)):
+ if (((self.prevCursorAddr >= self.lines[i].address) and (self.prevCursorAddr <= (self.lines[i].address + self.lines[i].length))) or
+ ((self.cursorAddr >= self.lines[i].address) and (self.cursorAddr <= (self.lines[i].address + self.lines[i].length)))):
+ self.viewport().update(0, (i - self.topLine) * render.getFontHeight(),
+ self.viewport().size().width(), render.getFontHeight() + 3)
+
+ def resizeEvent(self, event):
+ self.adjustSize(event.size().width(), event.size().height())
+
+ def paintEvent(self, event):
+ p = QPainter(self.viewport())
+ render = self.createRenderContext()
+ render.init(p)
+ charWidth = render.getFontWidth()
+ charHeight = render.getFontHeight()
+
+ # Compute range that needs to be updated
+ topY = event.rect().y()
+ botY = topY + event.rect().height()
+ topY = (topY - 2) // charHeight
+ botY = ((botY - 2) // charHeight) + 1
+
+ # Compute selection range
+ selection = False
+ selStart, selEnd = self.getSelectionOffsets()
+ if selStart != selEnd:
+ selection = True
+
+ # Draw selection
+ if selection:
+ startY = None
+ endY = None
+ startX = None
+ endX = None
+ for i in range(0, len(self.lines)):
+ if selStart >= self.lines[i].address:
+ startY = i - self.topLine
+ startX = selStart - self.lines[i].address
+ if startX > self.cols:
+ startX = self.cols
+ if selEnd >= self.lines[i].address:
+ endY = i - self.topLine
+ endX = selEnd - self.lines[i].address
+ if endX > self.cols:
+ endX = self.cols
+
+ if startY is not None and endY is not None:
+ p.setPen(binaryninjaui.getThemeColor(ThemeColor.SelectionColor))
+ p.setBrush(binaryninjaui.getThemeColor(ThemeColor.SelectionColor))
+ if startY == endY:
+ p.drawRect(2 + (self.addrWidth + 2 + startX) * charWidth, 2 + startY * charHeight,
+ (endX - startX) * charWidth, charHeight + 1)
+ else:
+ p.drawRect(2 + (self.addrWidth + 2 + startX) * charWidth, 2 + startY * charHeight,
+ (self.cols - startX) * charWidth, charHeight + 1)
+ if endX > 0:
+ p.drawRect(2 + (self.addrWidth + 2) * charWidth, 2 + endY * charHeight,
+ endX * charWidth, charHeight + 1)
+ if (endY - startY) > 1:
+ p.drawRect(2 + (self.addrWidth + 2) * charWidth, 2 + (startY + 1) * charHeight,
+ self.cols * charWidth, ((endY - startY) - 1) * charHeight + 1)
+
+ # Paint each line
+ color = self.palette().color(QPalette.WindowText)
+ for y in range(topY, botY):
+ if (y + self.topLine) < 0:
+ continue
+ if (y + self.topLine) >= len(self.lines):
+ break
+ if self.lines[y + self.topLine].separator:
+ render.drawLinearDisassemblyLineBackground(p, LinearDisassemblyLineType.NonContiguousSeparatorLineType,
+ QRect(0, 2 + y * charHeight, event.rect().width(), charHeight), 0)
+ continue
+
+ lineStartAddr = self.lines[y + self.topLine].address
+ addrStr = "%.8x" % lineStartAddr
+ length = self.lines[y + self.topLine].length
+ text = self.lines[y + self.topLine].text
+
+ cursorCol = None
+ if (((self.cursorAddr >= lineStartAddr) and (self.cursorAddr < (lineStartAddr + length))) or
+ (((y + self.topLine + 1) >= len(self.lines)) and (self.cursorAddr == (lineStartAddr + length)))):
+ cursorCol = self.cursorAddr - lineStartAddr
+
+ render.drawText(p, 2, 2 + y * charHeight, binaryninjaui.getThemeColor(ThemeColor.AddressColor), addrStr)
+ render.drawText(p, 2 + (self.addrWidth + 2) * charWidth, 2 + y * charHeight, color, text)
+
+ if self.caretVisible and self.caretBlink and not selection and cursorCol is not None:
+ p.setPen(Qt.NoPen)
+ p.setBrush(self.palette().color(QPalette.WindowText))
+ p.drawRect(2 + (self.addrWidth + 2 + cursorCol) * charWidth, 2 + y * charHeight, charWidth, charHeight + 1)
+ caretTextColor = self.palette().color(QPalette.Base)
+ byteValue = self.data.read(lineStartAddr + cursorCol, 1)
+ if len(byteValue) == 1:
+ byteStr = self.byte_mapping[ord(byteValue)]
+ render.drawText(p, 2 + (self.addrWidth + 2 + cursorCol) * charWidth, 2 + y * charHeight, caretTextColor, byteStr)
+
+ def wheelEvent(self, event):
+ if event.orientation() == Qt.Horizontal:
+ return
+ self.wheelDelta -= event.delta()
+ if (self.wheelDelta <= -40) or (self.wheelDelta >= 40):
+ lines = self.wheelDelta // 40
+ self.wheelDelta -= lines * 40
+ self.scrollLines(lines)
+
+ def scrollBarMoved(self, value):
+ if self.updatingScrollBar:
+ return
+ self.wheelDelta = 0
+ addr = self.getAddressForContiguousOffset(value * self.scrollBarMultiplier)
+ self.setTopToAddress(addr)
+ self.refreshLines()
+ for i in range(1, len(self.lines)):
+ if (self.lines[i].address + self.lines[i].length) > addr:
+ self.topLine = i - 1
+ break
+ self.updateCache()
+
+ def scrollBarAction(self, action):
+ if action == QAbstractSlider.SliderSingleStepAdd:
+ self.wheelDelta = 0
+ self.scrollLines(1)
+ elif action == QAbstractSlider.SliderSingleStepSub:
+ self.wheelDelta = 0
+ self.scrollLines(-1)
+ elif action == QAbstractSlider.SliderPageStepAdd:
+ self.wheelDelta = 0
+ self.scrollLines(self.visibleRows)
+ elif action == QAbstractSlider.SliderPageStepSub:
+ self.wheelDelta = 0
+ self.scrollLines(-self.visibleRows)
+ elif action == QAbstractSlider.SliderToMinimum:
+ self.wheelDelta = 0
+ self.setTopToAddress(self.getStart())
+ self.verticalScrollBar().setValue(self.getContiguousOffsetForAddress(self.topAddr) // self.scrollBarMultiplier)
+ self.refreshLines()
+ elif action == QAbstractSlider.SliderToMaximum:
+ self.wheelDelta = 0
+ self.setTopToAddress(self.getEnd())
+ self.verticalScrollBar().setValue(self.getContiguousOffsetForAddress(self.topAddr) // self.scrollBarMultiplier)
+ self.refreshLines()
+
+ def cursorTimerEvent(self):
+ self.caretBlink = not self.caretBlink
+ self.updateCaret()
+
+ def focusInEvent(self, event):
+ self.caretVisible = True
+ self.updateCaret()
+
+ def focusOutEvent(self, event):
+ self.caretVisible = False
+ self.leftButtonDown = False
+ self.updateCaret()
+
+ def selectNone(self):
+ for i in self.lines:
+ if (self.cursorAddr >= i.address) and (self.cursorAddr < (i.address + i.length)) and i.separator:
+ self.cursorAddr = i.address + i.length
+ break
+ self.selectionStartAddr = self.cursorAddr
+ if self.selectionVisible:
+ self.viewport().update()
+ self.repositionCaret()
+ UIContext.updateStatus()
+
+ def selectAll(self):
+ self.selectionStartAddr = self.getStart()
+ self.cursorAddr = self.getEnd()
+ self.viewport().update()
+ UIContext.updateStatus()
+
+ def adjustAddressAfterBackwardMovement(self):
+ lastAddr = self.getStart()
+ for i in self.ranges:
+ if (self.cursorAddr >= i.start) and (self.cursorAddr < i.end):
+ break
+ if i.start > self.cursorAddr:
+ self.cursorAddr = lastAddr
+ break
+ lastAddr = i.end - 1
+
+ def adjustAddressAfterForwardMovement(self):
+ for i in self.ranges:
+ if (self.cursorAddr >= i.start) and (self.cursorAddr < i.end):
+ break
+ if i.start > self.cursorAddr:
+ self.cursorAddr = i.start
+ break
+
+ def left(self, count, selecting):
+ if self.cursorAddr > (self.getStart() + count):
+ self.cursorAddr -= count
+ else:
+ self.cursorAddr = self.getStart()
+ self.adjustAddressAfterBackwardMovement()
+ if not selecting:
+ self.selectNone()
+ self.repositionCaret()
+ if self.selectionVisible or selecting:
+ self.viewport().update()
+
+ def right(self, count, selecting):
+ if self.cursorAddr <= (self.getEnd() - count):
+ self.cursorAddr += count
+ else:
+ self.cursorAddr = self.getEnd()
+ self.adjustAddressAfterForwardMovement()
+ if not selecting:
+ self.selectNone()
+ self.repositionCaret()
+ if self.selectionVisible or selecting:
+ self.viewport().update()
+
+ def up(self, selecting):
+ self.left(self.cols, selecting)
+
+ def down(self, selecting):
+ self.right(self.cols, selecting)
+
+ def pageUp(self, selecting):
+ for i in range(0, len(self.lines)):
+ if (((self.cursorAddr >= self.lines[i].address) and (self.cursorAddr < (self.lines[i].address + self.lines[i].length))) or
+ (((i + 1) == len(self.lines)) and (self.cursorAddr == (self.lines[i].address + self.lines[i].length)))):
+ if i < self.visibleRows:
+ self.cursorAddr = self.getStart()
+ else:
+ lineOfs = self.cursorAddr - self.lines[i].address
+ self.cursorAddr = self.lines[i - self.visibleRows].address + lineOfs
+ if self.cursorAddr < self.lines[i - self.visibleRows].address:
+ self.cursorAddr = self.lines[i - self.visibleRows].address
+ elif self.cursorAddr >= (self.lines[i - self.visibleRows].address + self.lines[i - self.visibleRows].length):
+ self.cursorAddr = self.lines[i - self.visibleRows].address + self.lines[i - self.visibleRows].length - 1
+ self.adjustAddressAfterBackwardMovement()
+ if self.topLine > self.visibleRows:
+ self.topLine -= self.visibleRows
+ else:
+ self.topLine = 0
+ if self.topLine < len(self.lines):
+ self.updatingScrollBar = True
+ addr = self.lines[self.topLine].address
+ self.verticalScrollBar().setValue(self.getContiguousOffsetForAddress(addr) // self.scrollBarMultiplier)
+ self.updatingScrollBar = False
+ if not selecting:
+ self.selectNone()
+ self.repositionCaret()
+ self.viewport().update()
+
+ def pageDown(self, selecting):
+ for i in range(0, len(self.lines)):
+ if (((self.cursorAddr >= self.lines[i].address) and (self.cursorAddr < (self.lines[i].address + self.lines[i].length))) or
+ (((i + 1) == len(self.lines)) and (self.cursorAddr == (self.lines[i].address + self.lines[i].length)))):
+ if i >= (len(self.lines) - self.visibleRows):
+ self.cursorAddr = self.getEnd()
+ else:
+ lineOfs = self.cursorAddr - self.lines[i].address
+ self.cursorAddr = self.lines[i + self.visibleRows].address + lineOfs
+ if self.cursorAddr < self.lines[i + self.visibleRows].address:
+ self.cursorAddr = self.lines[i + self.visibleRows].address
+ elif self.cursorAddr >= (self.lines[i + self.visibleRows].address + self.lines[i - self.visibleRows].length):
+ self.cursorAddr = self.lines[i + self.visibleRows].address + self.lines[i - self.visibleRows].length - 1
+ self.adjustAddressAfterForwardMovement()
+ if (self.topLine + self.visibleRows) < len(self.lines):
+ self.topLine += self.visibleRows
+ elif len(self.lines) > 0:
+ self.topLine = len(self.lines) - 1
+ if self.topLine < len(self.lines):
+ self.updatingScrollBar = True
+ addr = self.lines[self.topLine].address
+ self.verticalScrollBar().setValue(self.getContiguousOffsetForAddress(addr) // self.scrollBarMultiplier)
+ self.updatingScrollBar = False
+ if not selecting:
+ self.selectNone()
+ self.repositionCaret()
+ self.viewport().update()
+
+ def moveToStartOfLine(self, selecting):
+ for i in self.lines:
+ if (self.cursorAddr >= i.address) and (self.cursorAddr < (i.address + i.length)):
+ self.cursorAddr = i.address
+ break
+ if not selecting:
+ self.selectNone()
+ self.repositionCaret()
+ if self.selectionVisible or selecting:
+ self.viewport().update()
+
+ def moveToEndOfLine(self, selecting):
+ for i in self.lines:
+ if (self.cursorAddr >= i.address) and (self.cursorAddr < (i.address + i.length)):
+ self.cursorAddr = i.address + i.length - 1
+ break
+ if not selecting:
+ self.selectNone()
+ self.repositionCaret()
+ if self.selectionVisible or selecting:
+ self.viewport().update()
+
+ def moveToStartOfView(self, selecting):
+ self.cursorAddr = self.getStart()
+ if not selecting:
+ self.selectNone()
+ self.repositionCaret()
+ if self.selectionVisible or selecting:
+ self.viewport().update()
+
+ def moveToEndOfView(self, selecting):
+ self.cursorAddr = self.getEnd()
+ if not selecting:
+ self.selectNone()
+ self.repositionCaret()
+ if self.selectionVisible or selecting:
+ self.viewport().update()
+
+ def addressFromLocation(self, x, y):
+ if y < 0:
+ y = 0
+ if x < 0:
+ x = 0
+ if x > self.cols:
+ x = self.cols
+ if (y + self.topLine) >= len(self.lines):
+ return self.getEnd()
+ if self.lines[y + self.topLine].separator:
+ return self.lines[y + self.topLine].address - 1
+ result = self.lines[y + self.topLine].address + x
+ if result >= (self.lines[y + self.topLine].address + self.lines[y + self.topLine].length):
+ if (y + self.topLine) == (len(self.lines) - 1):
+ return self.getEnd()
+ else:
+ return self.lines[y + self.topLine].address + self.lines[y + self.topLine].length - 1
+ return result
+
+ def mousePressEvent(self, event):
+ if event.button() != Qt.LeftButton:
+ return
+ render = self.createRenderContext()
+ x = (event.x() - 2) // render.getFontWidth() - (self.addrWidth + 2)
+ y = (event.y() - 2) // render.getFontHeight()
+ self.lastMouseX = x
+ self.lastMouseY = y
+ self.cursorAddr = self.addressFromLocation(x, y)
+ if (event.modifiers() & Qt.ShiftModifier) == 0:
+ self.selectNone()
+ self.repositionCaret()
+ if (event.modifiers() & Qt.ShiftModifier) != 0:
+ self.viewport().update()
+ self.leftButtonDown = True
+
+ def mouseMoveEvent(self, event):
+ if not self.leftButtonDown:
+ return
+ render = self.createRenderContext()
+ x = (event.x() - 2) // render.getFontWidth() - (self.addrWidth + 2)
+ y = (event.y() - 2) // render.getFontHeight()
+ if (x == self.lastMouseX) and (y == self.lastMouseY):
+ return
+ self.lastMouseX = x
+ self.lastMouseY = y
+ self.cursorAddr = self.addressFromLocation(x, y)
+ self.repositionCaret()
+ self.viewport().update()
+
+ def mouseReleaseEvent(self, event):
+ if event.button() != Qt.LeftButton:
+ return
+ self.leftButtonDown = False
+
+
+class ByteViewType(ViewType):
+ def __init__(self):
+ super(ByteViewType, self).__init__("Bytes", "Byte Overview")
+
+ def getPriority(self, data, filename):
+ return 1
+
+ def create(self, data, view_frame):
+ return ByteView(view_frame, data)
+
+
+ViewType.registerViewType(ByteViewType())
diff --git a/python/examples/triage/entropy.py b/python/examples/triage/entropy.py
index 03249968..37ce295a 100644
--- a/python/examples/triage/entropy.py
+++ b/python/examples/triage/entropy.py
@@ -3,7 +3,8 @@ import threading
from PySide2.QtWidgets import QWidget
from PySide2.QtGui import QImage, QColor, QPainter
from PySide2.QtCore import Qt, QSize, QTimer
-from binaryninjaui import ViewFrame
+import binaryninjaui
+from binaryninjaui import ViewFrame, ThemeColor
class EntropyThread(threading.Thread):
@@ -17,30 +18,22 @@ class EntropyThread(threading.Thread):
def run(self):
width = self.image.width()
for i in range(0, width):
- block = self.data.read(self.data.start + i * self.block_size, self.block_size)
- if len(block) == 0:
- v = 0
- else:
- dist = [0] * 0x100
- for j in range(0, len(block)):
- value = ord(block[j:j+1])
- dist[value] += 1
- s = 0
- for j in range(0, 256):
- if dist[j] != 0:
- s += (float(dist[j]) / len(block)) * math.log(float(dist[j]) / len(block))
- s = s / math.log(1 / 256.0)
- v = int(s * 255)
+ v = int(self.data.get_entropy(self.data.start + i * self.block_size, self.block_size)[0] * 255)
if v >= 240:
- self.image.setPixelColor(i, 0, QColor(v, v, v / 4, 255))
+ color = binaryninjaui.getThemeColor(ThemeColor.YellowStandardHighlightColor)
+ self.image.setPixelColor(i, 0, color)
else:
- self.image.setPixelColor(i, 0, QColor(v / 4, v / 4, v, 255))
+ baseColor = binaryninjaui.getThemeColor(ThemeColor.FeatureMapBaseColor)
+ entropyColor = binaryninjaui.getThemeColor(ThemeColor.BlueStandardHighlightColor)
+ color = binaryninjaui.mixColor(baseColor, entropyColor, v)
+ self.image.setPixelColor(i, 0, color)
self.updated = True
class EntropyWidget(QWidget):
- def __init__(self, parent, data):
+ def __init__(self, parent, view, data):
super(EntropyWidget, self).__init__(parent)
+ self.view = view
self.data = data
self.raw_data = data.file.raw
@@ -54,7 +47,7 @@ class EntropyWidget(QWidget):
self.timer = QTimer()
self.timer.timeout.connect(self.timerEvent)
- self.timer.setInterval(250)
+ self.timer.setInterval(100)
self.timer.setSingleShot(False)
self.timer.start()
@@ -81,11 +74,4 @@ class EntropyWidget(QWidget):
return
frac = float(event.x()) / self.rect().width()
offset = int(frac * self.width * self.block_size)
- addr = self.data.get_address_for_data_offset(offset)
- view_frame = ViewFrame.viewFrameForWidget(self)
- if view_frame is None:
- return
- if addr is None:
- view_frame.navigate("Hex:Raw", offset)
- else:
- view_frame.navigate("Linear:" + view_frame.getCurrentDataType(), addr)
+ self.view.navigateToFileOffset(offset)
diff --git a/python/examples/triage/exports.py b/python/examples/triage/exports.py
new file mode 100644
index 00000000..0e2239e0
--- /dev/null
+++ b/python/examples/triage/exports.py
@@ -0,0 +1,123 @@
+from PySide2.QtWidgets import QTreeView
+from PySide2.QtCore import Qt, QAbstractItemModel, QModelIndex, QSize
+from binaryninja.enums import SymbolType, SymbolBinding
+import binaryninjaui
+from binaryninjaui import ViewFrame
+
+
+class GenericExportsModel(QAbstractItemModel):
+ def __init__(self, data):
+ super(GenericExportsModel, self).__init__()
+ self.entries = []
+ self.addr_col = 0
+ self.name_col = 1
+ self.ordinal_col = None
+ self.total_cols = 2
+ for sym in data.get_symbols_of_type(SymbolType.FunctionSymbol):
+ if sym.binding == SymbolBinding.GlobalBinding:
+ self.entries.append(sym)
+ for sym in data.get_symbols_of_type(SymbolType.DataSymbol):
+ if sym.binding == SymbolBinding.GlobalBinding:
+ self.entries.append(sym)
+ if data.view_type == "PE":
+ self.ordinal_col = 0
+ self.addr_col = 1
+ self.name_col = 2
+ self.total_cols = 3
+
+ def columnCount(self, parent):
+ return self.total_cols
+
+ def rowCount(self, parent):
+ if parent.isValid():
+ return 0
+ return len(self.entries)
+
+ def data(self, index, role):
+ if role != Qt.DisplayRole:
+ return None
+ if index.row() >= len(self.entries):
+ return None
+ if index.column() == self.addr_col:
+ return "0x%x" % self.entries[index.row()].address
+ if index.column() == self.name_col:
+ return self.entries[index.row()].full_name
+ if index.column() == self.ordinal_col:
+ return str(self.entries[index.row()].ordinal)
+ return None
+
+ def headerData(self, section, orientation, role):
+ if orientation == Qt.Vertical:
+ return None
+ if role != Qt.DisplayRole:
+ return None
+ if section == self.addr_col:
+ return "Address"
+ if section == self.name_col:
+ return "Name"
+ if section == self.ordinal_col:
+ return "Ordinal"
+ return None
+
+ def index(self, row, col, parent):
+ if parent.isValid():
+ return QModelIndex()
+ if row >= len(self.entries):
+ return QModelIndex()
+ if col >= self.total_cols:
+ return QModelIndex()
+ return self.createIndex(row, col)
+
+ def parent(self, index):
+ return QModelIndex()
+
+ def getSymbol(self, index):
+ if index.row() >= len(self.entries):
+ return None
+ return self.entries[index.row()]
+
+ def sort(self, col, order):
+ self.beginResetModel()
+ if col == self.addr_col:
+ self.entries.sort(key = lambda sym: sym.address, reverse = order != Qt.AscendingOrder)
+ elif col == self.name_col:
+ self.entries.sort(key = lambda sym: sym.full_name, reverse = order != Qt.AscendingOrder)
+ elif col == self.ordinal_col:
+ self.entries.sort(key = lambda sym: sym.ordinal, reverse = order != Qt.AscendingOrder)
+ self.endResetModel()
+
+
+class ExportsWidget(QTreeView):
+ def __init__(self, parent, view, data):
+ super(ExportsWidget, self).__init__(parent)
+ self.data = data
+ self.view = view
+
+ self.model = GenericExportsModel(self.data)
+ self.setModel(self.model)
+ self.setRootIsDecorated(False)
+ self.setUniformRowHeights(True)
+ self.setSortingEnabled(True)
+ self.sortByColumn(0, Qt.AscendingOrder)
+ if self.model.ordinal_col is not None:
+ self.setColumnWidth(self.model.ordinal_col, 55)
+ self.setMinimumSize(QSize(100, 196))
+
+ self.setFont(binaryninjaui.getMonospaceFont(self))
+
+ self.selectionModel().currentChanged.connect(self.exportSelected)
+ self.doubleClicked.connect(self.exportDoubleClicked)
+
+ def exportSelected(self, cur, prev):
+ sym = self.model.getSymbol(cur)
+ if sym is not None:
+ self.view.setCurrentOffset(sym.address)
+
+ def exportDoubleClicked(self, cur):
+ sym = self.model.getSymbol(cur)
+ if sym is not None:
+ viewFrame = ViewFrame.viewFrameForWidget(self)
+ if len(self.data.get_functions_at(sym.address)) > 0:
+ viewFrame.navigate("Graph:" + viewFrame.getCurrentDataType(), sym.address)
+ else:
+ viewFrame.navigate("Linear:" + viewFrame.getCurrentDataType(), sym.address)
diff --git a/python/examples/triage/files.py b/python/examples/triage/files.py
new file mode 100644
index 00000000..333bcf5a
--- /dev/null
+++ b/python/examples/triage/files.py
@@ -0,0 +1,140 @@
+import os
+from PySide2.QtWidgets import QWidget, QTreeView, QFileSystemModel, QVBoxLayout, QMessageBox, QAbstractItemView
+from PySide2.QtGui import QKeySequence
+from PySide2.QtCore import QSettings
+from binaryninjaui import UIActionHandler, UIAction, Menu, FileContext, ContextMenuManager
+from binaryninja.settings import Settings
+
+
+class TriageFilePicker(QWidget):
+ def __init__(self, context):
+ super(TriageFilePicker, self).__init__()
+ self.context = context
+ self.actionHandler = UIActionHandler()
+ self.actionHandler.setupActionHandler(self)
+ self.contextMenu = Menu()
+ self.contextMenuManager = ContextMenuManager(self)
+
+ layout = QVBoxLayout()
+ layout.setContentsMargins(0, 0, 0, 0)
+
+ self.model = QFileSystemModel()
+ self.model.setRootPath("")
+ self.tree = QTreeView(self)
+ self.tree.setModel(self.model)
+ self.tree.setSelectionMode(QAbstractItemView.ExtendedSelection)
+ self.tree.setColumnWidth(0, 500)
+ layout.addWidget(self.tree, 1)
+
+ self.setLayout(layout)
+
+ self.tree.doubleClicked.connect(self.onDoubleClick)
+
+ recentFile = QSettings().value("triage/recentFile", os.path.expanduser("~"))
+ while len(recentFile) > 0:
+ f = self.model.index(recentFile)
+ if f.isValid():
+ self.tree.scrollTo(f)
+ self.tree.setExpanded(f, True)
+ break
+ parentDir = os.path.dirname(recentFile)
+ if parentDir == recentFile:
+ break
+ recentFile = parentDir
+
+ self.actionHandler.bindAction("Open Selected Files", UIAction(
+ lambda context: self.openSelectedFiles(),
+ lambda context: self.areFilesSelected()))
+ self.contextMenu.addAction("Open Selected Files", "Open")
+
+ def contextMenuEvent(self, event):
+ self.contextMenuManager.show(self.contextMenu, self.actionHandler)
+
+ def onDoubleClick(self, index):
+ self.openSelectedFiles()
+
+ def openSelectedFiles(self):
+ failedToOpen = []
+ files = set()
+
+ for index in self.tree.selectionModel().selectedIndexes():
+ if self.model.fileInfo(index).isFile():
+ files.add(self.model.fileInfo(index).absoluteFilePath())
+
+ for filename in files:
+ QSettings().setValue("triage/recentFile", filename)
+
+ f = FileContext.openFilename(filename)
+ if not f:
+ failedToOpen.append(filename)
+ continue
+
+ f.createBinaryViews()
+ for data in f.getAllDataViews():
+ Settings().set_string("analysis.mode", Settings().get_string("triage.analysis_mode"), data)
+ Settings().set_bool("triage.always_prefer", True, data)
+ if data.view_type != "Raw":
+ linearSweepMode = Settings().get_string("triage.linear_sweep")
+ if linearSweepMode == "none":
+ Settings().set_bool("analysis.linearSweep.autorun", False, data)
+ elif linearSweepMode == "partial":
+ Settings().set_bool("analysis.linearSweep.autorun", True, data)
+ Settings().set_bool("analysis.linearSweep.controlFlowGraph", False, data)
+ elif linearSweepMode == "full":
+ Settings().set_bool("analysis.linearSweep.autorun", True, data)
+ Settings().set_bool("analysis.linearSweep.controlFlowGraph", True, data)
+
+ self.context.openFileContext(f)
+
+ if len(failedToOpen) > 0:
+ QMessageBox.critical(self, "Error", "Unable to open:\n" + "\n".join(failedToOpen))
+
+ def areFilesSelected(self):
+ return self.tree.selectionModel().hasSelection()
+
+
+def openForTriage(context):
+ currentContext = context.context
+ if currentContext is None:
+ return
+
+ # Do not try to set the parent window when creating tabs, as this will create a parent relationship in
+ # the bindings and will cause the widget to be destructed early. The correct parent will be assigned
+ # when createTabForWidget is called.
+ fp = TriageFilePicker(currentContext)
+ currentContext.createTabForWidget("Open for Triage", fp)
+
+
+Settings().register_setting("triage.analysis_mode", """
+ {
+ "title" : "Triage Analysis Mode",
+ "type" : "string",
+ "default" : "basic",
+ "description" : "Controls the amount of analysis performed on functions when opening for triage.",
+ "enum" : ["controlFlow", "basic", "full"],
+ "enumDescriptions" : [
+ "Only perform control flow analysis on the binary. Cross references are valid only for direct function calls.",
+ "Perform fast initial analysis of the binary. This mode does not analyze types or data flow through stack variables.",
+ "Perform full analysis of the binary." ]
+ }
+ """)
+
+Settings().register_setting("triage.linear_sweep", """
+ {
+ "title" : "Triage Linear Sweep Mode",
+ "type" : "string",
+ "default" : "partial",
+ "description" : "Controls the level of linear sweep performed when opening for triage.",
+ "enum" : ["none", "partial", "full"],
+ "enumDescriptions" : [
+ "Do not perform linear sweep of the binary.",
+ "Perform linear sweep on the binary, but skip the control flow graph analysis phase.",
+ "Perform full linear sweep on the binary." ]
+ }
+ """)
+
+UIAction.registerAction("Open for Triage...", QKeySequence("Ctrl+Alt+O"))
+UIAction.registerAction("Open Selected Files")
+
+UIActionHandler.globalActions().bindAction("Open for Triage...", UIAction(openForTriage))
+Menu.mainMenu("File").addAction("Open for Triage...", "Open")
diff --git a/python/examples/triage/headers.py b/python/examples/triage/headers.py
index d800086c..47869134 100644
--- a/python/examples/triage/headers.py
+++ b/python/examples/triage/headers.py
@@ -1,6 +1,53 @@
import time
from binaryninja.binaryview import StructuredDataView
+import binaryninjaui
+from binaryninjaui import ThemeColor, ViewFrame
from PySide2.QtWidgets import QWidget, QLabel, QGridLayout
+from PySide2.QtGui import QPalette
+
+
+class ClickableLabel(QLabel):
+ def __init__(self, text, color, func):
+ super(ClickableLabel, self).__init__(text)
+ style = QPalette(self.palette())
+ style.setColor(QPalette.WindowText, color)
+ self.setPalette(style)
+ self.setFont(binaryninjaui.getMonospaceFont(self))
+ self.func = func
+
+ def mousePressEvent(self, event):
+ self.func()
+
+
+class ClickableAddressLabel(ClickableLabel):
+ def __init__(self, text):
+ super(ClickableAddressLabel, self).__init__(text, binaryninjaui.getThemeColor(ThemeColor.AddressColor), self.clickEvent)
+ self.address = int(text, 0)
+
+ def clickEvent(self):
+ viewFrame = ViewFrame.viewFrameForWidget(self)
+ viewFrame.navigate("Linear:" + viewFrame.getCurrentDataType(), self.address)
+
+
+class ClickableCodeLabel(ClickableLabel):
+ def __init__(self, text):
+ super(ClickableCodeLabel, self).__init__(text, binaryninjaui.getThemeColor(ThemeColor.CodeSymbolColor), self.clickEvent)
+ self.address = int(text, 0)
+
+ def clickEvent(self):
+ viewFrame = ViewFrame.viewFrameForWidget(self)
+ viewFrame.navigate("Graph:" + viewFrame.getCurrentDataType(), self.address)
+
+
+class GenericHeaders(object):
+ def __init__(self, data):
+ self.fields = []
+ self.fields.append(("Type", data.view_type))
+ if data.platform is not None:
+ self.fields.append(("Platform", data.platform.name))
+ if data.is_valid_offset(data.entry_point):
+ self.fields.append(("Entry Point", "0x%x" % data.entry_point, "code"))
+ self.columns = 1
class PEHeaders(object):
@@ -42,10 +89,10 @@ class PEHeaders(object):
self.fields.append(("Timestamp", time.strftime("%c", time.localtime(int(coff.timeDateStamp)))))
base = int(peopt.imageBase)
- self.fields.append(("Image Base", "0x%x" % base))
+ self.fields.append(("Image Base", "0x%x" % base, "ptr"))
entry_point = base + int(peopt.addressOfEntryPoint)
- self.fields.append(("Entry Point", "0x%x" % entry_point))
+ self.fields.append(("Entry Point", "0x%x" % entry_point, "code"))
section_align = int(peopt.sectionAlignment)
self.fields.append(("Section Alignment", "0x%x" % section_align))
@@ -57,11 +104,11 @@ class PEHeaders(object):
self.fields.append(("Checksum", "0x%.8x" % checksum))
code_base = base + int(peopt.baseOfCode)
- self.fields.append(("Base of Code", "0x%x" % code_base))
+ self.fields.append(("Base of Code", "0x%x" % code_base, "ptr"))
if not is64bit:
data_base = base + int(peopt.baseOfData)
- self.fields.append(("Base of Data", "0x%x" % data_base))
+ self.fields.append(("Base of Data", "0x%x" % data_base, "ptr"))
code_size = int(peopt.sizeOfCode)
self.fields.append(("Size of Code", "0x%x" % code_size))
@@ -126,6 +173,9 @@ class PEHeaders(object):
if len(dll_char_values) > 0:
self.fields.append(("DLL Characteristics", dll_char_values))
+ self.columns = 3
+ self.rows_per_column = 9
+
class HeaderWidget(QWidget):
def __init__(self, parent, header):
@@ -138,18 +188,35 @@ class HeaderWidget(QWidget):
for field in header.fields:
name = field[0]
value = field[1]
- layout.addWidget(QLabel(name + ": "), row, col)
+ fieldType = ""
+ if len(field) > 2:
+ fieldType = field[2]
+ layout.addWidget(QLabel(name + ": "), row, col * 3)
if isinstance(value, list):
for i in range(0, len(value)):
- layout.addWidget(QLabel(value[i]), row, col + 1)
+ if fieldType == "ptr":
+ label = ClickableAddressLabel(value[i])
+ elif fieldType == "code":
+ label = ClickableCodeLabel(value[i])
+ else:
+ label = QLabel(value[i])
+ label.setFont(binaryninjaui.getMonospaceFont(self))
+ layout.addWidget(label, row, col * 3 + 1)
row += 1
else:
- layout.addWidget(QLabel(value), row, col + 1)
+ if fieldType == "ptr":
+ label = ClickableAddressLabel(value)
+ elif fieldType == "code":
+ label = ClickableCodeLabel(value)
+ else:
+ label = QLabel(value)
+ label.setFont(binaryninjaui.getMonospaceFont(self))
+ layout.addWidget(label, row, col * 3 + 1)
row += 1
- if (row >= 9) and (col < 6):
+ if (header.columns > 1) and (row >= header.rows_per_column) and ((col + 1) < header.columns):
row = 0
- col += 3
- layout.setColumnMinimumWidth(2, 20)
- layout.setColumnMinimumWidth(5, 20)
- layout.setColumnStretch(8, 1)
+ col += 1
+ for col in range(1, header.columns):
+ layout.setColumnMinimumWidth(col * 3 - 1, 20)
+ layout.setColumnStretch(header.columns * 3 - 1, 1)
self.setLayout(layout)
diff --git a/python/examples/triage/imports.py b/python/examples/triage/imports.py
index 2b2ed253..293ed239 100644
--- a/python/examples/triage/imports.py
+++ b/python/examples/triage/imports.py
@@ -1,7 +1,8 @@
from PySide2.QtWidgets import QTreeView
-from PySide2.QtCore import Qt, QAbstractItemModel, QModelIndex
+from PySide2.QtCore import Qt, QAbstractItemModel, QModelIndex, QSize
from binaryninja.enums import SymbolType
import binaryninjaui
+from binaryninjaui import ViewFrame
class GenericImportsModel(QAbstractItemModel):
@@ -11,20 +12,24 @@ class GenericImportsModel(QAbstractItemModel):
self.has_modules = False
self.name_col = 1
self.module_col = None
+ self.ordinal_col = None
self.total_cols = 2
for sym in data.get_symbols_of_type(SymbolType.ImportAddressSymbol):
self.entries.append(sym)
if str(sym.namespace) != "BNINTERNALNAMESPACE":
self.has_modules = True
if self.has_modules:
- self.name_col = 2
+ self.name_col = 3
self.module_col = 1
- self.total_cols = 3
+ self.ordinal_col = 2
+ self.total_cols = 4
def columnCount(self, parent):
return self.total_cols
def rowCount(self, parent):
+ if parent.isValid():
+ return 0
return len(self.entries)
def data(self, index, role):
@@ -35,7 +40,7 @@ class GenericImportsModel(QAbstractItemModel):
if index.column() == 0:
return "0x%x" % self.entries[index.row()].address
if index.column() == self.name_col:
- name = self.entries[index.row()].name
+ name = self.entries[index.row()].full_name
if name.endswith("@GOT"):
name = name[:-len("@GOT")]
elif name.endswith("@PLT"):
@@ -45,6 +50,8 @@ class GenericImportsModel(QAbstractItemModel):
return name
if index.column() == self.module_col:
return self.getNamespace(self.entries[index.row()])
+ if index.column() == self.ordinal_col:
+ return str(self.entries[index.row()].ordinal)
return None
def headerData(self, section, orientation, role):
@@ -58,6 +65,8 @@ class GenericImportsModel(QAbstractItemModel):
return "Name"
if section == self.module_col:
return "Module"
+ if section == self.ordinal_col:
+ return "Ordinal"
return None
def index(self, row, col, parent):
@@ -88,9 +97,11 @@ class GenericImportsModel(QAbstractItemModel):
if col == 0:
self.entries.sort(key = lambda sym: sym.address, reverse = order != Qt.AscendingOrder)
elif col == self.name_col:
- self.entries.sort(key = lambda sym: sym.name, reverse = order != Qt.AscendingOrder)
+ self.entries.sort(key = lambda sym: sym.full_name, reverse = order != Qt.AscendingOrder)
elif col == self.module_col:
self.entries.sort(key = lambda sym: self.getNamespace(sym), reverse = order != Qt.AscendingOrder)
+ elif col == self.ordinal_col:
+ self.entries.sort(key = lambda sym: sym.ordinal, reverse = order != Qt.AscendingOrder)
self.endResetModel()
@@ -106,12 +117,22 @@ class ImportsWidget(QTreeView):
self.setUniformRowHeights(True)
self.setSortingEnabled(True)
self.sortByColumn(0, Qt.AscendingOrder)
+ if self.model.ordinal_col is not None:
+ self.setColumnWidth(self.model.ordinal_col, 55)
+ self.setMinimumSize(QSize(100, 196))
self.setFont(binaryninjaui.getMonospaceFont(self))
- self.clicked.connect(self.importSelected)
+ self.selectionModel().currentChanged.connect(self.importSelected)
+ self.doubleClicked.connect(self.importDoubleClicked)
- def importSelected(self, index):
- sym = self.model.getSymbol(index)
+ def importSelected(self, cur, prev):
+ sym = self.model.getSymbol(cur)
if sym is not None:
self.view.setCurrentOffset(sym.address)
+
+ def importDoubleClicked(self, cur):
+ sym = self.model.getSymbol(cur)
+ if sym is not None:
+ viewFrame = ViewFrame.viewFrameForWidget(self)
+ viewFrame.navigate("Linear:" + viewFrame.getCurrentDataType(), sym.address)
diff --git a/python/examples/triage/sections.py b/python/examples/triage/sections.py
new file mode 100644
index 00000000..4a8d0f65
--- /dev/null
+++ b/python/examples/triage/sections.py
@@ -0,0 +1,147 @@
+# coding: utf8
+
+from PySide2.QtWidgets import QWidget, QLabel, QGridLayout, QHBoxLayout
+from binaryninja.enums import SectionSemantics
+import binaryninjaui
+from binaryninjaui import ThemeColor, ViewFrame
+from . import headers
+
+
+class SegmentsWidget(QWidget):
+ def __init__(self, parent, data):
+ super(SegmentsWidget, self).__init__(parent)
+
+ layout = QGridLayout()
+ layout.setContentsMargins(0, 0, 0, 0)
+ layout.setVerticalSpacing(1)
+ layout.setHorizontalSpacing(16)
+
+ self.segments = []
+ for segment in data.segments:
+ if segment.readable or segment.writable or segment.executable:
+ self.segments.append(segment)
+ self.segments.sort(key = lambda segment: segment.start)
+
+ row = 0
+ for segment in self.segments:
+ begin = "0x%x" % segment.start
+ end = "0x%x" % segment.end
+
+ permissions = ""
+ if segment.readable:
+ permissions += "r"
+ else:
+ permissions += "-"
+ if segment.writable:
+ permissions += "w"
+ else:
+ permissions += "-"
+ if segment.executable:
+ permissions += "x"
+ else:
+ permissions += "-"
+
+ rangeLayout = QHBoxLayout()
+ rangeLayout.setContentsMargins(0, 0, 0, 0)
+ beginLabel = headers.ClickableAddressLabel(begin)
+ dashLabel = QLabel("-")
+ dashLabel.setFont(binaryninjaui.getMonospaceFont(self))
+ endLabel = headers.ClickableAddressLabel(end)
+ rangeLayout.addWidget(beginLabel)
+ rangeLayout.addWidget(dashLabel)
+ rangeLayout.addWidget(endLabel)
+ layout.addLayout(rangeLayout, row, 0)
+
+ permissionsLabel = QLabel(permissions)
+ permissionsLabel.setFont(binaryninjaui.getMonospaceFont(self))
+ layout.addWidget(permissionsLabel, row, 1)
+
+ row += 1
+
+ layout.setColumnStretch(2, 1)
+ self.setLayout(layout)
+
+
+class SectionsWidget(QWidget):
+ def __init__(self, parent, data):
+ super(SectionsWidget, self).__init__(parent)
+
+ layout = QGridLayout()
+ layout.setContentsMargins(0, 0, 0, 0)
+ layout.setVerticalSpacing(1)
+ layout.setHorizontalSpacing(16)
+
+ maxNameLen = 0
+ for section in data.sections.values():
+ if len(section.name) > maxNameLen:
+ maxNameLen = len(section.name)
+ if maxNameLen > 32:
+ maxNameLen = 32
+
+ self.sections = []
+ for section in data.sections.values():
+ if section.semantics != SectionSemantics.ExternalSectionSemantics:
+ self.sections.append(section)
+ self.sections.sort(key = lambda section: section.start)
+
+ row = 0
+ for section in self.sections:
+ name = section.name
+ if len(name) > maxNameLen:
+ name = name[:maxNameLen - 1] + "…"
+
+ begin = "0x%x" % section.start
+ end = "0x%x" % section.end
+ typeName = section.type
+
+ permissions = ""
+ if data.is_offset_readable(section.start):
+ permissions += "r"
+ else:
+ permissions += "-"
+ if data.is_offset_writable(section.start):
+ permissions += "w"
+ else:
+ permissions += "-"
+ if data.is_offset_executable(section.start):
+ permissions += "x"
+ else:
+ permissions += "-"
+
+ semantics = ""
+ if section.semantics == SectionSemantics.ReadOnlyCodeSectionSemantics:
+ semantics = "Code"
+ elif section.semantics == SectionSemantics.ReadOnlyDataSectionSemantics:
+ semantics = "Ready-only Data"
+ elif section.semantics == SectionSemantics.ReadWriteDataSectionSemantics:
+ semantics = "Writable Data"
+
+ nameLabel = QLabel(name)
+ nameLabel.setFont(binaryninjaui.getMonospaceFont(self))
+ layout.addWidget(nameLabel, row, 0)
+
+ rangeLayout = QHBoxLayout()
+ rangeLayout.setContentsMargins(0, 0, 0, 0)
+ beginLabel = headers.ClickableAddressLabel(begin)
+ dashLabel = QLabel("-")
+ dashLabel.setFont(binaryninjaui.getMonospaceFont(self))
+ endLabel = headers.ClickableAddressLabel(end)
+ rangeLayout.addWidget(beginLabel)
+ rangeLayout.addWidget(dashLabel)
+ rangeLayout.addWidget(endLabel)
+ layout.addLayout(rangeLayout, row, 1)
+
+ permissionsLabel = QLabel(permissions)
+ permissionsLabel.setFont(binaryninjaui.getMonospaceFont(self))
+ layout.addWidget(permissionsLabel, row, 2)
+ typeLabel = QLabel(typeName)
+ typeLabel.setFont(binaryninjaui.getMonospaceFont(self))
+ layout.addWidget(typeLabel, row, 3)
+ semanticsLabel = QLabel(semantics)
+ semanticsLabel.setFont(binaryninjaui.getMonospaceFont(self))
+ layout.addWidget(semanticsLabel, row, 4)
+
+ row += 1
+
+ layout.setColumnStretch(5, 1)
+ self.setLayout(layout)
diff --git a/python/examples/triage/view.py b/python/examples/triage/view.py
index e06776ca..863ae045 100644
--- a/python/examples/triage/view.py
+++ b/python/examples/triage/view.py
@@ -2,11 +2,15 @@ import traceback
import binaryninjaui
from binaryninja.settings import Settings
from binaryninja import log
-from binaryninjaui import View, ViewType, UIContext
-from PySide2.QtWidgets import QScrollArea, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QGroupBox
+from binaryninjaui import View, ViewType, UIContext, ViewFrame
+from PySide2.QtWidgets import QScrollArea, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QGroupBox, QSplitter
+from PySide2.QtCore import Qt
from . import headers
from . import entropy
from . import imports
+from . import exports
+from . import sections
+from . import byte
class TriageView(QScrollArea, View):
@@ -16,13 +20,15 @@ class TriageView(QScrollArea, View):
self.setupView(self)
self.data = data
self.currentOffset = 0
+ self.byteView = None
+ self.fullAnalysisButton = None
container = QWidget(self)
layout = QVBoxLayout()
entropyGroup = QGroupBox("Entropy")
entropyLayout = QVBoxLayout()
- entropyLayout.addWidget(entropy.EntropyWidget(entropyGroup, self.data))
+ entropyLayout.addWidget(entropy.EntropyWidget(entropyGroup, self, self.data))
entropyGroup.setLayout(entropyLayout)
layout.addWidget(entropyGroup)
@@ -30,6 +36,8 @@ class TriageView(QScrollArea, View):
try:
if self.data.view_type == "PE":
hdr = headers.PEHeaders(self.data)
+ elif self.data.view_type != "Raw":
+ hdr = headers.GenericHeaders(self.data)
except:
log.log_error(traceback.format_exc())
@@ -41,33 +49,73 @@ class TriageView(QScrollArea, View):
headerGroup.setLayout(headerLayout)
layout.addWidget(headerGroup)
- importGroup = QGroupBox("Imports")
- importLayout = QVBoxLayout()
- importLayout.addWidget(imports.ImportsWidget(importGroup, self, self.data))
- importGroup.setLayout(importLayout)
- layout.addWidget(importGroup)
+ if self.data.executable:
+ importExportSplitter = QSplitter(Qt.Horizontal)
- button_layout = QHBoxLayout()
- button_layout.addStretch(1)
- self.full_analysis_button = QPushButton("Start Full Analysis")
- self.full_analysis_button.clicked.connect(self.startFullAnalysis)
- button_layout.addWidget(self.full_analysis_button)
- layout.addLayout(button_layout)
+ importGroup = QGroupBox("Imports")
+ importLayout = QVBoxLayout()
+ importLayout.addWidget(imports.ImportsWidget(importGroup, self, self.data))
+ importGroup.setLayout(importLayout)
+ importExportSplitter.addWidget(importGroup)
+
+ exportGroup = QGroupBox("Exports")
+ exportLayout = QVBoxLayout()
+ exportLayout.addWidget(exports.ExportsWidget(exportGroup, self, self.data))
+ exportGroup.setLayout(exportLayout)
+ importExportSplitter.addWidget(exportGroup)
+
+ layout.addWidget(importExportSplitter)
+
+ if self.data.view_type != "PE":
+ segmentsGroup = QGroupBox("Segments")
+ segmentsLayout = QVBoxLayout()
+ segmentsWidget = sections.SegmentsWidget(segmentsGroup, self.data)
+ segmentsLayout.addWidget(segmentsWidget)
+ segmentsGroup.setLayout(segmentsLayout)
+ layout.addWidget(segmentsGroup)
+ if len(segmentsWidget.segments) == 0:
+ segmentsGroup.hide()
+
+ sectionsGroup = QGroupBox("Sections")
+ sectionsLayout = QVBoxLayout()
+ sectionsWidget = sections.SectionsWidget(sectionsGroup, self.data)
+ sectionsLayout.addWidget(sectionsWidget)
+ sectionsGroup.setLayout(sectionsLayout)
+ layout.addWidget(sectionsGroup)
+ if len(sectionsWidget.sections) == 0:
+ sectionsGroup.hide()
+
+ buttonLayout = QHBoxLayout()
+ buttonLayout.addStretch(1)
+ self.fullAnalysisButton = QPushButton("Start Full Analysis")
+ self.fullAnalysisButton.clicked.connect(self.startFullAnalysis)
+ buttonLayout.addWidget(self.fullAnalysisButton)
+ layout.addLayout(buttonLayout)
+ layout.addStretch(1)
+ else:
+ self.byteView = byte.ByteView(self, self.data)
+ layout.addWidget(self.byteView, 1)
- layout.addStretch(1)
container.setLayout(layout)
self.setWidgetResizable(True)
self.setWidget(container)
- if Settings().get_string("analysis.mode", data) == "full":
- self.full_analysis_button.hide()
+ if self.fullAnalysisButton is not None and Settings().get_string("analysis.mode", data) == "full":
+ self.fullAnalysisButton.hide()
def getData(self):
return self.data
def getCurrentOffset(self):
+ if self.byteView is not None:
+ return self.byteView.getCurrentOffset()
return self.currentOffset
+ def getSelectionOffsets(self):
+ if self.byteView is not None:
+ return self.byteView.getSelectionOffsets()
+ return (self.currentOffset, self.currentOffset)
+
def setCurrentOffset(self, offset):
self.currentOffset = offset
UIContext.updateStatus(True)
@@ -76,6 +124,8 @@ class TriageView(QScrollArea, View):
return binaryninjaui.getMonospaceFont(self)
def navigate(self, addr):
+ if self.byteView is not None:
+ return self.navigate(addr)
return False
def startFullAnalysis(self):
@@ -84,7 +134,34 @@ class TriageView(QScrollArea, View):
if f.analysis_skipped:
f.reanalyze()
self.data.update_analysis()
- self.full_analysis_button.hide()
+ self.fullAnalysisButton.hide()
+
+ def navigateToFileOffset(self, offset):
+ if self.byteView is None:
+ addr = self.data.get_address_for_data_offset(offset)
+ view_frame = ViewFrame.viewFrameForWidget(self)
+ if view_frame is None:
+ return
+ if addr is None:
+ view_frame.navigate("Hex:Raw", offset)
+ else:
+ view_frame.navigate("Linear:" + view_frame.getCurrentDataType(), addr)
+ else:
+ if self.data == self.data.file.raw:
+ addr = offset
+ else:
+ addr = self.data.get_address_for_data_offset(offset)
+ if addr is None:
+ view_frame = ViewFrame.viewFrameForWidget(self)
+ if view_frame is not None:
+ view_frame.navigate("Hex:Raw", offset)
+ else:
+ self.byteView.navigate(addr)
+ self.byteView.setFocus(Qt.OtherFocusReason)
+
+ def focusInEvent(self, event):
+ if self.byteView is not None:
+ self.byteView.setFocus(Qt.OtherFocusReason)
class TriageViewType(ViewType):
@@ -93,16 +170,22 @@ class TriageViewType(ViewType):
def getPriority(self, data, filename):
is_full = Settings().get_string("analysis.mode", data) == "full"
- always_prefer = Settings().get_bool("ui.always_prefer_triage", data)
+ always_prefer = Settings().get_bool("triage.always_prefer", data)
+ prefer_for_raw = Settings().get_bool("triage.prefer_for_raw", data)
if data.executable and (always_prefer or not is_full):
return 100
- return 25
+ if len(data) > 0:
+ if always_prefer or data.executable or prefer_for_raw:
+ return 25
+ return 1
+ return 0
def create(self, data, view_frame):
return TriageView(view_frame, data)
-Settings().register_setting("ui.always_prefer_triage", """
+Settings().register_group("triage", "Triage")
+Settings().register_setting("triage.always_prefer", """
{
"title" : "Always Prefer Triage Summary View",
"type" : "boolean",
@@ -111,4 +194,13 @@ Settings().register_setting("ui.always_prefer_triage", """
}
""")
+Settings().register_setting("triage.prefer_for_raw", """
+ {
+ "title" : "Prefer Triage Summary View for Raw Files",
+ "type" : "boolean",
+ "default" : true,
+ "description" : "Prefer opening raw files in Triage Summary view."
+ }
+ """)
+
ViewType.registerViewType(TriageViewType())