diff options
| author | kat <kat@vector35.com> | 2025-03-19 09:12:52 -0400 |
|---|---|---|
| committer | kat <kat@vector35.com> | 2025-03-19 15:04:23 -0400 |
| commit | 7f3f394c8bb50c987a5237bc74aa503486571058 (patch) | |
| tree | 9f5ada34f6226562b79f0dce70bfebab124a1bdb /view/kernelcache/ui/kctriage.h | |
| parent | e6d4d38e2a5dc66d3c8004cc917bc08e39337482 (diff) | |
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
Diffstat (limited to 'view/kernelcache/ui/kctriage.h')
| -rw-r--r-- | view/kernelcache/ui/kctriage.h | 243 |
1 files changed, 243 insertions, 0 deletions
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 |
