From 39532d312c23fb0489ce2d15c725df263644cdc7 Mon Sep 17 00:00:00 2001 From: wargio Date: Fri, 7 Oct 2022 13:04:38 +0200 Subject: [PATCH] Initial work on bin diffing --- src/CMakeLists.txt | 3 + src/common/Configuration.cpp | 8 ++ src/common/RizinTask.cpp | 6 +- src/core/Basefind.cpp | 2 +- src/core/Cutter.cpp | 111 +++++++++++++++++++-- src/core/Cutter.h | 8 +- src/core/MainWindow.cpp | 52 ++++++++++ src/core/MainWindow.h | 9 ++ src/core/MainWindow.ui | 6 ++ src/dialogs/bindiff/BinDiffDialog.cpp | 137 ++++++++++++++++++++++++++ src/dialogs/bindiff/BinDiffDialog.h | 37 +++++++ src/dialogs/bindiff/BinDiffDialog.ui | 55 +++++++++++ 12 files changed, 418 insertions(+), 16 deletions(-) create mode 100644 src/dialogs/bindiff/BinDiffDialog.cpp create mode 100644 src/dialogs/bindiff/BinDiffDialog.h create mode 100644 src/dialogs/bindiff/BinDiffDialog.ui diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ad69522e..223d4ee7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -58,6 +58,7 @@ set(SOURCES widgets/DecompilerWidget.cpp widgets/VisualNavbar.cpp widgets/GraphView.cpp + dialogs/bindiff/BinDiffDialog.cpp dialogs/preferences/PreferencesDialog.cpp dialogs/preferences/AppearanceOptionsWidget.cpp dialogs/preferences/GraphOptionsWidget.cpp @@ -213,6 +214,7 @@ set(HEADER_FILES widgets/DecompilerWidget.h widgets/VisualNavbar.h widgets/GraphView.h + dialogs/bindiff/BinDiffDialog.h dialogs/preferences/PreferencesDialog.h dialogs/preferences/AppearanceOptionsWidget.h dialogs/preferences/PreferenceCategory.h @@ -345,6 +347,7 @@ set(UI_FILES widgets/GlobalsWidget.ui widgets/StringsWidget.ui widgets/HexdumpWidget.ui + dialogs/bindiff/BinDiffDialog.ui dialogs/preferences/PreferencesDialog.ui dialogs/preferences/AppearanceOptionsWidget.ui dialogs/preferences/GraphOptionsWidget.ui diff --git a/src/common/Configuration.cpp b/src/common/Configuration.cpp index 5f186fcf..02e2db83 100644 --- a/src/common/Configuration.cpp +++ b/src/common/Configuration.cpp @@ -78,6 +78,14 @@ const QHash> Configuration::cutterOptionColor { { DarkFlag, QColor(0x1c, 0x1f, 0x24) }, { LightFlag, QColor(0xf5, 0xfa, 0xff) } } }, { "gui.disass_selected", { { DarkFlag, QColor(0x1f, 0x22, 0x28) }, { LightFlag, QColor(0xff, 0xff, 0xff) } } }, + { "gui.match.perfect", + { { DarkFlag, QColor(0x4c, 0xaf, 0x50) }, { LightFlag, QColor(0x00, 0xc8, 0x53) } } }, + { "gui.match.partial", + { { DarkFlag, QColor(0xff, 0xc1, 0x07) }, { LightFlag, QColor(0xff, 0xa0, 0x00) } } }, + { "gui.match.none", + { { DarkFlag, QColor(0xf4, 0x43, 0x36) }, { LightFlag, QColor(0xd3, 0x2f, 0x2f) } } }, + { "gui.match.new", + { { DarkFlag, QColor(0x21, 0x96, 0xf3) }, { LightFlag, QColor(0x19, 0x76, 0xd2) } } }, { "lineHighlight", { { DarkFlag, QColor(0x15, 0x1d, 0x1d, 0x96) }, { LightFlag, QColor(0xd2, 0xd2, 0xff, 0x96) } } }, diff --git a/src/common/RizinTask.cpp b/src/common/RizinTask.cpp index 14433585..4219901d 100644 --- a/src/common/RizinTask.cpp +++ b/src/common/RizinTask.cpp @@ -11,17 +11,17 @@ RizinTask::~RizinTask() void RizinTask::startTask() { - rz_core_task_enqueue(&Core()->core_->tasks, task); + rz_core_task_enqueue(&Core()->core_a->tasks, task); } void RizinTask::breakTask() { - rz_core_task_break(&Core()->core_->tasks, task->id); + rz_core_task_break(&Core()->core_a->tasks, task->id); } void RizinTask::joinTask() { - rz_core_task_join(&Core()->core_->tasks, nullptr, task->id); + rz_core_task_join(&Core()->core_a->tasks, nullptr, task->id); } void RizinTask::taskFinished() diff --git a/src/core/Basefind.cpp b/src/core/Basefind.cpp index f1fdd4c7..16ad1a74 100644 --- a/src/core/Basefind.cpp +++ b/src/core/Basefind.cpp @@ -67,7 +67,7 @@ void Basefind::run() core->coreMutex.lock(); options.callback = threadCallback; options.user = this; - scores = rz_basefind(core->core_, &options); + scores = rz_basefind(core->core_a, &options); core->coreMutex.unlock(); emit complete(); diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 47fc607d..41ee1d5e 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -152,12 +152,12 @@ RzCoreLocked::~RzCoreLocked() RzCoreLocked::operator RzCore *() const { - return core->core_; + return core->core_a; } RzCore *RzCoreLocked::operator->() const { - return core->core_; + return core->core_a; } #define CORE_LOCK() RzCoreLocked core(this) @@ -198,20 +198,21 @@ void CutterCore::initialize(bool loadPlugins) #endif rz_cons_new(); // initialize console - core_ = rz_core_new(); - rz_core_task_sync_begin(&core_->tasks); + core_a = rz_core_new(); + core_b = nullptr; + rz_core_task_sync_begin(&core_a->tasks); coreBed = rz_cons_sleep_begin(); CORE_LOCK(); - rz_event_hook(core_->analysis->ev, RZ_EVENT_ALL, cutterREventCallback, this); + rz_event_hook(core_a->analysis->ev, RZ_EVENT_ALL, cutterREventCallback, this); if (loadPlugins) { setConfig("cfg.plugins", true); - rz_core_loadlibs(this->core_, RZ_CORE_LOADLIBS_ALL); + rz_core_loadlibs(this->core_a, RZ_CORE_LOADLIBS_ALL); } else { setConfig("cfg.plugins", false); } - // IMPLICIT rz_bin_iobind (core_->bin, core_->io); + // IMPLICIT rz_bin_iobind (core_a->bin, core_a->io); // Otherwise Rizin may ask the user for input and Cutter would freeze setConfig("scr.interactive", false); @@ -232,8 +233,11 @@ CutterCore::~CutterCore() { delete bbHighlighter; rz_cons_sleep_end(coreBed); - rz_core_task_sync_end(&core_->tasks); - rz_core_free(this->core_); + rz_core_free(this->core_b); + this->core_b = nullptr; + rz_core_task_sync_end(&core_a->tasks); + rz_core_free(this->core_a); + this->core_a = nullptr; rz_cons_free(); assert(uniqueInstance == this); uniqueInstance = nullptr; @@ -1044,7 +1048,7 @@ RVA CutterCore::nextOpAddr(RVA startAddr, int count) RVA CutterCore::getOffset() { - return core_->offset; + return core_a->offset; } void CutterCore::applySignature(const QString &filepath) @@ -4560,6 +4564,93 @@ bool CutterCore::isWriteModeEnabled() return false; } +RzAnalysisMatchResult *CutterCore::matchFunctionsFromNewFile(const QString &filePath) { + RzList *fcns_a = NULL, *fcns_b = NULL; + RzAnalysisMatchResult *result = NULL; + RzListIter *iter; + RzConfigNode *node; + void *ptr; + + if (core_b) { + rz_core_free(core_b); + core_b = nullptr; + } + + core_b = rz_core_new(); + if (!core_b) { + goto fail; + } + + rz_config_set_b(core_b->config, "io.va", rz_config_get_b(core_a->config, "io.va")); + + rz_core_loadlibs(core_b, RZ_CORE_LOADLIBS_ALL); + core_b->print->scr_prompt = false; + if (!rz_core_file_open(core_b, filePath.toUtf8().constData(), RZ_PERM_RX, 0)) { + qWarning() << "cannot open file " << filePath; + goto fail; + } + + if (!rz_core_bin_load(core_b, NULL, UT64_MAX)) { + qWarning() << "cannot load bin " << filePath; + goto fail; + } + + if (!rz_core_bin_update_arch_bits(core_b)) { + qWarning() << "cannot set architecture with bits"; + goto fail; + } + + rz_list_foreach (core_a->config->nodes, iter, ptr) { + node = reinterpret_cast(ptr); + if (!strcmp(node->name, "scr.color") || + !strcmp(node->name, "scr.interactive") || + !strcmp(node->name, "cfg.debug")) { + rz_config_set(core_b->config, node->name, "0"); + continue; + } + rz_config_set(core_b->config, node->name, node->value); + } + + if (!rz_core_analysis_everything(core_b, false, NULL)) { + qWarning() << "cannot analyze binary " << filePath; + goto fail; + } + + fcns_a = rz_list_clone(rz_analysis_get_fcns(core_a->analysis)); + if (rz_list_empty(fcns_a)) { + qWarning() << "no functions found in the current opened file"; + goto fail; + } + + fcns_b = rz_list_clone(rz_analysis_get_fcns(core_b->analysis)); + if (rz_list_empty(fcns_b)) { + qWarning() << "no functions found in " << filePath; + goto fail; + } + + rz_list_sort(fcns_a, core_a->analysis->columnSort); + rz_list_sort(fcns_b, core_b->analysis->columnSort); + + // calculate all the matches between the functions of the 2 different core files. + result = rz_analysis_match_functions_2(core_a->analysis, fcns_a, core_b->analysis, fcns_b); + if (!result) { + qWarning() << "failed to perform the function matching operation"; + goto fail; + } + + rz_list_free(fcns_a); + rz_list_free(fcns_b); + return result; + +fail: + rz_list_free(fcns_a); + rz_list_free(fcns_b); + rz_core_free(this->core_b); + this->core_b = nullptr; + return NULL; +} + + /** * @brief get a compact disassembly preview for tooltips * @param address - the address from which to print the disassembly diff --git a/src/core/Cutter.h b/src/core/Cutter.h index ff2b4682..33530f67 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -84,7 +84,7 @@ public: AsyncTaskManager *getAsyncTaskManager() { return asyncTaskManager; } - RVA getOffset() const { return core_->offset; } + RVA getOffset() const { return core_a->offset; } /* Core functions (commands) */ /* Almost the same as core_cmd_raw, @@ -754,6 +754,9 @@ public: */ void writeGraphvizGraphToFile(QString path, QString format, RzCoreGraphType type, RVA address); + /* Diffing/Matching */ + RzAnalysisMatchResult *matchFunctionsFromNewFile(const QString &filePath); + signals: void refreshAll(); @@ -826,7 +829,8 @@ private: * Internal reference to the RzCore. * NEVER use this directly! Always use the CORE_LOCK(); macro and access it like core->... */ - RzCore *core_ = nullptr; + RzCore *core_a = nullptr; + RzCore *core_b = nullptr; #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) QMutex coreMutex; #else diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp index e7364d43..35cb22e1 100644 --- a/src/core/MainWindow.cpp +++ b/src/core/MainWindow.cpp @@ -17,6 +17,7 @@ #include "CutterApplication.h" // Dialogs +#include "dialogs/RizinTaskDialog.h" #include "dialogs/WelcomeDialog.h" #include "dialogs/NewFileDialog.h" #include "dialogs/InitialOptionsDialog.h" @@ -1613,11 +1614,62 @@ void MainWindow::on_actionBackward_triggered() core->seekPrev(); } +void MainWindow::on_actionDiff_triggered() +{ + QFileDialog dialog(this); + dialog.setWindowTitle(tr("Select a file to use for diffing")); + dialog.setNameFilters({ tr("All files (*)") }); + + if (!dialog.exec()) { + return; + } + + const QString &fileName = QDir::toNativeSeparators(dialog.selectedFiles().first()); + + if (fileName.isEmpty()) { + return; + } + + auto compareTask = new RizinFunctionTask([=](RzCore *) { + return Core()->matchFunctionsFromNewFile(fileName); + }, false); + + task = QSharedPointer(compareTask); + taskDialog = new RizinTaskDialog(task); + taskDialog->setAttribute(Qt::WA_DeleteOnClose); + taskDialog->setDesc(tr("Performing function comparison...")); + taskDialog->show(); + + connect(task.data(), &RizinTask::finished, this, [this, compareTask]() { + RzAnalysisMatchResult *result = static_cast(compareTask->getResult()); + openBinDiffDialog(result); + delete taskDialog; + taskDialog = nullptr; + }); + + compareTask->startTask(); +} + void MainWindow::on_actionForward_triggered() { core->seekNext(); } +void MainWindow::openBinDiffDialog(RzAnalysisMatchResult *result) +{ + if (!result) { + messageBoxWarning(tr("Error"), tr("Failed to perform the function matching.")); + return; + } + + if (binDiffDialog) { + delete binDiffDialog; + } + + binDiffDialog = new BinDiffDialog(result, this); + binDiffDialog->show(); +} + void MainWindow::on_actionDisasAdd_comment_triggered() { CommentsDialog c(this); diff --git a/src/core/MainWindow.h b/src/core/MainWindow.h index 72a265b8..8a3b8289 100644 --- a/src/core/MainWindow.h +++ b/src/core/MainWindow.h @@ -4,6 +4,7 @@ #include "core/Cutter.h" // only needed for ut64 #include "dialogs/NewFileDialog.h" #include "dialogs/WelcomeDialog.h" +#include "dialogs/bindiff/BinDiffDialog.h" #include "common/Configuration.h" #include "common/InitialOptions.h" #include "common/IOModesController.h" @@ -151,6 +152,8 @@ public slots: void openNewFileFailed(); + void openBinDiffDialog(RzAnalysisMatchResult *result); + void toggleOverview(bool visibility, GraphWidget *targetGraph); private slots: void on_actionBaseFind_triggered(); @@ -176,6 +179,8 @@ private slots: void on_actionBackward_triggered(); void on_actionForward_triggered(); + void on_actionDiff_triggered(); + void on_actionMap_triggered(); void on_actionTabs_on_Top_triggered(); @@ -329,6 +334,10 @@ private: MemoryDockWidget *lastSyncMemoryWidget = nullptr; MemoryDockWidget *lastMemoryWidget = nullptr; int functionDockWidthToRestore = 0; + + QSharedPointer task; + RizinTaskDialog *taskDialog = nullptr; + BinDiffDialog *binDiffDialog = nullptr; }; #endif // MAINWINDOW_H diff --git a/src/core/MainWindow.ui b/src/core/MainWindow.ui index 0cf17945..f6d6e728 100644 --- a/src/core/MainWindow.ui +++ b/src/core/MainWindow.ui @@ -133,6 +133,7 @@ Tools + @@ -877,6 +878,11 @@ Manage layouts + + + Diff With File.. + + diff --git a/src/dialogs/bindiff/BinDiffDialog.cpp b/src/dialogs/bindiff/BinDiffDialog.cpp new file mode 100644 index 00000000..8b518f56 --- /dev/null +++ b/src/dialogs/bindiff/BinDiffDialog.cpp @@ -0,0 +1,137 @@ +#include "core/Cutter.h" +#include "BinDiffDialog.h" + +#include "ui_BinDiffDialog.h" +#include "common/Configuration.h" +#include "common/BugReporting.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "CutterConfig.h" + +typedef struct { + QColor perfect; + QColor partial; + QColor none; +} DiffColors; + +inline QString doubleToString(double number) +{ + return QString::asprintf("%.6f", number); +} + +inline QTableWidgetItem *newColoredCell(QString text, double similarity, const DiffColors *colors) { + auto item = new QTableWidgetItem(text); + if (similarity >= 1.0) { + item->setBackground(colors->perfect); + } else if (similarity >= RZ_ANALYSIS_SIMILARITY_THRESHOLD) { + item->setBackground(colors->partial); + } else { + item->setBackground(colors->none); + } + item->setForeground(Qt::black); + return item; +} + +BinDiffDialog::BinDiffDialog(RzAnalysisMatchResult *result, QWidget *parent) : QDialog(parent), ui(new Ui::BinDiffDialog) +{ + ui->setupUi(this); + setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); + + void *ptr; + RzListIter *iter = nullptr; + RzAnalysisMatchPair *pair = nullptr; + const RzAnalysisFunction *fcn_a = nullptr, *fcn_b = nullptr; + + const DiffColors colors = { + .perfect = Config()->getColor("gui.match.perfect"), + .partial = Config()->getColor("gui.match.partial"), + .none = Config()->getColor("gui.match.none"), + }; + + size_t n_raws = rz_list_length(result->matches); + n_raws += rz_list_length(result->unmatch_a); + n_raws += rz_list_length(result->unmatch_b); + + QStringList tableHeader = { + tr("name"), tr("size"), tr("addr"), + tr("type"), tr("similarity"), + tr("addr"), tr("size"), tr("name") + }; + + ui->resultTable->setRowCount(n_raws); + ui->resultTable->setColumnCount(8); + ui->resultTable->setHorizontalHeaderLabels(tableHeader); + ui->resultTable->verticalHeader()->setVisible(false); + ui->resultTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + + n_raws = 0; + rz_list_foreach(result->matches, iter, ptr) { + pair = static_cast(ptr); + fcn_a = static_cast(pair->pair_a); + fcn_b = static_cast(pair->pair_b); + + QString size_a = RzSizeString(rz_analysis_function_realsize(fcn_a)); + QString size_b = RzSizeString(rz_analysis_function_realsize(fcn_b)); + QString addr_a = RzAddressString(fcn_a->addr); + QString addr_b = RzAddressString(fcn_b->addr); + QString simtype = RZ_ANALYSIS_SIMILARITY_TYPE_STR(pair->similarity); + QString similarity = doubleToString(pair->similarity); + + ui->resultTable->setItem(n_raws, ColumnSourceName, newColoredCell(fcn_a->name, pair->similarity, &colors)); + ui->resultTable->setItem(n_raws, ColumnSourceSize, newColoredCell(size_a, pair->similarity, &colors)); + ui->resultTable->setItem(n_raws, ColumnSourceAddr, newColoredCell(addr_a, pair->similarity, &colors)); + ui->resultTable->setItem(n_raws, ColumnSimilarityType, newColoredCell(simtype, pair->similarity, &colors)); + ui->resultTable->setItem(n_raws, ColumnSimilarityNum, newColoredCell(similarity, pair->similarity, &colors)); + ui->resultTable->setItem(n_raws, ColumnMatchAddr, newColoredCell(addr_b, pair->similarity, &colors)); + ui->resultTable->setItem(n_raws, ColumnMatchSize, newColoredCell(size_b, pair->similarity, &colors)); + ui->resultTable->setItem(n_raws, ColumnMatchName, newColoredCell(fcn_b->name, pair->similarity, &colors)); + n_raws++; + } + + rz_list_foreach(result->unmatch_a, iter, ptr) { + fcn_a = static_cast(ptr); + QString size_a = RzSizeString(rz_analysis_function_realsize(fcn_a)); + QString addr_a = RzAddressString(fcn_a->addr); + ui->resultTable->setItem(n_raws, ColumnSourceName, newColoredCell(fcn_a->name, 0.0, &colors)); + ui->resultTable->setItem(n_raws, ColumnSourceSize, newColoredCell(size_a, 0.0, &colors)); + ui->resultTable->setItem(n_raws, ColumnSourceAddr, newColoredCell(addr_a, 0.0, &colors)); + ui->resultTable->setItem(n_raws, ColumnSimilarityType, newColoredCell(RZ_ANALYSIS_SIMILARITY_UNLIKE_STR, 0.0, &colors)); + ui->resultTable->setItem(n_raws, ColumnSimilarityNum, newColoredCell("0.000000", 0.0, &colors)); + ui->resultTable->setItem(n_raws, ColumnMatchAddr, newColoredCell("------", 0.0, &colors)); + ui->resultTable->setItem(n_raws, ColumnMatchSize, newColoredCell("------", 0.0, &colors)); + ui->resultTable->setItem(n_raws, ColumnMatchName, newColoredCell("------", 0.0, &colors)); + n_raws++; + } + + rz_list_foreach(result->unmatch_b, iter, ptr) { + fcn_b = static_cast(ptr); + QString size_b = RzSizeString(rz_analysis_function_realsize(fcn_b)); + QString addr_b = RzAddressString(fcn_b->addr); + ui->resultTable->setItem(n_raws, ColumnSourceName, newColoredCell("------", 0.0, &colors)); + ui->resultTable->setItem(n_raws, ColumnSourceSize, newColoredCell("------", 0.0, &colors)); + ui->resultTable->setItem(n_raws, ColumnSourceAddr, newColoredCell("------", 0.0, &colors)); + ui->resultTable->setItem(n_raws, ColumnSimilarityType, newColoredCell(RZ_ANALYSIS_SIMILARITY_UNLIKE_STR, 0.0, &colors)); + ui->resultTable->setItem(n_raws, ColumnSimilarityNum, newColoredCell("0.000000", 0.0, &colors)); + ui->resultTable->setItem(n_raws, ColumnMatchAddr, newColoredCell(fcn_b->name, 0.0, &colors)); + ui->resultTable->setItem(n_raws, ColumnMatchSize, newColoredCell(size_b, 0.0, &colors)); + ui->resultTable->setItem(n_raws, ColumnMatchName, newColoredCell(addr_b, 0.0, &colors)); + n_raws++; + } +} + +BinDiffDialog::~BinDiffDialog() {} + +void BinDiffDialog::on_buttonBox_rejected() +{ + close(); +} + diff --git a/src/dialogs/bindiff/BinDiffDialog.h b/src/dialogs/bindiff/BinDiffDialog.h new file mode 100644 index 00000000..fb52b0fe --- /dev/null +++ b/src/dialogs/bindiff/BinDiffDialog.h @@ -0,0 +1,37 @@ +#ifndef BINDIFFDIALOG_H +#define BINDIFFDIALOG_H + +#include +#include +#include + +namespace Ui { +class BinDiffDialog; +} + +class BinDiffDialog : public QDialog +{ + Q_OBJECT + +public: + explicit BinDiffDialog(RzAnalysisMatchResult *result, QWidget *parent = nullptr); + ~BinDiffDialog(); + +private slots: + void on_buttonBox_rejected(); + +private: + enum { + ColumnSourceName = 0, + ColumnSourceSize, + ColumnSourceAddr, + ColumnSimilarityType, + ColumnSimilarityNum, + ColumnMatchAddr, + ColumnMatchSize, + ColumnMatchName + } BinDiffTableColumn; + std::unique_ptr ui; +}; + +#endif // BINDIFFDIALOG_H diff --git a/src/dialogs/bindiff/BinDiffDialog.ui b/src/dialogs/bindiff/BinDiffDialog.ui new file mode 100644 index 00000000..1d97e564 --- /dev/null +++ b/src/dialogs/bindiff/BinDiffDialog.ui @@ -0,0 +1,55 @@ + + + BinDiffDialog + + + + 0 + 0 + 935 + 554 + + + + Cutter Binary Diffing Tool + + + + + + + 0 + 0 + 1013 + 22 + + + + false + + + true + + + + Export + + + + + + + + + + + + QDialogButtonBox::Close + + + + + + + +