summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorRusty Wagner <rusty@vector35.com>2019-04-02 15:30:45 -0400
committerRusty Wagner <rusty@vector35.com>2019-04-02 15:36:40 -0400
commit73a9399f5d1d86c7e291498b7fe836550f2f76ab (patch)
treee7c7cfe9a2f27a93ddac41cf600e7bc1fc638a83 /examples
parentc17af471c48ea092434917c803f9c88fe91022a6 (diff)
Add a C++ version of the triage plugin (no Makefile yet)
Diffstat (limited to 'examples')
-rw-r--r--examples/triage/byte.cpp1114
-rw-r--r--examples/triage/byte.h119
-rw-r--r--examples/triage/entropy.cpp113
-rw-r--r--examples/triage/entropy.h51
-rw-r--r--examples/triage/exports.cpp280
-rw-r--r--examples/triage/exports.h73
-rw-r--r--examples/triage/files.cpp123
-rw-r--r--examples/triage/files.h31
-rw-r--r--examples/triage/headers.cpp329
-rw-r--r--examples/triage/headers.h99
-rw-r--r--examples/triage/imports.cpp306
-rw-r--r--examples/triage/imports.h75
-rw-r--r--examples/triage/sections.cpp151
-rw-r--r--examples/triage/sections.h24
-rw-r--r--examples/triage/triage.cpp82
-rw-r--r--examples/triage/view.cpp241
-rw-r--r--examples/triage/view.h42
17 files changed, 3253 insertions, 0 deletions
diff --git a/examples/triage/byte.cpp b/examples/triage/byte.cpp
new file mode 100644
index 00000000..b1ff46fd
--- /dev/null
+++ b/examples/triage/byte.cpp
@@ -0,0 +1,1114 @@
+#include <QtWidgets/QScrollBar>
+#include <QtCore/QTimer>
+#include "byte.h"
+#include "fontsettings.h"
+#include "theme.h"
+
+
+static const char* g_byteMapping[] =
+{
+ " ", "☺", "☻", "♥", "♦", "♣", "♠", "•", "◘", "○", "◙", "♂", "♀", "♪", "♫", "☼",
+ "▸", "◂", "↕", "‼", "¶", "§", "▬", "↨", "↑", "↓", "→", "←", "∟", "↔", "▴", "▾",
+ " ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/",
+ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?",
+ "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O",
+ "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_",
+ "`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
+ "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~", "⌂",
+ "Ç", "ü", "é", "â", "ä", "à", "å", "ç", "ê", "ë", "è", "ï", "î", "ì", "Ä", "Å",
+ "É", "æ", "Æ", "ô", "ö", "ò", "û", "ù", "ÿ", "Ö", "Ü", "¢", "£", "¥", "₧", "ƒ",
+ "á", "í", "ó", "ú", "ñ", "Ñ", "ª", "º", "¿", "⌐", "¬", "½", "¼", "¡", "«", "»",
+ "░", "▒", "▓", "│", "┤", "╡", "╢", "╖", "╕", "╣", "║", "╗", "╝", "╜", "╛", "┐",
+ "└", "┴", "┬", "├", "─", "┼", "╞", "╟", "╚", "╔", "╩", "╦", "╠", "═", "╬", "╧",
+ "╨", "╤", "╥", "╙", "╘", "╒", "╓", "╫", "╪", "┘", "┌", "█", "▄", "▌", "▐", "▀",
+ "α", "ß", "Γ", "π", "Σ", "σ", "µ", "τ", "Φ", "Θ", "Ω", "δ", "∞", "φ", "ε", "∩",
+ "≡", "±", "≥", "≤", "⌠", "⌡", "÷", "≈", "°", "∙", "·", "√", "ⁿ", "²", "■", " "
+};
+
+
+ByteView::ByteView(QWidget* parent, BinaryViewRef data): QAbstractScrollArea(parent), m_render(this)
+{
+ setBinaryDataNavigable(true);
+ setupView(this);
+ m_data = data;
+
+ setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
+ setFocusPolicy(Qt::StrongFocus);
+
+ m_cursorAddr = m_data->GetStart();
+ m_prevCursorAddr = m_cursorAddr;
+ m_selectionStartAddr = m_cursorAddr;
+ m_topAddr = m_cursorAddr;
+ m_topLine = 0;
+ m_selectionVisible = false;
+ m_caretVisible = false;
+ m_caretBlink = true;
+ m_leftButtonDown = false;
+ m_cols = 128;
+ m_updatesRequired = false;
+ m_visibleRows = 1;
+
+ updateRanges();
+
+ QSize areaSize = viewport()->size();
+ adjustSize(areaSize.width(), areaSize.height());
+
+ if (m_allocatedLength > 0x7fffffff)
+ m_scrollBarMultiplier = (m_allocatedLength / 0x7fffffff) + 1;
+ else
+ m_scrollBarMultiplier = 1;
+ m_wheelDelta = 0;
+ m_updatingScrollBar = false;
+ verticalScrollBar()->setRange(0, (int)((m_allocatedLength - 1) / m_scrollBarMultiplier));
+ connect(verticalScrollBar(), &QScrollBar::sliderMoved, this, &ByteView::scrollBarMoved);
+ connect(verticalScrollBar(), &QScrollBar::actionTriggered, this, &ByteView::scrollBarAction);
+
+ m_cursorTimer = new QTimer(this);
+ m_cursorTimer->setInterval(500);
+ m_cursorTimer->setSingleShot(false);
+ connect(m_cursorTimer, &QTimer::timeout, this, &ByteView::cursorTimerEvent);
+ m_cursorTimer->start();
+
+ m_updateTimer = new QTimer(this);
+ m_updateTimer->setInterval(200);
+ m_updateTimer->setSingleShot(false);
+ //connect(m_updateTimer, &QTimer::timeout, this, &ByteView::updateTimerEvent);
+
+ actionHandler()->bindAction("Move Cursor Up", UIAction([=]() { up(false); }));
+ actionHandler()->bindAction("Move Cursor Down", UIAction([=]() { down(false); }));
+ actionHandler()->bindAction("Move Cursor Left", UIAction([=]() { left(1, false); }));
+ actionHandler()->bindAction("Move Cursor Right", UIAction([=]() { right(1, false); }));
+ actionHandler()->bindAction("Move Cursor Word Left", UIAction([=]() { left(8, false); }));
+ actionHandler()->bindAction("Move Cursor Word Right", UIAction([=]() { right(8, false); }));
+ actionHandler()->bindAction("Extend Selection Up", UIAction([=]() { up(true); }));
+ actionHandler()->bindAction("Extend Selection Down", UIAction([=]() { down(true); }));
+ actionHandler()->bindAction("Extend Selection Left", UIAction([=]() { left(1, true); }));
+ actionHandler()->bindAction("Extend Selection Right", UIAction([=]() { right(1, true); }));
+ actionHandler()->bindAction("Extend Selection Word Left", UIAction([=]() { left(8, true); }));
+ actionHandler()->bindAction("Extend Selection Word Right", UIAction([=]() { right(8, true); }));
+ actionHandler()->bindAction("Page Up", UIAction([=]() { pageUp(false); }));
+ actionHandler()->bindAction("Page Down", UIAction([=]() { pageDown(false); }));
+ actionHandler()->bindAction("Extend Selection Page Up", UIAction([=]() { pageUp(true); }));
+ actionHandler()->bindAction("Extend Selection Page Down", UIAction([=]() { pageDown(true); }));
+ actionHandler()->bindAction("Move Cursor to Start of Line", UIAction([=]() { moveToStartOfLine(false); }));
+ actionHandler()->bindAction("Move Cursor to End of Line", UIAction([=]() { moveToEndOfLine(false); }));
+ actionHandler()->bindAction("Move Cursor to Start of View", UIAction([=]() { moveToStartOfView(false); }));
+ actionHandler()->bindAction("Move Cursor to End of View", UIAction([=]() { moveToEndOfView(false); }));
+ actionHandler()->bindAction("Extend Selection to Start of Line", UIAction([=]() { moveToStartOfLine(true); }));
+ actionHandler()->bindAction("Extend Selection to End of Line", UIAction([=]() { moveToEndOfLine(true); }));
+ actionHandler()->bindAction("Extend Selection to Start of View", UIAction([=]() { moveToStartOfView(true); }));
+ actionHandler()->bindAction("Extend Selection to End of View", UIAction([=]() { moveToEndOfView(true); }));
+}
+
+
+BinaryViewRef ByteView::getData()
+{
+ return m_data;
+}
+
+
+QFont ByteView::getFont()
+{
+ QFont userFont = getMonospaceFont(this);
+#ifdef Q_OS_MAC
+ // Some fonts aren't fixed width across all characters, use a known good one
+ QFont font("Menlo", userFont.pointSize());
+ font.setKerning(false);
+#else
+ QFont font = userFont;
+#endif
+ return font;
+}
+
+
+uint64_t ByteView::getStart()
+{
+ return m_data->GetStart();
+}
+
+
+uint64_t ByteView::getEnd()
+{
+ return m_data->GetEnd();
+}
+
+
+uint64_t ByteView::getLength()
+{
+ return getEnd() - getStart();
+}
+
+
+uint64_t ByteView::getCurrentOffset()
+{
+ return m_cursorAddr;
+}
+
+
+void ByteView::getSelectionOffsets(uint64_t& start, uint64_t& end)
+{
+ start = m_selectionStartAddr;
+ end = m_cursorAddr;
+ if (end < start)
+ {
+ uint64_t t = start;
+ start = end;
+ end = t;
+ }
+}
+
+
+void ByteView::updateRanges()
+{
+ m_ranges = m_data->GetAllocatedRanges();
+ // Remove regions not backed by the file
+ for (auto& i: m_data->GetSegments())
+ if (i->GetDataLength() < i->GetLength())
+ removeRange(i->GetStart() + i->GetDataLength(), i->GetEnd());
+ m_allocatedLength = 0;
+ for (auto& i: m_ranges)
+ m_allocatedLength += i.end - i.start;
+}
+
+
+void ByteView::removeRange(uint64_t begin, uint64_t end)
+{
+ std::vector<BNAddressRange> newRanges;
+ for (auto& i: m_ranges)
+ {
+ if ((end <= i.start) || (begin >= i.end))
+ {
+ newRanges.push_back(i);
+ }
+ else if ((begin <= i.start) && (end >= i.end))
+ {
+ continue;
+ }
+ else if ((begin <= i.start) && (end < i.end))
+ {
+ newRanges.push_back(BNAddressRange { end, i.end });
+ }
+ else if ((begin > i.start) && (end >= i.end))
+ {
+ newRanges.push_back(BNAddressRange { i.start, begin });
+ }
+ else
+ {
+ newRanges.push_back(BNAddressRange { i.start, begin });
+ newRanges.push_back(BNAddressRange { end, i.end });
+ }
+ }
+ m_ranges = newRanges;
+}
+
+
+void ByteView::setTopToAddress(uint64_t addr)
+{
+ for (auto& i: m_ranges)
+ {
+ if ((addr >= i.start) && (addr <= i.end))
+ {
+ m_topAddr = addr - ((addr - i.start) % (uint64_t)m_cols);
+ if (m_topAddr < i.start)
+ m_topAddr = i.start;
+ return;
+ }
+ if (i.start > addr)
+ {
+ m_topAddr = i.start;
+ return;
+ }
+ }
+ m_topAddr = m_data->GetEnd();
+}
+
+
+bool ByteView::navigate(uint64_t addr)
+{
+ if (addr < getStart())
+ return false;
+ if (addr > getEnd())
+ return false;
+ m_cursorAddr = getStart();
+ for (auto& i: m_ranges)
+ {
+ if (i.start > addr)
+ break;
+ if (addr > i.end)
+ m_cursorAddr = i.end;
+ else if (addr >= i.start)
+ m_cursorAddr = addr;
+ else
+ m_cursorAddr = i.start;
+ }
+ setTopToAddress(m_cursorAddr);
+ refreshLines();
+ showContextAroundTop();
+ selectNone();
+ repositionCaret();
+ return true;
+}
+
+
+void ByteView::updateFonts()
+{
+ QSize areaSize = viewport()->size();
+ adjustSize(areaSize.width(), areaSize.height());
+}
+
+
+void ByteView::adjustSize(int width, int height)
+{
+ m_render.setFont(getFont());
+
+ m_addrWidth = QString::number(m_data->GetEnd(), 16).size();
+ if (m_addrWidth < 8)
+ m_addrWidth = 8;
+ int cols = ((width - 4) / m_render.getFontWidth()) - (m_addrWidth + 2);
+ if (cols < 1)
+ cols = 1;
+ if ((size_t)cols != m_cols)
+ {
+ m_cols = (size_t)cols;
+ if (m_topLine < m_lines.size())
+ setTopToAddress(m_lines[m_topLine].address);
+ else
+ setTopToAddress(m_cursorAddr);
+ refreshLines();
+ }
+ m_visibleRows = (size_t)((height - 4) / m_render.getFontHeight());
+ verticalScrollBar()->setPageStep((int)(m_visibleRows * m_cols / m_scrollBarMultiplier));
+ refreshAtCurrentLocation();
+ viewport()->update();
+}
+
+
+uint64_t ByteView::getContiguousOffsetForAddress(uint64_t addr)
+{
+ uint64_t offset = 0;
+ for (auto& i: m_ranges)
+ {
+ if ((addr >= i.start) && (addr <= i.end))
+ {
+ offset += addr - i.start;
+ break;
+ }
+ offset += i.end - i.start;
+ }
+ return offset;
+}
+
+
+uint64_t ByteView::getAddressForContiguousOffset(uint64_t offset)
+{
+ uint64_t cur = 0;
+ for (auto& i: m_ranges)
+ {
+ if (offset < (cur + (i.end - i.start)))
+ return i.start + (offset - cur);
+ cur += i.end - i.start;
+ }
+ return m_data->GetEnd();
+}
+
+
+void ByteView::refreshLines()
+{
+ uint64_t addr = m_topAddr;
+ m_lines.clear();
+ m_topLine = 0;
+ m_bottomAddr = m_topAddr;
+ updateRanges();
+ if (m_allocatedLength > 0x7fffffff)
+ m_scrollBarMultiplier = (m_allocatedLength / 0x7fffffff) + 1;
+ else
+ m_scrollBarMultiplier = 1;
+ m_updatingScrollBar = true;
+ verticalScrollBar()->setRange(0, (int)((m_allocatedLength - 1) / m_scrollBarMultiplier));
+ verticalScrollBar()->setValue((int)(getContiguousOffsetForAddress(addr) / m_scrollBarMultiplier));
+ m_updatingScrollBar = false;
+ updateCache();
+ viewport()->update();
+ UIContext::updateStatus();
+}
+
+
+void ByteView::refreshAtCurrentLocation()
+{
+ if (m_topLine < m_lines.size())
+ m_topAddr = m_lines[m_topLine].address;
+ refreshLines();
+}
+
+
+ByteViewLine ByteView::createLine(uint64_t addr, size_t length, bool separator)
+{
+ if (separator)
+ {
+ return ByteViewLine { addr, length, "", true };
+ }
+ else
+ {
+ BinaryNinja::DataBuffer data = m_data->ReadBuffer(addr, length);
+ QString line;
+ for (size_t i = 0; i < data.GetLength(); i++)
+ line.append(QString(g_byteMapping[data[i]]));
+ return ByteViewLine { addr, length, line, false };
+ }
+}
+
+
+bool ByteView::cachePreviousLines()
+{
+ bool prevEndValid = false;
+ uint64_t prevEnd = 0;
+ for (auto& i: m_ranges)
+ {
+ if ((m_topAddr > i.start) && (m_topAddr <= i.end))
+ {
+ uint64_t startLine = m_topAddr - ((m_topAddr - i.start) % m_cols);
+ if (startLine == m_topAddr)
+ startLine -= m_cols;
+ if (startLine < i.start)
+ startLine = i.start;
+ ByteViewLine line = createLine(startLine, m_topAddr - startLine, false);
+ m_lines.insert(m_lines.begin(), line);
+ m_topLine++;
+ m_topAddr = startLine;
+ return true;
+ }
+ else if (i.start >= m_topAddr)
+ {
+ if (!prevEndValid)
+ return false;
+ ByteViewLine line = createLine(prevEnd, i.start - prevEnd, true);
+ m_lines.insert(m_lines.begin(), line);
+ m_topLine++;
+ m_topAddr = prevEnd;
+ return true;
+ }
+ prevEnd = i.end;
+ prevEndValid = true;
+ }
+ if (!prevEndValid)
+ return false;
+ ByteViewLine line = createLine(prevEnd, m_topAddr - prevEnd, true);
+ m_lines.insert(m_lines.begin(), line);
+ m_topLine++;
+ m_topAddr = prevEnd;
+ return true;
+}
+
+
+bool ByteView::cacheNextLines()
+{
+ uint64_t lastAddr = m_data->GetStart();
+ for (auto& i: m_ranges)
+ {
+ if ((m_bottomAddr >= i.start) && (m_bottomAddr < i.end))
+ {
+ uint64_t endLine = m_bottomAddr + m_cols;
+ if (endLine > i.end)
+ endLine = i.end;
+ ByteViewLine line = createLine(m_bottomAddr, endLine - m_bottomAddr, false);
+ m_lines.push_back(line);
+ m_bottomAddr = endLine;
+ return true;
+ }
+ else if (i.start > m_bottomAddr)
+ {
+ ByteViewLine line = createLine(m_bottomAddr, i.start - m_bottomAddr, true);
+ m_lines.push_back(line);
+ m_bottomAddr = i.start;
+ return true;
+ }
+ lastAddr = i.end;
+ }
+ if (m_bottomAddr == lastAddr)
+ {
+ // Ensure there is a place for the cursor at the end of the file
+ if ((m_lines.size() > 0) && (m_lines[m_lines.size() - 1].length != m_cols))
+ return false;
+ ByteViewLine line = createLine(lastAddr, 0, false);
+ m_lines.push_back(line);
+ m_bottomAddr++;
+ return true;
+ }
+ return false;
+}
+
+
+void ByteView::updateCache()
+{
+ // Cache enough for the current page and the next page
+ while ((m_lines.size() - m_topLine) <= (m_visibleRows * 2))
+ {
+ if (!cacheNextLines())
+ break;
+ }
+ // Cache enough for the previous page
+ while (m_topLine <= m_visibleRows)
+ {
+ if (!cachePreviousLines())
+ break;
+ }
+ // Trim cache
+ if (m_topLine > (m_visibleRows * 4))
+ {
+ m_lines.erase(m_lines.begin(), m_lines.begin() + (m_topLine - (m_visibleRows * 4)));
+ m_topLine = m_visibleRows * 4;
+ m_topAddr = m_lines[0].address;
+ }
+ if ((m_lines.size() - m_topLine) > (m_visibleRows * 5))
+ {
+ m_bottomAddr = m_lines[m_topLine + (m_visibleRows * 5)].address;
+ m_lines.erase(m_lines.begin() + (m_topLine + (m_visibleRows * 5)), m_lines.end());
+ }
+}
+
+
+void ByteView::scrollLines(int count)
+{
+ int newOffset = (int)m_topLine + count;
+ if (newOffset < 0)
+ m_topLine = 0;
+ else if (newOffset >= (int)m_lines.size())
+ m_topLine = m_lines.size() - 1;
+ else
+ m_topLine = newOffset;
+ updateCache();
+ viewport()->update();
+ if (m_topLine < m_lines.size())
+ {
+ m_updatingScrollBar = true;
+ uint64_t addr = m_lines[m_topLine].address;
+ verticalScrollBar()->setValue((int)(getContiguousOffsetForAddress(addr) / m_scrollBarMultiplier));
+ m_updatingScrollBar = false;
+ }
+}
+
+
+void ByteView::showContextAroundTop()
+{
+ size_t scroll = m_visibleRows / 4;
+ if (scroll > m_topLine)
+ m_topLine = 0;
+ else
+ m_topLine -= scroll;
+ if (m_topLine < m_lines.size())
+ {
+ m_updatingScrollBar = true;
+ uint64_t addr = m_lines[m_topLine].address;
+ verticalScrollBar()->setValue((int)(getContiguousOffsetForAddress(addr) / m_scrollBarMultiplier));
+ m_updatingScrollBar = false;
+ }
+ updateCache();
+}
+
+
+void ByteView::repositionCaret()
+{
+ updateCache();
+ bool found = false;
+ for (size_t i = 0; i < m_lines.size(); i++)
+ {
+ if (((m_cursorAddr >= m_lines[i].address) && (m_cursorAddr < (m_lines[i].address + m_lines[i].length))) ||
+ (((i + 1) == m_lines.size()) && (m_cursorAddr == (m_lines[i].address + m_lines[i].length))))
+ {
+ if (i < m_topLine)
+ m_topLine = i;
+ else if (i > (m_topLine + m_visibleRows - 1))
+ m_topLine = i - (m_visibleRows - 1);
+ m_updatingScrollBar = true;
+ uint64_t addr = m_lines[m_topLine].address;
+ verticalScrollBar()->setValue((int)(getContiguousOffsetForAddress(addr) / m_scrollBarMultiplier));
+ m_updatingScrollBar = false;
+ updateCache();
+ viewport()->update();
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ {
+ setTopToAddress(m_cursorAddr);
+ refreshLines();
+ showContextAroundTop();
+ }
+ // Force caret to be visible and repaint
+ m_caretBlink = true;
+ m_cursorTimer->stop();
+ m_cursorTimer->start();
+ updateCaret();
+ UIContext::updateStatus();
+}
+
+
+void ByteView::updateCaret()
+{
+ // Rerender both the old caret position and the new caret position
+ for (size_t i = m_topLine; (i < m_lines.size()) && (i < (m_topLine + m_visibleRows)); i++)
+ {
+ if (((m_prevCursorAddr >= m_lines[i].address) && (m_prevCursorAddr <= (m_lines[i].address + m_lines[i].length))) ||
+ ((m_cursorAddr >= m_lines[i].address) && (m_cursorAddr <= (m_lines[i].address + m_lines[i].length))))
+ {
+ viewport()->update(0, (i - m_topLine) * m_render.getFontHeight(),
+ viewport()->size().width(), m_render.getFontHeight() + 3);
+ }
+ }
+}
+
+
+void ByteView::resizeEvent(QResizeEvent* event)
+{
+ adjustSize(event->size().width(), event->size().height());
+}
+
+
+void ByteView::paintEvent(QPaintEvent* event)
+{
+ QPainter p(viewport());
+ m_render.init(p);
+ int charWidth = m_render.getFontWidth();
+ int charHeight = m_render.getFontHeight();
+
+ // Compute range that needs to be updated
+ int topY = event->rect().y();
+ int botY = topY + event->rect().height();
+ topY = (topY - 2) / charHeight;
+ botY = ((botY - 2) / charHeight) + 1;
+
+ // Compute selection range
+ bool selection = false;
+ uint64_t selStart, selEnd;
+ getSelectionOffsets(selStart, selEnd);
+ if (selStart != selEnd)
+ selection = true;
+
+ // Draw selection
+ if (selection)
+ {
+ bool startValid = false;
+ bool endValid = false;
+ int startY = 0;
+ int endY = 0;
+ int startX = 0;
+ int endX = 0;
+ for (size_t i = 0; i < m_lines.size(); i++)
+ {
+ if (selStart >= m_lines[i].address)
+ {
+ startY = (int)(i - m_topLine);
+ startX = (int)(selStart - m_lines[i].address);
+ if (startX > (int)m_cols)
+ startX = (int)m_cols;
+ startValid = true;
+ }
+ if (selEnd >= m_lines[i].address)
+ {
+ endY = (int)(i - m_topLine);
+ endX = (int)(selEnd - m_lines[i].address);
+ if (endX > (int)m_cols)
+ endX = (int)m_cols;
+ endValid = true;
+ }
+ }
+
+ if (startValid && endValid)
+ {
+ p.setPen(getThemeColor(SelectionColor));
+ p.setBrush(getThemeColor(SelectionColor));
+ if (startY == endY)
+ {
+ p.drawRect(2 + ((int)m_addrWidth + 2 + startX) * charWidth, 2 + startY * charHeight,
+ (endX - startX) * charWidth, charHeight + 1);
+ }
+ else
+ {
+ p.drawRect(2 + ((int)m_addrWidth + 2 + startX) * charWidth, 2 + startY * charHeight,
+ ((int)m_cols - startX) * charWidth, charHeight + 1);
+ if (endX > 0)
+ {
+ p.drawRect(2 + ((int)m_addrWidth + 2) * charWidth, 2 + endY * charHeight,
+ endX * charWidth, charHeight + 1);
+ }
+ }
+ if ((endY - startY) > 1)
+ {
+ p.drawRect(2 + ((int)m_addrWidth + 2) * charWidth, 2 + (startY + 1) * charHeight,
+ (int)m_cols * charWidth, ((endY - startY) - 1) * charHeight + 1);
+ }
+ }
+ }
+
+ // Paint each line
+ QColor color = palette().color(QPalette::WindowText);
+ for (int y = topY; y < botY; y++)
+ {
+ if ((y + (int)m_topLine) < 0)
+ continue;
+ if ((y + (int)m_topLine) >= (int)m_lines.size())
+ break;
+ if (m_lines[y + m_topLine].separator)
+ {
+ m_render.drawLinearDisassemblyLineBackground(p, NonContiguousSeparatorLineType,
+ QRect(0, 2 + y * charHeight, event->rect().width(), charHeight), 0);
+ continue;
+ }
+
+ uint64_t lineStartAddr = m_lines[y + m_topLine].address;
+ QString addrStr = QString::number(lineStartAddr, 16).rightJustified(8, '0');
+ size_t length = m_lines[y + m_topLine].length;
+ QString text = m_lines[y + m_topLine].text;
+
+ bool hasCursor = false;
+ int cursorCol = 0;
+ if (((m_cursorAddr >= lineStartAddr) && (m_cursorAddr < (lineStartAddr + length))) ||
+ (((y + (int)m_topLine + 1) >= (int)m_lines.size()) && (m_cursorAddr == (lineStartAddr + length))))
+ {
+ cursorCol = (int)(m_cursorAddr - lineStartAddr);
+ hasCursor = true;
+ }
+
+ m_render.drawText(p, 2, 2 + y * charHeight, getThemeColor(AddressColor), addrStr);
+ m_render.drawText(p, 2 + ((int)m_addrWidth + 2) * charWidth, 2 + y * charHeight, color, text);
+
+ if (m_caretVisible && m_caretBlink && !selection && hasCursor)
+ {
+ p.setPen(Qt::NoPen);
+ p.setBrush(palette().color(QPalette::WindowText));
+ p.drawRect(2 + ((int)m_addrWidth + 2 + cursorCol) * charWidth, 2 + y * charHeight, charWidth, charHeight + 1);
+ QColor caretTextColor = palette().color(QPalette::Base);
+ BinaryNinja::DataBuffer byteValue = m_data->ReadBuffer(lineStartAddr + cursorCol, 1);
+ if (byteValue.GetLength() == 1)
+ {
+ QString byteStr = g_byteMapping[byteValue[0]];
+ m_render.drawText(p, 2 + ((int)m_addrWidth + 2 + cursorCol) * charWidth, 2 + y * charHeight, caretTextColor, byteStr);
+ }
+ }
+ }
+}
+
+
+void ByteView::wheelEvent(QWheelEvent* event)
+{
+ if (event->orientation() == Qt::Horizontal)
+ return;
+ m_wheelDelta -= event->delta();
+ if ((m_wheelDelta <= -40) || (m_wheelDelta >= 40))
+ {
+ int lines = m_wheelDelta / 40;
+ m_wheelDelta -= lines * 40;
+ scrollLines(lines);
+ }
+}
+
+
+void ByteView::scrollBarMoved(int value)
+{
+ if (m_updatingScrollBar)
+ return;
+ m_wheelDelta = 0;
+ uint64_t addr = getAddressForContiguousOffset((uint64_t)value * m_scrollBarMultiplier);
+ setTopToAddress(addr);
+ refreshLines();
+ if (m_lines.size() > 0)
+ {
+ for (size_t i = 1; i < m_lines.size() - 1; i++)
+ {
+ if ((m_lines[i].address + m_lines[i].length) > addr)
+ {
+ m_topLine = i - 1;
+ break;
+ }
+ }
+ }
+ updateCache();
+}
+
+
+void ByteView::scrollBarAction(int action)
+{
+ switch (action)
+ {
+ case QAbstractSlider::SliderSingleStepAdd:
+ m_wheelDelta = 0;
+ scrollLines(1);
+ break;
+ case QAbstractSlider::SliderSingleStepSub:
+ m_wheelDelta = 0;
+ scrollLines(-1);
+ break;
+ case QAbstractSlider::SliderPageStepAdd:
+ m_wheelDelta = 0;
+ scrollLines((int)m_visibleRows);
+ break;
+ case QAbstractSlider::SliderPageStepSub:
+ m_wheelDelta = 0;
+ scrollLines(-(int)m_visibleRows);
+ break;
+ case QAbstractSlider::SliderToMinimum:
+ m_wheelDelta = 0;
+ setTopToAddress(getStart());
+ verticalScrollBar()->setValue((int)(getContiguousOffsetForAddress(m_topAddr) / m_scrollBarMultiplier));
+ refreshLines();
+ break;
+ case QAbstractSlider::SliderToMaximum:
+ m_wheelDelta = 0;
+ setTopToAddress(getEnd());
+ verticalScrollBar()->setValue((int)(getContiguousOffsetForAddress(m_topAddr) / m_scrollBarMultiplier));
+ refreshLines();
+ break;
+ default:
+ break;
+ }
+}
+
+
+void ByteView::cursorTimerEvent()
+{
+ m_caretBlink = !m_caretBlink;
+ updateCaret();
+}
+
+
+void ByteView::focusInEvent(QFocusEvent*)
+{
+ m_caretVisible = true;
+ updateCaret();
+}
+
+
+void ByteView::focusOutEvent(QFocusEvent*)
+{
+ m_caretVisible = false;
+ m_leftButtonDown = false;
+ updateCaret();
+}
+
+
+void ByteView::selectNone()
+{
+ for (auto& i: m_lines)
+ {
+ if ((m_cursorAddr >= i.address) && (m_cursorAddr < (i.address + i.length)) && i.separator)
+ {
+ m_cursorAddr = i.address + i.length;
+ break;
+ }
+ }
+ m_selectionStartAddr = m_cursorAddr;
+ if (m_selectionVisible)
+ viewport()->update();
+ repositionCaret();
+ UIContext::updateStatus();
+}
+
+
+void ByteView::selectAll()
+{
+ m_selectionStartAddr = getStart();
+ m_cursorAddr = getEnd();
+ viewport()->update();
+ UIContext::updateStatus();
+}
+
+
+void ByteView::adjustAddressAfterBackwardMovement()
+{
+ uint64_t lastAddr = getStart();
+ for (auto& i: m_ranges)
+ {
+ if ((m_cursorAddr >= i.start) && (m_cursorAddr < i.end))
+ break;
+ if (i.start > m_cursorAddr)
+ {
+ m_cursorAddr = lastAddr;
+ break;
+ }
+ lastAddr = i.end - 1;
+ }
+}
+
+
+void ByteView::adjustAddressAfterForwardMovement()
+{
+ for (auto& i: m_ranges)
+ {
+ if ((m_cursorAddr >= i.start) && (m_cursorAddr < i.end))
+ break;
+ if (i.start > m_cursorAddr)
+ {
+ m_cursorAddr = i.start;
+ break;
+ }
+ }
+}
+
+
+void ByteView::left(int count, bool selecting)
+{
+ if (m_cursorAddr > (getStart() + count))
+ m_cursorAddr -= count;
+ else
+ m_cursorAddr = getStart();
+ adjustAddressAfterBackwardMovement();
+ if (!selecting)
+ selectNone();
+ repositionCaret();
+ if (m_selectionVisible || selecting)
+ viewport()->update();
+}
+
+
+void ByteView::right(int count, bool selecting)
+{
+ if (m_cursorAddr <= (getEnd() - count))
+ m_cursorAddr += count;
+ else
+ m_cursorAddr = getEnd();
+ adjustAddressAfterForwardMovement();
+ if (!selecting)
+ selectNone();
+ repositionCaret();
+ if (m_selectionVisible || selecting)
+ viewport()->update();
+}
+
+
+void ByteView::up(bool selecting)
+{
+ left((int)m_cols, selecting);
+}
+
+
+void ByteView::down(bool selecting)
+{
+ right((int)m_cols, selecting);
+}
+
+
+void ByteView::pageUp(bool selecting)
+{
+ for (size_t i = 0; i < m_lines.size(); i++)
+ {
+ if (((m_cursorAddr >= m_lines[i].address) && (m_cursorAddr < (m_lines[i].address + m_lines[i].length))) ||
+ (((i + 1) == m_lines.size()) && (m_cursorAddr == (m_lines[i].address + m_lines[i].length))))
+ {
+ if (i < m_visibleRows)
+ {
+ m_cursorAddr = getStart();
+ }
+ else
+ {
+ uint64_t lineOfs = m_cursorAddr - m_lines[i].address;
+ m_cursorAddr = m_lines[i - m_visibleRows].address + lineOfs;
+ if (m_cursorAddr < m_lines[i - m_visibleRows].address)
+ m_cursorAddr = m_lines[i - m_visibleRows].address;
+ else if (m_cursorAddr >= (m_lines[i - m_visibleRows].address + m_lines[i - m_visibleRows].length))
+ m_cursorAddr = m_lines[i - m_visibleRows].address + m_lines[i - m_visibleRows].length - 1;
+ break;
+ }
+ }
+ }
+ adjustAddressAfterBackwardMovement();
+ if (m_topLine > m_visibleRows)
+ m_topLine -= m_visibleRows;
+ else
+ m_topLine = 0;
+ if (m_topLine < m_lines.size())
+ {
+ m_updatingScrollBar = true;
+ uint64_t addr = m_lines[m_topLine].address;
+ verticalScrollBar()->setValue((int)(getContiguousOffsetForAddress(addr) / m_scrollBarMultiplier));
+ m_updatingScrollBar = false;
+ }
+ if (!selecting)
+ selectNone();
+ repositionCaret();
+ viewport()->update();
+}
+
+
+void ByteView::pageDown(bool selecting)
+{
+ for (size_t i = 0; i < m_lines.size(); i++)
+ {
+ if (((m_cursorAddr >= m_lines[i].address) && (m_cursorAddr < (m_lines[i].address + m_lines[i].length))) ||
+ (((i + 1) == m_lines.size()) && (m_cursorAddr == (m_lines[i].address + m_lines[i].length))))
+ {
+ if (i >= (m_lines.size() - m_visibleRows))
+ {
+ m_cursorAddr = getEnd();
+ }
+ else
+ {
+ uint64_t lineOfs = m_cursorAddr - m_lines[i].address;
+ m_cursorAddr = m_lines[i + m_visibleRows].address + lineOfs;
+ if (m_cursorAddr < m_lines[i + m_visibleRows].address)
+ m_cursorAddr = m_lines[i + m_visibleRows].address;
+ else if (m_cursorAddr >= (m_lines[i + m_visibleRows].address + m_lines[i + m_visibleRows].length))
+ m_cursorAddr = m_lines[i + m_visibleRows].address + m_lines[i + m_visibleRows].length - 1;
+ break;
+ }
+ }
+ }
+ adjustAddressAfterForwardMovement();
+ if ((m_topLine + m_visibleRows) < m_lines.size())
+ m_topLine += m_visibleRows;
+ else if (m_lines.size() > 0)
+ m_topLine = m_lines.size() - 1;
+ if (m_topLine < m_lines.size())
+ {
+ m_updatingScrollBar = true;
+ uint64_t addr = m_lines[m_topLine].address;
+ verticalScrollBar()->setValue((int)(getContiguousOffsetForAddress(addr) / m_scrollBarMultiplier));
+ m_updatingScrollBar = false;
+ }
+ if (!selecting)
+ selectNone();
+ repositionCaret();
+ viewport()->update();
+}
+
+
+void ByteView::moveToStartOfLine(bool selecting)
+{
+ for (auto& i: m_lines)
+ {
+ if ((m_cursorAddr >= i.address) && (m_cursorAddr < (i.address + i.length)))
+ {
+ m_cursorAddr = i.address;
+ break;
+ }
+ }
+ if (!selecting)
+ selectNone();
+ repositionCaret();
+ if (m_selectionVisible || selecting)
+ viewport()->update();
+}
+
+
+void ByteView::moveToEndOfLine(bool selecting)
+{
+ for (auto& i: m_lines)
+ {
+ if ((m_cursorAddr >= i.address) && (m_cursorAddr < (i.address + i.length)))
+ {
+ m_cursorAddr = i.address + i.length - 1;
+ break;
+ }
+ }
+ if (!selecting)
+ selectNone();
+ repositionCaret();
+ if (m_selectionVisible || selecting)
+ viewport()->update();
+}
+
+
+void ByteView::moveToStartOfView(bool selecting)
+{
+ m_cursorAddr = getStart();
+ if (!selecting)
+ selectNone();
+ repositionCaret();
+ if (m_selectionVisible || selecting)
+ viewport()->update();
+}
+
+
+void ByteView::moveToEndOfView(bool selecting)
+{
+ m_cursorAddr = getEnd();
+ if (!selecting)
+ selectNone();
+ repositionCaret();
+ if (m_selectionVisible || selecting)
+ viewport()->update();
+}
+
+
+uint64_t ByteView::addressFromLocation(int x, int y)
+{
+ if (y < 0)
+ y = 0;
+ if (x < 0)
+ x = 0;
+ if (x > (int)m_cols)
+ x = (int)m_cols;
+ if ((y + (int)m_topLine) >= (int)m_lines.size())
+ return getEnd();
+ if (m_lines[y + m_topLine].separator)
+ return m_lines[y + m_topLine].address - 1;
+ uint64_t result = m_lines[y + m_topLine].address + x;
+ if (result >= (m_lines[y + m_topLine].address + m_lines[y + m_topLine].length))
+ {
+ if ((y + (int)m_topLine) == ((int)m_lines.size() - 1))
+ return getEnd();
+ else
+ return m_lines[y + m_topLine].address + m_lines[y + m_topLine].length - 1;
+ }
+ return result;
+}
+
+
+void ByteView::mousePressEvent(QMouseEvent* event)
+{
+ if (event->button() != Qt::LeftButton)
+ return;
+ int x = (event->x() - 2) / m_render.getFontWidth() - ((int)m_addrWidth + 2);
+ int y = (event->y() - 2) / m_render.getFontHeight();
+ m_lastMouseX = x;
+ m_lastMouseY = y;
+ m_cursorAddr = addressFromLocation(x, y);
+ if ((event->modifiers() & Qt::ShiftModifier) == 0)
+ selectNone();
+ repositionCaret();
+ if ((event->modifiers() & Qt::ShiftModifier) != 0)
+ viewport()->update();
+ m_leftButtonDown = true;
+}
+
+
+void ByteView::mouseMoveEvent(QMouseEvent* event)
+{
+ if (!m_leftButtonDown)
+ return;
+ int x = (event->x() - 2) / m_render.getFontWidth() - ((int)m_addrWidth + 2);
+ int y = (event->y() - 2) / m_render.getFontHeight();
+ if ((x == m_lastMouseX) && (y == m_lastMouseY))
+ return;
+ m_lastMouseX = x;
+ m_lastMouseY = y;
+ m_cursorAddr = addressFromLocation(x, y);
+ repositionCaret();
+ viewport()->update();
+}
+
+
+void ByteView::mouseReleaseEvent(QMouseEvent* event)
+{
+ if (event->button() != Qt::LeftButton)
+ return;
+ m_leftButtonDown = false;
+}
+
+
+ByteViewType::ByteViewType(): ViewType("Bytes", "Byte Overview")
+{
+}
+
+
+int ByteViewType::getPriority(BinaryViewRef, const QString&)
+{
+ return 1;
+}
+
+
+QWidget* ByteViewType::create(BinaryViewRef data, ViewFrame* frame)
+{
+ return new ByteView(frame, data);
+}
diff --git a/examples/triage/byte.h b/examples/triage/byte.h
new file mode 100644
index 00000000..acf64eb6
--- /dev/null
+++ b/examples/triage/byte.h
@@ -0,0 +1,119 @@
+#pragma once
+
+#include <QtWidgets/QAbstractScrollArea>
+#include "viewframe.h"
+#include "render.h"
+
+
+struct ByteViewLine
+{
+ uint64_t address;
+ size_t length;
+ QString text;
+ bool separator;
+};
+
+
+class ByteView: public QAbstractScrollArea, public View
+{
+ BinaryViewRef m_data;
+ RenderContext m_render;
+
+ uint64_t m_cursorAddr, m_prevCursorAddr, m_selectionStartAddr, m_topAddr, m_bottomAddr;
+ bool m_selectionVisible;
+ size_t m_cols, m_visibleRows, m_addrWidth;
+
+ bool m_updatesRequired;
+ QTimer* m_updateTimer;
+
+ std::vector<ByteViewLine> m_lines;
+ size_t m_topLine;
+
+ std::vector<BNAddressRange> m_ranges;
+ uint64_t m_allocatedLength, m_scrollBarMultiplier;
+
+ bool m_leftButtonDown;
+ int m_lastMouseX, m_lastMouseY;
+ int m_wheelDelta;
+ bool m_updatingScrollBar;
+
+ bool m_caretVisible, m_caretBlink;
+ QTimer* m_cursorTimer;
+
+ void updateRanges();
+ void removeRange(uint64_t begin, uint64_t end);
+
+ void setTopToAddress(uint64_t addr);
+
+ void adjustSize(int width, int height);
+
+ uint64_t getContiguousOffsetForAddress(uint64_t addr);
+ uint64_t getAddressForContiguousOffset(uint64_t offset);
+
+ void refreshLines();
+ void refreshAtCurrentLocation();
+
+ ByteViewLine createLine(uint64_t addr, size_t length, bool separator);
+ bool cachePreviousLines();
+ bool cacheNextLines();
+ void updateCache();
+ void scrollLines(int count);
+ void showContextAroundTop();
+ void repositionCaret();
+ void updateCaret();
+
+ void adjustAddressAfterBackwardMovement();
+ void adjustAddressAfterForwardMovement();
+ void left(int count, bool selecting);
+ void right(int count, bool selecting);
+ void up(bool selecting);
+ void down(bool selecting);
+ void pageUp(bool selecting);
+ void pageDown(bool selecting);
+ void moveToStartOfLine(bool selecting);
+ void moveToEndOfLine(bool selecting);
+ void moveToStartOfView(bool selecting);
+ void moveToEndOfView(bool selecting);
+
+ uint64_t addressFromLocation(int x, int y);
+
+public:
+ ByteView(QWidget* parent, BinaryViewRef data);
+
+ virtual BinaryViewRef getData() override;
+ virtual QFont getFont() override;
+ virtual uint64_t getCurrentOffset() override;
+ virtual void getSelectionOffsets(uint64_t& begin, uint64_t& end) override;
+ virtual bool navigate(uint64_t addr) override;
+ virtual void updateFonts() override;
+ virtual void selectAll();
+ virtual void selectNone();
+
+ uint64_t getStart();
+ uint64_t getEnd();
+ uint64_t getLength();
+
+protected:
+ virtual void resizeEvent(QResizeEvent* event) override;
+ virtual void paintEvent(QPaintEvent* event) override;
+ virtual void wheelEvent(QWheelEvent* event) override;
+ virtual void focusInEvent(QFocusEvent* event) override;
+ virtual void focusOutEvent(QFocusEvent* event) override;
+ virtual void mousePressEvent(QMouseEvent* event) override;
+ virtual void mouseMoveEvent(QMouseEvent* event) override;
+ virtual void mouseReleaseEvent(QMouseEvent* event) override;
+
+private Q_SLOTS:
+ void scrollBarMoved(int value);
+ void scrollBarAction(int action);
+ void cursorTimerEvent();
+};
+
+
+class ByteViewType: public ViewType
+{
+public:
+ ByteViewType();
+ virtual int getPriority(BinaryViewRef data, const QString& filename) override;
+ virtual QWidget* create(BinaryViewRef data, ViewFrame* frame) override;
+};
diff --git a/examples/triage/entropy.cpp b/examples/triage/entropy.cpp
new file mode 100644
index 00000000..e87615d5
--- /dev/null
+++ b/examples/triage/entropy.cpp
@@ -0,0 +1,113 @@
+#include <QtCore/QTimer>
+#include <QtGui/QPainter>
+#include "entropy.h"
+#include "view.h"
+#include "theme.h"
+
+
+EntropyThread::EntropyThread(BinaryViewRef data, int width, size_t blockSize)
+{
+ m_data = data;
+ m_image = QImage(width, 1, QImage::Format_ARGB32);
+ m_blockSize = blockSize;
+ m_updated = false;
+ m_running = true;
+ m_thread = std::thread([=]() { Run(); });
+}
+
+
+EntropyThread::~EntropyThread()
+{
+ m_running = false;
+ m_thread.join();
+}
+
+
+void EntropyThread::Run()
+{
+ int width = m_image.width();
+ for (int i = 0; i < width; i++)
+ {
+ if (!m_running)
+ break;
+ std::vector<float> entropy = m_data->GetEntropy(m_data->GetStart() + ((uint64_t)i * m_blockSize), m_blockSize, m_blockSize);
+ int v;
+ if (entropy.size() == 0)
+ v = 0;
+ else
+ v = (int)(entropy[0] * 255);
+ if (v >= 240)
+ {
+ QColor color = getThemeColor(YellowStandardHighlightColor);
+ m_image.setPixelColor(i, 0, color);
+ }
+ else
+ {
+ QColor baseColor = getThemeColor(FeatureMapBaseColor);
+ QColor entropyColor = getThemeColor(BlueStandardHighlightColor);
+ QColor color = mixColor(baseColor, entropyColor, (uint8_t)v);
+ m_image.setPixelColor(i, 0, color);
+ }
+ m_updated = true;
+ }
+}
+
+
+EntropyWidget::EntropyWidget(QWidget* parent, TriageView* view, BinaryViewRef data): QWidget(parent)
+{
+ m_view = view;
+ m_data = data;
+ m_rawData = data->GetFile()->GetViewOfType("Raw");
+
+ m_blockSize = 1024;
+ m_width = (int)(m_rawData->GetLength() / (uint64_t)m_blockSize);
+ m_thread = new EntropyThread(m_rawData, m_width, m_blockSize);
+
+ QTimer* timer = new QTimer();
+ connect(timer, &QTimer::timeout, this, &EntropyWidget::timerExpired);
+ timer->setInterval(100);
+ timer->setSingleShot(false);
+ timer->start();
+
+ setMinimumHeight(UIContext::getScaledWindowSize(32, 32).height());
+}
+
+
+EntropyWidget::~EntropyWidget()
+{
+ delete m_thread;
+}
+
+
+void EntropyWidget::paintEvent(QPaintEvent*)
+{
+ QPainter p(this);
+ p.drawImage(rect(), m_thread->GetImage());
+ p.drawRect(rect());
+}
+
+
+QSize EntropyWidget::sizeHint() const
+{
+ return QSize(640, 32);
+}
+
+
+void EntropyWidget::timerExpired()
+{
+ if (m_thread->IsUpdated())
+ {
+ m_thread->ResetUpdated();
+ update();
+ }
+}
+
+
+void EntropyWidget::mousePressEvent(QMouseEvent* event)
+{
+ if (event->button() != Qt::LeftButton)
+ return;
+ float frac = (float)event->x() / (float)rect().width();
+ uint64_t offset = (uint64_t)(frac * m_width * m_blockSize);
+ m_view->navigateToFileOffset(offset);
+}
diff --git a/examples/triage/entropy.h b/examples/triage/entropy.h
new file mode 100644
index 00000000..abe591b4
--- /dev/null
+++ b/examples/triage/entropy.h
@@ -0,0 +1,51 @@
+#pragma once
+
+#include <QtWidgets/QWidget>
+#include <QtGui/QImage>
+#include <thread>
+#include "uitypes.h"
+
+
+class EntropyThread
+{
+ BinaryViewRef m_data;
+ QImage m_image;
+ size_t m_blockSize;
+ bool m_updated, m_running;
+ std::thread m_thread;
+
+public:
+ EntropyThread(BinaryViewRef data, int width, size_t blockSize);
+ ~EntropyThread();
+
+ void Run();
+ bool IsUpdated() { return m_updated; }
+ void ResetUpdated() { m_updated = false; }
+
+ const QImage& GetImage() { return m_image; }
+};
+
+
+class TriageView;
+
+class EntropyWidget: public QWidget
+{
+ TriageView* m_view;
+ BinaryViewRef m_data, m_rawData;
+ size_t m_blockSize;
+ int m_width;
+ EntropyThread* m_thread;
+
+public:
+ EntropyWidget(QWidget* parent, TriageView* view, BinaryViewRef data);
+ virtual ~EntropyWidget();
+
+ virtual QSize sizeHint() const override;
+
+protected:
+ virtual void paintEvent(QPaintEvent* event) override;
+ virtual void mousePressEvent(QMouseEvent* event) override;
+
+private Q_SLOTS:
+ void timerExpired();
+};
diff --git a/examples/triage/exports.cpp b/examples/triage/exports.cpp
new file mode 100644
index 00000000..97bdcc75
--- /dev/null
+++ b/examples/triage/exports.cpp
@@ -0,0 +1,280 @@
+#include <algorithm>
+#include "exports.h"
+#include "view.h"
+#include "fontsettings.h"
+
+
+GenericExportsModel::GenericExportsModel(BinaryViewRef data)
+{
+ m_addrCol = 0;
+ m_nameCol = 1;
+ m_ordinalCol = -1;
+ m_totalCols = 2;
+ m_sortCol = 0;
+ m_sortOrder = Qt::AscendingOrder;
+ for (auto& sym: data->GetSymbolsOfType(FunctionSymbol))
+ {
+ if (sym->GetBinding() == GlobalBinding)
+ m_allEntries.push_back(sym);
+ }
+ for (auto& sym: data->GetSymbolsOfType(DataSymbol))
+ {
+ if (sym->GetBinding() == GlobalBinding)
+ m_allEntries.push_back(sym);
+ }
+ if (data->GetTypeName() == "PE")
+ {
+ m_ordinalCol = 0;
+ m_addrCol = 1;
+ m_nameCol = 2;
+ m_totalCols = 3;
+ }
+ m_entries = m_allEntries;
+}
+
+
+int GenericExportsModel::columnCount(const QModelIndex&) const
+{
+ return m_totalCols;
+}
+
+
+int GenericExportsModel::rowCount(const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+ return (int)m_entries.size();
+}
+
+
+QVariant GenericExportsModel::data(const QModelIndex& index, int role) const
+{
+ if (role != Qt::DisplayRole)
+ return QVariant();
+ if (index.row() >= (int)m_entries.size())
+ return QVariant();
+ if (index.column() == m_addrCol)
+ return QString("0x") + QString::number(m_entries[index.row()]->GetAddress(), 16);
+ if (index.column() == m_nameCol)
+ return QString::fromStdString(m_entries[index.row()]->GetFullName());
+ if (index.column() == m_ordinalCol)
+ return QString::number(m_entries[index.row()]->GetOrdinal());
+ return QVariant();
+}
+
+
+QVariant GenericExportsModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Vertical)
+ return QVariant();
+ if (role != Qt::DisplayRole)
+ return QVariant();
+ if (section == m_addrCol)
+ return QString("Address");
+ if (section == m_nameCol)
+ return QString("Name");
+ if (section == m_ordinalCol)
+ return QString("Ordinal");
+ return QVariant();
+}
+
+
+QModelIndex GenericExportsModel::index(int row, int col, const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return QModelIndex();
+ if (row >= (int)m_entries.size())
+ return QModelIndex();
+ if (col >= m_totalCols)
+ return QModelIndex();
+ return createIndex(row, col);
+}
+
+
+QModelIndex GenericExportsModel::parent(const QModelIndex&) const
+{
+ return QModelIndex();
+}
+
+
+SymbolRef GenericExportsModel::getSymbol(const QModelIndex& index)
+{
+ if (index.row() >= (int)m_entries.size())
+ return nullptr;
+ return m_entries[index.row()];
+}
+
+
+void GenericExportsModel::performSort(int col, Qt::SortOrder order)
+{
+ std::sort(m_entries.begin(), m_entries.end(), [&](SymbolRef a, SymbolRef b) {
+ if (col == m_addrCol)
+ {
+ if (order == Qt::AscendingOrder)
+ return a->GetAddress() < b->GetAddress();
+ else
+ return a->GetAddress() > b->GetAddress();
+ }
+ else if (col == m_nameCol)
+ {
+ if (order == Qt::AscendingOrder)
+ return a->GetFullName() < b->GetFullName();
+ else
+ return a->GetFullName() > b->GetFullName();
+ }
+ else if (col == m_ordinalCol)
+ {
+ if (order == Qt::AscendingOrder)
+ return a->GetOrdinal() < b->GetOrdinal();
+ else
+ return a->GetOrdinal() > b->GetOrdinal();
+ }
+ return false;
+ });
+}
+
+
+void GenericExportsModel::sort(int col, Qt::SortOrder order)
+{
+ beginResetModel();
+ m_sortCol = col;
+ m_sortOrder = order;
+ performSort(col, order);
+ endResetModel();
+}
+
+
+void GenericExportsModel::setFilter(const std::string& filterText)
+{
+ beginResetModel();
+ m_entries.clear();
+ for (auto& entry: m_allEntries)
+ {
+ if (FilteredView::match(entry->GetFullName(), filterText))
+ m_entries.push_back(entry);
+ }
+ performSort(m_sortCol, m_sortOrder);
+ endResetModel();
+}
+
+
+ExportsTreeView::ExportsTreeView(ExportsWidget* parent, TriageView* view, BinaryViewRef data): QTreeView(parent)
+{
+ m_data = data;
+ m_parent = parent;
+ m_view = view;
+
+ // Allow view-specific shortcuts when imports are focused
+ m_actionHandler.setupActionHandler(this);
+ m_actionHandler.setActionContext([=]() { return m_view->actionContext(); });
+
+ m_model = new GenericExportsModel(m_data);
+ setModel(m_model);
+ setRootIsDecorated(false);
+ setUniformRowHeights(true);
+ setSortingEnabled(true);
+ sortByColumn(0, Qt::AscendingOrder);
+ if (m_model->HasOrdinalCol())
+ setColumnWidth(m_model->GetOrdinalCol(), 55);
+
+ setFont(getMonospaceFont(this));
+
+ connect(selectionModel(), &QItemSelectionModel::currentChanged, this, &ExportsTreeView::exportSelected);
+ connect(this, &QTreeView::doubleClicked, this, &ExportsTreeView::exportDoubleClicked);
+}
+
+
+void ExportsTreeView::exportSelected(const QModelIndex& cur, const QModelIndex&)
+{
+ SymbolRef sym = m_model->getSymbol(cur);
+ if (sym)
+ m_view->setCurrentOffset(sym->GetAddress());
+}
+
+
+void ExportsTreeView::exportDoubleClicked(const QModelIndex& cur)
+{
+ SymbolRef sym = m_model->getSymbol(cur);
+ if (sym)
+ {
+ ViewFrame* viewFrame = ViewFrame::viewFrameForWidget(this);
+ if (viewFrame)
+ {
+ if (m_data->GetAnalysisFunctionsForAddress(sym->GetAddress()).size() > 0)
+ viewFrame->navigate("Graph:" + viewFrame->getCurrentDataType(), sym->GetAddress());
+ else
+ viewFrame->navigate("Linear:" + viewFrame->getCurrentDataType(), sym->GetAddress());
+ }
+ }
+}
+
+
+void ExportsTreeView::setFilter(const std::string& filterText)
+{
+ m_model->setFilter(filterText);
+}
+
+
+void ExportsTreeView::scrollToFirstItem()
+{
+ scrollToTop();
+}
+
+
+void ExportsTreeView::scrollToCurrentItem()
+{
+ scrollTo(currentIndex());
+}
+
+
+void ExportsTreeView::selectFirstItem()
+{
+ setCurrentIndex(m_model->index(0, 0, QModelIndex()));
+}
+
+
+void ExportsTreeView::activateFirstItem()
+{
+ exportDoubleClicked(m_model->index(0, 0, QModelIndex()));
+}
+
+
+void ExportsTreeView::closeFilter()
+{
+ setFocus(Qt::OtherFocusReason);
+}
+
+
+void ExportsTreeView::keyPressEvent(QKeyEvent* event)
+{
+ if ((event->text().size() == 1) && (event->text()[0] > ' ') && (event->text()[0] < 127))
+ {
+ m_parent->showFilter(event->text());
+ event->accept();
+ }
+ else if ((event->key() == Qt::Key_Return) || (event->key() == Qt::Key_Enter))
+ {
+ QList<QModelIndex> sel = selectionModel()->selectedIndexes();
+ if (sel.size() != 0)
+ exportDoubleClicked(sel[0]);
+ }
+ QTreeView::keyPressEvent(event);
+}
+
+
+ExportsWidget::ExportsWidget(QWidget* parent, TriageView* view, BinaryViewRef data): QWidget(parent)
+{
+ QVBoxLayout* layout = new QVBoxLayout();
+ layout->setContentsMargins(0, 0, 0, 0);
+ ExportsTreeView* exports = new ExportsTreeView(this, view, data);
+ m_filter = new FilteredView(this, exports, exports);
+ layout->addWidget(m_filter, 1);
+ setLayout(layout);
+ setMinimumSize(UIContext::getScaledWindowSize(100, 196));
+}
+
+
+void ExportsWidget::showFilter(const QString& filter)
+{
+ m_filter->showFilter(filter);
+}
diff --git a/examples/triage/exports.h b/examples/triage/exports.h
new file mode 100644
index 00000000..e79fe7eb
--- /dev/null
+++ b/examples/triage/exports.h
@@ -0,0 +1,73 @@
+#pragma once
+
+#include <QtCore/QAbstractItemModel>
+#include <QtWidgets/QTreeView>
+#include "filter.h"
+
+
+class GenericExportsModel: public QAbstractItemModel
+{
+ std::vector<SymbolRef> m_allEntries, m_entries;
+ int m_addrCol, m_nameCol, m_ordinalCol;
+ int m_totalCols, m_sortCol;
+ Qt::SortOrder m_sortOrder;
+
+ void performSort(int col, Qt::SortOrder order);
+
+public:
+ GenericExportsModel(BinaryViewRef data);
+
+ virtual int columnCount(const QModelIndex& parent) const override;
+ virtual int rowCount(const QModelIndex& parent) const override;
+ virtual QVariant data(const QModelIndex& index, int role) const override;
+ virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
+ virtual QModelIndex index(int row, int col, const QModelIndex& parent) const override;
+ virtual QModelIndex parent(const QModelIndex& index) const override;
+ virtual void sort(int col, Qt::SortOrder order) override;
+ void setFilter(const std::string& filterText);
+
+ SymbolRef getSymbol(const QModelIndex& index);
+
+ bool HasOrdinalCol() const { return m_ordinalCol != -1; }
+ int GetOrdinalCol() const { return m_ordinalCol; }
+};
+
+
+class TriageView;
+class ExportsWidget;
+
+class ExportsTreeView: public QTreeView, public FilterTarget
+{
+ BinaryViewRef m_data;
+ ExportsWidget* m_parent;
+ TriageView* m_view;
+ UIActionHandler m_actionHandler;
+ GenericExportsModel* m_model;
+
+public:
+ ExportsTreeView(ExportsWidget* parent, TriageView* view, BinaryViewRef data);
+
+ virtual void setFilter(const std::string& filterText) override;
+ virtual void scrollToFirstItem() override;
+ virtual void scrollToCurrentItem() override;
+ virtual void selectFirstItem() override;
+ virtual void activateFirstItem() override;
+ virtual void closeFilter() override;
+
+protected:
+ virtual void keyPressEvent(QKeyEvent* event) override;
+
+private Q_SLOTS:
+ void exportSelected(const QModelIndex& cur, const QModelIndex& prev);
+ void exportDoubleClicked(const QModelIndex& cur);
+};
+
+
+class ExportsWidget: public QWidget
+{
+ FilteredView* m_filter;
+
+public:
+ ExportsWidget(QWidget* parent, TriageView* view, BinaryViewRef data);
+ void showFilter(const QString& filter);
+};
diff --git a/examples/triage/files.cpp b/examples/triage/files.cpp
new file mode 100644
index 00000000..a736b81d
--- /dev/null
+++ b/examples/triage/files.cpp
@@ -0,0 +1,123 @@
+#include <QtWidgets/QVBoxLayout>
+#include <QtWidgets/QMessageBox>
+#include <QtCore/QSettings>
+#include "files.h"
+
+
+TriageFilePicker::TriageFilePicker(UIContext* context): m_contextMenuManager(this)
+{
+ m_context = context;
+ m_actionHandler.setupActionHandler(this);
+
+ QVBoxLayout* layout = new QVBoxLayout();
+ layout->setContentsMargins(0, 0, 0, 0);
+
+ m_model = new QFileSystemModel();
+ m_model->setRootPath("");
+ m_tree = new QTreeView(this);
+ m_tree->setModel(m_model);
+ m_tree->setSelectionMode(QAbstractItemView::ExtendedSelection);
+ m_tree->setColumnWidth(0, 500);
+ layout->addWidget(m_tree, 1);
+
+ setLayout(layout);
+
+ connect(m_tree, &QTreeView::doubleClicked, this, &TriageFilePicker::onDoubleClick);
+
+ QString recentFile = QSettings().value("triage/recentFile", QDir::homePath()).toString();
+ while (recentFile.size() > 0)
+ {
+ QModelIndex f = m_model->index(recentFile);
+ if (f.isValid())
+ {
+ m_tree->scrollTo(f);
+ m_tree->setExpanded(f, true);
+ break;
+ }
+ QString parentDir = QFileInfo(recentFile).path();
+ if (parentDir == recentFile)
+ break;
+ recentFile = parentDir;
+ }
+
+ m_actionHandler.bindAction("Open Selected Files", UIAction(
+ [=]() { openSelectedFiles(); },
+ [=]() { return areFilesSelected(); }));
+ m_contextMenu.addAction("Open Selected Files", "Open");
+}
+
+
+void TriageFilePicker::contextMenuEvent(QContextMenuEvent*)
+{
+ m_contextMenuManager.show(&m_contextMenu, &m_actionHandler);
+}
+
+
+void TriageFilePicker::onDoubleClick(const QModelIndex&)
+{
+ openSelectedFiles();
+}
+
+
+void TriageFilePicker::openSelectedFiles()
+{
+ std::vector<QString> failedToOpen;
+ std::set<QString> files;
+
+ for (auto& index: m_tree->selectionModel()->selectedIndexes())
+ if (m_model->fileInfo(index).isFile())
+ files.insert(m_model->fileInfo(index).absoluteFilePath());
+
+ for (auto& filename: files)
+ {
+ QSettings().setValue("triage/recentFile", filename);
+
+ FileContext* f = FileContext::openFilename(filename);
+ if (!f)
+ {
+ failedToOpen.push_back(filename);
+ continue;
+ }
+
+ f->createBinaryViews();
+ for (auto data: f->getAllDataViews())
+ {
+ BinaryNinja::Settings().Set("analysis.mode", BinaryNinja::Settings().Get<std::string>("triage.analysis_mode"), data);
+ BinaryNinja::Settings().Set("triage.always_prefer", true, data);
+ if (data->GetTypeName() != "Raw")
+ {
+ std::string linearSweepMode = BinaryNinja::Settings().Get<std::string>("triage.linear_sweep");
+ if (linearSweepMode == "none")
+ {
+ BinaryNinja::Settings().Set("analysis.linearSweep.autorun", false, data);
+ }
+ else if (linearSweepMode == "partial")
+ {
+ BinaryNinja::Settings().Set("analysis.linearSweep.autorun", true, data);
+ BinaryNinja::Settings().Set("analysis.linearSweep.controlFlowGraph", false, data);
+ }
+ else if (linearSweepMode == "full")
+ {
+ BinaryNinja::Settings().Set("analysis.linearSweep.autorun", true, data);
+ BinaryNinja::Settings().Set("analysis.linearSweep.controlFlowGraph", true, data);
+ }
+ }
+ }
+
+ m_context->openFileContext(f);
+ }
+
+ if (failedToOpen.size() > 0)
+ {
+ QString message = "Unable to open:\n";
+ for (auto& name: failedToOpen)
+ message += name + "\n";
+ QMessageBox::critical(this, "Error", message);
+ }
+}
+
+
+bool TriageFilePicker::areFilesSelected()
+{
+ return m_tree->selectionModel()->hasSelection();
+}
diff --git a/examples/triage/files.h b/examples/triage/files.h
new file mode 100644
index 00000000..6919c614
--- /dev/null
+++ b/examples/triage/files.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <QtWidgets/QWidget>
+#include <QtWidgets/QTreeView>
+#include <QtWidgets/QFileSystemModel>
+#include "action.h"
+#include "menus.h"
+
+
+class TriageFilePicker: public QWidget
+{
+ UIContext* m_context;
+ UIActionHandler m_actionHandler;
+ Menu m_contextMenu;
+ ContextMenuManager m_contextMenuManager;
+
+ QFileSystemModel* m_model;
+ QTreeView* m_tree;
+
+ void openSelectedFiles();
+ bool areFilesSelected();
+
+public:
+ TriageFilePicker(UIContext* context);
+
+protected:
+ virtual void contextMenuEvent(QContextMenuEvent*) override;
+
+private Q_SLOTS:
+ void onDoubleClick(const QModelIndex& idx);
+};
diff --git a/examples/triage/headers.cpp b/examples/triage/headers.cpp
new file mode 100644
index 00000000..e5d962ca
--- /dev/null
+++ b/examples/triage/headers.cpp
@@ -0,0 +1,329 @@
+#include <string.h>
+#include <time.h>
+#include "headers.h"
+#include "fontsettings.h"
+#include "theme.h"
+#include "viewframe.h"
+
+
+NavigationLabel::NavigationLabel(const QString& text, QColor color, const std::function<void()>& func):
+ QLabel(text), m_func(func)
+{
+ QPalette style(palette());
+ style.setColor(QPalette::WindowText, color);
+ setPalette(style);
+ setFont(getMonospaceFont(this));
+}
+
+
+void NavigationLabel::mousePressEvent(QMouseEvent*)
+{
+ m_func();
+}
+
+
+NavigationAddressLabel::NavigationAddressLabel(const QString& text):
+ NavigationLabel(text, getThemeColor(AddressColor), [this]() { clickEvent(); })
+{
+ m_address = text.toULongLong(nullptr, 0);
+}
+
+
+void NavigationAddressLabel::clickEvent()
+{
+ ViewFrame* viewFrame = ViewFrame::viewFrameForWidget(this);
+ if (viewFrame)
+ viewFrame->navigate("Linear:" + viewFrame->getCurrentDataType(), m_address);
+}
+
+
+NavigationCodeLabel::NavigationCodeLabel(const QString& text):
+ NavigationLabel(text, getThemeColor(CodeSymbolColor), [this]() { clickEvent(); })
+{
+ m_address = text.toULongLong(nullptr, 0);
+}
+
+
+void NavigationCodeLabel::clickEvent()
+{
+ ViewFrame* viewFrame = ViewFrame::viewFrameForWidget(this);
+ if (viewFrame)
+ viewFrame->navigate("Graph:" + viewFrame->getCurrentDataType(), m_address);
+}
+
+
+Headers::Headers(): m_columns(1), m_rowsPerColumn(8)
+{
+}
+
+
+void Headers::AddField(const QString& title, const QString& value, HeaderFieldType type)
+{
+ m_fields.push_back(HeaderField { title, {value}, type });
+}
+
+
+void Headers::AddField(const QString& title, const std::vector<QString>& values, HeaderFieldType type)
+{
+ m_fields.push_back(HeaderField { title, values, type });
+}
+
+
+GenericHeaders::GenericHeaders(BinaryViewRef data)
+{
+ AddField("Type", QString::fromStdString(data->GetTypeName()));
+ if (data->GetDefaultPlatform())
+ AddField("Platform", QString::fromStdString(data->GetDefaultPlatform()->GetName()));
+ if (data->IsValidOffset(data->GetEntryPoint()))
+ AddField("Entry Point", QString("0x") + QString::number(data->GetEntryPoint(), 16), CodeHeaderField);
+}
+
+
+PEHeaders::PEHeaders(BinaryViewRef data)
+{
+ uint64_t peOffset = data->GetStart() + GetValueOfStructMember(data, "DOS_Header", data->GetStart(), "e_lfanew");
+ uint64_t optHeaderStart = GetAddressAfterStruct(data, "COFF_Header", peOffset);
+
+ BinaryNinja::DataBuffer peMagic = data->ReadBuffer(optHeaderStart, 2);
+ bool is64bit;
+ std::string optHeaderName;
+ if ((peMagic.GetLength() == 2) && (peMagic[0] == 0x0b) && (peMagic[1] == 0x01))
+ {
+ optHeaderName = "PE32_Optional_Header";
+ AddField("Type", "PE 32-bit");
+ is64bit = false;
+ }
+ else if ((peMagic.GetLength() == 2) && (peMagic[0] == 0x0b) && (peMagic[1] == 0x02))
+ {
+ optHeaderName = "PE64_Optional_Header";
+ AddField("Type", "PE 64-bit");
+ is64bit = true;
+ }
+ else
+ {
+ AddField("Type", QString::fromStdString(data->GetTypeName()));
+ if (data->GetDefaultPlatform())
+ AddField("Platform", QString::fromStdString(data->GetDefaultPlatform()->GetName()));
+ if (data->IsValidOffset(data->GetEntryPoint()))
+ AddField("Entry Point", QString("0x") + QString::number(data->GetEntryPoint(), 16), CodeHeaderField);
+ return;
+ }
+
+ uint64_t machineValue = GetValueOfStructMember(data, "COFF_Header", peOffset, "machine");
+ QString machineName = GetNameOfEnumerationMember(data, "coff_machine", machineValue);
+ if (machineName.startsWith("IMAGE_FILE_MACHINE_"))
+ machineName = machineName.mid(strlen("IMAGE_FILE_MACHINE_"));
+ AddField("Machine", machineName);
+
+ uint64_t subsysValue = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "subsystem");
+ QString subsysName = GetNameOfEnumerationMember(data, "pe_subsystem", subsysValue);
+ if (subsysName.startsWith("IMAGE_SUBSYSTEM_"))
+ subsysName = subsysName.mid(strlen("IMAGE_SUBSYSTEM_"));
+ AddField("Subsystem", subsysName);
+
+ uint64_t secs = GetValueOfStructMember(data, "COFF_Header", peOffset, "timeDateStamp");
+ QDateTime t = QDateTime::fromSecsSinceEpoch(secs);
+ AddField("Timestamp", t.toString());
+
+ uint64_t base = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "imageBase");
+ AddField("Image Base", QString("0x") + QString::number(base, 16), AddressHeaderField);
+
+ uint64_t entryPoint = base + GetValueOfStructMember(data, optHeaderName, optHeaderStart, "addressOfEntryPoint");
+ AddField("Entry Point", QString("0x") + QString::number(entryPoint, 16), CodeHeaderField);
+
+ uint64_t sectionAlign = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "sectionAlignment");
+ AddField("Section Alignment", QString("0x") + QString::number(sectionAlign, 16));
+
+ uint64_t fileAlign = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "fileAlignment");
+ AddField("File Alignment", QString("0x") + QString::number(fileAlign, 16));
+
+ uint64_t checksum = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "checkSum");
+ AddField("Checksum", QString("0x") + QString::number(checksum, 16));
+
+ uint64_t codeBase = base + GetValueOfStructMember(data, optHeaderName, optHeaderStart, "baseOfCode");
+ AddField("Base of Code", QString("0x") + QString::number(codeBase, 16), AddressHeaderField);
+
+ if (!is64bit)
+ {
+ uint64_t dataBase = base + GetValueOfStructMember(data, optHeaderName, optHeaderStart, "baseOfData");
+ AddField("Base of Data", QString("0x") + QString::number(dataBase, 16), AddressHeaderField);
+ }
+
+ uint64_t codeSize = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "sizeOfCode");
+ AddField("Size of Code", QString("0x") + QString::number(codeSize, 16));
+
+ uint64_t initDataSize = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "sizeOfInitializedData");
+ AddField("Size of Init Data", QString("0x") + QString::number(initDataSize, 16));
+
+ uint64_t uninitDataSize = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "sizeOfUninitializedData");
+ AddField("Size of Uninit Data", QString("0x") + QString::number(uninitDataSize, 16));
+
+ uint64_t headerSize = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "sizeOfHeaders");
+ AddField("Size of Headers", QString("0x") + QString::number(headerSize, 16));
+
+ uint64_t imageSize = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "sizeOfImage");
+ AddField("Size of Image", QString("0x") + QString::number(imageSize, 16));
+
+ uint64_t stackCommit = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "sizeOfStackCommit");
+ uint64_t stackReserve = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "sizeOfStackReserve");
+ AddField("Stack Size", QString("0x") + QString::number(stackCommit, 16) + QString(" / 0x") +
+ QString::number(stackReserve, 16));
+
+ uint64_t heapCommit = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "sizeOfHeapCommit");
+ uint64_t heapReserve = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "sizeOfHeapReserve");
+ AddField("Heap Size", QString("0x") + QString::number(heapCommit, 16) + QString(" / 0x") +
+ QString::number(heapReserve, 16));
+
+ uint64_t linkerMajor = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "majorLinkerVersion");
+ uint64_t linkerMinor = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "minorLinkerVersion");
+ AddField("Linker Version", QString::number(linkerMajor) + QString(".") +
+ QString::number(linkerMinor).rightJustified(2, '0'));
+
+ uint64_t imageMajor = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "majorImageVersion");
+ uint64_t imageMinor = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "minorImageVersion");
+ AddField("Image Version", QString::number(imageMajor) + QString(".") +
+ QString::number(imageMinor).rightJustified(2, '0'));
+
+ uint64_t osMajor = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "majorOperatingSystemVersion");
+ uint64_t osMinor = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "minorOperatingSystemVersion");
+ AddField("OS Version", QString::number(osMajor) + QString(".") +
+ QString::number(osMinor).rightJustified(2, '0'));
+
+ uint64_t subMajor = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "majorSubsystemVersion");
+ uint64_t subMinor = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "minorSubsystemVersion");
+ AddField("Subsystem Version", QString::number(subMajor) + QString(".") +
+ QString::number(subMinor).rightJustified(2, '0'));
+
+ uint64_t coffCharValue = GetValueOfStructMember(data, "COFF_Header", peOffset, "characteristics");
+ TypeRef coffCharEnum = data->GetTypeByName(BinaryNinja::QualifiedName("coff_characteristics"));
+ if (coffCharEnum && (coffCharEnum->GetClass() == EnumerationTypeClass))
+ {
+ std::vector<QString> coffCharValues;
+ for (auto& member: coffCharEnum->GetEnumeration()->GetMembers())
+ {
+ if (coffCharValue & member.value)
+ {
+ if (QString::fromStdString(member.name).startsWith("IMAGE_FILE_"))
+ coffCharValues.push_back(QString::fromStdString(member.name).mid(strlen("IMAGE_FILE_")));
+ else
+ coffCharValues.push_back(QString::fromStdString(member.name));
+ }
+ }
+ if (coffCharValues.size() > 0)
+ AddField("COFF Characteristics", coffCharValues);
+ }
+
+ uint64_t dllCharValue = GetValueOfStructMember(data, optHeaderName, optHeaderStart, "dllCharacteristics");
+ TypeRef dllCharEnum = data->GetTypeByName(BinaryNinja::QualifiedName("pe_dll_characteristics"));
+ if (dllCharEnum && (dllCharEnum->GetClass() == EnumerationTypeClass))
+ {
+ std::vector<QString> dllCharValues;
+ for (auto& member: dllCharEnum->GetEnumeration()->GetMembers())
+ {
+ if (dllCharValue & member.value)
+ {
+ if (QString::fromStdString(member.name).startsWith("IMAGE_DLLCHARACTERISTICS_"))
+ dllCharValues.push_back(QString::fromStdString(member.name).mid(strlen("IMAGE_DLLCHARACTERISTICS_")));
+ else
+ dllCharValues.push_back(QString::fromStdString(member.name));
+ }
+ }
+ if (dllCharValues.size() > 0)
+ AddField("DLL Characteristics", dllCharValues);
+ }
+
+ SetColumns(3);
+ SetRowsPerColumn(9);
+}
+
+
+uint64_t PEHeaders::GetValueOfStructMember(BinaryViewRef data, const std::string& structName, uint64_t structStart,
+ const std::string& fieldName)
+{
+ TypeRef type = data->GetTypeByName(structName);
+ if (!type)
+ return 0;
+ if (type->GetClass() != StructureTypeClass)
+ return 0;
+ StructureRef s = type->GetStructure();
+ for (auto& member: s->GetMembers())
+ {
+ if (member.name == fieldName)
+ {
+ uint64_t offset = structStart + member.offset;
+ size_t width = member.type->GetWidth();
+ if (width > 8)
+ return 0;
+ uint64_t value = 0;
+ data->Read(&value, offset, width);
+ return value;
+ }
+ }
+ return 0;
+}
+
+
+uint64_t PEHeaders::GetAddressAfterStruct(BinaryViewRef data, const std::string& structName, uint64_t structStart)
+{
+ TypeRef type = data->GetTypeByName(structName);
+ if (!type)
+ return structStart;
+ return structStart + type->GetWidth();
+}
+
+
+QString PEHeaders::GetNameOfEnumerationMember(BinaryViewRef data, const std::string& enumName, uint64_t value)
+{
+ TypeRef type = data->GetTypeByName(enumName);
+ if (type && (type->GetClass() == EnumerationTypeClass))
+ {
+ for (auto& member: type->GetEnumeration()->GetMembers())
+ {
+ if (member.value == value)
+ return QString::fromStdString(member.name);
+ }
+ }
+ return QString("0x") + QString::number(value, 16);
+}
+
+
+HeaderWidget::HeaderWidget(QWidget* parent, const Headers& header): QWidget(parent)
+{
+ QGridLayout* layout = new QGridLayout();
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->setVerticalSpacing(1);
+ int row = 0;
+ int col = 0;
+ for (auto& field: header.GetFields())
+ {
+ layout->addWidget(new QLabel(field.title + ": "), row, col * 3);
+ for (auto& value: field.values)
+ {
+ QWidget* label;
+ if (field.type == AddressHeaderField)
+ {
+ label = new NavigationAddressLabel(value);
+ }
+ else if (field.type == CodeHeaderField)
+ {
+ label = new NavigationCodeLabel(value);
+ }
+ else
+ {
+ label = new QLabel(value);
+ label->setFont(getMonospaceFont(this));
+ }
+ layout->addWidget(label, row, col * 3 + 1);
+ row++;
+ }
+ if ((header.GetColumns() > 1) && (row >= (int)header.GetRowsPerColumn()) && ((col + 1) < (int)header.GetColumns()))
+ {
+ row = 0;
+ col++;
+ }
+ }
+ for (col = 1; col < (int)header.GetColumns(); col++)
+ layout->setColumnMinimumWidth(col * 3 - 1, UIContext::getScaledWindowSize(20, 20).width());
+ layout->setColumnStretch(header.GetColumns() * 3 - 1, 1);
+ setLayout(layout);
+}
diff --git a/examples/triage/headers.h b/examples/triage/headers.h
new file mode 100644
index 00000000..0a9af5ff
--- /dev/null
+++ b/examples/triage/headers.h
@@ -0,0 +1,99 @@
+#pragma once
+
+#include <QtWidgets/QLabel>
+#include <QtWidgets/QWidget>
+#include <functional>
+#include "uitypes.h"
+
+
+class NavigationLabel: public QLabel
+{
+ std::function<void()> m_func;
+
+public:
+ NavigationLabel(const QString& text, QColor color, const std::function<void()>& func);
+
+protected:
+ virtual void mousePressEvent(QMouseEvent* event) override;
+};
+
+
+class NavigationAddressLabel: public NavigationLabel
+{
+ uint64_t m_address;
+
+ void clickEvent();
+
+public:
+ NavigationAddressLabel(const QString& text);
+};
+
+
+class NavigationCodeLabel: public NavigationLabel
+{
+ uint64_t m_address;
+
+ void clickEvent();
+
+public:
+ NavigationCodeLabel(const QString& text);
+};
+
+
+enum HeaderFieldType
+{
+ TextHeaderField,
+ AddressHeaderField,
+ CodeHeaderField
+};
+
+
+struct HeaderField
+{
+ QString title;
+ std::vector<QString> values;
+ HeaderFieldType type;
+};
+
+
+class Headers
+{
+ std::vector<HeaderField> m_fields;
+ size_t m_columns, m_rowsPerColumn;
+
+public:
+ Headers();
+ void AddField(const QString& title, const QString& value, HeaderFieldType type = TextHeaderField);
+ void AddField(const QString& title, const std::vector<QString>& values, HeaderFieldType type = TextHeaderField);
+ const std::vector<HeaderField>& GetFields() const { return m_fields; }
+ void SetColumns(size_t cols) { m_columns = cols; }
+ void SetRowsPerColumn(size_t rows) { m_rowsPerColumn = rows; }
+ size_t GetColumns() const { return m_columns; }
+ size_t GetRowsPerColumn() const { return m_rowsPerColumn; }
+};
+
+
+class GenericHeaders: public Headers
+{
+public:
+ GenericHeaders(BinaryViewRef data);
+};
+
+
+class PEHeaders: public Headers
+{
+ uint64_t GetValueOfStructMember(BinaryViewRef data, const std::string& structName, uint64_t structStart,
+ const std::string& fieldName);
+ uint64_t GetAddressAfterStruct(BinaryViewRef data, const std::string& structName, uint64_t structStart);
+ QString GetNameOfEnumerationMember(BinaryViewRef data, const std::string& enumName, uint64_t value);
+
+public:
+ PEHeaders(BinaryViewRef data);
+};
+
+
+class HeaderWidget: public QWidget
+{
+public:
+ HeaderWidget(QWidget* parent, const Headers& headers);
+};
diff --git a/examples/triage/imports.cpp b/examples/triage/imports.cpp
new file mode 100644
index 00000000..89683878
--- /dev/null
+++ b/examples/triage/imports.cpp
@@ -0,0 +1,306 @@
+#include <string.h>
+#include <algorithm>
+#include "imports.h"
+#include "view.h"
+#include "fontsettings.h"
+
+
+GenericImportsModel::GenericImportsModel(BinaryViewRef data)
+{
+ m_nameCol = 1;
+ m_moduleCol = -1;
+ m_ordinalCol = -1;
+ m_totalCols = 2;
+ m_sortCol = 0;
+ m_sortOrder = Qt::AscendingOrder;
+ m_allEntries = data->GetSymbolsOfType(ImportAddressSymbol);
+ for (auto& sym: m_allEntries)
+ {
+ if ((sym->GetNameSpace().size() != 1) || (sym->GetNameSpace()[0] != "BNINTERNALNAMESPACE"))
+ {
+ m_hasModules = true;
+ break;
+ }
+ }
+ if (m_hasModules)
+ {
+ m_nameCol = 3;
+ m_moduleCol = 1;
+ m_ordinalCol = 2;
+ m_totalCols = 4;
+ }
+ m_entries = m_allEntries;
+}
+
+
+int GenericImportsModel::columnCount(const QModelIndex&) const
+{
+ return m_totalCols;
+}
+
+
+int GenericImportsModel::rowCount(const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+ return (int)m_entries.size();
+}
+
+
+QVariant GenericImportsModel::data(const QModelIndex& index, int role) const
+{
+ if (role != Qt::DisplayRole)
+ return QVariant();
+ if (index.row() >= (int)m_entries.size())
+ return QVariant();
+ if (index.column() == 0)
+ return QString("0x") + QString::number(m_entries[index.row()]->GetAddress(), 16);
+ if (index.column() == m_nameCol)
+ {
+ QString name = QString::fromStdString(m_entries[index.row()]->GetFullName());
+ if (name.endsWith("@GOT"))
+ name = name.mid(0, name.size() - strlen("@GOT"));
+ else if (name.endsWith("@PLT"))
+ name = name.mid(0, name.size() - strlen("@PLT"));
+ else if (name.endsWith("@IAT"))
+ name = name.mid(0, name.size() - strlen("@IAT"));
+ return name;
+ }
+ if (index.column() == m_moduleCol)
+ return getNamespace(m_entries[index.row()]);
+ if (index.column() == m_ordinalCol)
+ return QString::number(m_entries[index.row()]->GetOrdinal());
+ return QVariant();
+}
+
+
+QVariant GenericImportsModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Vertical)
+ return QVariant();
+ if (role != Qt::DisplayRole)
+ return QVariant();
+ if (section == 0)
+ return QString("Entry");
+ if (section == m_nameCol)
+ return QString("Name");
+ if (section == m_moduleCol)
+ return QString("Module");
+ if (section == m_ordinalCol)
+ return QString("Ordinal");
+ return QVariant();
+}
+
+
+QModelIndex GenericImportsModel::index(int row, int col, const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return QModelIndex();
+ if (row >= (int)m_entries.size())
+ return QModelIndex();
+ if (col >= m_totalCols)
+ return QModelIndex();
+ return createIndex(row, col);
+}
+
+
+QModelIndex GenericImportsModel::parent(const QModelIndex&) const
+{
+ return QModelIndex();
+}
+
+
+SymbolRef GenericImportsModel::getSymbol(const QModelIndex& index)
+{
+ if (index.row() >= (int)m_entries.size())
+ return nullptr;
+ return m_entries[index.row()];
+}
+
+
+QString GenericImportsModel::getNamespace(SymbolRef sym) const
+{
+ QString name = QString::fromStdString(sym->GetNameSpace().GetString());
+ if (name == "BNINTERNALNAMESPACE")
+ return "";
+ return name;
+}
+
+
+void GenericImportsModel::performSort(int col, Qt::SortOrder order)
+{
+ std::sort(m_entries.begin(), m_entries.end(), [&](SymbolRef a, SymbolRef b) {
+ if (col == 0)
+ {
+ if (order == Qt::AscendingOrder)
+ return a->GetAddress() < b->GetAddress();
+ else
+ return a->GetAddress() > b->GetAddress();
+ }
+ else if (col == m_nameCol)
+ {
+ if (order == Qt::AscendingOrder)
+ return a->GetFullName() < b->GetFullName();
+ else
+ return a->GetFullName() > b->GetFullName();
+ }
+ else if (col == m_moduleCol)
+ {
+ if (order == Qt::AscendingOrder)
+ return getNamespace(a) < getNamespace(b);
+ else
+ return getNamespace(a) > getNamespace(b);
+ }
+ else if (col == m_ordinalCol)
+ {
+ if (order == Qt::AscendingOrder)
+ return a->GetOrdinal() < b->GetOrdinal();
+ else
+ return a->GetOrdinal() > b->GetOrdinal();
+ }
+ return false;
+ });
+}
+
+
+void GenericImportsModel::sort(int col, Qt::SortOrder order)
+{
+ beginResetModel();
+ m_sortCol = col;
+ m_sortOrder = order;
+ performSort(col, order);
+ endResetModel();
+}
+
+
+void GenericImportsModel::setFilter(const std::string& filterText)
+{
+ beginResetModel();
+ m_entries.clear();
+ for (auto& entry: m_allEntries)
+ {
+ if (FilteredView::match(entry->GetFullName(), filterText))
+ m_entries.push_back(entry);
+ else if (FilteredView::match(getNamespace(entry).toStdString(), filterText))
+ m_entries.push_back(entry);
+ }
+ performSort(m_sortCol, m_sortOrder);
+ endResetModel();
+}
+
+
+ImportsTreeView::ImportsTreeView(ImportsWidget* parent, TriageView* view, BinaryViewRef data): QTreeView(parent)
+{
+ m_data = data;
+ m_parent = parent;
+ m_view = view;
+
+ // Allow view-specific shortcuts when imports are focused
+ m_actionHandler.setupActionHandler(this);
+ m_actionHandler.setActionContext([=]() { return m_view->actionContext(); });
+
+ m_model = new GenericImportsModel(m_data);
+ setModel(m_model);
+ setRootIsDecorated(false);
+ setUniformRowHeights(true);
+ setSortingEnabled(true);
+ sortByColumn(0, Qt::AscendingOrder);
+ if (m_model->HasOrdinalCol())
+ setColumnWidth(m_model->GetOrdinalCol(), 55);
+
+ setFont(getMonospaceFont(this));
+
+ connect(selectionModel(), &QItemSelectionModel::currentChanged, this, &ImportsTreeView::importSelected);
+ connect(this, &QTreeView::doubleClicked, this, &ImportsTreeView::importDoubleClicked);
+}
+
+
+void ImportsTreeView::importSelected(const QModelIndex& cur, const QModelIndex&)
+{
+ SymbolRef sym = m_model->getSymbol(cur);
+ if (sym)
+ m_view->setCurrentOffset(sym->GetAddress());
+}
+
+
+void ImportsTreeView::importDoubleClicked(const QModelIndex& cur)
+{
+ SymbolRef sym = m_model->getSymbol(cur);
+ if (sym)
+ {
+ ViewFrame* viewFrame = ViewFrame::viewFrameForWidget(this);
+ if (viewFrame)
+ viewFrame->navigate("Linear:" + viewFrame->getCurrentDataType(), sym->GetAddress());
+ }
+}
+
+
+void ImportsTreeView::setFilter(const std::string& filterText)
+{
+ m_model->setFilter(filterText);
+}
+
+
+void ImportsTreeView::scrollToFirstItem()
+{
+ scrollToTop();
+}
+
+
+void ImportsTreeView::scrollToCurrentItem()
+{
+ scrollTo(currentIndex());
+}
+
+
+void ImportsTreeView::selectFirstItem()
+{
+ setCurrentIndex(m_model->index(0, 0, QModelIndex()));
+}
+
+
+void ImportsTreeView::activateFirstItem()
+{
+ importDoubleClicked(m_model->index(0, 0, QModelIndex()));
+}
+
+
+void ImportsTreeView::closeFilter()
+{
+ setFocus(Qt::OtherFocusReason);
+}
+
+
+void ImportsTreeView::keyPressEvent(QKeyEvent* event)
+{
+ if ((event->text().size() == 1) && (event->text()[0] > ' ') && (event->text()[0] < 127))
+ {
+ m_parent->showFilter(event->text());
+ event->accept();
+ }
+ else if ((event->key() == Qt::Key_Return) || (event->key() == Qt::Key_Enter))
+ {
+ QList<QModelIndex> sel = selectionModel()->selectedIndexes();
+ if (sel.size() != 0)
+ importDoubleClicked(sel[0]);
+ }
+ QTreeView::keyPressEvent(event);
+}
+
+
+ImportsWidget::ImportsWidget(QWidget* parent, TriageView* view, BinaryViewRef data): QWidget(parent)
+{
+ QVBoxLayout* layout = new QVBoxLayout();
+ layout->setContentsMargins(0, 0, 0, 0);
+ ImportsTreeView* imports = new ImportsTreeView(this, view, data);
+ m_filter = new FilteredView(this, imports, imports);
+ layout->addWidget(m_filter, 1);
+ setLayout(layout);
+ setMinimumSize(UIContext::getScaledWindowSize(100, 196));
+}
+
+
+void ImportsWidget::showFilter(const QString& filter)
+{
+ m_filter->showFilter(filter);
+}
diff --git a/examples/triage/imports.h b/examples/triage/imports.h
new file mode 100644
index 00000000..cacda087
--- /dev/null
+++ b/examples/triage/imports.h
@@ -0,0 +1,75 @@
+#pragma once
+
+#include <QtCore/QAbstractItemModel>
+#include <QtWidgets/QTreeView>
+#include "filter.h"
+
+
+class GenericImportsModel: public QAbstractItemModel
+{
+ std::vector<SymbolRef> m_allEntries, m_entries;
+ bool m_hasModules;
+ int m_nameCol, m_moduleCol, m_ordinalCol;
+ int m_totalCols, m_sortCol;
+ Qt::SortOrder m_sortOrder;
+
+ QString getNamespace(SymbolRef sym) const;
+ void performSort(int col, Qt::SortOrder order);
+
+public:
+ GenericImportsModel(BinaryViewRef data);
+
+ virtual int columnCount(const QModelIndex& parent) const override;
+ virtual int rowCount(const QModelIndex& parent) const override;
+ virtual QVariant data(const QModelIndex& index, int role) const override;
+ virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
+ virtual QModelIndex index(int row, int col, const QModelIndex& parent) const override;
+ virtual QModelIndex parent(const QModelIndex& index) const override;
+ virtual void sort(int col, Qt::SortOrder order) override;
+ void setFilter(const std::string& filterText);
+
+ SymbolRef getSymbol(const QModelIndex& index);
+
+ bool HasOrdinalCol() const { return m_ordinalCol != -1; }
+ int GetOrdinalCol() const { return m_ordinalCol; }
+};
+
+
+class TriageView;
+class ImportsWidget;
+
+class ImportsTreeView: public QTreeView, public FilterTarget
+{
+ BinaryViewRef m_data;
+ ImportsWidget* m_parent;
+ TriageView* m_view;
+ UIActionHandler m_actionHandler;
+ GenericImportsModel* m_model;
+
+public:
+ ImportsTreeView(ImportsWidget* parent, TriageView* view, BinaryViewRef data);
+
+ virtual void setFilter(const std::string& filterText) override;
+ virtual void scrollToFirstItem() override;
+ virtual void scrollToCurrentItem() override;
+ virtual void selectFirstItem() override;
+ virtual void activateFirstItem() override;
+ virtual void closeFilter() override;
+
+protected:
+ virtual void keyPressEvent(QKeyEvent* event) override;
+
+private Q_SLOTS:
+ void importSelected(const QModelIndex& cur, const QModelIndex& prev);
+ void importDoubleClicked(const QModelIndex& cur);
+};
+
+
+class ImportsWidget: public QWidget
+{
+ FilteredView* m_filter;
+
+public:
+ ImportsWidget(QWidget* parent, TriageView* view, BinaryViewRef data);
+ void showFilter(const QString& filter);
+};
diff --git a/examples/triage/sections.cpp b/examples/triage/sections.cpp
new file mode 100644
index 00000000..b6acfd86
--- /dev/null
+++ b/examples/triage/sections.cpp
@@ -0,0 +1,151 @@
+#include <QtWidgets/QGridLayout>
+#include <QtWidgets/QHBoxLayout>
+#include <algorithm>
+#include <vector>
+#include "sections.h"
+#include "headers.h"
+#include "fontsettings.h"
+
+
+SegmentsWidget::SegmentsWidget(QWidget* parent, BinaryViewRef data): QWidget(parent)
+{
+ QGridLayout* layout = new QGridLayout();
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->setVerticalSpacing(1);
+ layout->setHorizontalSpacing(UIContext::getScaledWindowSize(16, 16).width());
+
+ for (auto& segment: data->GetSegments())
+ if ((segment->GetFlags() & (SegmentReadable | SegmentWritable | SegmentExecutable)) != 0)
+ m_segments.push_back(segment);
+ sort(m_segments.begin(), m_segments.end(), [&](SegmentRef a, SegmentRef b) {
+ return a->GetStart() < b->GetStart();
+ });
+
+ int row = 0;
+ for (auto& segment: m_segments)
+ {
+ QString begin = QString("0x") + QString::number(segment->GetStart(), 16);
+ QString end = QString("0x") + QString::number(segment->GetEnd(), 16);
+
+ QString permissions;
+ if (segment->GetFlags() & SegmentReadable)
+ permissions += "r";
+ else
+ permissions += "-";
+ if (segment->GetFlags() & SegmentWritable)
+ permissions += "w";
+ else
+ permissions += "-";
+ if (segment->GetFlags() & SegmentExecutable)
+ permissions += "x";
+ else
+ permissions += "-";
+
+ QHBoxLayout* rangeLayout = new QHBoxLayout();
+ rangeLayout->setContentsMargins(0, 0, 0, 0);
+ NavigationAddressLabel* beginLabel = new NavigationAddressLabel(begin);
+ QLabel* dashLabel = new QLabel("-");
+ dashLabel->setFont(getMonospaceFont(this));
+ NavigationAddressLabel* endLabel = new NavigationAddressLabel(end);
+ rangeLayout->addWidget(beginLabel);
+ rangeLayout->addWidget(dashLabel);
+ rangeLayout->addWidget(endLabel);
+ layout->addLayout(rangeLayout, row, 0);
+
+ QLabel* permissionsLabel = new QLabel(permissions);
+ permissionsLabel->setFont(getMonospaceFont(this));
+ layout->addWidget(permissionsLabel, row, 1);
+
+ row++;
+ }
+
+ layout->setColumnStretch(2, 1);
+ setLayout(layout);
+}
+
+
+SectionsWidget::SectionsWidget(QWidget* parent, BinaryViewRef data): QWidget(parent)
+{
+ QGridLayout* layout = new QGridLayout();
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->setVerticalSpacing(1);
+ layout->setHorizontalSpacing(UIContext::getScaledWindowSize(16, 16).width());
+
+ size_t maxNameLen = 0;
+ for (auto& section: data->GetSections())
+ if (section->GetName().size() > maxNameLen)
+ maxNameLen = section->GetName().size();
+ if (maxNameLen > 32)
+ maxNameLen = 32;
+
+ for (auto& section: data->GetSections())
+ if (section->GetSemantics() != ExternalSectionSemantics)
+ m_sections.push_back(section);
+ sort(m_sections.begin(), m_sections.end(), [&](SectionRef a, SectionRef b) {
+ return a->GetStart() < b->GetStart();
+ });
+
+ int row = 0;
+ for (auto& section: m_sections)
+ {
+ std::string name = section->GetName();
+ if (name.size() > maxNameLen)
+ name = name.substr(0, maxNameLen - 1) + std::string("…");
+
+ QString begin = QString("0x") + QString::number(section->GetStart(), 16);
+ QString end = QString("0x") + QString::number(section->GetStart() + section->GetLength(), 16);
+ QString typeName = QString::fromStdString(section->GetType());
+
+ QString permissions;
+ if (data->IsOffsetReadable(section->GetStart()))
+ permissions += "r";
+ else
+ permissions += "-";
+ if (data->IsOffsetWritable(section->GetStart()))
+ permissions += "w";
+ else
+ permissions += "-";
+ if (data->IsOffsetExecutable(section->GetStart()))
+ permissions += "x";
+ else
+ permissions += "-";
+
+ QString semantics;
+ if (section->GetSemantics() == ReadOnlyCodeSectionSemantics)
+ semantics = "Code";
+ else if (section->GetSemantics() == ReadOnlyDataSectionSemantics)
+ semantics = "Ready-only Data";
+ else if (section->GetSemantics() == ReadWriteDataSectionSemantics)
+ semantics = "Writable Data";
+
+ QLabel* nameLabel = new QLabel(QString::fromStdString(name));
+ nameLabel->setFont(getMonospaceFont(this));
+ layout->addWidget(nameLabel, row, 0);
+
+ QHBoxLayout* rangeLayout = new QHBoxLayout();
+ rangeLayout->setContentsMargins(0, 0, 0, 0);
+ NavigationAddressLabel* beginLabel = new NavigationAddressLabel(begin);
+ QLabel* dashLabel = new QLabel("-");
+ dashLabel->setFont(getMonospaceFont(this));
+ NavigationAddressLabel* endLabel = new NavigationAddressLabel(end);
+ rangeLayout->addWidget(beginLabel);
+ rangeLayout->addWidget(dashLabel);
+ rangeLayout->addWidget(endLabel);
+ layout->addLayout(rangeLayout, row, 1);
+
+ QLabel* permissionsLabel = new QLabel(permissions);
+ permissionsLabel->setFont(getMonospaceFont(this));
+ layout->addWidget(permissionsLabel, row, 2);
+ QLabel* typeLabel = new QLabel(typeName);
+ typeLabel->setFont(getMonospaceFont(this));
+ layout->addWidget(typeLabel, row, 3);
+ QLabel* semanticsLabel = new QLabel(semantics);
+ semanticsLabel->setFont(getMonospaceFont(this));
+ layout->addWidget(semanticsLabel, row, 4);
+
+ row++;
+ }
+
+ layout->setColumnStretch(5, 1);
+ setLayout(layout);
+}
diff --git a/examples/triage/sections.h b/examples/triage/sections.h
new file mode 100644
index 00000000..09c9ea63
--- /dev/null
+++ b/examples/triage/sections.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <QtWidgets/QWidget>
+#include "uitypes.h"
+
+
+class SegmentsWidget: public QWidget
+{
+ std::vector<SegmentRef> m_segments;
+
+public:
+ SegmentsWidget(QWidget* parent, BinaryViewRef data);
+ const std::vector<SegmentRef>& GetSegments() const { return m_segments; }
+};
+
+
+class SectionsWidget: public QWidget
+{
+ std::vector<SectionRef> m_sections;
+
+public:
+ SectionsWidget(QWidget* parent, BinaryViewRef data);
+ const std::vector<SectionRef>& GetSections() const { return m_sections; }
+};
diff --git a/examples/triage/triage.cpp b/examples/triage/triage.cpp
new file mode 100644
index 00000000..d88180a0
--- /dev/null
+++ b/examples/triage/triage.cpp
@@ -0,0 +1,82 @@
+#include "uitypes.h"
+#include "view.h"
+#include "files.h"
+#include "byte.h"
+
+
+extern "C"
+{
+#ifdef DEMO_VERSION
+ bool TriagePluginInit()
+#else
+ BINARYNINJAPLUGIN bool UIPluginInit()
+#endif
+ {
+ BinaryNinja::Settings().RegisterGroup("triage", "Triage");
+ BinaryNinja::Settings().RegisterSetting("triage.always_prefer",
+ R"({
+ "title" : "Always Prefer Triage Summary View",
+ "type" : "boolean",
+ "default" : false,
+ "description" : "Always prefer opening binaries in Triage Summary view, even when performing full analysis."
+ })");
+
+ BinaryNinja::Settings().RegisterSetting("triage.prefer_for_raw",
+ R"({
+ "title" : "Prefer Triage Summary View for Raw Files",
+ "type" : "boolean",
+ "default" : true,
+ "description" : "Prefer opening raw files in Triage Summary view."
+ })");
+
+ ViewType::registerViewType(new TriageViewType());
+
+ BinaryNinja::Settings().RegisterSetting("triage.analysis_mode",
+ R"({
+ "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." ]
+ })");
+
+ BinaryNinja::Settings().RegisterSetting("triage.linear_sweep",
+ R"({
+ "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([](const UIActionContext& context) {
+ UIContext* currentContext = context.context;
+ if (!currentContext)
+ 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.
+ TriageFilePicker* fp = new TriageFilePicker(currentContext);
+ currentContext->createTabForWidget("Open for Triage", fp);
+ }));
+
+ Menu::mainMenu("File")->addAction("Open for Triage...", "Open");
+
+ UIContext::registerFileOpenMode("Triage...", "Open file(s) for quick analysis in the Triage Summary view.", "Open for Triage...");
+
+ ViewType::registerViewType(new ByteViewType());
+ return true;
+ }
+}
diff --git a/examples/triage/view.cpp b/examples/triage/view.cpp
new file mode 100644
index 00000000..73e036a5
--- /dev/null
+++ b/examples/triage/view.cpp
@@ -0,0 +1,241 @@
+#include <QtWidgets/QGroupBox>
+#include <QtWidgets/QVBoxLayout>
+#include <QtWidgets/QSplitter>
+#include "view.h"
+#include "entropy.h"
+#include "imports.h"
+#include "exports.h"
+#include "sections.h"
+#include "headers.h"
+#include "fontsettings.h"
+
+
+TriageView::TriageView(QWidget* parent, BinaryViewRef data): QScrollArea(parent)
+{
+ setupView(this);
+ m_data = data;
+
+ QWidget* container = new QWidget(this);
+ QVBoxLayout* layout = new QVBoxLayout();
+
+ QGroupBox* entropyGroup = new QGroupBox("Entropy", container);
+ QVBoxLayout* entropyLayout = new QVBoxLayout();
+ entropyLayout->addWidget(new EntropyWidget(entropyGroup, this, m_data));
+ entropyGroup->setLayout(entropyLayout);
+ layout->addWidget(entropyGroup);
+
+ Headers* hdr = nullptr;
+ if (m_data->GetTypeName() == "PE")
+ hdr = new PEHeaders(m_data);
+ else if (m_data->GetTypeName() != "Raw")
+ hdr = new GenericHeaders(m_data);
+
+ if (hdr)
+ {
+ QGroupBox* headerGroup = new QGroupBox("Headers", container);
+ QVBoxLayout* headerLayout = new QVBoxLayout();
+ HeaderWidget* headerWidget = new HeaderWidget(headerGroup, *hdr);
+ headerLayout->addWidget(headerWidget);
+ headerGroup->setLayout(headerLayout);
+ layout->addWidget(headerGroup);
+ delete hdr;
+ }
+
+ if (m_data->IsExecutable())
+ {
+ QSplitter* importExportSplitter = new QSplitter(Qt::Horizontal);
+
+ QGroupBox* importGroup = new QGroupBox("Imports", container);
+ QVBoxLayout* importLayout = new QVBoxLayout();
+ importLayout->addWidget(new ImportsWidget(importGroup, this, m_data));
+ importGroup->setLayout(importLayout);
+ importExportSplitter->addWidget(importGroup);
+
+ QGroupBox* exportGroup = new QGroupBox("Exports", container);
+ QVBoxLayout* exportLayout = new QVBoxLayout();
+ exportLayout->addWidget(new ExportsWidget(exportGroup, this, m_data));
+ exportGroup->setLayout(exportLayout);
+ importExportSplitter->addWidget(exportGroup);
+
+ layout->addWidget(importExportSplitter);
+
+ if (m_data->GetTypeName() != "PE")
+ {
+ QGroupBox* segmentsGroup = new QGroupBox("Segments", container);
+ QVBoxLayout* segmentsLayout = new QVBoxLayout();
+ SegmentsWidget* segmentsWidget = new SegmentsWidget(segmentsGroup, m_data);
+ segmentsLayout->addWidget(segmentsWidget);
+ segmentsGroup->setLayout(segmentsLayout);
+ layout->addWidget(segmentsGroup);
+ if (segmentsWidget->GetSegments().size() == 0)
+ segmentsGroup->hide();
+ }
+
+ QGroupBox* sectionsGroup = new QGroupBox("Sections", container);
+ QVBoxLayout* sectionsLayout = new QVBoxLayout();
+ SectionsWidget* sectionsWidget = new SectionsWidget(sectionsGroup, m_data);
+ sectionsLayout->addWidget(sectionsWidget);
+ sectionsGroup->setLayout(sectionsLayout);
+ layout->addWidget(sectionsGroup);
+ if (sectionsWidget->GetSections().size() == 0)
+ sectionsGroup->hide();
+
+ QHBoxLayout* buttonLayout = new QHBoxLayout();
+ buttonLayout->addStretch(1);
+ m_fullAnalysisButton = new QPushButton("Start Full Analysis");
+ connect(m_fullAnalysisButton, &QPushButton::clicked, this, &TriageView::startFullAnalysis);
+ buttonLayout->addWidget(m_fullAnalysisButton);
+ layout->addLayout(buttonLayout);
+ layout->addStretch(1);
+ }
+ else
+ {
+ m_byteView = new ByteView(this, m_data);
+ setBinaryDataNavigable(true);
+ layout->addWidget(m_byteView, 1);
+ }
+
+ container->setLayout(layout);
+ setWidgetResizable(true);
+ setWidget(container);
+
+ if (m_fullAnalysisButton && (BinaryNinja::Settings().Get<std::string>("analysis.mode", data) == "full"))
+ m_fullAnalysisButton->hide();
+}
+
+
+BinaryViewRef TriageView::getData()
+{
+ return m_data;
+}
+
+
+uint64_t TriageView::getCurrentOffset()
+{
+ if (m_byteView)
+ return m_byteView->getCurrentOffset();
+ return m_currentOffset;
+}
+
+
+void TriageView::getSelectionOffsets(uint64_t& begin, uint64_t& end)
+{
+ if (m_byteView)
+ {
+ m_byteView->getSelectionOffsets(begin, end);
+ return;
+ }
+ begin = m_currentOffset;
+ end = m_currentOffset;
+}
+
+
+void TriageView::setCurrentOffset(uint64_t offset)
+{
+ m_currentOffset = offset;
+ UIContext::updateStatus(true);
+}
+
+
+QFont TriageView::getFont()
+{
+ return getMonospaceFont(this);
+}
+
+
+bool TriageView::navigate(uint64_t addr)
+{
+ if (m_byteView)
+ return m_byteView->navigate(addr);
+ return false;
+}
+
+
+void TriageView::startFullAnalysis()
+{
+ BinaryNinja::Settings().Set("analysis.mode", "full", m_data);
+ for (auto& f: m_data->GetAnalysisFunctionList())
+ {
+ if (f->IsAnalysisSkipped())
+ f->Reanalyze();
+ }
+ m_data->UpdateAnalysis();
+ m_fullAnalysisButton->hide();
+}
+
+
+void TriageView::navigateToFileOffset(uint64_t offset)
+{
+ if (!m_byteView)
+ {
+ uint64_t addr = 0;
+ bool hasAddr = m_data->GetAddressForDataOffset(offset, addr);
+ ViewFrame* frame = ViewFrame::viewFrameForWidget(this);
+ if (!frame)
+ return;
+ if (!hasAddr)
+ frame->navigate("Hex:Raw", offset);
+ else
+ frame->navigate("Linear:" + frame->getCurrentDataType(), addr);
+ }
+ else
+ {
+ uint64_t addr;
+ bool hasAddr;
+ if (m_data == m_data->GetFile()->GetViewOfType("Raw"))
+ {
+ addr = offset;
+ hasAddr = true;
+ }
+ else
+ {
+ hasAddr = m_data->GetAddressForDataOffset(offset, addr);
+ }
+ if (!hasAddr)
+ {
+ ViewFrame* frame = ViewFrame::viewFrameForWidget(this);
+ if (frame)
+ frame->navigate("Hex:Raw", offset);
+ }
+ else
+ {
+ m_byteView->navigate(addr);
+ m_byteView->setFocus(Qt::OtherFocusReason);
+ }
+ }
+}
+
+
+void TriageView::focusInEvent(QFocusEvent*)
+{
+ if (m_byteView)
+ m_byteView->setFocus(Qt::OtherFocusReason);
+}
+
+
+TriageViewType::TriageViewType(): ViewType("Triage", "Triage Summary")
+{
+}
+
+
+int TriageViewType::getPriority(BinaryViewRef data, const QString&)
+{
+ bool full = BinaryNinja::Settings().Get<std::string>("analysis.mode", data) == "full";
+ bool alwaysPrefer = BinaryNinja::Settings().Get<bool>("triage.always_prefer", data);
+ bool preferForRaw = BinaryNinja::Settings().Get<bool>("triage.prefer_for_raw", data);
+ if (data->IsExecutable() && (alwaysPrefer || !full))
+ return 100;
+ if (data->GetLength() > 0)
+ {
+ if (alwaysPrefer || data->IsExecutable() || preferForRaw)
+ return 25;
+ return 1;
+ }
+ return 0;
+}
+
+
+QWidget* TriageViewType::create(BinaryViewRef data, ViewFrame* frame)
+{
+ return new TriageView(frame, data);
+}
diff --git a/examples/triage/view.h b/examples/triage/view.h
new file mode 100644
index 00000000..8c33c7b2
--- /dev/null
+++ b/examples/triage/view.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <QtWidgets/QScrollArea>
+#include <QtWidgets/QPushButton>
+#include "viewframe.h"
+#include "byte.h"
+
+
+class TriageView: public QScrollArea, public View
+{
+ BinaryViewRef m_data;
+ uint64_t m_currentOffset = 0;
+ ByteView* m_byteView = nullptr;
+ QPushButton* m_fullAnalysisButton = nullptr;
+
+public:
+ TriageView(QWidget* parent, BinaryViewRef data);
+
+ virtual BinaryViewRef getData() override;
+ virtual uint64_t getCurrentOffset() override;
+ virtual void getSelectionOffsets(uint64_t& begin, uint64_t& end) override;
+ virtual QFont getFont() override;
+ virtual bool navigate(uint64_t addr) override;
+
+ void setCurrentOffset(uint64_t offset);
+ void navigateToFileOffset(uint64_t offset);
+
+protected:
+ virtual void focusInEvent(QFocusEvent* event) override;
+
+private Q_SLOTS:
+ void startFullAnalysis();
+};
+
+
+class TriageViewType: public ViewType
+{
+public:
+ TriageViewType();
+ virtual int getPriority(BinaryViewRef data, const QString& filename) override;
+ virtual QWidget* create(BinaryViewRef data, ViewFrame* frame) override;
+};