diff options
Diffstat (limited to 'view/kernelcache/ui')
| -rw-r--r-- | view/kernelcache/ui/CMakeLists.txt | 95 | ||||
| -rw-r--r-- | view/kernelcache/ui/KernelCacheUINotifications.cpp | 92 | ||||
| -rw-r--r-- | view/kernelcache/ui/KernelCacheUINotifications.h | 23 | ||||
| -rw-r--r-- | view/kernelcache/ui/Plugin.cpp | 22 | ||||
| -rw-r--r-- | view/kernelcache/ui/kctriage.cpp | 556 | ||||
| -rw-r--r-- | view/kernelcache/ui/kctriage.h | 243 |
6 files changed, 1031 insertions, 0 deletions
diff --git a/view/kernelcache/ui/CMakeLists.txt b/view/kernelcache/ui/CMakeLists.txt new file mode 100644 index 00000000..369dc603 --- /dev/null +++ b/view/kernelcache/ui/CMakeLists.txt @@ -0,0 +1,95 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(kernelcacheui) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +find_package(Qt6 COMPONENTS Core Gui Widgets REQUIRED) + +file(GLOB SOURCES *.cpp *.h) +list(FILTER SOURCES EXCLUDE REGEX moc_.*) +list(FILTER SOURCES EXCLUDE REGEX qrc_.*) + +add_library(kernelcacheui SHARED ${SOURCES}) + +set(COMPILE_DEFS "") + +if (HARD_FAIL_MODE) + set(COMPILE_DEFS "${COMPILE_DEFS} ABORT_FAILURES;") +endif() + +if (BN_REF_COUNT_DEBUG) + set(COMPILE_DEFS "${COMPILE_DEFS} BN_REF_COUNT_DEBUG;") +endif() + +if (SLIDEINFO_DEBUG_TAGS) + set(COMPILE_DEFS "${COMPILE_DEFS} SLIDEINFO_DEBUG_TAGS;") +endif() + +if (METADATA_VERSION) + set(COMPILE_DEFS "${COMPILE_DEFS} METADATA_VERSION=${METADATA_VERSION};") +else() + message(FATAL_ERROR "No metadata version provided. Fatal.") +endif() + +if (KC_VIEW_NAME) + set(COMPILE_DEFS "${COMPILE_DEFS} KC_VIEW_NAME=\"${KC_VIEW_NAME}\";") +else() + message(FATAL_ERROR "No view name provided. Fatal.") +endif() + +target_compile_definitions(kernelcacheui PRIVATE ${COMPILE_DEFS}) + + +if(BN_INTERNAL_BUILD) + set_target_properties(kernelcacheui PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR} + RUNTIME_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR}) +else() + set_target_properties(kernelcacheui PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out/plugins + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out/plugins + ) +endif() + +set_target_properties(kernelcacheui PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON + POSITION_INDEPENDENT_CODE ON + ) + +function(get_recursive_include_dirs target result) + # Initialize an empty list to store include directories + set(include_dirs "") + + # Get the include directories of the current target + get_target_property(current_target_includes ${target} INTERFACE_INCLUDE_DIRECTORIES) + if(current_target_includes) + list(APPEND include_dirs ${current_target_includes}) + endif() + + # Get the libraries that this target links to + get_target_property(linked_libraries ${target} INTERFACE_LINK_LIBRARIES) + if(linked_libraries) + foreach(linked_library IN LISTS linked_libraries) + # Skip plain library names (non-target libraries) + if(TARGET ${linked_library}) + # Recursively get include directories from linked libraries + get_recursive_include_dirs(${linked_library} linked_library_includes) + list(APPEND include_dirs ${linked_library_includes}) + endif() + endforeach() + endif() + + # Set the result to the collected include directories + set(${result} ${include_dirs} PARENT_SCOPE) +endfunction() + +get_recursive_include_dirs(kernelcacheapi INCLUDES) + +target_include_directories(kernelcacheui PRIVATE ${INCLUDES}) + +target_link_libraries(kernelcacheui kernelcacheapi kernelcache binaryninjaui Qt6::Core Qt6::Gui Qt6::Widgets) + diff --git a/view/kernelcache/ui/KernelCacheUINotifications.cpp b/view/kernelcache/ui/KernelCacheUINotifications.cpp new file mode 100644 index 00000000..6bd8fdf0 --- /dev/null +++ b/view/kernelcache/ui/KernelCacheUINotifications.cpp @@ -0,0 +1,92 @@ +// +// Created by kat on 5/8/23. +// + +#include "KernelCacheUINotifications.h" +#include <QLayout> +#include <kernelcacheapi.h> +#include "ui/sidebar.h" +#include "ui/linearview.h" +#include "ui/viewframe.h" +#include "progresstask.h" + +UINotifications* UINotifications::m_instance = nullptr; + +void UINotifications::init() +{ + m_instance = new UINotifications; + UIContext::registerNotification(m_instance); +} + + +void UINotifications::OnViewChange(UIContext* context, ViewFrame* frame, const QString& type) +{ + if (!frame) + return; + + // FIXME there is a bv func for this + static std::function<bool(Ref<BinaryView>, uint64_t)> isAddrMapped = [](Ref<BinaryView> view, uint64_t addr) { + if (view && view->GetTypeName() == KC_VIEW_NAME) + { + for (const auto& seg : view->GetSegments()) + { + if (seg->GetStart() <= addr && seg->GetEnd() > addr) + return true; + } + } + return false; + }; + + auto view = frame->getCurrentBinaryView(); + if (view && view->GetTypeName() == KC_VIEW_NAME) + { + if (auto viewInt = frame->getCurrentViewInterface()) + { + auto ah = viewInt->actionHandler(); + if (!ah->isBoundAction("KC Load IMGHERE")) + { + ah->bindAction("KC Load IMGHERE", + UIAction( + [](const UIActionContext& ctx) { + Ref<BinaryView> view = ctx.binaryView; + Ref<KernelCacheAPI::KernelCache> cache = new KernelCacheAPI::KernelCache(view); + uint64_t addr = ctx.token.token.value; + if (addr) + { + BackgroundThread::create(ctx.context->mainWindow())->thenBackground( + [cache=cache, addr=addr]() { + cache->LoadImageContainingAddress(addr); + })->start(); + } + }, + [](const UIActionContext& ctx) { + Ref<KernelCacheAPI::KernelCache> cache = new KernelCacheAPI::KernelCache(ctx.binaryView); + uint64_t addr = ctx.token.token.value; + if (isAddrMapped(ctx.binaryView, addr)) + return false; + return addr && cache->GetImageNameForAddress(addr) != ""; // bool + })); + ah->setActionDisplayName("KC Load IMGHERE", [](const UIActionContext& ctx) { + Ref<KernelCacheAPI::KernelCache> cache = new KernelCacheAPI::KernelCache(ctx.binaryView); + uint64_t addr = ctx.token.token.value; + if (addr) + return QString("Load ") + cache->GetImageNameForAddress(addr).c_str(); + return QString("Error"); + }); + if (auto linearView = qobject_cast<LinearView*>(viewInt->widget())) + { + linearView->contextMenu().addAction("KC Load IMGHERE", KC_VIEW_NAME); + linearView->contextMenu().setGroupOrdering(KC_VIEW_NAME, 0); + } + } + } + } +} +void UINotifications::OnAfterOpenFile(UIContext* context, FileContext* file, ViewFrame* frame) +{ + if (frame->getCurrentBinaryView()) + { + // Register BD notifications. We dont use them right now. + } + UIContextNotification::OnAfterOpenFile(context, file, frame); +} diff --git a/view/kernelcache/ui/KernelCacheUINotifications.h b/view/kernelcache/ui/KernelCacheUINotifications.h new file mode 100644 index 00000000..9ea4b50d --- /dev/null +++ b/view/kernelcache/ui/KernelCacheUINotifications.h @@ -0,0 +1,23 @@ +// +// Created by kat on 5/8/23. +// +#include "ui/uicontext.h" + +#ifndef SHAREDCACHE_NOTIFICATIONS_H +#define SHAREDCACHE_NOTIFICATIONS_H + +class UINotifications : public UIContextNotification { + static UINotifications* m_instance; + + std::vector<size_t> m_sessionsAlreadyDisplayedPickerFor; + +public: + virtual void OnViewChange(UIContext *context, ViewFrame *frame, const QString &type) override; + // bool OnAfterOpenDatabase(UIContext* context, FileMetadataRef metadata, BinaryViewRef data) override; + void OnAfterOpenFile(UIContext* context, FileContext* file, ViewFrame* frame) override; + + static void init(); +}; + + +#endif //SHAREDCACHE_NOTIFICATIONS_H diff --git a/view/kernelcache/ui/Plugin.cpp b/view/kernelcache/ui/Plugin.cpp new file mode 100644 index 00000000..cfa92fe4 --- /dev/null +++ b/view/kernelcache/ui/Plugin.cpp @@ -0,0 +1,22 @@ +// +// Created by kat on 8/6/24. +// +#include <binaryninjaapi.h> +#include "KernelCacheUINotifications.h" +#include "kctriage.h" + +extern "C" +{ + BN_DECLARE_CORE_ABI_VERSION + BN_DECLARE_UI_ABI_VERSION + + BINARYNINJAPLUGIN bool UIPluginInit() + { + UINotifications::init(); + UIAction::registerAction("KC Load IMGHERE"); + + KCTriageViewType::Register(); + + return true; + } +}
\ No newline at end of file diff --git a/view/kernelcache/ui/kctriage.cpp b/view/kernelcache/ui/kctriage.cpp new file mode 100644 index 00000000..5f0fa9ce --- /dev/null +++ b/view/kernelcache/ui/kctriage.cpp @@ -0,0 +1,556 @@ +// +// Created by kat on 8/15/24. +// + +#include "kctriage.h" +#include "ui/fontsettings.h" +#include <QPainter> +#include <QTextBrowser> +#include "tabwidget.h" +#include "globalarea.h" +#include "progresstask.h" + +#include <cmath> +#include <QMessageBox> + + +#define QSETTINGS_KEY_SELECTED_TAB "KCTriage-SelectedTab" +#define QSETTINGS_KEY_TAB_LAYOUT "KCTriage-TabLayout" +#define QSETTINGS_KEY_IMAGELOAD_TAB_LAYOUT "KCTriage-ImageLoadTabLayout" +#define QSETTINGS_KEY_ALPHA_POPUP_SEEN "KCTriage-AlphaPopupSeenV2" + + +SymbolTableModel::SymbolTableModel(SymbolTableView* parent) + : QAbstractTableModel(parent), m_parent(parent) { +} + +int SymbolTableModel::rowCount(const QModelIndex& parent) const { + Q_UNUSED(parent); + return static_cast<int>(m_symbols.size()); +} + +int SymbolTableModel::columnCount(const QModelIndex& parent) const { + Q_UNUSED(parent); + // We have 3 columns: Address, Name, and Image + return 3; +} + +QVariant SymbolTableModel::data(const QModelIndex& index, int role) const { + if (!index.isValid() || role != Qt::DisplayRole) { + return QVariant(); + } + + const KernelCacheAPI::KCSymbol& symbol = m_symbols.at(index.row()); + + switch (index.column()) { + case 0: // Address column + return QString("0x%1").arg(symbol.address, 0, 16); // Display address as hexadecimal + case 1: // Name column + return QString::fromStdString(symbol.name); + case 2: // Image column + return QString::fromStdString(symbol.image); + default: + return QVariant(); + } +} + +QVariant SymbolTableModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (role != Qt::DisplayRole || orientation != Qt::Horizontal) { + return QVariant(); + } + + switch (section) { + case 0: + return QString("Address"); + case 1: + return QString("Name"); + case 2: + return QString("Image"); + default: + return QVariant(); + } +} + +void SymbolTableModel::updateSymbols() { + m_symbols = m_parent->m_symbols; + setFilter(m_filter); +} + +const KernelCacheAPI::KCSymbol& SymbolTableModel::symbolAt(int row) const { + return m_symbols.at(row); +} + + +void SymbolTableModel::setFilter(std::string text) +{ + beginResetModel(); + + m_filter = text; + m_symbols.clear(); + + if (m_filter.empty()) + { + m_symbols = m_parent->m_symbols; + } + else + { + m_symbols.reserve(m_parent->m_symbols.size()); + for (const auto& symbol : m_parent->m_symbols) + { + if (symbol.name.find(m_filter) != std::string::npos) + { + m_symbols.push_back(symbol); + } + } + m_symbols.shrink_to_fit(); + } + + endResetModel(); +} + + +SymbolTableView::SymbolTableView(QWidget* parent, Ref<KernelCacheAPI::KernelCache> cache) + : m_model(new SymbolTableModel(this)) { + + // Set up the filter model + setModel(m_model); + + // Configure view settings + horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + setSelectionBehavior(QAbstractItemView::SelectRows); + setSelectionMode(QAbstractItemView::SingleSelection); + + BackgroundThread::create(this)->thenBackground([this, cache=cache](){ + // LogInfo("Symbol Search: Loading symbols..."); + m_symbols = cache->LoadAllSymbolsAndWait(); + // LogInfo("Symbol Search: Loaded 0x%zx symbols", m_symbols.size()); + })->thenMainThread([this](){ + m_model->updateSymbols(); + })->start(); +} + +SymbolTableView::~SymbolTableView() { + delete m_model; +} + +void SymbolTableView::setFilter(const std::string& filter) { + m_model->setFilter(filter); +} + + +KCTriageView::KCTriageView(QWidget* parent, BinaryViewRef data) : QWidget(parent), View(), m_data(data), m_cache(new KernelCacheAPI::KernelCache(data)) +{ + setBinaryDataNavigable(true); + setupView(this); + + m_triageCollection = new DockableTabCollection(); + m_triageTabs = new SplitTabWidget(m_triageCollection); + + auto triageTabStyle = new GlobalAreaTabStyle(); + m_triageTabs->setTabStyle(triageTabStyle); + + QSplitter* containerWidget = new QSplitter; + containerWidget->setOrientation(Qt::Vertical); + + QWidget* defaultWidget = nullptr; + + m_bottomRegionCollection = new DockableTabCollection(); + m_bottomRegionTabs = new SplitTabWidget(m_bottomRegionCollection); + m_bottomRegionTabs->setTabStyle(new GlobalAreaTabStyle()); + + auto loadImageTable = new FilterableTableView; + { + auto loadImageModel = new QStandardItemModel(0, 2, loadImageTable); + { + loadImageModel->setHorizontalHeaderLabels({"Name", "VM Address"}); + BackgroundThread::create(loadImageTable)->thenBackground([this, loadImageModel](QVariant var) + { + QVariantList rows; + + auto images = m_cache->GetImages(); + + auto newHeaders = std::make_shared<std::vector<KernelCacheAPI::KernelCacheMachOHeader>>(); + newHeaders->reserve(images.size()); + + for (const auto& img : images) + { + if (auto header = m_cache->GetMachOHeaderForImage(img.name); header) + { + newHeaders->push_back(*header); + rows.push_back(QList<QVariant>{ + QString::fromStdString(img.name), + QString("0x%1").arg(header->textBase, 0, 16) + }); + } + } + + { + std::unique_lock<std::mutex> lock(m_headersMutex); + m_headers.swap(newHeaders); + } + + return QVariant(rows); + })->thenMainThread([this, loadImageModel, loadImageTable](QVariant var){ + QVariantList rows = var.toList(); + + if (loadImageModel->rowCount() > 0) + { + loadImageModel->removeRows(0, loadImageModel->rowCount()); + } + + for (const QVariant &rowVariant : rows) { + QVariantList row = rowVariant.toList(); + + QList<QStandardItem*> items; + for (const QVariant &cellValue : row) { + items.append(new QStandardItem(cellValue.toString())); + } + + loadImageModel->appendRow(items); + loadImageTable->resizeColumnsToContents(); + } + })->start(); + } // loadImageModel + + auto loadImageButton = new CustomStyleFlatPushButton(); + { + connect(loadImageButton, &QPushButton::clicked, + [this, loadImageTable](bool) { + auto selected = loadImageTable->selectionModel()->selectedRows(); + if (selected.size() == 0) + { + return; + } + + for (const auto& selection : selected) + { + auto name = selection.data().toString().toStdString(); + WorkerPriorityEnqueue([this, name]() { m_cache->LoadImageWithInstallName(name); }); + } + }); + loadImageButton->setText("Load"); + + loadImageButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + loadImageButton->setMinimumWidth(100); + loadImageButton->setMinimumHeight(30); + + } // loadImageButton + loadImageTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + + auto loadImageFilterEdit = new FilterEdit(loadImageTable); + { + connect(loadImageFilterEdit, &FilterEdit::textChanged, [loadImageTable](const QString& filter) { + loadImageTable->setFilter(filter.toStdString()); + }); + } // loadImageFilterEdit + + connect(loadImageTable, &FilterableTableView::activated, this, [=](const QModelIndex& index) + { + auto selected = loadImageTable->selectionModel()->selectedRows(); + if (selected.size() == 0) + { + return; + } + + for (const auto& selection : selected) + { + auto name = selection.data().toString().toStdString(); + WorkerPriorityEnqueue([this, name]() { m_cache->LoadImageWithInstallName(name); }); + } + }); + connect(loadImageTable, &FilterableTableView::doubleClicked, this, [=](const QModelIndex& index) + { + auto selected = loadImageTable->selectionModel()->selectedRows(); + if (selected.size() == 0) + { + return; + } + + for (const auto& selection : selected) + { + auto name = selection.data().toString().toStdString(); + WorkerPriorityEnqueue([this, name]() { m_cache->LoadImageWithInstallName(name); }); + } + }); + + auto loadImageLayout = new QVBoxLayout; + loadImageLayout->addWidget(loadImageFilterEdit); + loadImageLayout->addWidget(loadImageTable); + loadImageLayout->addWidget(loadImageButton); + + auto loadImageWidget = new QWidget; + loadImageWidget->setLayout(loadImageLayout); + + m_bottomRegionTabs->addTab(loadImageWidget, "Load an Image"); + + loadImageTable->setModel(loadImageModel); + + loadImageTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + loadImageTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + + loadImageTable->setSelectionBehavior(QAbstractItemView::SelectRows); + + m_triageTabs->addTab(loadImageWidget, "Images"); + defaultWidget = loadImageWidget; + m_triageTabs->setCanCloseTab(loadImageWidget, false); + } // loadImageTable + + auto symbolSearch = new SymbolTableView(this, m_cache); + { + auto symbolFilterEdit = new FilterEdit(symbolSearch); + { + connect(symbolFilterEdit, &FilterEdit::textChanged, [symbolSearch](const QString& filter) { + symbolSearch->setFilter(filter.toStdString()); + }); + } + + auto symbolLayout = new QVBoxLayout; + symbolLayout->addWidget(symbolFilterEdit); + symbolLayout->addWidget(symbolSearch); + + auto symbolWidget = new QWidget; + symbolWidget->setLayout(symbolLayout); + + symbolSearch->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); // Address + symbolSearch->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); // Name + symbolSearch->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); // Image + + symbolSearch->setSelectionBehavior(QAbstractItemView::SelectRows); + symbolSearch->setSelectionMode(QAbstractItemView::SingleSelection); + + connect(symbolSearch, &SymbolTableView::activated, this, [=](const QModelIndex& index) + { + auto symbol = symbolSearch->getSymbolAtRow(index.row()); + auto dialog = new QMessageBox(this); + dialog->setText("Load " + QString::fromStdString(symbol.image) + "?"); + dialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No); + + connect(dialog, &QMessageBox::buttonClicked, this, [=](QAbstractButton* button) + { + if (button == dialog->button(QMessageBox::Yes)) + { + WorkerPriorityEnqueue([this, symbol]() + { + m_cache->LoadImageWithInstallName(symbol.image); + }); + } + }); + dialog->exec(); + }); + + m_triageTabs->addTab(symbolWidget, "Symbol Search"); + m_triageTabs->setCanCloseTab(symbolWidget, false); + } // symbolSearch + + auto loadedRegions = new QTreeView; + { + auto loadedRegionsModel = new QStandardItemModel(0, 3, loadedRegions); + loadedRegionsModel->setHorizontalHeaderLabels({"VM Address", "Size", "Pretty Name"}); + + auto loadedRegionsLayout = new QVBoxLayout; + loadedRegionsLayout->addWidget(loadedRegions); + + auto loadedRegionsWidget = new QWidget; + loadedRegionsWidget->setLayout(loadedRegionsLayout); + + loadedRegions->setModel(loadedRegionsModel); + + loadedRegions->header()->setSectionResizeMode(QHeaderView::Stretch); + + loadedRegions->setSelectionBehavior(QAbstractItemView::SelectRows); + loadedRegions->setSelectionMode(QAbstractItemView::SingleSelection); + + connect(loadedRegions, &QTreeView::doubleClicked, this, [=](const QModelIndex& index) + { + auto addr = loadedRegionsModel->item(index.row(), 0)->text().toULongLong(nullptr, 16); + }); + + connect(loadedRegions, &QTreeView::activated, this, [=](const QModelIndex& index) + { + auto addr = loadedRegionsModel->item(index.row(), 0)->text().toULongLong(nullptr, 16); + }); + + // m_triageTabs->addTab(loadedRegionsWidget, "Loaded Regions"); + } // loadedRegions + + + { // Doc tabs + + QTextBrowser *mainDocBrowser = new QTextBrowser(this); + { + mainDocBrowser->setOpenExternalLinks(true); + auto alphaHtml = + R"( +<h1>KernelCache Parser</h1> + +<p> This parser has support for MH_FILESET (>= macOS 11, >= iOS 14) kernels. </p> + +<h2> Getting the latest version of the plugin </h2> +<p> We frequently release "dev" builds which will contain the latest version of the KernelCache plugin (and many other things). </p> +<p> You can find instructions on how to install these builds <a href="https://docs.binary.ninja/guide/index.html#development-branch">here</a>. </p> + +<h3> Reading / building the source </h3> +<p> Like most of our platforms, architectures, debug information parsing, and the entire API and documentation, this plugin is open source. </p> +<p> You can read the source and find instructions for building it <a href="https://github.com/Vector35/binaryninja-api/tree/dev/view/kernelcache">here</a>. </p> +<p> Contributions are always welcome! </p> +)"; + mainDocBrowser->setHtml(alphaHtml); + + m_triageTabs->addTab(mainDocBrowser, "KernelCache Parser"); + m_triageTabs->setCanCloseTab(mainDocBrowser, false); + + } + } + + containerWidget->addWidget(m_bottomRegionTabs); + + m_layout = new QVBoxLayout(this); + m_layout->addWidget(m_triageTabs); + setLayout(m_layout); + + m_triageTabs->selectWidget(defaultWidget); +} + + +QFont KCTriageView::getFont() +{ + return getMonospaceFont(this); +} + + +BinaryViewRef KCTriageView::getData() +{ + return m_data; +} + + +bool KCTriageView::navigate(uint64_t offset) +{ + return false; +} + + +uint64_t KCTriageView::getCurrentOffset() +{ + return 0; +} + + +CollapsibleSection::CollapsibleSection(QWidget* parent) + : QWidget(parent) +{ + auto layout = new QVBoxLayout(this); + { + layout->setContentsMargins(0, 0, 0, 0); + + auto hLayout = new QHBoxLayout; + { + hLayout->setContentsMargins(0, 0, 0, 0); + + m_titleLabel = new QLabel; + m_titleLabel->setStyleSheet("font-weight: bold; font-size: 16px;"); + hLayout->addWidget(m_titleLabel, 1); + + m_subtitleRightLabel = new QLabel; + m_subtitleRightLabel->setStyleSheet("font-size: 12px;"); + hLayout->addWidget(m_subtitleRightLabel); + + m_collapseButton = new CustomStyleFlatPushButton; + m_collapseButton->setFlat(true); + m_collapseButton->setCheckable(true); + } + + layout->addLayout(hLayout); + } + + m_contentWidgetContainer = new QWidget; + { + layout->addWidget(m_contentWidgetContainer); + new QVBoxLayout(m_contentWidgetContainer); + } + +} + + +void CollapsibleSection::setTitle(const QString& title) +{ + m_titleLabel->setText(title); +} + + +void CollapsibleSection::setSubtitleRight(const QString& subtitle) +{ + m_subtitleRightLabel->setVisible(subtitle != ""); + m_subtitleRightLabel->setText(subtitle); +} + + +void CollapsibleSection::setContentWidget(QWidget* contentWidget) +{ + m_contentWidget = contentWidget; + m_contentWidgetContainer->layout()->addWidget(contentWidget); +} + + +QSize CollapsibleSection::sizeHint() const +{ + return QWidget::sizeHint(); +} + + +void CollapsibleSection::setCollapsed(bool collapsed, bool animated) +{ + if (collapsed == m_collapsed) + { + return; + } + + m_collapsed = collapsed; + + if (m_collapsed) + { + m_contentWidget->hide(); + } + else + { + m_contentWidget->show(); + } + + if (animated) + { + m_onContentAddedAnimation->start(); + } +} + + +KCTriageViewType::KCTriageViewType() + : ViewType("KCTriage", "Kernel Cache Triage") +{ + +} + + +int KCTriageViewType::getPriority(BinaryViewRef data, const QString& filename) +{ + if (data->GetTypeName() == KC_VIEW_NAME) + { + return 100; + } + return 0; +} + + +QWidget* KCTriageViewType::create(BinaryViewRef data, ViewFrame* viewFrame) +{ + if (data->GetTypeName() != KC_VIEW_NAME) + { + return nullptr; + } + return new KCTriageView(viewFrame, data); +} + + +void KCTriageViewType::Register() +{ + ViewType::registerViewType(new KCTriageViewType()); +} diff --git a/view/kernelcache/ui/kctriage.h b/view/kernelcache/ui/kctriage.h new file mode 100644 index 00000000..a6b7f7ac --- /dev/null +++ b/view/kernelcache/ui/kctriage.h @@ -0,0 +1,243 @@ +// +// Created by kat on 8/15/24. +// + +#include <kernelcacheapi.h> +#include <binaryninjaapi.h> +#include "uitypes.h" +#include "viewframe.h" +#include "animation.h" +#include "uicontext.h" + +#include <QTableView> +#include <QStandardItemModel> +#include <QSortFilterProxyModel> +#include <QHeaderView> +#include "filter.h" + +#ifndef BINARYNINJA_KCTRIAGE_H +#define BINARYNINJA_KCTRIAGE_H + +class CollapsibleSection : public QWidget +{ + Q_OBJECT + + QLabel* m_titleLabel; + QLabel* m_subtitleRightLabel; + QPushButton* m_collapseButton; + + bool m_collapsed = true; + + Animation* m_onContentAddedAnimation; + + QWidget* m_contentWidgetContainer; + QWidget* m_contentWidget; + +protected: + QSize sizeHint() const override; + +public: + CollapsibleSection(QWidget* parent); + void setTitle(const QString& title); + void setSubtitleRight(const QString& subtitle); + + void setContentWidget(QWidget* contentWidget); + + void setCollapsed(bool collapsed, bool animated = true); + bool isCollapsed() const { return m_collapsed; } +}; + + +class FilterableTableView : public QTableView, public FilterTarget { + Q_OBJECT + + bool m_filterByHiding; + +public: + FilterableTableView(QWidget* parent = nullptr, bool filterByHiding = true) + : QTableView(parent), m_filterByHiding(filterByHiding) { + viewport()->installEventFilter(this); + } + + ~FilterableTableView() override {} + + void setFilter(const std::string& filter) override { + if (!m_filterByHiding) + { + emit filterTextChanged(QString::fromStdString(filter)); + return; + } + QString qFilter = QString::fromStdString(filter); + for (int row = 0; row < model()->rowCount(); ++row) { + bool match = false; + for (int col = 0; col < model()->columnCount(); ++col) { + QModelIndex index = model()->index(row, col); + QString data = model()->data(index).toString(); + if (data.contains(qFilter, Qt::CaseInsensitive)) { + match = true; + break; + } + } + setRowHidden(row, !match); + } + } + + void scrollToFirstItem() override { + if (model()->rowCount() > 0) { + scrollTo(model()->index(0, 0)); + } + } + + void scrollToCurrentItem() override { + QModelIndex currentIndex = selectionModel()->currentIndex(); + if (currentIndex.isValid()) { + scrollTo(currentIndex); + } + } + + void selectFirstItem() override { + if (model()->rowCount() > 0) { + QModelIndex firstIndex = model()->index(0, 0); + selectionModel()->select(firstIndex, QItemSelectionModel::ClearAndSelect); + } + } + + void activateFirstItem() override { + if (model()->rowCount() > 0) { + QModelIndex firstIndex = model()->index(0, 0); + setCurrentIndex(firstIndex); + emit activated(firstIndex); + } + } + + bool eventFilter(QObject* obj, QEvent* event) override { + if (event->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); + if (keyEvent->key() == Qt::Key_Escape) { + clearSelection(); + return true; + } + if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) { + emit activated(currentIndex()); + return true; + } + } + return QTableView::eventFilter(obj, event); + } + +signals: + void filterTextChanged(const QString& text); +}; + +class SymbolTableView; + +class SymbolTableModel : public QAbstractTableModel { + Q_OBJECT + + SymbolTableView* m_parent; + std::string m_filter; + std::vector<KernelCacheAPI::KCSymbol> m_symbols; + +public: + explicit SymbolTableModel(SymbolTableView* parent); + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + void updateSymbols(); + + void setFilter(std::string text); + + const KernelCacheAPI::KCSymbol& symbolAt(int row) const; +}; + + +class SymbolTableView : public QTableView, public FilterTarget +{ + Q_OBJECT + friend class SymbolTableModel; + + std::vector<KernelCacheAPI::KCSymbol> m_symbols; + + SymbolTableModel* m_model; + +public: + SymbolTableView(QWidget* parent, Ref<KernelCacheAPI::KernelCache> cache); + virtual ~SymbolTableView() override; + + void scrollToFirstItem() override { + if (model()->rowCount() > 0) { + scrollTo(model()->index(0, 0)); + } + } + + void scrollToCurrentItem() override { + QModelIndex currentIndex = selectionModel()->currentIndex(); + if (currentIndex.isValid()) { + scrollTo(currentIndex); + } + } + + void selectFirstItem() override { + if (model()->rowCount() > 0) { + QModelIndex firstIndex = model()->index(0, 0); + selectionModel()->select(firstIndex, QItemSelectionModel::ClearAndSelect); + } + } + + void activateFirstItem() override { + if (model()->rowCount() > 0) { + QModelIndex firstIndex = model()->index(0, 0); + setCurrentIndex(firstIndex); + emit activated(firstIndex); + } + } + + KernelCacheAPI::KCSymbol getSymbolAtRow(int row) const + { + return m_model->symbolAt(row); + } + + void setFilter(const std::string& filter) override; +}; + + +class KCTriageView : public QWidget, public View +{ + BinaryViewRef m_data; + QVBoxLayout* m_layout; + Ref<KernelCacheAPI::KernelCache> m_cache; + + std::mutex m_headersMutex; + std::shared_ptr<std::vector<KernelCacheAPI::KernelCacheMachOHeader>> m_headers; + + SplitTabWidget* m_triageTabs; + DockableTabCollection* m_triageCollection; + + SplitTabWidget* m_bottomRegionTabs; + DockableTabCollection* m_bottomRegionCollection; + + +public: + KCTriageView(QWidget* parent, BinaryViewRef data); + BinaryViewRef getData() override; + void setSelectionOffsets(BNAddressRange range) override {}; + QFont getFont() override; + bool navigate(uint64_t offset) override; + uint64_t getCurrentOffset() override; +}; + + +class KCTriageViewType : public ViewType +{ +public: + KCTriageViewType(); + int getPriority(BinaryViewRef data, const QString& filename) override; + QWidget* create(BinaryViewRef data, ViewFrame* viewFrame) override; + static void Register(); +}; + + +#endif // BINARYNINJA_KCTRIAGE_H |
