/* ### * IP: GHIDRA * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // //Launches a GUI allowing users to generate YARA search strings based on a set of selected instructions. // //@category Search.YARA import java.awt.BorderLayout; import java.util.Observable; import java.util.Observer; import javax.swing.*; import ghidra.app.plugin.core.instructionsearch.InstructionSearchPlugin; import ghidra.app.plugin.core.instructionsearch.model.InstructionSearchData; import ghidra.app.plugin.core.instructionsearch.model.InstructionSearchData.UpdateType; import ghidra.app.plugin.core.instructionsearch.ui.InstructionSearchDialog; import ghidra.app.plugin.core.instructionsearch.ui.InstructionTablePanel; import ghidra.app.plugin.core.instructionsearch.util.InstructionSearchUtils; import ghidra.app.script.GhidraScript; import ghidra.util.Msg; /** * This script launches the {@link InstructionSearchDialog} and populates it * with the currently- selected bytes in the listing. The YARA search string * matching the instructions selected is displayed in a text window and may be * modified in two ways: * * 1. By directly editing the text 2. By toggling the cells in the instruction * table, which masks the appropriate mnemonic/instruction * * The generated string will appear similar to the following: * * rule { strings: $STR1 = { 4? 56 4? 55 4? 54 ?? 50 4? 8b ?? c7 4d * 00 00 4?8d 1d 08 4e 00 00 } * * condition: $STR1 } * * Note that this script uses only a portion of the existing search dialog * mentioned above; that entire dialog is far too bulky for what is needed here, * so we just launch the part that allows users to toggle the mnemonics and * operands on/off (masking). * * In addition to this existing dialog, some custom components are added to it * in order to display the resulting Yara string. {@link #YaraDialog}. * */ public class YaraGhidraGUIScript extends GhidraScript { /** * The plugin provides all access to the {@link InstructionSearchDialog} and * its components. */ private InstructionSearchPlugin plugin; private InstructionSearchDialog dialog; /********************************************************************************************* * PROTECTED METHODS ********************************************************************************************/ @Override protected void run() throws Exception { // First we have to find the plugin - if this hasn't been installed it won't be available, // hence the null check. plugin = InstructionSearchUtils.getInstructionSearchPlugin(state.getTool()); // Now check some error conditions and notify the user if there are issues. if (plugin == null) { popup("Instruction Pattern Search plugin not installed! Please install and " + "re-run script."); return; } if (currentProgram == null) { popup("Please open a program before running this script."); return; } if (currentSelection == null) { popup( "Please make a valid selection in the program and select 'reload'. Or select the " + "'manual entry' option from the toolbar."); } // Next, create and open a new Yara dialog. dialog = new YaraDialog(); state.getTool().showDialog(dialog); // Finally, load whatever instructions are selected in the listing. dialog.loadInstructions(plugin); } /********************************************************************************************* * PRIVATE METHODS ********************************************************************************************/ /** * Creates a correctly-formatted Yara string with masking where appropriate. * Yara strings have the following general format: * * rule { strings: $STR1 = { 4? 56 4? 55 4? 54 ?? 50 4? 8b ?? c7 * 4d 00 00 4?8d 1d 08 4e 00 00 } * * condition: $STR1 } * * @param ruleName * @return */ private String generateYaraString(String ruleName) { StringBuilder yaraString = new StringBuilder("\n\nrule " + ruleName + "\n"); yaraString.append("{\n\tstrings:\n"); String fullStr = ""; // Get the "combined string" from the search data object; this is the ENTIRE set of // instructions in one string, with all masking applied. if (dialog == null || dialog.getSearchData() == null) { return null; } String instrStr = dialog.getSearchData().getCombinedString(); // Loop over the combined string, converting each nibble to hex. If we don't have enough // bytes to create a nibble, or if part of the nibble is masked, fill with a '?' char. This // is a Yara constraint - it can only handle displaying data down to the nibble level. // // ie: 10100110 -> A6 // 101001.. -> A? // ..11.001 -> ?? // for (int i = 0; i < instrStr.length(); i += 8) { String curByte = instrStr.length() >= 8 ? instrStr.substring(i, i + 8) : instrStr.substring(i); String nibble1 = curByte.length() >= 4 ? curByte.substring(0, 4) : curByte.substring(0); String nibble2 = curByte.length() >= 8 ? curByte.substring(4, 8) : curByte.length() >= 4 ? curByte.substring(4) : ""; if (nibble1.contains(".")) { fullStr += "?"; } else { fullStr += InstructionSearchUtils.toHex(nibble1, false).trim(); } if (nibble2.contains(".")) { fullStr += "?"; } else { fullStr += InstructionSearchUtils.toHex(nibble2, false).trim(); } fullStr += " "; } // Add the formatted string to our final output, and add some boilerplate Yara // stuff. yaraString.append("\t\t$STR" + 1 + " = { " + fullStr + " }\n"); yaraString.append("\n\tcondition:\n"); yaraString.append("\t\t$STR1"); yaraString.append("\n}\n"); return yaraString.toString(); } /********************************************************************************************* * PRIVATE CLASSES ********************************************************************************************/ /** * This dialog is a hybrid, containing parts of the * {@link InstructionTablePanel}, which allows users to mask * mnemonics/operands in an instruction set, and some custom pieces for * displaying the Yara string. * * The layout: * * --------------------------- | | | Instruction Table Panel | | | * |-------------------------| | | | YARA Text | | | * --------------------------- */ private class YaraDialog extends InstructionSearchDialog { // The area where the yara search string is displayed. private JTextArea yaraTA; JScrollPane scrollPane; // Use a splitter to separate the masking panel from the yara text area. private JSplitPane verticalSplitter; // Keep track of the splitter location so it can be restored when the dialog is // refreshed. private int splitterSave = 200; /** * Constructor. */ private YaraDialog() { super(plugin, "Yara Search String Generator", null); revalidate(); setPreferredSize(500, 400); } /** * The dialog we're using here is a modified form of the * {@link InstructionSearchDialog}; this one contains only the * {@link InstructionTablePanel} portion, which provides the operand * masking capability. * * To accomplish this we override the method that constructs the UI * components and add just the components we need. * * @return */ @Override protected JPanel createWorkPanel() { // Create the main text area and give it a scroll bar. yaraTA = new JTextArea(12, 0); scrollPane = new JScrollPane(yaraTA); yaraTA.setWrapStyleWord(true); yaraTA.setLineWrap(true); // Create the instruction table and set it as a listener of the table model, so // this gui will be notified when changes have been made (when the user has adjusted // the mask settings). This allows us to dynamically update the yara string as // the user is changing things. InstructionTablePanel instructionTablePanel = new InstructionTablePanel(searchData.getMaxNumOperands(), plugin, this); instructionTablePanel.getTable().getModel().addTableModelListener(e -> { generateYara(); }); // Finally, set up the main panel and create a split pane so the user can adjust // the dimensions of the masking table and the yara text display. JPanel mainPanel = new JPanel(); mainPanel.setLayout(new BorderLayout()); verticalSplitter = new JSplitPane(JSplitPane.VERTICAL_SPLIT, instructionTablePanel.getWorkPanel(), scrollPane); mainPanel.add(verticalSplitter); // Tell the data model to listen for changes the table; when the GUI is updated, the // model needs to be told to update itself to reflect the new mask settings. searchData.registerForGuiUpdates(instructionTablePanel.getTable()); // Now restore the splitter location to whatever it was before. verticalSplitter.setDividerLocation(splitterSave); return mainPanel; } /** * Creates a properly-formatted yara string and displays it in the text * area. */ private void generateYara() { try { yaraTA.setText(generateYaraString("")); } catch (Exception e1) { Msg.error(this, "Error generating yara string: " + e1); } } /** * Part of the {@link Observer} structure. This is invoked whenever the * {@link InstructionSearchData} class is updated, indicating that the * user has selected a new set of instructions, or changed mask * settings. When this happens we need to save off the splitter location * and reload the dialog if necessary. * * @param o * @param arg */ @Override public void update(Observable o, Object arg) { // Before rebuilding the UI, remember the splitter location so we can reset it // afterwards. if (verticalSplitter != null) { splitterSave = verticalSplitter.getDividerLocation(); } if (arg instanceof UpdateType) { UpdateType type = (UpdateType) arg; switch (type) { case RELOAD: revalidate(); break; case UPDATE: // do nothing } } } /** * Updates the GUI when the user has made a new selection. */ @Override protected void revalidate() { removeWorkPanel(); addWorkPanel(createWorkPanel()); generateYara(); } } }