summaryrefslogtreecommitdiff
path: root/view/kernelcache/ui
diff options
context:
space:
mode:
Diffstat (limited to 'view/kernelcache/ui')
-rw-r--r--view/kernelcache/ui/CMakeLists.txt95
-rw-r--r--view/kernelcache/ui/KernelCacheUINotifications.cpp92
-rw-r--r--view/kernelcache/ui/KernelCacheUINotifications.h23
-rw-r--r--view/kernelcache/ui/Plugin.cpp22
-rw-r--r--view/kernelcache/ui/kctriage.cpp556
-rw-r--r--view/kernelcache/ui/kctriage.h243
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