500 lines
17 KiB
Java
500 lines
17 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.
|
|
*/
|
|
//The script is called from several scripts to use selected instructions with or without operands and constants (depending on the script
|
|
//that called it) and build a combined mask/value buffer.
|
|
//Memory is then searched looking for this combined value buffer that represents the selected instructions.
|
|
//This automates the process of searching through memory for a particular ordering of instructions by hand.
|
|
//@category Search.InstructionPattern
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.LinkedHashMap;
|
|
|
|
import ghidra.app.plugin.processors.sleigh.SleighDebugLogger;
|
|
import ghidra.app.plugin.processors.sleigh.SleighDebugLogger.SleighDebugMode;
|
|
import ghidra.app.script.GhidraScript;
|
|
import ghidra.app.script.ImproperUseException;
|
|
import ghidra.program.model.address.Address;
|
|
import ghidra.program.model.address.AddressRange;
|
|
import ghidra.program.model.listing.*;
|
|
import ghidra.program.model.mem.Memory;
|
|
|
|
public class SearchBaseExtended extends GhidraScript {
|
|
|
|
//holds the mask and value for all the mnemonics, or commands like cmp, jmp, jnz etc
|
|
ArrayList<Case> mnemonics = new ArrayList<>();
|
|
|
|
/*
|
|
* Holds the masks and values for all the operands. The arraylist portion will correspond to the operand number. An example is
|
|
* arraylist.get(0) will refer to the first operand. Arraylist.get(1) will return the second operands data. The previous commands will
|
|
* give you a hashmap that maps the mnemonics MVCase object to its operands, if they exist. For example to get the first operand for the
|
|
* second mnemonic that was seen you would call arraylist.get(0).get(mnemonicArrayList.get(1)); That will return the MVCase that refers to
|
|
* operand. I set it up this was to conserve memory and allow for a dynamically growing collection.
|
|
*/
|
|
ArrayList<LinkedHashMap<Case, OperandCase>> ops =
|
|
new ArrayList<>();//holds masks and values for all operands.
|
|
|
|
ArrayList<Case> db = new ArrayList<>();//holds the search results.
|
|
|
|
//These control the detail at which a scan is performed.
|
|
//They determine how specific the instructions must match the currently selected ones
|
|
ArrayList<SLMaskControl> controlList = new ArrayList<>();
|
|
|
|
@Override
|
|
public void run() throws Exception {
|
|
loadSelectedInstructions();
|
|
executeSearch();
|
|
}
|
|
|
|
public void run(boolean mneonics, boolean op1, boolean op2, boolean constants) {
|
|
controlList = new ArrayList<>();
|
|
controlList.add(new SLMaskControl(mneonics, op1, op2, constants));
|
|
loadSelectedInstructions();
|
|
executeSearch();
|
|
}
|
|
|
|
public void run(ArrayList<SLMaskControl> control) {
|
|
controlList = control;
|
|
loadSelectedInstructions();
|
|
executeSearch();
|
|
}
|
|
|
|
public void clearResults() {
|
|
db = new ArrayList<>();
|
|
}
|
|
|
|
public void setState(SLMaskControl newState) {
|
|
controlList = new ArrayList<>();
|
|
controlList.add(newState);
|
|
}
|
|
|
|
public void setState(ArrayList<SLMaskControl> newState) {
|
|
controlList = newState;
|
|
}
|
|
|
|
public void loadSelectedInstructions() {
|
|
if (currentProgram == null || currentSelection == null) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
//Builds object that is used to extract all the instructions masks and values
|
|
SleighDebugLogger logger = null;
|
|
|
|
//Grabs the AddressRange for the first continuous selection of instructions
|
|
AddressRange addrRange = currentSelection.getFirstRange();
|
|
if (addrRange == null) {
|
|
return;
|
|
}//makes sure that something was returned
|
|
|
|
//Creates a list that is used to determine the location of the instructions starting byte
|
|
Listing list = currentProgram.getListing();
|
|
|
|
Address tempAddr = addrRange.getMinAddress();//sets the beginning scan address
|
|
|
|
/*
|
|
* The purpose of this while loop is to iterate through the memory that is currently selected by user.
|
|
* All the instructions within this selection range are extracted and corresponding masks made from them.
|
|
* These masks will then be used to search through memory to find instructions of the same type.
|
|
*/
|
|
while (tempAddr.compareTo(addrRange.getMaxAddress()) <= 0) {
|
|
|
|
//Determines if current location is start of instruction, tempIns will be null if not beginning of instruction
|
|
Instruction tempIns = list.getInstructionAt(tempAddr);
|
|
|
|
if (tempIns != null) {//means that current address is start of an instruction
|
|
|
|
logger =
|
|
new SleighDebugLogger(currentProgram, tempAddr, SleighDebugMode.VERBOSE);
|
|
if (logger.parseFailed()) {
|
|
break;
|
|
}
|
|
|
|
//This takes care of the headers
|
|
byte[] mask = logger.getInstructionMask();
|
|
byte[] value = logger.getMaskedBytes(mask);
|
|
if (mask == null || value == null) {
|
|
break;
|
|
}//need to move on to next byte, not sure if this is right command to use
|
|
|
|
//Builds a structure representing the recently found instruction mask and value
|
|
Case tCase = new Case();
|
|
tCase.mask = mask;
|
|
tCase.value = value;
|
|
tCase.textRep = tempIns.getMnemonicString();
|
|
mnemonics.add(tCase); //adds the mnemonic mask and value to the arraylist
|
|
|
|
//Gets a code unit which can be used to determine if the operands are constants.
|
|
CodeUnit cu = list.getCodeUnitAt(tempAddr);
|
|
|
|
//Iterates through all the operands for the currently selected instruction and stores them accordingly
|
|
for (int x = 1; x <= logger.getNumOperands(); x++) {
|
|
mask = logger.getOperandValueMask(x - 1);
|
|
value = logger.getMaskedBytes(mask);
|
|
|
|
if (mask == null || value == null) {
|
|
break;
|
|
}//move on to next instruction
|
|
|
|
//Builds case to store the operands mask and value
|
|
OperandCase otCase = new OperandCase();
|
|
otCase.mask = mask;
|
|
otCase.value = value;
|
|
//Object hey = tempIns.getDefaultOperandRepresentationList(x-1);
|
|
otCase.textRep = tempIns.getDefaultOperandRepresentation(x - 1);
|
|
|
|
//Determines if the given operand is a constant value. If it is a constant then proper flag is set.
|
|
if (cu.getScalar(x - 1) != null) {
|
|
otCase.constant = true;
|
|
}
|
|
|
|
//Determines if current structure is large enough to hold new operand, if it isn't increases structure size
|
|
if (ops.size() < x && ops.size() > -1) {
|
|
ops.add(new LinkedHashMap<Case, OperandCase>());
|
|
}
|
|
|
|
//Adds the operand to the data-structure with a mapping to the instruction mnemonic extracted earlier in line 87
|
|
ops.get(x - 1).put(tCase, otCase);
|
|
}
|
|
//Increments the address pointer to point to the beginning of next instruction.
|
|
tempAddr = tempAddr.add(tempIns.getLength());
|
|
}
|
|
else {
|
|
|
|
//Increments the address pointer by 1
|
|
//This would be hit if the tempAddr didn't get offset correctly.
|
|
tempAddr.addWrap(1);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e) {
|
|
println(e.getMessage());
|
|
}
|
|
}
|
|
|
|
//performs the application of filters and search instructions for matches. Doesn't load instructions or fill the controlList object.
|
|
//Must have mnemonics, ops and controlList populated prior to running this method.
|
|
public void executeSearch() {
|
|
|
|
//Applies the filters provided through the controlList object to the instructions provided through the mnemonics and ops structures.
|
|
MaskValueCase finalSearchString = getFinalMaskAndValue(mnemonics, ops, controlList);
|
|
|
|
String valueString = new String();
|
|
String maskString = new String();
|
|
|
|
for (byte element : finalSearchString.value) {
|
|
valueString = valueString.concat(toHexString(element, true, false)) + " ";
|
|
}
|
|
|
|
for (byte element : finalSearchString.mask) {
|
|
maskString = maskString.concat(toHexString(element, true, false)) + " ";
|
|
}
|
|
|
|
printf("Final Search Bytes: ");
|
|
println(valueString);
|
|
printf("Final Search Mask: ");
|
|
println(maskString);
|
|
|
|
//Searches memory for matches to provided instructions with given filters. db structure is populated with the results.
|
|
findLocations(finalSearchString, db);
|
|
|
|
//Displays results in a table.
|
|
Address[] tableArray = new Address[db.size()];
|
|
for (int x = 0; x < db.size(); x++) {
|
|
tableArray[x] = db.get(x).addr;
|
|
}
|
|
|
|
try {
|
|
show(tableArray);
|
|
}
|
|
catch (ImproperUseException iue) {
|
|
// Do nothing; this code should only be run headed, anyway
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Builds the mask and value byte streams for a single instruction that is represented by the mnemonic entered into the first parameter.
|
|
*
|
|
* That mnemonic is used as a key to withdrawal the operands from the data-structure and the SLMaskControl contains the filter information to be
|
|
* applied to the instruction.
|
|
*/
|
|
private MaskValueCase buildSingleInstructionMask(Case mnemonic,
|
|
ArrayList<LinkedHashMap<Case, OperandCase>> localOperands, SLMaskControl localState) {
|
|
|
|
byte[] tempMask = new byte[mnemonic.mask.length];
|
|
byte[] tempValue = new byte[mnemonic.value.length];
|
|
|
|
//Applies the mnemonic's value and mask if needed
|
|
if (localState.useMnemonic) {
|
|
tempValue = byteArrayOr(tempValue, mnemonic.value);
|
|
tempMask = byteArrayOr(tempMask, mnemonic.mask);
|
|
}
|
|
//Applies the first operands mask and value to running stream if needed
|
|
if (localState.useOp1 && localOperands.size() >= 1 &&
|
|
localOperands.get(0).get(mnemonic) != null) {
|
|
|
|
OperandCase temp = localOperands.get(0).get(mnemonic);
|
|
|
|
if (localState.useConst || !temp.constant) {
|
|
|
|
if (temp.value != null && temp.mask != null && tempValue != null &&
|
|
tempMask != null) {
|
|
tempValue = byteArrayOr(tempValue, temp.value);
|
|
tempMask = byteArrayOr(tempMask, temp.mask);
|
|
}
|
|
}
|
|
}
|
|
//Applies the second operands mask and value to running stream if needed
|
|
if (localState.useOp2 && localOperands.size() >= 2 &&
|
|
localOperands.get(1).get(mnemonic) != null) {
|
|
|
|
OperandCase temp = localOperands.get(1).get(mnemonic);
|
|
|
|
if (localState.useConst || !temp.constant) {
|
|
|
|
if (temp.value != null && temp.mask != null && tempValue != null &&
|
|
tempMask != null) {
|
|
tempValue = byteArrayOr(tempValue, temp.value);
|
|
tempMask = byteArrayOr(tempMask, temp.mask);
|
|
}
|
|
}
|
|
}
|
|
|
|
MaskValueCase tempCase = new MaskValueCase();
|
|
tempCase.mask = tempMask;
|
|
tempCase.value = tempValue;
|
|
|
|
return tempCase;
|
|
}
|
|
|
|
/*
|
|
* Combines all the masks in the data-structure together into a single byte stream. This stream is used to search memory.
|
|
*/
|
|
private MaskValueCase combineInstructionMasks(ArrayList<byte[]> masks, ArrayList<byte[]> values,
|
|
int length) {
|
|
|
|
if (masks.size() != values.size()) {
|
|
throw new IllegalArgumentException();
|
|
}
|
|
|
|
int index = 0;
|
|
|
|
/*
|
|
* This portion of code takes the masks and values from each of the different commands and concatenates them together to form
|
|
* a single mask and value byte array. This byte array is then used to scan memory to look for other sequences of commands.
|
|
*/
|
|
byte[] finalValueArray = new byte[length];
|
|
byte[] finalMaskArray = new byte[length];
|
|
for (int x = 0; x < values.size(); x++) { //iterates through each mnemonic
|
|
|
|
for (int i = 0; i < values.get(x).length && index < length; i++) { //iterate through the mnemonics bytes
|
|
finalValueArray[index] = values.get(x)[i];
|
|
finalMaskArray[index] = masks.get(x)[i];
|
|
index++;
|
|
}
|
|
}
|
|
|
|
MaskValueCase tempFinalParams = new MaskValueCase();
|
|
tempFinalParams.mask = finalMaskArray;
|
|
tempFinalParams.value = finalValueArray;
|
|
|
|
return tempFinalParams;
|
|
}
|
|
|
|
/*
|
|
* Calculates the final mask and value byte streams. These streams are then used to search through memory to look for similar
|
|
* assembly instructions.
|
|
*/
|
|
private MaskValueCase getFinalMaskAndValue(ArrayList<Case> privateMnemonics,
|
|
ArrayList<LinkedHashMap<Case, OperandCase>> localOperands,
|
|
ArrayList<SLMaskControl> control) {
|
|
|
|
ArrayList<byte[]> masks = new ArrayList<>();
|
|
ArrayList<byte[]> values = new ArrayList<>();
|
|
|
|
//used for storing the byte stream currently being work on prior to being added to final data structure
|
|
int totalLength = 0;
|
|
|
|
boolean continueFilter = true;//controls whether the last mask given will be applied on the rest of the mnemonics if the controlList structure is smaller than mnemonics
|
|
|
|
if (privateMnemonics == null || localOperands == null || control == null) {
|
|
throw new IllegalArgumentException("Null Structure");
|
|
}
|
|
|
|
//TODO Need to do some thorough checking on values entered into the control and mnemonics
|
|
//TODO Need to make sure that all possible cases of unequal amounts are considered.
|
|
|
|
/*
|
|
* This loop will scan through the mnemonics and apply the provided MaskControls.
|
|
*
|
|
* The masks will be applied from the arraylist until there aren't any remaining in the list. At this point
|
|
* a mask of all falses will be applied. This will in effect ignore rest of the mnemonics
|
|
*
|
|
* TODO Apply a switch that will allow the last mask in the array to be applied to rest of the mnemonics within selection.
|
|
*/
|
|
for (int x = 0; x < privateMnemonics.size(); x++) {
|
|
|
|
MaskValueCase result = new MaskValueCase();
|
|
|
|
if (x < control.size()) {
|
|
//need to apply the filter from control
|
|
result = buildSingleInstructionMask(privateMnemonics.get(x), localOperands,
|
|
control.get(x));
|
|
}
|
|
else if (continueFilter) {
|
|
//apply the last available mask in the control structure
|
|
result = buildSingleInstructionMask(privateMnemonics.get(x), localOperands,
|
|
control.get(control.size() - 1));
|
|
}
|
|
else {
|
|
//apply an empty filter basically ignoring
|
|
result = buildSingleInstructionMask(privateMnemonics.get(x), localOperands,
|
|
new SLMaskControl(false, false, false, false));
|
|
}
|
|
|
|
if (result.value.length == result.mask.length) {
|
|
masks.add(result.mask);
|
|
values.add(result.value);
|
|
totalLength += result.value.length;
|
|
}
|
|
}
|
|
|
|
return combineInstructionMasks(masks, values, totalLength);
|
|
}
|
|
|
|
/*
|
|
* Populates the database with the locations where the specified byte arrays are found.
|
|
* The first parameter should only have two elements. Element 0 should contain mask and element 1
|
|
* contain the value.
|
|
*
|
|
* The second parameter gets populated with the results of the search.
|
|
*/
|
|
private void findLocations(MaskValueCase searchArrays, ArrayList<Case> localDatabase) {
|
|
|
|
if (currentProgram == null || localDatabase == null || searchArrays == null) {
|
|
throw new IllegalArgumentException("Null Data-Structure");
|
|
}
|
|
if (searchArrays.mask.length != searchArrays.value.length) {
|
|
throw new IllegalArgumentException("Mask and value lengths are different.");
|
|
}
|
|
|
|
if (containsOnBit(searchArrays.mask)) {
|
|
Memory mem = currentProgram.getMemory();
|
|
|
|
//Gets the start and end address to search through
|
|
Address endAddress = currentProgram.getMaxAddress();
|
|
|
|
Address currentPosition = currentProgram.getMinAddress();
|
|
while (currentPosition.compareTo(endAddress) < 0) {
|
|
|
|
//Searches memory for the given mask and value.
|
|
currentPosition = mem.findBytes(currentPosition, endAddress, searchArrays.value,
|
|
searchArrays.mask, true, monitor);
|
|
|
|
//Determines if a new location was found.
|
|
if (currentPosition == null) {
|
|
break;
|
|
}
|
|
|
|
Case temp = new Case();
|
|
temp.mask = searchArrays.mask;
|
|
temp.value = searchArrays.value;
|
|
temp.addr = currentPosition;
|
|
localDatabase.add(temp);
|
|
|
|
currentPosition = currentPosition.add(1);
|
|
}
|
|
}
|
|
else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Used for determining if there is a "On" bit in a byte stream.
|
|
*
|
|
* This is necessary because if you do a memory search for a mask with no "on" bit then it will return every memory address.
|
|
*/
|
|
private boolean containsOnBit(byte[] array) {
|
|
|
|
for (byte element : array) {
|
|
Byte temp = new Byte(element);
|
|
int value = temp.intValue();
|
|
if (value != 0) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Takes two arrays of bytes and performs a bitwise or operation and returns the result. Returns null if the arguments aren't of same length.
|
|
*/
|
|
private byte[] byteArrayOr(byte[] arr1, byte[] arr2) {
|
|
byte[] result = new byte[arr1.length];
|
|
|
|
if (arr1.length != arr2.length) {
|
|
return null;
|
|
}
|
|
|
|
for (int x = 0; x < arr1.length; x++) {
|
|
result[x] = (byte) (arr1[x] | arr2[x]);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Used for storing data about an instructions mnemonic
|
|
*/
|
|
public class Case {
|
|
public Address addr;
|
|
public byte[] mask;
|
|
public byte[] value;
|
|
public String textRep;
|
|
}
|
|
|
|
public class OperandCase extends Case {
|
|
public boolean constant = false;
|
|
}
|
|
|
|
private class MaskValueCase {
|
|
public byte[] mask;
|
|
public byte[] value;
|
|
}
|
|
|
|
/*
|
|
* Represents a filter for a single instruction.
|
|
* Controls which portions of the instruction will be used when performing the search through memory.
|
|
*/
|
|
public class SLMaskControl {
|
|
boolean useMnemonic = true;
|
|
boolean useOp1 = false;
|
|
boolean useOp2 = false;
|
|
boolean useConst = false;
|
|
|
|
public SLMaskControl() {
|
|
}
|
|
|
|
public SLMaskControl(boolean mnemonic, boolean useop1, boolean useop2, boolean constant) {
|
|
useMnemonic = mnemonic;
|
|
useOp1 = useop1;
|
|
useOp2 = useop2;
|
|
useConst = constant;
|
|
}
|
|
}
|
|
}
|