mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 17:59:46 +02:00
Candidate release of source code.
This commit is contained in:
parent
db81e6b3b0
commit
79d8f164f8
12449 changed files with 2800756 additions and 16 deletions
321
Ghidra/Features/Base/ghidra_scripts/YaraGhidraGUIScript.java
Normal file
321
Ghidra/Features/Base/ghidra_scripts/YaraGhidraGUIScript.java
Normal file
|
@ -0,0 +1,321 @@
|
|||
/* ###
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue