diff options
| author | Brandon Miller <brandon@vector35.com> | 2024-03-21 10:52:21 -0400 |
|---|---|---|
| committer | Brandon Miller <bkmiller89@icloud.com> | 2024-04-25 10:56:18 -0400 |
| commit | 4b2b3d561b92a01f4d29b86d5510d39e4d1accf3 (patch) | |
| tree | 2d51e8c9c1fd539f27ffc52bcfe464ab6bd73ef0 /examples | |
| parent | 6ba7605eafb5419b82e2721026e9dc922de95347 (diff) | |
Base address detection widget in Triage view
Initial implementation of base address detection UI widget in triage
summary
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/triage/baseaddress.cpp | 355 | ||||
| -rw-r--r-- | examples/triage/baseaddress.h | 81 | ||||
| -rw-r--r-- | examples/triage/view.cpp | 13 |
3 files changed, 449 insertions, 0 deletions
diff --git a/examples/triage/baseaddress.cpp b/examples/triage/baseaddress.cpp new file mode 100644 index 00000000..41068cfb --- /dev/null +++ b/examples/triage/baseaddress.cpp @@ -0,0 +1,355 @@ +#include "baseaddress.h" + +using namespace std; + + +BNBaseAddressDetectionPOISetting BaseAddressDetectionPOISettingFromString(const std::string& setting) +{ + if (setting == "Strings only") + return POI_ANALYSIS_STRINGS_ONLY; + if (setting == "Functions only") + return POI_ANALYSIS_FUNCTIONS_ONLY; + return POI_ANALYSIS_ALL; // Default to All +} + + +std::string BaseAddressDetectionPOITypeToString(BNBaseAddressDetectionPOIType type) +{ + switch (type) + { + case POI_STRING: + return "String"; + case POI_FUNCTION: + return "Function"; + case POI_DATA_VARIABLE: + return "Data variable"; + case POI_FILE_END: + return "File end"; + case POI_FILE_START: + return "File start"; + default: + return "Unknown"; + } +} + + +std::string BaseAddressDetectionConfidenceToString(BinaryNinja::BaseAddressDetectionConfidence level) +{ + switch (level) + { + case BinaryNinja::NoConfidence: + return "Unassigned"; + case BinaryNinja::HighConfidence: + return "High"; + case BinaryNinja::LowConfidence: + return "Low"; + default: + return "Unknown"; + } +} + + +uint32_t HexOrDecimalQStringToUint32(const QString& str) +{ + if (str.startsWith("0x")) + return str.mid(2).toUInt(nullptr, 16); + return str.toUInt(); +} + + +uint64_t HexOrDecimalQStringToUint64(const QString& str) +{ + if (str.startsWith("0x")) + return str.mid(2).toULongLong(nullptr, 16); + return str.toULongLong(); +} + + +void BaseAddressDetectionThread::run() +{ + BaseAddressDetectionQtResults results; + uint32_t alignment = HexOrDecimalQStringToUint32(m_inputs->AlignmentLineEdit->text()); + if (alignment == 0) + { + results.Status = "Invalid alignment value"; + emit ResultReady(results); + return; + } + + uint32_t minStrlen = HexOrDecimalQStringToUint32(m_inputs->StrlenLineEdit->text()); + if (minStrlen == 0) + { + results.Status = "Invalid minimum string length"; + emit ResultReady(results); + return; + } + + uint64_t upperBoundary = HexOrDecimalQStringToUint64(m_inputs->UpperBoundary->text()); + if (upperBoundary == 0) + { + results.Status = "Invalid upper boundary address"; + emit ResultReady(results); + return; + } + + uint64_t lowerBoundary = HexOrDecimalQStringToUint64(m_inputs->LowerBoundary->text()); + if (lowerBoundary >= upperBoundary) + { + results.Status = "Upper boundary address is less than lower"; + emit ResultReady(results); + return; + } + + uint32_t maxPointersPerCluster = HexOrDecimalQStringToUint32(m_inputs->MaxPointersPerCluster->text()); + if (maxPointersPerCluster < 2) + { + results.Status = "Invalid max pointers (must be >= 2)"; + emit ResultReady(results); + return; + } + + BNBaseAddressDetectionPOISetting poiSetting = BaseAddressDetectionPOISettingFromString( + m_inputs->POIBox->currentText().toStdString()); + BinaryNinja::BaseAddressDetectionSettings settings = { + m_inputs->ArchitectureBox->currentText().toStdString(), + m_inputs->AnalysisBox->currentText().toStdString(), + minStrlen, + alignment, + lowerBoundary, + upperBoundary, + poiSetting, + maxPointersPerCluster, + }; + + if (!m_baseDetection->DetectBaseAddress(settings)) + emit ResultReady(results); + + auto scores = m_baseDetection->GetScores(&results.Confidence); + results.Scores = scores; + emit ResultReady(results); +} + + +void BaseAddressDetectionWidget::HandleResults(const BaseAddressDetectionQtResults& results) +{ + if (!results.Status.empty()) + m_status->setText(QString::fromStdString(results.Status)); + + /* TODO + if (results.Status.empty() && m_worker->IsAborted()) + m_status->setText("Aborted by user (Last Base: 0x" + QString::number(results.Results.LastTestedBaseAddress, 16) + ")"); + */ + + if (results.Scores.empty()) + { + if (!m_worker->IsAborted()) + m_status->setText("Completed with no results"); + m_preferredBase->setText("Not available"); + m_confidence->setText("Not available"); + } + else + { + m_rebaseButton->setEnabled(true); + if (results.Status.empty() && !m_worker->IsAborted()) + m_status->setText("Completed with results"); + m_preferredBase->setText("0x" + QString::number(results.Scores.rbegin()->second, 16)); + m_confidence->setText(QString::fromStdString(BaseAddressDetectionConfidenceToString(results.Confidence)) + + " (Score: " + QString::number(results.Scores.rbegin()->first) + ")"); + m_reloadBase->setText("0x" + QString::number(results.Scores.rbegin()->second, 16)); + } + + m_resultsTableWidget->clearContents(); + /* TODO + size_t numRows = 0; + for (auto rit = results.Results.Scores.rbegin(); rit != results.Results.Scores.rend(); rit++) + numRows += results.Results.Reasons.at(rit->second).size(); + + m_resultsTableWidget->setRowCount(numRows); + size_t row = 0; + for (auto rit = results.Results.Scores.rbegin(); rit != results.Results.Scores.rend(); rit++) + { + auto [score, baseaddr] = *rit; + for (const auto& reason : results.Results.Reasons.at(baseaddr)) + { + m_resultsTableWidget->setItem(row, 0, new QTableWidgetItem("0x" + QString::number(baseaddr, 16))); + m_resultsTableWidget->setItem(row, 1, new QTableWidgetItem("0x" + QString::number(reason.Pointer, 16))); + m_resultsTableWidget->setItem(row, 2, new QTableWidgetItem("0x" + QString::number(reason.POIOffset, 16))); + m_resultsTableWidget->setItem(row, 3, new QTableWidgetItem( + QString::fromStdString(BaseAddressDetectionPOITypeToString(reason.BaseAddressDetectionPOIType)))); + row++; + } + } + */ + + m_detectBaseAddressButton->setEnabled(true); + m_abortButton->setHidden(true); +} + + +void BaseAddressDetectionWidget::DetectBaseAddress() +{ + m_status->setText("Running..."); + m_resultsTableWidget->clearContents(); + m_preferredBase->setText("Not available"); + m_confidence->setText("Not available"); + m_detectBaseAddressButton->setEnabled(false); + m_worker = new BaseAddressDetectionThread(&m_inputs, m_view); + connect(m_worker, &BaseAddressDetectionThread::ResultReady, this, &BaseAddressDetectionWidget::HandleResults); + connect(m_worker, &BaseAddressDetectionThread::finished, m_worker, &QObject::deleteLater); + m_worker->start(); + m_abortButton->setHidden(false); +} + + +void BaseAddressDetectionWidget::Abort() +{ + m_worker->Abort(); + m_abortButton->setHidden(true); +} + + +void BaseAddressDetectionWidget::RebaseWithFullAnalysis() +{ + auto mappedView = m_view->GetFile()->GetViewOfType("Mapped"); + if (!mappedView) + return; + + auto fileMetadata = m_view->GetFile(); + if (!fileMetadata) + return; + + uint64_t address = HexOrDecimalQStringToUint64(m_reloadBase->text()); + if (!fileMetadata->Rebase(mappedView, address)) + return; + + BinaryNinja::Settings::Instance()->Set("analysis.mode", "full", mappedView); + mappedView->Reanalyze(); + + auto frame = ViewFrame::viewFrameForWidget(this); + if (!frame) + return; + + auto fileContext = frame->getFileContext(); + if (!fileContext) + return; + + auto uiContext = UIContext::contextForWidget(this); + if (!uiContext) + return; + + uiContext->recreateViewFrames(fileContext); + fileContext->refreshDataViewCache(); + auto view = frame->getCurrentViewInterface(); + if (!view) + return; + + if (!view->navigate(address)) + m_view->Navigate(std::string("Linear:" + frame->getCurrentDataType().toStdString()), address); +} + + +BaseAddressDetectionWidget::BaseAddressDetectionWidget(QWidget* parent, BinaryNinja::Ref<BinaryNinja::BinaryView> bv) +{ + m_view = bv->GetParentView() ? bv->GetParentView() : bv; + m_layout = new QGridLayout(); + int32_t row = 0; + int32_t column = 0; + + m_layout->addWidget(new QLabel("Architecture:"), row, column, Qt::AlignLeft); + m_inputs.ArchitectureBox = new QComboBox(this); + auto architectures = BinaryNinja::Architecture::GetList(); + auto archItemList = QStringList(); + archItemList << "auto detect"; + for (const auto& arch : architectures) + archItemList << QString::fromStdString(arch->GetName()); + m_inputs.ArchitectureBox->addItems(archItemList); + m_layout->addWidget(m_inputs.ArchitectureBox, row, column + 1, Qt::AlignLeft); + + m_layout->addWidget(new QLabel("Analysis Level:"), row, column + 2, Qt::AlignLeft); + m_inputs.AnalysisBox = new QComboBox(this); + auto analysisItemList = QStringList() << "basic" << "controlFlow" << "full"; + m_inputs.AnalysisBox->addItems(analysisItemList); + m_layout->addWidget(m_inputs.AnalysisBox, row++, column + 3, Qt::AlignLeft); + + m_layout->addWidget(new QLabel("Min. String Length:"), row, column, Qt::AlignLeft); + m_inputs.StrlenLineEdit = new QLineEdit("10"); + m_layout->addWidget(m_inputs.StrlenLineEdit, row, column + 1, Qt::AlignLeft); + + m_layout->addWidget(new QLabel("Alignment:"), row, column + 2, Qt::AlignLeft); + m_inputs.AlignmentLineEdit = new QLineEdit("1024"); + m_layout->addWidget(m_inputs.AlignmentLineEdit, row++, column + 3, Qt::AlignLeft); + + m_layout->addWidget(new QLabel("Lower Boundary:"), row, column, Qt::AlignLeft); + m_inputs.LowerBoundary = new QLineEdit("0x0"); + m_layout->addWidget(m_inputs.LowerBoundary, row, column + 1, Qt::AlignLeft); + + m_layout->addWidget(new QLabel("Upper Boundary:"), row, column + 2, Qt::AlignLeft); + m_inputs.UpperBoundary = new QLineEdit("0xffffffffffffffff"); + m_layout->addWidget(m_inputs.UpperBoundary, row++, column + 3, Qt::AlignLeft); + + m_layout->addWidget(new QLabel("Points Of Interest:"), row, column, Qt::AlignLeft); + auto poiList = QStringList() << "All" << "Strings only" << "Functions only"; + m_inputs.POIBox = new QComboBox(this); + m_inputs.POIBox->addItems(poiList); + m_layout->addWidget(m_inputs.POIBox, row, column + 1, Qt::AlignLeft); + + m_layout->addWidget(new QLabel("Max Pointers:"), row, column + 2, Qt::AlignLeft); + m_inputs.MaxPointersPerCluster = new QLineEdit("128"); + m_layout->addWidget(m_inputs.MaxPointersPerCluster, row++, column + 3, Qt::AlignLeft); + + m_detectBaseAddressButton = new QPushButton("Start"); + connect(m_detectBaseAddressButton, &QPushButton::clicked, this, &BaseAddressDetectionWidget::DetectBaseAddress); + m_layout->addWidget(m_detectBaseAddressButton, row, column, Qt::AlignLeft); + + m_abortButton = new QPushButton("Abort"); + connect(m_abortButton, &QPushButton::clicked, this, &BaseAddressDetectionWidget::Abort); + m_abortButton->setHidden(true); + m_layout->addWidget(m_abortButton, row++, column + 1, Qt::AlignLeft); + + m_layout->addWidget(new QLabel("Status:"), row, column, Qt::AlignLeft); + m_status = new QLabel("Not running"); + auto palette = m_status->palette(); + palette.setColor(QPalette::WindowText, getThemeColor(AlphanumericHighlightColor)); + m_status->setPalette(palette); + m_status->setFont(getMonospaceFont(this)); + m_layout->addWidget(m_status, row++, column + 1, 1, 2, Qt::AlignLeft); + + m_layout->addWidget(new QLabel("Preferred Base:"), row, column, Qt::AlignLeft); + m_preferredBase = new QLabel("Not available"); + m_preferredBase->setTextInteractionFlags(Qt::TextSelectableByMouse); + m_preferredBase->setFont(getMonospaceFont(this)); + m_preferredBase->setPalette(palette); + m_layout->addWidget(m_preferredBase, row, column + 1, Qt::AlignLeft); + + m_layout->addWidget(new QLabel("Confidence:"), row, column + 2, Qt::AlignLeft); + m_confidence = new QLabel("Not available"); + m_confidence->setFont(getMonospaceFont(this)); + m_confidence->setPalette(palette); + m_layout->addWidget(m_confidence, row++, column + 3, Qt::AlignLeft); + + m_resultsTableWidget = new QTableWidget(this); + m_resultsTableWidget->setColumnCount(4); + QStringList header; + header << "Base Address" << "Pointer" << "POI Offset" << "POI Type"; + m_resultsTableWidget->setHorizontalHeaderLabels(header); + m_resultsTableWidget->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); + m_resultsTableWidget->horizontalHeader()->setStretchLastSection(true); + m_resultsTableWidget->verticalHeader()->setVisible(false); + m_resultsTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); + m_resultsTableWidget->setSelectionBehavior(QAbstractItemView::SelectItems); + m_resultsTableWidget->setSelectionMode(QAbstractItemView::SingleSelection); + m_resultsTableWidget->setMinimumHeight(150); + m_layout->addWidget(m_resultsTableWidget, row++, column, 1, 4); + + m_layout->addWidget(new QLabel("Rebase At:"), row, column, Qt::AlignLeft); + m_reloadBase = new QLineEdit("0x0"); + m_layout->addWidget(m_reloadBase, row++, column + 1, Qt::AlignLeft); + + m_rebaseButton = new QPushButton("Start Full Analysis"); + m_rebaseButton->setEnabled(false); + connect(m_rebaseButton, &QPushButton::clicked, this, &BaseAddressDetectionWidget::RebaseWithFullAnalysis); + m_layout->addWidget(m_rebaseButton, row, column, Qt::AlignLeft); + + m_layout->setColumnStretch(3, 1); + setLayout(m_layout); +}
\ No newline at end of file diff --git a/examples/triage/baseaddress.h b/examples/triage/baseaddress.h new file mode 100644 index 00000000..aa5e70ab --- /dev/null +++ b/examples/triage/baseaddress.h @@ -0,0 +1,81 @@ +#pragma once + +#include <QThread> +#include <QtWidgets/QPushButton> +#include <QtWidgets/QLineEdit> +#include <QtWidgets/QComboBox> +#include <QtWidgets/QTableWidget> +#include <QHeaderView> +#include "theme.h" +#include "fontsettings.h" +#include "viewframe.h" +#include "binaryninjaapi.h" +#include "binaryninjacore.h" + +struct BaseAddressDetectionQtInputs +{ + QComboBox* ArchitectureBox; + QComboBox* AnalysisBox; + QLineEdit* StrlenLineEdit; + QLineEdit* AlignmentLineEdit; + QLineEdit* LowerBoundary; + QLineEdit* UpperBoundary; + QComboBox* POIBox; + QLineEdit* MaxPointersPerCluster; +}; + +struct BaseAddressDetectionQtResults +{ + std::string Status; + std::set<std::pair<size_t, uint64_t>> Scores; + BinaryNinja::BaseAddressDetectionConfidence Confidence; +}; + +class BaseAddressDetectionThread : public QThread +{ + Q_OBJECT + BinaryNinja::Ref<BinaryNinja::BinaryView> m_view; + BinaryNinja::BaseAddressDetection* m_baseDetection; + BaseAddressDetectionQtInputs* m_inputs {}; + void run() override; + +public: + BaseAddressDetectionThread(BaseAddressDetectionQtInputs* widgetInputs, BinaryNinja::Ref<BinaryNinja::BinaryView> bv) + { + m_inputs = widgetInputs; + m_view = bv; + m_baseDetection = new BinaryNinja::BaseAddressDetection(m_view); + } + + void Abort() { m_baseDetection->Abort(); } + bool IsAborted() { return m_baseDetection->IsAborted(); } + +signals: + void ResultReady(const BaseAddressDetectionQtResults& result); +}; + +class BaseAddressDetectionWidget : public QWidget +{ + BaseAddressDetectionThread* m_worker; + BinaryNinja::Ref<BinaryNinja::BinaryView> m_view; + QGridLayout* m_layout {}; + + QPushButton* m_detectBaseAddressButton = nullptr; + QPushButton* m_abortButton = nullptr; + + BaseAddressDetectionQtInputs m_inputs; + QLabel* m_preferredBase; + QLabel* m_confidence; + QLabel* m_status; + QLineEdit* m_reloadBase; + QPushButton* m_rebaseButton; + QTableWidget* m_resultsTableWidget; + + void DetectBaseAddress(); + void RebaseWithFullAnalysis(); + void Abort(); + void HandleResults(const BaseAddressDetectionQtResults& results); + +public: + BaseAddressDetectionWidget(QWidget* parent, BinaryNinja::Ref<BinaryNinja::BinaryView> bv); +};
\ No newline at end of file diff --git a/examples/triage/view.cpp b/examples/triage/view.cpp index 87cdb61a..579f8917 100644 --- a/examples/triage/view.cpp +++ b/examples/triage/view.cpp @@ -10,6 +10,7 @@ #include "librariesinfo.h" #include "headers.h" #include "strings.h" +#include "baseaddress.h" #include "fontsettings.h" #include <binaryninjacore.h> @@ -52,6 +53,18 @@ TriageView::TriageView(QWidget* parent, BinaryViewRef data) : QScrollArea(parent delete hdr; } + auto fileMetadata = m_data->GetFile(); + auto existingViews = fileMetadata->GetExistingViews(); + if ((existingViews.size() == 2 && fileMetadata->GetViewOfType("Mapped")) || existingViews.size() == 1) + { + // Binary either only has raw view (Open for triage mode) or raw and mapped view + QGroupBox* baseDetectionGroup = new QGroupBox("Base Address Detection", container); + QVBoxLayout* baseDetectionLayout = new QVBoxLayout(); + baseDetectionLayout->addWidget(new BaseAddressDetectionWidget(this, data)); + baseDetectionGroup->setLayout(baseDetectionLayout); + layout->addWidget(baseDetectionGroup); + } + QGroupBox* librariesGroup = new QGroupBox("Libraries", container); QVBoxLayout* librariesLayout = new QVBoxLayout(); librariesLayout->addWidget(new LibrariesWidget(this, data)); |
