-
Notifications
You must be signed in to change notification settings - Fork 5.6k
/
YaraGhidraGUIScript.java
320 lines (277 loc) · 10.4 KB
/
YaraGhidraGUIScript.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
/* ###
* 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("\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();
}
}
}