Initial work on bin diffing

This commit is contained in:
wargio 2022-10-07 13:04:38 +02:00
parent f97ab44072
commit 39532d312c
12 changed files with 418 additions and 16 deletions

View File

@ -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

View File

@ -78,6 +78,14 @@ const QHash<QString, QHash<ColorFlags, QColor>> 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) } } },

View File

@ -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()

View File

@ -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();

View File

@ -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<RzConfigNode *>(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

View File

@ -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

View File

@ -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<RizinTask>(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<RzAnalysisMatchResult *>(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);

View File

@ -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<RizinTask> task;
RizinTaskDialog *taskDialog = nullptr;
BinDiffDialog *binDiffDialog = nullptr;
};
#endif // MAINWINDOW_H

View File

@ -133,6 +133,7 @@
<string>Tools</string>
</property>
<addaction name="actionBaseFind"/>
<addaction name="actionDiff"/>
</widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
@ -877,6 +878,11 @@
<string>Manage layouts</string>
</property>
</action>
<action name="actionDiff">
<property name="text">
<string>Diff With File..</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources>

View File

@ -0,0 +1,137 @@
#include "core/Cutter.h"
#include "BinDiffDialog.h"
#include "ui_BinDiffDialog.h"
#include "common/Configuration.h"
#include "common/BugReporting.h"
#include <QUrl>
#include <QTimer>
#include <QEventLoop>
#include <QJsonObject>
#include <QProgressBar>
#include <QProgressDialog>
#include <UpdateWorker.h>
#include <QtNetwork/QNetworkRequest>
#include <QtNetwork/QNetworkAccessManager>
#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<RzAnalysisMatchPair *>(ptr);
fcn_a = static_cast<const RzAnalysisFunction *>(pair->pair_a);
fcn_b = static_cast<const RzAnalysisFunction *>(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<RzAnalysisFunction *>(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<RzAnalysisFunction *>(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();
}

View File

@ -0,0 +1,37 @@
#ifndef BINDIFFDIALOG_H
#define BINDIFFDIALOG_H
#include <QDialog>
#include <memory>
#include <QtNetwork/QNetworkReply>
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::BinDiffDialog> ui;
};
#endif // BINDIFFDIALOG_H

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>BinDiffDialog</class>
<widget class="QDialog" name="BinDiffDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>935</width>
<height>554</height>
</rect>
</property>
<property name="windowTitle">
<string>Cutter Binary Diffing Tool</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1013</width>
<height>22</height>
</rect>
</property>
<property name="defaultUp">
<bool>false</bool>
</property>
<property name="nativeMenuBar">
<bool>true</bool>
</property>
<widget class="QMenu" name="menuExport">
<property name="title">
<string>Export</string>
</property>
</widget>
<addaction name="menuExport"/>
</widget>
</item>
<item>
<widget class="QTableWidget" name="resultTable"/>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>