ghidra/Ghidra/Features/Base/ghidra_scripts/YaraGhidraGUIScript.java

322 lines
10 KiB
Java

/* ###
* 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 <rule name> { 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 <rule name> { 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(" or $STR" + (1));
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("<insert name>"));
}
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();
}
}
}