From 7f3f394c8bb50c987a5237bc74aa503486571058 Mon Sep 17 00:00:00 2001 From: kat Date: Wed, 19 Mar 2025 09:12:52 -0400 Subject: Add iOS/macOS MH_FILESET KernelCache View and loader. This loader is inspired by/based on our dyld_shared_cache loader, following the same design language. It targets primarily the latest kernels, but should support any with the MH_FILESET format. It allows you to decide which images you would like to map in (the kernel itself included), resulting in targeted analysis when you may only need to load a singular image. It also supports dropping in compressed KernelCaches, directly from the ipsw. This is an early solution and we have many more changes and improvements planned. We look forward to your feedback --- view/kernelcache/ui/kctriage.h | 243 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 view/kernelcache/ui/kctriage.h (limited to 'view/kernelcache/ui/kctriage.h') 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 +#include +#include "uitypes.h" +#include "viewframe.h" +#include "animation.h" +#include "uicontext.h" + +#include +#include +#include +#include +#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(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 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 m_symbols; + + SymbolTableModel* m_model; + +public: + SymbolTableView(QWidget* parent, Ref 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 m_cache; + + std::mutex m_headersMutex; + std::shared_ptr> 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 -- cgit v1.3.1