Skip to content

Instantly share code, notes, and snippets.

@fariss
Created January 20, 2026 22:34
Show Gist options
  • Select an option

  • Save fariss/6b1a2f4b11b75120d1b4850c3dd13cb5 to your computer and use it in GitHub Desktop.

Select an option

Save fariss/6b1a2f4b11b75120d1b4850c3dd13cb5 to your computer and use it in GitHub Desktop.

CAPA TreeTable Ghidra Extension

Installation
  1. Copy all four Java files to your Ghidra scripts directory:

    • macOS/Linux: ~/.ghidra/.ghidra_<version>/scripts/
  2. Run TreeTableGhidraScript.java from the Script Manager

Files Included

This extension provides a hierarchical tree table view for displaying CAPA analysis results in Ghidra.


TreeTableGhidraScript.java
// Display hierarchical data using JTreeTable component
// @author 
// @category Data.Structures
// @keybinding 
// @menupath Tools.Show TreeTable
// @toolbar 

import ghidra.app.script.GhidraScript;
import javax.swing.*;

public class TreeTableGhidraScript extends GhidraScript {
    @Override
    protected void run() throws Exception {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("Ghidra TreeTable Demo");
            frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            frame.setSize(500, 400);
            
            CapaTreeModel model = new CapaTreeModel();
            JTreeTable treeTable = new JTreeTable(model);
            
            frame.add(new JScrollPane(treeTable));
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
}

CapaTreeModel.java
import javax.swing.tree.DefaultMutableTreeNode;

public class CapaTreeModel extends AbstractTreeTableModel {
    private String[] columnNames = {"Rule Information", "Address", "Details"};
    private Class[] columnTypes = {TreeTableModel.class, String.class, String.class};

    public CapaTreeModel() {
        super(createData());
    }

    private static Object createData() {
        DefaultMutableTreeNode root = new DefaultMutableTreeNode(new CapaNodeData("get Program Files directory (2 matches)", "", "anti-analysis/anti-forensic/self-deletion"));

        DefaultMutableTreeNode mammals = new DefaultMutableTreeNode(new CapaNodeData("function (FUN_100000368)", "0x100000358", ""));
        DefaultMutableTreeNode and = new DefaultMutableTreeNode(new CapaNodeData("and", "", ""));
        and.add(new DefaultMutableTreeNode(new CapaNodeData("api(shell32.SHGetFolderPath)", "0x100000368", "call shell32.SHGetFolderPath")));
        mammals.add(and);
        root.add(mammals);

        DefaultMutableTreeNode basicBlock = new DefaultMutableTreeNode(new CapaNodeData("basic block @ 0x14003755D", "", ""));
        DefaultMutableTreeNode and2 = new DefaultMutableTreeNode(new CapaNodeData("and", "", ""));
        DefaultMutableTreeNode or1 = new DefaultMutableTreeNode(new CapaNodeData("or", "", ""));
        or1.add(new DefaultMutableTreeNode(new CapaNodeData("number: 0x26 = CSIDL_PROGRAM_FILES", "0x140037560", "mov eax, 0x26")));
        and2.add(or1);
        basicBlock.add(and2);
        DefaultMutableTreeNode or2 = new DefaultMutableTreeNode(new CapaNodeData("or", "", ""));
        or2.add(new DefaultMutableTreeNode(new CapaNodeData("api: SHGetSpecialFolderPath", "0x14003756C", "call SHGetSpecialFolderPath")));
        basicBlock.add(or2);
        root.add(basicBlock);

        return root;
    }

    public int getColumnCount() {
        return columnNames.length;
    }

    public String getColumnName(int column) {
        return columnNames[column];
    }

    public Class getColumnClass(int column) {
        return columnTypes[column];
    }

    public Object getValueAt(Object node, int column) {
        DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) node;
        CapaNodeData data = (CapaNodeData) treeNode.getUserObject();

        switch(column) {
            case 0: return data;
            case 1: return data.getType();
            case 2: return data.getDetails();
            default: return null;
        }
    }

    public Object getChild(Object parent, int index) {
        return ((DefaultMutableTreeNode) parent).getChildAt(index);
    }

    public int getChildCount(Object parent) {
        return ((DefaultMutableTreeNode) parent).getChildCount();
    }
}

CapaNodeData.java
public class CapaNodeData {
    private String name;
    private String type;
    private String details;

    public CapaNodeData(String name, String type, String details) {
        this.name = name;
        this.type = type;
        this.details = details;
    }

    public String getName() { return name; }
    public String getType() { return type; }
    public String getDetails() { return details; }

    @Override
    public String toString() {
        return name;
    }
}

JTreeTable.java

import javax.swing.*;
import javax.swing.tree.*;
import javax.swing.table.*;
import java.awt.Dimension;
import java.awt.Component;
import java.awt.Graphics;

public class JTreeTable extends JTable {
    protected TreeTableCellRenderer tree;

    public JTreeTable(TreeTableModel treeTableModel) {
        super();
        tree = new TreeTableCellRenderer(treeTableModel);
        super.setModel(new TreeTableModelAdapter(treeTableModel, tree));
        tree.setSelectionModel(new DefaultTreeSelectionModel() {
            { setSelectionModel(listSelectionModel); }
        });
        tree.setRowHeight(getRowHeight());
        setDefaultRenderer(TreeTableModel.class, tree);
        setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());
        setShowGrid(false);
        setIntercellSpacing(new Dimension(0, 0));
        
        DefaultTableCellRenderer headerRenderer = new DefaultTableCellRenderer();
        headerRenderer.setHorizontalAlignment(JLabel.LEFT);
        getTableHeader().setDefaultRenderer(headerRenderer);
    }

    public int getEditingRow() {
        return (getColumnClass(editingColumn) == TreeTableModel.class) ? -1 : editingRow;
    }

    public class TreeTableCellRenderer extends JTree implements TableCellRenderer {
        protected int visibleRow;

        public TreeTableCellRenderer(TreeModel model) {
            super(model);
        }

        public void setBounds(int x, int y, int w, int h) {
            super.setBounds(x, 0, w, JTreeTable.this.getHeight());
        }

        public void paint(Graphics g) {
            g.translate(0, -visibleRow * getRowHeight());
            super.paint(g);
        }

        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            if(isSelected) setBackground(table.getSelectionBackground());
            else setBackground(table.getBackground());
            visibleRow = row;
            return this;
        }
    }

    public class TreeTableCellEditor extends AbstractCellEditor implements TableCellEditor {
        public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int r, int c) {
            return tree;
        }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment