diff options
| author | Rusty Wagner <rusty@vector35.com> | 2019-04-02 15:30:45 -0400 |
|---|---|---|
| committer | Rusty Wagner <rusty@vector35.com> | 2019-04-02 15:36:40 -0400 |
| commit | 73a9399f5d1d86c7e291498b7fe836550f2f76ab (patch) | |
| tree | e7c7cfe9a2f27a93ddac41cf600e7bc1fc638a83 | |
| parent | c17af471c48ea092434917c803f9c88fe91022a6 (diff) | |
Add a C++ version of the triage plugin (no Makefile yet)
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | examples/triage/byte.cpp | 1114 | ||||
| -rw-r--r-- | examples/triage/byte.h | 119 | ||||
| -rw-r--r-- | examples/triage/entropy.cpp | 113 | ||||
| -rw-r--r-- | examples/triage/entropy.h | 51 | ||||
| -rw-r--r-- | examples/triage/exports.cpp | 280 | ||||
| -rw-r--r-- | examples/triage/exports.h | 73 | ||||
| -rw-r--r-- | examples/triage/files.cpp | 123 | ||||
| -rw-r--r-- | examples/triage/files.h | 31 | ||||
| -rw-r--r-- | examples/triage/headers.cpp | 329 | ||||
| -rw-r--r-- | examples/triage/headers.h | 99 | ||||
| -rw-r--r-- | examples/triage/imports.cpp | 306 | ||||
| -rw-r--r-- | examples/triage/imports.h | 75 | ||||
| -rw-r--r-- | examples/triage/sections.cpp | 151 | ||||
| -rw-r--r-- | examples/triage/sections.h | 24 | ||||
| -rw-r--r-- | examples/triage/triage.cpp | 82 | ||||
| -rw-r--r-- | examples/triage/view.cpp | 241 | ||||
| -rw-r--r-- | examples/triage/view.h | 42 | ||||
| -rw-r--r-- | python/examples/triage/byte.py | 10 | ||||
| -rw-r--r-- | python/examples/triage/view.py | 3 |
20 files changed, 3264 insertions, 5 deletions
@@ -6,6 +6,9 @@ Thumbs.db *.ilk *.exp *.pkl +*.a +*.so +*.dylib libbinaryninja* binaryninjacore.lib binaryninjacore.dll 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; +}; diff --git a/python/examples/triage/byte.py b/python/examples/triage/byte.py index 4caf228f..fe887fb4 100644 --- a/python/examples/triage/byte.py +++ b/python/examples/triage/byte.py @@ -207,8 +207,7 @@ class ByteView(QAbstractScrollArea, View): areaSize = self.viewport().size() self.adjustSize(areaSize.width(), areaSize.height()) - def createRenderContext(self): - render = RenderContext(self) + def getFont(self): userFont = binaryninjaui.getMonospaceFont(self) if sys.platform == "darwin": # Some fonts aren't fixed width across all characters, use a known good one @@ -216,7 +215,11 @@ class ByteView(QAbstractScrollArea, View): font.setKerning(False) else: font = userFont - render.setFont(font) + return font + + def createRenderContext(self): + render = RenderContext(self) + render.setFont(self.getFont()) return render def adjustSize(self, width, height): @@ -314,6 +317,7 @@ class ByteView(QAbstractScrollArea, View): self.lines.insert(0, line) self.topLine += 1 self.topAddr = prevEnd + return True def cacheNextLines(self): lastAddr = self.data.start diff --git a/python/examples/triage/view.py b/python/examples/triage/view.py index f5e5ac70..d6d3588f 100644 --- a/python/examples/triage/view.py +++ b/python/examples/triage/view.py @@ -127,8 +127,7 @@ class TriageView(QScrollArea, View): def navigate(self, addr): if self.byteView: return self.byteView.navigate(addr) - else: - return self.navigate(addr) + return False def startFullAnalysis(self): Settings().set_string("analysis.mode", "full", self.data) |
