mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-09-20 09:31:47 +00:00
Merge remote-tracking branch 'origin/GP-4911_ghidragon_remove_old_memory_search_code--SQUASHED'
This commit is contained in:
commit
3af17efcf6
|
@ -1,169 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.app.plugin.core.searchmem;
|
|
||||||
|
|
||||||
import java.awt.FlowLayout;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import javax.swing.border.TitledBorder;
|
|
||||||
import javax.swing.event.ChangeEvent;
|
|
||||||
import javax.swing.event.ChangeListener;
|
|
||||||
|
|
||||||
import docking.widgets.checkbox.GCheckBox;
|
|
||||||
import docking.widgets.combobox.GComboBox;
|
|
||||||
import docking.widgets.label.GDLabel;
|
|
||||||
import ghidra.util.StringUtilities;
|
|
||||||
|
|
||||||
public class AsciiSearchFormat extends SearchFormat {
|
|
||||||
private JLabel searchType;
|
|
||||||
private JComboBox<Charset> encodingCB;
|
|
||||||
private JCheckBox caseSensitiveCkB;
|
|
||||||
private JCheckBox escapeSequencesCkB;
|
|
||||||
private Charset[] supportedCharsets =
|
|
||||||
{ StandardCharsets.US_ASCII, StandardCharsets.UTF_8, StandardCharsets.UTF_16 };
|
|
||||||
|
|
||||||
public AsciiSearchFormat(ChangeListener listener) {
|
|
||||||
super("String", listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getToolTip() {
|
|
||||||
return "Interpret value as a sequence of characters.";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public JPanel getOptionsPanel() {
|
|
||||||
ActionListener al = new ActionListener() {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
changeListener.stateChanged(new ChangeEvent(this));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
searchType = new GDLabel("Encoding: ");
|
|
||||||
|
|
||||||
encodingCB = new GComboBox<>(supportedCharsets);
|
|
||||||
encodingCB.setName("Encoding Options");
|
|
||||||
encodingCB.setSelectedIndex(0);
|
|
||||||
encodingCB.addActionListener(al);
|
|
||||||
|
|
||||||
caseSensitiveCkB = new GCheckBox("Case Sensitive");
|
|
||||||
caseSensitiveCkB.setToolTipText("Allows for case sensitive searching.");
|
|
||||||
caseSensitiveCkB.addActionListener(al);
|
|
||||||
|
|
||||||
escapeSequencesCkB = new GCheckBox("Escape Sequences");
|
|
||||||
escapeSequencesCkB.setToolTipText(
|
|
||||||
"Allows specifying control characters using escape sequences " +
|
|
||||||
"(i.e., allows \\n to be searched for as a single line feed character).");
|
|
||||||
escapeSequencesCkB.addActionListener(al);
|
|
||||||
|
|
||||||
JPanel stringOptionsPanel = new JPanel();
|
|
||||||
stringOptionsPanel.setLayout(new BoxLayout(stringOptionsPanel, BoxLayout.Y_AXIS));
|
|
||||||
stringOptionsPanel.setBorder(new TitledBorder("Format Options"));
|
|
||||||
JPanel encodingOptionsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
|
||||||
encodingOptionsPanel.add(searchType);
|
|
||||||
encodingOptionsPanel.add(encodingCB);
|
|
||||||
stringOptionsPanel.add(encodingOptionsPanel);
|
|
||||||
JPanel caseSensitivePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
|
||||||
caseSensitivePanel.add(caseSensitiveCkB);
|
|
||||||
stringOptionsPanel.add(caseSensitivePanel);
|
|
||||||
JPanel escapeSequencesPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
|
||||||
escapeSequencesPanel.add(escapeSequencesCkB);
|
|
||||||
stringOptionsPanel.add(escapeSequencesPanel);
|
|
||||||
return stringOptionsPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean usesEndieness() {
|
|
||||||
return encodingCB.getSelectedItem() == StandardCharsets.UTF_16; // Only UTF-16 uses Endianness.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SearchData getSearchData(String input) {
|
|
||||||
final byte MASK_BYTE = (byte) 0xdf;
|
|
||||||
|
|
||||||
int inputLength = input.length();
|
|
||||||
Charset encodingSelection = (Charset) encodingCB.getSelectedItem();
|
|
||||||
if (encodingSelection == StandardCharsets.UTF_16) {
|
|
||||||
encodingSelection =
|
|
||||||
(isBigEndian) ? StandardCharsets.UTF_16BE : StandardCharsets.UTF_16LE;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Escape sequences in the "input" are 2 Characters long.
|
|
||||||
if (escapeSequencesCkB.isSelected() && inputLength >= 2) {
|
|
||||||
input = StringUtilities.convertEscapeSequences(input);
|
|
||||||
}
|
|
||||||
byte[] byteArray = input.getBytes(encodingSelection);
|
|
||||||
byte[] maskArray = new byte[byteArray.length];
|
|
||||||
Arrays.fill(maskArray, (byte) 0xff);
|
|
||||||
|
|
||||||
// Time to mask some bytes for case insensitive searching.
|
|
||||||
if (!caseSensitiveCkB.isSelected()) {
|
|
||||||
int i = 0;
|
|
||||||
while (i < byteArray.length) {
|
|
||||||
if (encodingSelection == StandardCharsets.US_ASCII &&
|
|
||||||
Character.isLetter(byteArray[i])) {
|
|
||||||
maskArray[i] = MASK_BYTE;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
else if (encodingSelection == StandardCharsets.UTF_8) {
|
|
||||||
int numBytes = bytesPerCharUTF8(byteArray[i]);
|
|
||||||
if (numBytes == 1 && Character.isLetter(byteArray[i])) {
|
|
||||||
maskArray[i] = MASK_BYTE;
|
|
||||||
}
|
|
||||||
i += numBytes;
|
|
||||||
}
|
|
||||||
// Assumes UTF-16 will return 2 Bytes for each character.
|
|
||||||
// 4-byte UTF-16 will never satisfy the below checks because
|
|
||||||
// none of their bytes can ever be 0.
|
|
||||||
else if (encodingSelection == StandardCharsets.UTF_16BE) {
|
|
||||||
if (byteArray[i] == (byte) 0x0 && Character.isLetter(byteArray[i + 1])) { // Checks if ascii character.
|
|
||||||
maskArray[i + 1] = MASK_BYTE;
|
|
||||||
}
|
|
||||||
i += 2;
|
|
||||||
}
|
|
||||||
else if (encodingSelection == StandardCharsets.UTF_16LE) {
|
|
||||||
if (byteArray[i + 1] == (byte) 0x0 && Character.isLetter(byteArray[i])) { // Checks if ascii character.
|
|
||||||
maskArray[i] = MASK_BYTE;
|
|
||||||
}
|
|
||||||
i += 2;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return SearchData.createSearchData(input, byteArray, maskArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int bytesPerCharUTF8(byte zByte) {
|
|
||||||
// This method is intended for UTF-8 encoding.
|
|
||||||
// The first byte in a sequence of UTF-8 bytes can tell
|
|
||||||
// us how many bytes make up a char.
|
|
||||||
int offset = 1;
|
|
||||||
// If the char is ascii, this loop will be skipped.
|
|
||||||
while ((zByte & 0x80) != 0x00) {
|
|
||||||
zByte <<= 1;
|
|
||||||
offset++;
|
|
||||||
}
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,116 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.app.plugin.core.searchmem;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import javax.swing.event.ChangeListener;
|
|
||||||
|
|
||||||
import ghidra.util.HTMLUtilities;
|
|
||||||
|
|
||||||
public class BinarySearchFormat extends SearchFormat {
|
|
||||||
private static final String VALID_CHARS = "01x?.";
|
|
||||||
private String statusText;
|
|
||||||
|
|
||||||
public BinarySearchFormat(ChangeListener listener) {
|
|
||||||
super("Binary", listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getToolTip() {
|
|
||||||
return HTMLUtilities.toHTML(
|
|
||||||
"Interpret value as a sequence of binary digits.\n"+
|
|
||||||
"Spaces will start the next byte. Bit sequences less\n"+
|
|
||||||
"than 8 bits are padded with 0's to the left. \n"+
|
|
||||||
"Enter 'x', '.' or '?' for a wildcard bit");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SearchData getSearchData(String input) {
|
|
||||||
StringTokenizer st = new StringTokenizer(input);
|
|
||||||
int n = st.countTokens();
|
|
||||||
byte[] bytes = new byte[n];
|
|
||||||
byte[] mask = new byte[n];
|
|
||||||
|
|
||||||
int index = 0;
|
|
||||||
while (st.hasMoreTokens()) {
|
|
||||||
String token = st.nextToken();
|
|
||||||
|
|
||||||
if (!isValidBinary(token)) {
|
|
||||||
return SearchData.createInvalidInputSearchData(statusText);
|
|
||||||
}
|
|
||||||
bytes[index] = getByte(token);
|
|
||||||
mask[index] = getMask(token);
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
return SearchData.createSearchData(input, bytes, mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isValidBinary(String str) {
|
|
||||||
if (str.length() > 8) {
|
|
||||||
statusText = "Max group size exceeded. Enter <space> to add more.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
statusText = "";
|
|
||||||
for(int i=0;i<str.length();i++) {
|
|
||||||
if (VALID_CHARS.indexOf(str.charAt(i)) < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte getByte(String token) {
|
|
||||||
byte b = 0;
|
|
||||||
for(int i=0;i<token.length();i++) {
|
|
||||||
b <<= 1;
|
|
||||||
char c = token.charAt(i);
|
|
||||||
if (c == '1') {
|
|
||||||
b |= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a mask byte that has a bit set to 1 for each bit that is not a wildcard. Any bits
|
|
||||||
* that aren't specified (i.e. token.lenght < 8) are treated as valid test bits.
|
|
||||||
* @param token the string of bits to determine a mask for.
|
|
||||||
*/
|
|
||||||
private byte getMask(String token) {
|
|
||||||
byte b = 0;
|
|
||||||
for(int i=0;i<8;i++) {
|
|
||||||
b <<= 1;
|
|
||||||
if (i < token.length()) {
|
|
||||||
char c = token.charAt(i);
|
|
||||||
if (c == '1' || c == '0') {
|
|
||||||
b |= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
b |= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public boolean usesEndieness() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,235 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.app.plugin.core.searchmem;
|
|
||||||
|
|
||||||
import java.awt.GridLayout;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import javax.swing.event.ChangeEvent;
|
|
||||||
import javax.swing.event.ChangeListener;
|
|
||||||
|
|
||||||
import docking.widgets.button.GRadioButton;
|
|
||||||
import ghidra.util.HTMLUtilities;
|
|
||||||
import ghidra.util.exception.AssertException;
|
|
||||||
|
|
||||||
public class DecimalSearchFormat extends SearchFormat {
|
|
||||||
|
|
||||||
private static final String MINUS_SIGN = "-";
|
|
||||||
private static final int BYTE = 0;
|
|
||||||
private static final int WORD = 1;
|
|
||||||
private static final int DWORD = 2;
|
|
||||||
private static final int QWORD = 3;
|
|
||||||
private static final int FLOAT = 4;
|
|
||||||
private static final int DOUBLE = 5;
|
|
||||||
|
|
||||||
private int decimalFormat = WORD;
|
|
||||||
|
|
||||||
public DecimalSearchFormat(ChangeListener listener) {
|
|
||||||
super("Decimal", listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getToolTip() {
|
|
||||||
return HTMLUtilities.toHTML(
|
|
||||||
"Interpret values as a sequence of\n" + "decimal numbers, separated by spaces");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setDecimalFormat(int format) {
|
|
||||||
decimalFormat = format;
|
|
||||||
changeListener.stateChanged(new ChangeEvent(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public JPanel getOptionsPanel() {
|
|
||||||
ButtonGroup decimalGroup = new ButtonGroup();
|
|
||||||
|
|
||||||
GRadioButton decimalByte = new GRadioButton("Byte", false);
|
|
||||||
GRadioButton decimalWord = new GRadioButton("Word", true);
|
|
||||||
GRadioButton decimalDWord = new GRadioButton("DWord", false);
|
|
||||||
GRadioButton decimalQWord = new GRadioButton("QWord", false);
|
|
||||||
GRadioButton decimalFloat = new GRadioButton("Float", false);
|
|
||||||
GRadioButton decimalDouble = new GRadioButton("Double", false);
|
|
||||||
|
|
||||||
decimalByte.addActionListener(new ActionListener() {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent ev) {
|
|
||||||
setDecimalFormat(BYTE);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
decimalWord.addActionListener(new ActionListener() {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent ev) {
|
|
||||||
setDecimalFormat(WORD);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
decimalDWord.addActionListener(new ActionListener() {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent ev) {
|
|
||||||
setDecimalFormat(DWORD);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
decimalQWord.addActionListener(new ActionListener() {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent ev) {
|
|
||||||
setDecimalFormat(QWORD);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
decimalFloat.addActionListener(new ActionListener() {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent ev) {
|
|
||||||
setDecimalFormat(FLOAT);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
decimalDouble.addActionListener(new ActionListener() {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent ev) {
|
|
||||||
setDecimalFormat(DOUBLE);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
decimalGroup.add(decimalByte);
|
|
||||||
decimalGroup.add(decimalWord);
|
|
||||||
decimalGroup.add(decimalDWord);
|
|
||||||
decimalGroup.add(decimalQWord);
|
|
||||||
decimalGroup.add(decimalFloat);
|
|
||||||
decimalGroup.add(decimalDouble);
|
|
||||||
|
|
||||||
JPanel decimalOptionsPanel = new JPanel();
|
|
||||||
decimalOptionsPanel.setLayout(new GridLayout(3, 2));
|
|
||||||
decimalOptionsPanel.add(decimalByte);
|
|
||||||
decimalOptionsPanel.add(decimalWord);
|
|
||||||
decimalOptionsPanel.add(decimalDWord);
|
|
||||||
decimalOptionsPanel.add(decimalQWord);
|
|
||||||
decimalOptionsPanel.add(decimalFloat);
|
|
||||||
decimalOptionsPanel.add(decimalDouble);
|
|
||||||
decimalOptionsPanel.setBorder(BorderFactory.createTitledBorder("Format Options"));
|
|
||||||
|
|
||||||
return decimalOptionsPanel;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SearchData getSearchData(String input) {
|
|
||||||
List<Byte> bytesList = new ArrayList<>();
|
|
||||||
StringTokenizer tokenizer = new StringTokenizer(input);
|
|
||||||
while (tokenizer.hasMoreTokens()) {
|
|
||||||
String tok = tokenizer.nextToken();
|
|
||||||
if (tok.equals(MINUS_SIGN)) {
|
|
||||||
if (!input.endsWith(MINUS_SIGN)) {
|
|
||||||
return SearchData.createInvalidInputSearchData("Cannot have space after a '-'");
|
|
||||||
}
|
|
||||||
return SearchData.createIncompleteSearchData("");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
bytesList.addAll(getBytes(tok));
|
|
||||||
}
|
|
||||||
catch (NumberFormatException ex) {
|
|
||||||
return SearchData.createInvalidInputSearchData("");
|
|
||||||
}
|
|
||||||
catch (RuntimeException re) {
|
|
||||||
return SearchData.createInvalidInputSearchData(re.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return SearchData.createSearchData(input, getDataBytes(bytesList), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] getDataBytes(List<Byte> bytesList) {
|
|
||||||
byte[] bytes = new byte[bytesList.size()];
|
|
||||||
for (int i = 0; i < bytes.length; i++) {
|
|
||||||
bytes[i] = (bytesList.get(i)).byteValue();
|
|
||||||
}
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Byte> getBytes(long value, int n) {
|
|
||||||
List<Byte> list = new ArrayList<>();
|
|
||||||
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
byte b = (byte) value;
|
|
||||||
list.add(Byte.valueOf(b));
|
|
||||||
value >>= 8;
|
|
||||||
}
|
|
||||||
if (isBigEndian) {
|
|
||||||
Collections.reverse(list);
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkValue(long value, long min, long max) {
|
|
||||||
if (value < min || value > max) {
|
|
||||||
// I know, icky
|
|
||||||
throw new RuntimeException("Number must be in the range [" + min + "," + max + "]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Byte> getBytes(String tok) {
|
|
||||||
switch (decimalFormat) {
|
|
||||||
case BYTE:
|
|
||||||
long value = Short.parseShort(tok);
|
|
||||||
checkValue(value, Byte.MIN_VALUE, 255);
|
|
||||||
return getBytes(value, 1);
|
|
||||||
case WORD:
|
|
||||||
value = Integer.parseInt(tok);
|
|
||||||
checkValue(value, Short.MIN_VALUE, 65535);
|
|
||||||
return getBytes(value, 2);
|
|
||||||
case DWORD:
|
|
||||||
value = Long.parseLong(tok);
|
|
||||||
checkValue(value, Integer.MIN_VALUE, 4294967295l);
|
|
||||||
return getBytes(value, 4);
|
|
||||||
case QWORD:
|
|
||||||
value = Long.parseLong(tok);
|
|
||||||
return getBytes(value, 8);
|
|
||||||
case FLOAT:
|
|
||||||
tok = preProcessFloat(tok);
|
|
||||||
float floatValue = Float.parseFloat(tok);
|
|
||||||
value = Float.floatToIntBits(floatValue);
|
|
||||||
return getBytes(value, 4);
|
|
||||||
case DOUBLE:
|
|
||||||
tok = preProcessFloat(tok);
|
|
||||||
double dvalue = Double.parseDouble(tok);
|
|
||||||
value = Double.doubleToLongBits(dvalue);
|
|
||||||
return getBytes(value, 8);
|
|
||||||
default:
|
|
||||||
throw new AssertException("Unexpected format type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks for parsable characters that we don't want to allow (dDfF) and removes
|
|
||||||
* the start of an exponent expression (example 2.34e would become 2.34. So woudl 2.34-)
|
|
||||||
* @param the string that will be parsed into a float or double
|
|
||||||
* @return the parsable string
|
|
||||||
* @exception NumberFormatException thrown if the the tok contains any of "dDfF".
|
|
||||||
*/
|
|
||||||
private String preProcessFloat(String tok) {
|
|
||||||
if ((tok.indexOf('d') >= 0) || (tok.indexOf('D') >= 0) || (tok.indexOf('F') >= 0) ||
|
|
||||||
(tok.indexOf('f') >= 0)) {
|
|
||||||
throw new NumberFormatException();
|
|
||||||
}
|
|
||||||
if (tok.endsWith("E") || tok.endsWith("e")) {
|
|
||||||
tok = tok.substring(0, tok.length() - 1);
|
|
||||||
}
|
|
||||||
if (tok.endsWith("E-") || tok.endsWith("e-")) {
|
|
||||||
tok = tok.substring(0, tok.length() - 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tok;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,158 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.app.plugin.core.searchmem;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import javax.swing.event.ChangeListener;
|
|
||||||
|
|
||||||
import ghidra.util.HTMLUtilities;
|
|
||||||
|
|
||||||
public class HexSearchFormat extends SearchFormat {
|
|
||||||
|
|
||||||
private static final String WILD_CARDS = ".?";
|
|
||||||
private static final String HEX_CHARS = "0123456789abcdefABCDEF" + WILD_CARDS;
|
|
||||||
private String statusText;
|
|
||||||
|
|
||||||
public HexSearchFormat(ChangeListener listener) {
|
|
||||||
super("Hex", listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getToolTip() {
|
|
||||||
return HTMLUtilities.toHTML("Interpret value as a sequence of\n" +
|
|
||||||
"hex numbers, separated by spaces.\n" + "Enter '.' or '?' for a wildcard match");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SearchData getSearchData(String input) {
|
|
||||||
List<String> list = new ArrayList<String>();
|
|
||||||
StringTokenizer st = new StringTokenizer(input);
|
|
||||||
while (st.hasMoreTokens()) {
|
|
||||||
String token = st.nextToken();
|
|
||||||
|
|
||||||
if (!isValidHex(token)) {
|
|
||||||
return SearchData.createInvalidInputSearchData(statusText);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> byteList = getByteStrings(token);
|
|
||||||
if (!isBigEndian) {
|
|
||||||
Collections.reverse(byteList);
|
|
||||||
}
|
|
||||||
list.addAll(byteList);
|
|
||||||
}
|
|
||||||
byte[] bytes = new byte[list.size()];
|
|
||||||
byte[] mask = new byte[list.size()];
|
|
||||||
for (int i = 0; i < list.size(); i++) {
|
|
||||||
String byteString = list.get(i);
|
|
||||||
bytes[i] = getByte(byteString);
|
|
||||||
mask[i] = getMask(byteString);
|
|
||||||
}
|
|
||||||
return SearchData.createSearchData(input, bytes, mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> getByteStrings(String token) {
|
|
||||||
|
|
||||||
if (isSingleWildCardChar(token)) {
|
|
||||||
// treat single wildcards as a double wildcard entry, as this is more intuitive to users
|
|
||||||
token += token;
|
|
||||||
}
|
|
||||||
else if (token.length() % 2 != 0) {
|
|
||||||
// pad an odd number of nibbles with 0; assuming users leave off leading 0
|
|
||||||
token = "0" + token;
|
|
||||||
}
|
|
||||||
|
|
||||||
int n = token.length() / 2;
|
|
||||||
List<String> list = new ArrayList<String>(n);
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
list.add(token.substring(i * 2, i * 2 + 2));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isSingleWildCardChar(String token) {
|
|
||||||
if (token.length() == 1) {
|
|
||||||
char c = token.charAt(0);
|
|
||||||
return WILD_CARDS.indexOf(c) >= 0;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isValidHex(String str) {
|
|
||||||
if (str.length() > 16) {
|
|
||||||
statusText = "Max group size exceeded. Enter <space> to add more.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
statusText = "";
|
|
||||||
for (int i = 0; i < str.length(); i++) {
|
|
||||||
if (HEX_CHARS.indexOf(str.charAt(i)) < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the byte value to be used for the given hex bytes. Handles wildcard characters by
|
|
||||||
* return treating them as 0s.
|
|
||||||
*/
|
|
||||||
private byte getByte(String tok) {
|
|
||||||
char c1 = tok.charAt(0);
|
|
||||||
char c2 = tok.charAt(1);
|
|
||||||
// note: the hexValueOf() method will turn wildcard chars into 0s
|
|
||||||
return (byte) (hexValueOf(c1) * 16 + hexValueOf(c2));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the search mask for the given hex byte string. Normal hex digits result
|
|
||||||
* in a "1111" mask and wildcard digits result in a "0000" mask.
|
|
||||||
*/
|
|
||||||
private byte getMask(String tok) {
|
|
||||||
char c1 = tok.charAt(0);
|
|
||||||
char c2 = tok.charAt(1);
|
|
||||||
int index1 = WILD_CARDS.indexOf(c1);
|
|
||||||
int index2 = WILD_CARDS.indexOf(c2);
|
|
||||||
if (index1 >= 0 && index2 >= 0) {
|
|
||||||
return (byte) 0x00;
|
|
||||||
}
|
|
||||||
if (index1 >= 0 && index2 < 0) {
|
|
||||||
return (byte) 0x0F;
|
|
||||||
}
|
|
||||||
if (index1 < 0 && index2 >= 0) {
|
|
||||||
return (byte) 0xF0;
|
|
||||||
}
|
|
||||||
return (byte) 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value of the given hex digit character.
|
|
||||||
*/
|
|
||||||
private int hexValueOf(char c) {
|
|
||||||
if ((c >= '0') && (c <= '9')) {
|
|
||||||
return c - '0';
|
|
||||||
}
|
|
||||||
else if ((c >= 'a') && (c <= 'f')) {
|
|
||||||
return c - 'a' + 10;
|
|
||||||
}
|
|
||||||
else if ((c >= 'A') && (c <= 'F')) {
|
|
||||||
return c - 'A' + 10;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,848 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.app.plugin.core.searchmem;
|
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.awt.event.ItemListener;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import javax.swing.border.TitledBorder;
|
|
||||||
import javax.swing.event.ChangeListener;
|
|
||||||
import javax.swing.text.*;
|
|
||||||
|
|
||||||
import docking.*;
|
|
||||||
import docking.widgets.button.GRadioButton;
|
|
||||||
import docking.widgets.checkbox.GCheckBox;
|
|
||||||
import docking.widgets.combobox.GhidraComboBox;
|
|
||||||
import docking.widgets.label.GDLabel;
|
|
||||||
import docking.widgets.label.GLabel;
|
|
||||||
import ghidra.app.util.HelpTopics;
|
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
|
||||||
import ghidra.util.*;
|
|
||||||
import ghidra.util.exception.InvalidInputException;
|
|
||||||
import ghidra.util.layout.VariableRowHeightGridLayout;
|
|
||||||
import ghidra.util.layout.VerticalLayout;
|
|
||||||
import ghidra.util.search.memory.CodeUnitSearchInfo;
|
|
||||||
import ghidra.util.search.memory.SearchInfo;
|
|
||||||
import ghidra.util.task.Task;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dialog for MemSearch Plugin.
|
|
||||||
* Displays a set of formats for search string.
|
|
||||||
* The user can type in a hex, ascii or decimal string to search
|
|
||||||
* for among the memory bytes. The user can indicate forward or
|
|
||||||
* backward searching, proper endianness and whether to find just
|
|
||||||
* the next occurrence or all of them in the program.
|
|
||||||
*/
|
|
||||||
class MemSearchDialog extends ReusableDialogComponentProvider {
|
|
||||||
|
|
||||||
static final String ADVANCED_BUTTON_NAME = "mem.search.advanced";
|
|
||||||
private static final String CODE_UNIT_SCOPE_NAME = "Code Unit Scope";
|
|
||||||
private static final int DEFAULT_MAX_ENTRIES = 10;
|
|
||||||
public static final String ENTER_TEXT_MESSAGE = "Please enter a search value";
|
|
||||||
private static final SearchData DEFAULT_SEARCH_DATA =
|
|
||||||
SearchData.createInvalidInputSearchData(ENTER_TEXT_MESSAGE);
|
|
||||||
|
|
||||||
// The fields that use this value take in user input. If the user input large, it affects
|
|
||||||
// the minimum and preferred size of the the fields. This, in turn, affects how this dialog
|
|
||||||
// gets packed when the Advanced button is toggled. Without using a size restriction, this
|
|
||||||
// dialog's contents may move as the dialog is re-packed.
|
|
||||||
private static final int INPUT_FIELD_MIN_SIZE_WIDTH = 140;
|
|
||||||
private static final int INPUT_FIELD_MIN_SIZE_HEIGHT = 25;
|
|
||||||
|
|
||||||
MemSearchPlugin plugin;
|
|
||||||
boolean isMnemonic;
|
|
||||||
|
|
||||||
private JButton nextButton;
|
|
||||||
private JButton previousButton;
|
|
||||||
private JButton allButton;
|
|
||||||
private GhidraComboBox<String> valueComboBox;
|
|
||||||
private List<String> history = new LinkedList<>();
|
|
||||||
private JLabel hexSeqField;
|
|
||||||
private CardLayout formatOptionsLayout;
|
|
||||||
private JRadioButton searchSelectionRadioButton;
|
|
||||||
private JLabel alignLabel;
|
|
||||||
private JTextField alignField;
|
|
||||||
private JPanel formatOptionsPanel;
|
|
||||||
private JRadioButton loadedBlocks;
|
|
||||||
private JRadioButton allBlocks;
|
|
||||||
private boolean navigatableHasSelection;
|
|
||||||
|
|
||||||
private ChangeListener changeListener = e -> updateDisplay();
|
|
||||||
|
|
||||||
private SearchData searchData = DEFAULT_SEARCH_DATA;
|
|
||||||
private SearchFormat[] allFormats = new SearchFormat[] { new HexSearchFormat(changeListener),
|
|
||||||
new AsciiSearchFormat(changeListener), new DecimalSearchFormat(changeListener),
|
|
||||||
new BinarySearchFormat(changeListener), new RegExSearchFormat(changeListener) };
|
|
||||||
private SearchFormat currentFormat = allFormats[0];
|
|
||||||
private JRadioButton littleEndian;
|
|
||||||
private JRadioButton bigEndian;
|
|
||||||
|
|
||||||
private Container advancedPanel;
|
|
||||||
private JRadioButton searchAllRadioButton;
|
|
||||||
private List<JCheckBox> codeUnitTypesList;
|
|
||||||
private JPanel mainPanel;
|
|
||||||
private JToggleButton advancedButton;
|
|
||||||
|
|
||||||
private boolean isSearching;
|
|
||||||
private boolean hasValidSearchData;
|
|
||||||
private boolean searchEnabled = true;
|
|
||||||
|
|
||||||
MemSearchDialog(MemSearchPlugin plugin, boolean isBigEndian, boolean isMnemonic) {
|
|
||||||
super("Search Memory", false, true, true, true);
|
|
||||||
this.plugin = plugin;
|
|
||||||
this.isMnemonic = isMnemonic;
|
|
||||||
|
|
||||||
setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Search_Memory"));
|
|
||||||
mainPanel = buildMainPanel();
|
|
||||||
addWorkPanel(mainPanel);
|
|
||||||
buildButtons();
|
|
||||||
setEndianness(isBigEndian);
|
|
||||||
setAlignment(1);
|
|
||||||
setUseSharedLocation(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setBytes(byte[] bytes) {
|
|
||||||
if (valueComboBox != null) {
|
|
||||||
valueComboBox.setText(null);
|
|
||||||
}
|
|
||||||
String convertBytesToString = NumericUtilities.convertBytesToString(bytes, " ");
|
|
||||||
valueComboBox.setText(convertBytesToString);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setAlignment(int alignment) {
|
|
||||||
alignField.setText("" + alignment);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSearchText(String maskedString) {
|
|
||||||
valueComboBox.setText(maskedString);
|
|
||||||
updateDisplay();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setEndianness(boolean isBigEndian) {
|
|
||||||
if (isBigEndian) {
|
|
||||||
bigEndian.setSelected(isBigEndian);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
littleEndian.setSelected(true);
|
|
||||||
}
|
|
||||||
updateDisplay();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void taskCancelled(Task task) {
|
|
||||||
super.taskCancelled(task);
|
|
||||||
isSearching = false;
|
|
||||||
updateSearchButtonEnablement();
|
|
||||||
clearStatusText();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void taskCompleted(Task task) {
|
|
||||||
super.taskCompleted(task);
|
|
||||||
isSearching = false;
|
|
||||||
updateSearchButtonEnablement();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setEndianEnabled(boolean enabled) {
|
|
||||||
littleEndian.setEnabled(enabled);
|
|
||||||
bigEndian.setEnabled(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void executeProgressTask(Task task, int delay) {
|
|
||||||
super.executeProgressTask(task, delay);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSearchButtonEnablement() {
|
|
||||||
nextButton.setEnabled(searchEnabled && !isSearching && hasValidSearchData);
|
|
||||||
previousButton.setEnabled(searchEnabled && !isSearching && hasValidSearchData &&
|
|
||||||
currentFormat.supportsBackwardsSearch());
|
|
||||||
allButton.setEnabled(searchEnabled && !isSearching && hasValidSearchData);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setHasSelection(boolean hasSelection, boolean autoRestrictSelection) {
|
|
||||||
searchSelectionRadioButton.setEnabled(hasSelection);
|
|
||||||
if (navigatableHasSelection == hasSelection) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (autoRestrictSelection) {
|
|
||||||
navigatableHasSelection = hasSelection;
|
|
||||||
if (hasSelection && !isMnemonic) {
|
|
||||||
searchSelectionRadioButton.setSelected(true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
searchAllRadioButton.setSelected(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void dismissCallback() {
|
|
||||||
valueComboBox.setText(null);
|
|
||||||
hexSeqField.setText(null);
|
|
||||||
cancelCurrentTask();
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void show(ComponentProvider provider) {
|
|
||||||
clearStatusText();
|
|
||||||
valueComboBox.requestFocus();
|
|
||||||
valueComboBox.selectAll();
|
|
||||||
PluginTool tool = plugin.getTool();
|
|
||||||
tool.showDialog(MemSearchDialog.this, provider);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addToHistory(String input) {
|
|
||||||
history.remove(input);
|
|
||||||
history.add(0, input);
|
|
||||||
truncateHistoryAsNeeded();
|
|
||||||
updateCombo();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateCombo() {
|
|
||||||
String[] list = new String[history.size()];
|
|
||||||
history.toArray(list);
|
|
||||||
valueComboBox.setModel(new DefaultComboBoxModel<>(list));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void truncateHistoryAsNeeded() {
|
|
||||||
int maxEntries = DEFAULT_MAX_ENTRIES;
|
|
||||||
int historySize = history.size();
|
|
||||||
|
|
||||||
if (historySize > maxEntries) {
|
|
||||||
int numToRemove = historySize - maxEntries;
|
|
||||||
|
|
||||||
for (int i = 0; i < numToRemove; i++) {
|
|
||||||
history.remove(history.size() - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private CodeUnitSearchInfo createCodeUnitSearchInfo() {
|
|
||||||
return new CodeUnitSearchInfo(codeUnitTypesList.get(0).isSelected(),
|
|
||||||
codeUnitTypesList.get(1).isSelected(), codeUnitTypesList.get(2).isSelected());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void nextPreviousCallback(boolean forward) {
|
|
||||||
int alignment = 1;
|
|
||||||
try {
|
|
||||||
alignment = getAlignment();
|
|
||||||
}
|
|
||||||
catch (InvalidInputException e) {
|
|
||||||
plugin.disableSearchAgain();
|
|
||||||
setStatusText(e.getMessage());
|
|
||||||
alignField.selectAll();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (searchData.isValidSearchData()) {
|
|
||||||
if (plugin.searchOnce(new SearchInfo(searchData, 1,
|
|
||||||
searchSelectionRadioButton.isSelected(), forward, alignment, allBlocks.isSelected(),
|
|
||||||
createCodeUnitSearchInfo(), plugin.createTaskListener()))) {
|
|
||||||
addToHistory(valueComboBox.getText());
|
|
||||||
setStatusText("Searching...");
|
|
||||||
isSearching = true;
|
|
||||||
updateSearchButtonEnablement();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
plugin.disableSearchAgain();
|
|
||||||
setStatusText(searchData.getStatusMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void allCallback() {
|
|
||||||
int alignment = 1;
|
|
||||||
try {
|
|
||||||
alignment = getAlignment();
|
|
||||||
}
|
|
||||||
catch (InvalidInputException e) {
|
|
||||||
plugin.disableSearchAgain();
|
|
||||||
setStatusText(e.getMessage());
|
|
||||||
alignField.selectAll();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (searchData.isValidSearchData()) {
|
|
||||||
|
|
||||||
if (plugin.searchAll(new SearchAllSearchInfo(searchData, plugin.getSearchLimit(),
|
|
||||||
searchSelectionRadioButton.isSelected(), true, alignment, allBlocks.isSelected(),
|
|
||||||
createCodeUnitSearchInfo()))) {
|
|
||||||
addToHistory(valueComboBox.getText());
|
|
||||||
setStatusText("Searching...");
|
|
||||||
isSearching = true;
|
|
||||||
updateSearchButtonEnablement();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
plugin.disableSearchAgain();
|
|
||||||
setStatusText(searchData.getStatusMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private JPanel buildSearchPanel() {
|
|
||||||
JPanel labelPanel = new JPanel();
|
|
||||||
labelPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10));
|
|
||||||
labelPanel.setLayout(new GridLayout(0, 1));
|
|
||||||
labelPanel.add(new GLabel("Search Value: "));
|
|
||||||
labelPanel.add(new GLabel("Hex Sequence: "));
|
|
||||||
|
|
||||||
JPanel inputPanel = new JPanel();
|
|
||||||
inputPanel.setLayout(new GridLayout(0, 1));
|
|
||||||
valueComboBox = new GhidraComboBox<>();
|
|
||||||
valueComboBox.setAutoCompleteEnabled(false); // we do our own completion with validation
|
|
||||||
valueComboBox.setEditable(true);
|
|
||||||
valueComboBox.setToolTipText(currentFormat.getToolTip());
|
|
||||||
valueComboBox.setDocument(new RestrictedInputDocument());
|
|
||||||
valueComboBox.addActionListener(ev -> {
|
|
||||||
if (nextButton.isEnabled()) {
|
|
||||||
nextPreviousCallback(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
inputPanel.add(valueComboBox);
|
|
||||||
hexSeqField = new GDLabel();
|
|
||||||
hexSeqField.setName("HexSequenceField");
|
|
||||||
hexSeqField.setBorder(BorderFactory.createLoweredBevelBorder());
|
|
||||||
|
|
||||||
// see note for field minimum size field above
|
|
||||||
Dimension size = new Dimension(INPUT_FIELD_MIN_SIZE_WIDTH, INPUT_FIELD_MIN_SIZE_HEIGHT);
|
|
||||||
valueComboBox.setPreferredSize(size);
|
|
||||||
valueComboBox.setMinimumSize(size);
|
|
||||||
|
|
||||||
hexSeqField.setPreferredSize(size);
|
|
||||||
hexSeqField.setMinimumSize(size);
|
|
||||||
|
|
||||||
inputPanel.add(hexSeqField);
|
|
||||||
|
|
||||||
JPanel searchPanel = new JPanel(new BorderLayout());
|
|
||||||
searchPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
|
||||||
searchPanel.add(labelPanel, BorderLayout.WEST);
|
|
||||||
searchPanel.add(inputPanel, BorderLayout.CENTER);
|
|
||||||
return searchPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SearchFormat findFormat(String name) {
|
|
||||||
for (SearchFormat element : allFormats) {
|
|
||||||
if (element.getName().equals(name)) {
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return allFormats[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
private JPanel buildMainPanel() {
|
|
||||||
|
|
||||||
JPanel newMainPanel = new JPanel();
|
|
||||||
|
|
||||||
newMainPanel.setLayout(new BorderLayout());
|
|
||||||
newMainPanel.add(buildSearchPanel(), BorderLayout.NORTH);
|
|
||||||
newMainPanel.add(buildOptionsPanel(), BorderLayout.CENTER);
|
|
||||||
advancedPanel = buildAdvancedPanel();
|
|
||||||
|
|
||||||
JPanel searchOptionsPanel = new JPanel(new BorderLayout());
|
|
||||||
newMainPanel.add(searchOptionsPanel, BorderLayout.SOUTH);
|
|
||||||
|
|
||||||
return newMainPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setAdvancedPanelVisible(boolean visible) {
|
|
||||||
if (visible) {
|
|
||||||
mainPanel.add(advancedPanel, BorderLayout.EAST);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
mainPanel.remove(advancedPanel);
|
|
||||||
}
|
|
||||||
repack();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Container createSeparatorPanel() {
|
|
||||||
JPanel panel = new JPanel(new GridLayout(1, 1));
|
|
||||||
panel.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 10));
|
|
||||||
panel.add(new JSeparator(SwingConstants.VERTICAL));
|
|
||||||
return panel;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Container buildAdvancedPanel() {
|
|
||||||
JPanel panel = new JPanel(new BorderLayout());
|
|
||||||
panel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 10));
|
|
||||||
panel.add(createSeparatorPanel(), BorderLayout.WEST);
|
|
||||||
panel.add(buildAdvancedPanelContents());
|
|
||||||
return panel;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Container buildAdvancedPanelContents() {
|
|
||||||
JPanel panel = new JPanel(new VerticalLayout(5));
|
|
||||||
panel.add(buildEndienessPanel());
|
|
||||||
panel.add(buildCodeUnitTypesPanel());
|
|
||||||
panel.add(buildAlignmentPanel());
|
|
||||||
return panel;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Container buildEndienessPanel() {
|
|
||||||
ButtonGroup endianGroup = new ButtonGroup();
|
|
||||||
littleEndian = new GRadioButton("Little Endian", true);
|
|
||||||
bigEndian = new GRadioButton("Big Endian", false);
|
|
||||||
endianGroup.add(bigEndian);
|
|
||||||
endianGroup.add(littleEndian);
|
|
||||||
|
|
||||||
littleEndian.addActionListener(ev -> {
|
|
||||||
currentFormat.setEndieness(false);
|
|
||||||
updateDisplay();
|
|
||||||
});
|
|
||||||
bigEndian.addActionListener(ev -> {
|
|
||||||
currentFormat.setEndieness(true);
|
|
||||||
updateDisplay();
|
|
||||||
});
|
|
||||||
|
|
||||||
JPanel endianPanel = new JPanel();
|
|
||||||
endianPanel.setLayout(new BoxLayout(endianPanel, BoxLayout.Y_AXIS));
|
|
||||||
endianPanel.add(littleEndian);
|
|
||||||
endianPanel.add(bigEndian);
|
|
||||||
endianPanel.setBorder(BorderFactory.createTitledBorder("Byte Order"));
|
|
||||||
|
|
||||||
return endianPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Container buildCodeUnitTypesPanel() {
|
|
||||||
final JCheckBox instructionsCheckBox = new GCheckBox("Instructions", true);
|
|
||||||
final JCheckBox definedCheckBox = new GCheckBox("Defined Data", true);
|
|
||||||
final JCheckBox undefinedCheckBox = new GCheckBox("Undefined Data", true);
|
|
||||||
|
|
||||||
ItemListener stateListener = e -> validate();
|
|
||||||
|
|
||||||
codeUnitTypesList = new ArrayList<>();
|
|
||||||
codeUnitTypesList.add(instructionsCheckBox);
|
|
||||||
codeUnitTypesList.add(definedCheckBox);
|
|
||||||
codeUnitTypesList.add(undefinedCheckBox);
|
|
||||||
|
|
||||||
instructionsCheckBox.addItemListener(stateListener);
|
|
||||||
definedCheckBox.addItemListener(stateListener);
|
|
||||||
undefinedCheckBox.addItemListener(stateListener);
|
|
||||||
|
|
||||||
JPanel codeUnitTypePanel = new JPanel();
|
|
||||||
codeUnitTypePanel.setLayout(new BoxLayout(codeUnitTypePanel, BoxLayout.Y_AXIS));
|
|
||||||
codeUnitTypePanel.add(instructionsCheckBox);
|
|
||||||
codeUnitTypePanel.add(definedCheckBox);
|
|
||||||
codeUnitTypePanel.add(undefinedCheckBox);
|
|
||||||
codeUnitTypePanel.setBorder(BorderFactory.createTitledBorder(CODE_UNIT_SCOPE_NAME));
|
|
||||||
|
|
||||||
return codeUnitTypePanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Component buildSelectionPanel() {
|
|
||||||
JPanel panel = new JPanel();
|
|
||||||
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
|
||||||
panel.setBorder(new TitledBorder("Selection Scope"));
|
|
||||||
|
|
||||||
searchSelectionRadioButton = new GRadioButton("Search Selection");
|
|
||||||
searchAllRadioButton = new GRadioButton("Search All");
|
|
||||||
|
|
||||||
ButtonGroup buttonGroup = new ButtonGroup();
|
|
||||||
buttonGroup.add(searchSelectionRadioButton);
|
|
||||||
buttonGroup.add(searchAllRadioButton);
|
|
||||||
|
|
||||||
searchAllRadioButton.setSelected(true);
|
|
||||||
|
|
||||||
panel.add(searchAllRadioButton);
|
|
||||||
panel.add(searchSelectionRadioButton);
|
|
||||||
|
|
||||||
JPanel selectionPanel = new JPanel();
|
|
||||||
selectionPanel.setLayout(new BorderLayout());
|
|
||||||
selectionPanel.add(panel, BorderLayout.NORTH);
|
|
||||||
return selectionPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Component buildAlignmentPanel() {
|
|
||||||
alignLabel = new GDLabel("Alignment: ");
|
|
||||||
alignField = new JTextField(5);
|
|
||||||
alignField.setName("Alignment");
|
|
||||||
alignField.setText("0");
|
|
||||||
|
|
||||||
JPanel alignPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
|
||||||
alignPanel.add(alignLabel);
|
|
||||||
alignPanel.add(alignField);
|
|
||||||
return alignPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds the basic format selection panel.
|
|
||||||
*/
|
|
||||||
private JPanel buildFormatPanel() {
|
|
||||||
JPanel formatPanel = new JPanel();
|
|
||||||
formatPanel.setBorder(BorderFactory.createTitledBorder("Format"));
|
|
||||||
formatPanel.setLayout(new GridLayout(0, 1));
|
|
||||||
|
|
||||||
ButtonGroup formatGroup = new ButtonGroup();
|
|
||||||
ActionListener formatButtonListener = ev -> {
|
|
||||||
String formatName = ((JRadioButton) ev.getSource()).getText();
|
|
||||||
currentFormat = findFormat(formatName);
|
|
||||||
formatOptionsLayout.show(formatOptionsPanel, currentFormat.getName());
|
|
||||||
updateDisplay();
|
|
||||||
};
|
|
||||||
|
|
||||||
for (SearchFormat element : allFormats) {
|
|
||||||
GRadioButton formatButton = new GRadioButton(element.getName(), true);
|
|
||||||
formatButton.setToolTipText(element.getToolTip());
|
|
||||||
|
|
||||||
formatGroup.add(formatButton);
|
|
||||||
formatButton.addActionListener(formatButtonListener);
|
|
||||||
formatPanel.add(formatButton);
|
|
||||||
if (element.getName().equals("Binary") && isMnemonic) {
|
|
||||||
formatButton.setSelected(true);
|
|
||||||
currentFormat = element;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return formatPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
private JPanel buildFormatOptionsPanel() {
|
|
||||||
formatOptionsPanel = new JPanel();
|
|
||||||
formatOptionsLayout = new CardLayout();
|
|
||||||
formatOptionsPanel.setLayout(formatOptionsLayout);
|
|
||||||
|
|
||||||
for (SearchFormat element : allFormats) {
|
|
||||||
JPanel panel = element.getOptionsPanel();
|
|
||||||
formatOptionsPanel.add(panel, element.getName());
|
|
||||||
}
|
|
||||||
return formatOptionsPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* builds the panel that contains the format Panel, options panel and the extras panel.
|
|
||||||
*/
|
|
||||||
private JPanel buildOptionsPanel() {
|
|
||||||
JPanel formatPanel = buildFormatPanel();
|
|
||||||
formatOptionsPanel = buildFormatOptionsPanel();
|
|
||||||
JPanel extrasPanel = buildExtrasPanel();
|
|
||||||
|
|
||||||
JPanel northPanel = new JPanel();
|
|
||||||
northPanel.setLayout(new VariableRowHeightGridLayout(10, 10, 2));
|
|
||||||
|
|
||||||
northPanel.add(formatPanel);
|
|
||||||
northPanel.add(formatOptionsPanel);
|
|
||||||
northPanel.add(extrasPanel);
|
|
||||||
northPanel.add(buildSelectionPanel());
|
|
||||||
|
|
||||||
advancedButton = new JToggleButton("Advanced >>");
|
|
||||||
advancedButton.setName(ADVANCED_BUTTON_NAME);
|
|
||||||
advancedButton.addActionListener(e -> {
|
|
||||||
boolean selected = advancedButton.isSelected();
|
|
||||||
if (selected) {
|
|
||||||
advancedButton.setText("Advanced <<");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
advancedButton.setText("Advanced >>");
|
|
||||||
}
|
|
||||||
|
|
||||||
setAdvancedPanelVisible(advancedButton.isSelected());
|
|
||||||
});
|
|
||||||
JPanel advancedButtonPanel = new JPanel();
|
|
||||||
advancedButtonPanel.setLayout(new BoxLayout(advancedButtonPanel, BoxLayout.X_AXIS));
|
|
||||||
advancedButtonPanel.add(Box.createHorizontalGlue());
|
|
||||||
advancedButtonPanel.add(Box.createVerticalStrut(40));
|
|
||||||
advancedButtonPanel.add(advancedButton);
|
|
||||||
|
|
||||||
JPanel optionsPanel = new JPanel();
|
|
||||||
optionsPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 20, 10));
|
|
||||||
optionsPanel.setLayout(new BoxLayout(optionsPanel, BoxLayout.Y_AXIS));
|
|
||||||
optionsPanel.add(northPanel);
|
|
||||||
optionsPanel.add(advancedButtonPanel);
|
|
||||||
|
|
||||||
return optionsPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* builds the extras panel.
|
|
||||||
*/
|
|
||||||
private JPanel buildExtrasPanel() {
|
|
||||||
ButtonGroup memoryBlockGroup = new ButtonGroup();
|
|
||||||
loadedBlocks = new GRadioButton("Loaded Blocks", true);
|
|
||||||
allBlocks = new GRadioButton("All Blocks", false);
|
|
||||||
memoryBlockGroup.add(loadedBlocks);
|
|
||||||
memoryBlockGroup.add(allBlocks);
|
|
||||||
|
|
||||||
loadedBlocks.setToolTipText(HTMLUtilities
|
|
||||||
.toHTML("Only searches memory blocks that are loaded in a running executable.\n " +
|
|
||||||
"Ghidra now includes memory blocks for other data such as section headers.\n" +
|
|
||||||
"This option exludes these OTHER (non loaded) blocks."));
|
|
||||||
allBlocks.setToolTipText(
|
|
||||||
"Searches all memory blocks including blocks that are not actually loaded in a running executable");
|
|
||||||
|
|
||||||
JPanel directionPanel = new JPanel();
|
|
||||||
directionPanel.setLayout(new BoxLayout(directionPanel, BoxLayout.Y_AXIS));
|
|
||||||
directionPanel.add(loadedBlocks);
|
|
||||||
directionPanel.add(allBlocks);
|
|
||||||
directionPanel.setBorder(BorderFactory.createTitledBorder("Memory Block Types"));
|
|
||||||
|
|
||||||
JPanel extrasPanel = new JPanel();
|
|
||||||
extrasPanel.setLayout(new BorderLayout());
|
|
||||||
extrasPanel.add(directionPanel, BorderLayout.NORTH);
|
|
||||||
return extrasPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void buildButtons() {
|
|
||||||
nextButton = new JButton("Next");
|
|
||||||
nextButton.setMnemonic('N');
|
|
||||||
nextButton.addActionListener(ev -> nextPreviousCallback(true));
|
|
||||||
this.addButton(nextButton);
|
|
||||||
|
|
||||||
previousButton = new JButton("Previous");
|
|
||||||
previousButton.setMnemonic('P');
|
|
||||||
previousButton.addActionListener(ev -> nextPreviousCallback(false));
|
|
||||||
this.addButton(previousButton);
|
|
||||||
|
|
||||||
allButton = new JButton("Search All");
|
|
||||||
allButton.setMnemonic('A');
|
|
||||||
allButton.addActionListener(ev -> allCallback());
|
|
||||||
allButton.setName("Search All");
|
|
||||||
this.addButton(allButton);
|
|
||||||
|
|
||||||
addDismissButton();
|
|
||||||
updateSearchButtonEnablement();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSearchData(SearchData newSearchData) {
|
|
||||||
searchData = newSearchData;
|
|
||||||
hexSeqField.setText(searchData.getHexString());
|
|
||||||
validate();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validate() {
|
|
||||||
|
|
||||||
if (!searchData.isValidSearchData() || !searchData.isValidInputData()) {
|
|
||||||
setStatusText(searchData.getStatusMessage());
|
|
||||||
hasValidSearchData = false;
|
|
||||||
}
|
|
||||||
else if (!isValidCodeUnitSearchType()) {
|
|
||||||
setStatusText("You must select at least one type of code unit to search in " +
|
|
||||||
CODE_UNIT_SCOPE_NAME);
|
|
||||||
hasValidSearchData = false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setStatusText("");
|
|
||||||
hasValidSearchData = true;
|
|
||||||
}
|
|
||||||
updateSearchButtonEnablement();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isValidCodeUnitSearchType() {
|
|
||||||
for (JCheckBox checkBox : codeUnitTypesList) {
|
|
||||||
if (checkBox.isSelected()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected TaskScheduler getTaskScheduler() {
|
|
||||||
return super.getTaskScheduler();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateDisplay() {
|
|
||||||
clearStatusText();
|
|
||||||
|
|
||||||
updateSearchData();
|
|
||||||
|
|
||||||
setEndianEnabled(currentFormat.usesEndieness());
|
|
||||||
updateSearchButtonEnablement();
|
|
||||||
valueComboBox.setToolTipText(currentFormat.getToolTip());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSearchData() {
|
|
||||||
currentFormat.setEndieness(bigEndian.isSelected());
|
|
||||||
SearchData inputData = currentFormat.getSearchData(valueComboBox.getText());
|
|
||||||
if (valueComboBox.getText().trim().length() != 0 && inputData.isValidInputData()) {
|
|
||||||
updateSearchData(inputData);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
valueComboBox.setText("");
|
|
||||||
updateSearchData(DEFAULT_SEARCH_DATA);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getAlignment() throws InvalidInputException {
|
|
||||||
String alignStr = alignField.getText();
|
|
||||||
int len = 0;
|
|
||||||
try {
|
|
||||||
Integer ilen = Integer.decode(alignStr);
|
|
||||||
len = ilen.intValue();
|
|
||||||
}
|
|
||||||
catch (NumberFormatException e) {
|
|
||||||
throw new InvalidInputException("The alignment must be a number greater than 0.");
|
|
||||||
}
|
|
||||||
if (len <= 0) {
|
|
||||||
throw new InvalidInputException("The alignment must be a number greater than 0.");
|
|
||||||
}
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom Document that validates user input on the fly.
|
|
||||||
*/
|
|
||||||
public class RestrictedInputDocument extends DefaultStyledDocument {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called before new user input is inserted into the entry text field. The super
|
|
||||||
* method is called if the input is accepted.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
|
|
||||||
clearStatusText();
|
|
||||||
|
|
||||||
// allow pasting numbers in the forms like 0xABC or ABCh
|
|
||||||
str = removeNumberBasePrefixAndSuffix(str);
|
|
||||||
|
|
||||||
String currentText = getText(0, getLength());
|
|
||||||
String beforeOffset = currentText.substring(0, offs);
|
|
||||||
String afterOffset = currentText.substring(offs, currentText.length());
|
|
||||||
String proposedText = beforeOffset + str + afterOffset;
|
|
||||||
|
|
||||||
// show history
|
|
||||||
String match = handleHistoryMatch(currentText, proposedText);
|
|
||||||
if (match != null) {
|
|
||||||
super.insertString(offs, match.substring(beforeOffset.length()), a);
|
|
||||||
valueComboBox.setSelectionStart(proposedText.length());
|
|
||||||
valueComboBox.setSelectionEnd(match.length());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// no history to show
|
|
||||||
SearchData inputData = currentFormat.getSearchData(proposedText);
|
|
||||||
if (inputData.isValidInputData()) {
|
|
||||||
updateSearchData(inputData);
|
|
||||||
super.insertString(offs, str, a);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setStatusText(inputData.getStatusMessage());
|
|
||||||
Toolkit.getDefaultToolkit().beep();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called before the user deletes some text. If the result is valid, the super
|
|
||||||
* method is called.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void remove(int offs, int len) throws BadLocationException {
|
|
||||||
clearStatusText();
|
|
||||||
|
|
||||||
String currentText = getText(0, getLength());
|
|
||||||
String beforeOffset = currentText.substring(0, offs);
|
|
||||||
String afterOffset = currentText.substring(len + offs, currentText.length());
|
|
||||||
String proposedResult = beforeOffset + afterOffset;
|
|
||||||
|
|
||||||
if (proposedResult.length() == 0) {
|
|
||||||
updateSearchData(DEFAULT_SEARCH_DATA);
|
|
||||||
super.remove(offs, len);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SearchData inputData = currentFormat.getSearchData(proposedResult);
|
|
||||||
if (inputData.isValidInputData()) {
|
|
||||||
super.remove(offs, len);
|
|
||||||
updateSearchData(inputData);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Toolkit.getDefaultToolkit().beep();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String handleHistoryMatch(String currentText, String proposedText) {
|
|
||||||
boolean textAppended = proposedText.startsWith(currentText);
|
|
||||||
String match = findHistoryMatchString(proposedText);
|
|
||||||
if (match != null && textAppended) {
|
|
||||||
SearchData matchData = currentFormat.getSearchData(match);
|
|
||||||
if (matchData.isValidInputData()) {
|
|
||||||
updateSearchData(matchData);
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String findHistoryMatchString(String input) {
|
|
||||||
Iterator<String> itr = history.iterator();
|
|
||||||
while (itr.hasNext()) {
|
|
||||||
String historyString = itr.next();
|
|
||||||
if (historyString.startsWith(input)) {
|
|
||||||
return historyString;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String removeNumberBasePrefixAndSuffix(String str) {
|
|
||||||
if (!(currentFormat instanceof HexSearchFormat ||
|
|
||||||
currentFormat instanceof BinarySearchFormat)) {
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
String numMaybe = str.strip();
|
|
||||||
String lowercase = numMaybe.toLowerCase();
|
|
||||||
if (currentFormat instanceof HexSearchFormat) {
|
|
||||||
if (lowercase.startsWith("0x")) {
|
|
||||||
numMaybe = numMaybe.substring(2);
|
|
||||||
}
|
|
||||||
else if (lowercase.startsWith("$")) {
|
|
||||||
numMaybe = numMaybe.substring(1);
|
|
||||||
}
|
|
||||||
else if (lowercase.endsWith("h")) {
|
|
||||||
numMaybe = numMaybe.substring(0, numMaybe.length() - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (lowercase.startsWith("0b")) {
|
|
||||||
numMaybe = numMaybe.substring(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if the resultant number looks valid for insertion (i.e. not empty)
|
|
||||||
if (!numMaybe.isEmpty()) {
|
|
||||||
return numMaybe;
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean getShowAdvancedOptions() {
|
|
||||||
return advancedButton.isSelected();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setShowAdvancedOptions(boolean selected) {
|
|
||||||
if (advancedButton.isSelected() != selected) {
|
|
||||||
advancedButton.doClick();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setSearchEnabled(boolean b) {
|
|
||||||
searchEnabled = b;
|
|
||||||
}
|
|
||||||
|
|
||||||
void searchCompleted() {
|
|
||||||
isSearching = false;
|
|
||||||
updateSearchButtonEnablement();
|
|
||||||
}
|
|
||||||
|
|
||||||
String getSearchText() {
|
|
||||||
return valueComboBox.getText();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,907 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.app.plugin.core.searchmem;
|
|
||||||
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import javax.swing.Icon;
|
|
||||||
import javax.swing.JComponent;
|
|
||||||
|
|
||||||
import docking.*;
|
|
||||||
import docking.action.DockingAction;
|
|
||||||
import docking.action.MenuData;
|
|
||||||
import docking.tool.ToolConstants;
|
|
||||||
import docking.widgets.fieldpanel.support.Highlight;
|
|
||||||
import docking.widgets.table.threaded.*;
|
|
||||||
import generic.theme.GIcon;
|
|
||||||
import ghidra.GhidraOptions;
|
|
||||||
import ghidra.app.CorePluginPackage;
|
|
||||||
import ghidra.app.context.NavigatableActionContext;
|
|
||||||
import ghidra.app.context.NavigatableContextAction;
|
|
||||||
import ghidra.app.events.ProgramSelectionPluginEvent;
|
|
||||||
import ghidra.app.nav.Navigatable;
|
|
||||||
import ghidra.app.nav.NavigatableRemovalListener;
|
|
||||||
import ghidra.app.plugin.PluginCategoryNames;
|
|
||||||
import ghidra.app.plugin.core.table.TableComponentProvider;
|
|
||||||
import ghidra.app.services.*;
|
|
||||||
import ghidra.app.util.*;
|
|
||||||
import ghidra.app.util.query.TableService;
|
|
||||||
import ghidra.app.util.viewer.field.*;
|
|
||||||
import ghidra.app.util.viewer.proxy.ProxyObj;
|
|
||||||
import ghidra.framework.model.DomainObject;
|
|
||||||
import ghidra.framework.options.*;
|
|
||||||
import ghidra.framework.plugintool.*;
|
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
|
||||||
import ghidra.program.model.address.Address;
|
|
||||||
import ghidra.program.model.listing.CodeUnit;
|
|
||||||
import ghidra.program.model.listing.Program;
|
|
||||||
import ghidra.program.model.mem.Memory;
|
|
||||||
import ghidra.program.model.mem.MemoryAccessException;
|
|
||||||
import ghidra.program.util.*;
|
|
||||||
import ghidra.util.HelpLocation;
|
|
||||||
import ghidra.util.Msg;
|
|
||||||
import ghidra.util.bean.opteditor.OptionsVetoException;
|
|
||||||
import ghidra.util.search.memory.*;
|
|
||||||
import ghidra.util.table.GhidraProgramTableModel;
|
|
||||||
import ghidra.util.task.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class to handle memory searching of code bytes in a program.
|
|
||||||
*/
|
|
||||||
//@formatter:off
|
|
||||||
@PluginInfo(
|
|
||||||
status = PluginStatus.DEPRECATED,
|
|
||||||
packageName = CorePluginPackage.NAME,
|
|
||||||
category = PluginCategoryNames.SEARCH,
|
|
||||||
shortDescription = "Search bytes in memory",
|
|
||||||
description = "This plugin searches bytes in memory; the search " +
|
|
||||||
"is based on a value entered as hex or decimal numbers, or strings." +
|
|
||||||
" The value may contain \"wildcards\" or regular expressions" +
|
|
||||||
" that will match any byte or nibble.",
|
|
||||||
servicesRequired = { ProgramManager.class, GoToService.class, TableService.class, CodeViewerService.class },
|
|
||||||
// servicesProvided = { MemorySearchService.class },
|
|
||||||
eventsConsumed = { ProgramSelectionPluginEvent.class }
|
|
||||||
)
|
|
||||||
//@formatter:on
|
|
||||||
public class MemSearchPlugin extends Plugin implements OptionsChangeListener,
|
|
||||||
DockingContextListener, NavigatableRemovalListener {
|
|
||||||
|
|
||||||
/** Constant for read/writeConfig() for dialog options */
|
|
||||||
private static final String SHOW_ADVANCED_OPTIONS = "Show Advanced Options";
|
|
||||||
|
|
||||||
private static final int MAX_PRE_POPULTATE_BYTE_COUNT = 20;
|
|
||||||
|
|
||||||
private static final String PRE_POPULATE_MEM_SEARCH = "Pre-populate Memory Search";
|
|
||||||
|
|
||||||
private static final String AUTO_RESTRICT_SELECTION =
|
|
||||||
"Auto Restrict Memory Search on Selection";
|
|
||||||
|
|
||||||
private DockingAction searchAction;
|
|
||||||
private DockingAction searchAgainAction;
|
|
||||||
private MemSearchDialog searchDialog;
|
|
||||||
private GoToService goToService;
|
|
||||||
private int searchLimit;
|
|
||||||
private static final Icon SEARCH_MARKER_ICON = new GIcon("icon.base.search.marker");
|
|
||||||
|
|
||||||
private boolean doHighlight;
|
|
||||||
private int byteGroupSize;
|
|
||||||
private String byteDelimiter;
|
|
||||||
private boolean showAdvancedOptions;
|
|
||||||
private boolean prepopulateSearch;
|
|
||||||
private boolean autoRestrictSelection;
|
|
||||||
|
|
||||||
private TableComponentProvider<MemSearchResult> currentResultsTableProvider;
|
|
||||||
private TaskMonitor searchAllTaskMonitor;
|
|
||||||
private TableLoadingListener currentTableListener;
|
|
||||||
private volatile boolean waitingForSearchAll;
|
|
||||||
private SearchInfo searchInfo;
|
|
||||||
private Address lastMatchingAddress;
|
|
||||||
|
|
||||||
private Navigatable navigatable;
|
|
||||||
private boolean isMnemonic = false;
|
|
||||||
|
|
||||||
public MemSearchPlugin(PluginTool tool) {
|
|
||||||
super(tool);
|
|
||||||
|
|
||||||
createActions();
|
|
||||||
initializeOptionListeners();
|
|
||||||
loadOptions();
|
|
||||||
tool.addContextListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void dispose() {
|
|
||||||
tool.removeContextListener(this);
|
|
||||||
ToolOptions opt = tool.getOptions(ToolConstants.TOOL_OPTIONS);
|
|
||||||
opt.removeOptionsChangeListener(this);
|
|
||||||
|
|
||||||
opt = tool.getOptions(SearchConstants.SEARCH_OPTION_NAME);
|
|
||||||
opt.removeOptionsChangeListener(this);
|
|
||||||
|
|
||||||
if (searchAction != null) {
|
|
||||||
searchAction.dispose();
|
|
||||||
searchAction = null;
|
|
||||||
}
|
|
||||||
if (searchAgainAction != null) {
|
|
||||||
searchAgainAction.dispose();
|
|
||||||
searchAgainAction = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchAllTaskMonitor != null) {
|
|
||||||
searchAllTaskMonitor.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchDialog != null) {
|
|
||||||
searchDialog.dispose();
|
|
||||||
searchDialog = null;
|
|
||||||
}
|
|
||||||
goToService = null;
|
|
||||||
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
int getSearchLimit() {
|
|
||||||
return searchLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean searchAll(SearchInfo newSearchInfo) {
|
|
||||||
this.searchInfo = newSearchInfo;
|
|
||||||
return performSearch(newSearchInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean searchOnce(SearchInfo newSearchInfo) {
|
|
||||||
this.searchInfo = newSearchInfo;
|
|
||||||
return performSearch(searchInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean performSearch(SearchInfo localSearchInfo) {
|
|
||||||
Program program = navigatable.getProgram();
|
|
||||||
if (program == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
searchAgainAction.setEnabled(true);
|
|
||||||
|
|
||||||
if (localSearchInfo.isSearchAll()) {
|
|
||||||
waitingForSearchAll = true;
|
|
||||||
showIncrementalSearchResults(localSearchInfo);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
Address start = getSearchStartAddress(localSearchInfo);
|
|
||||||
ProgramSelection selection = navigatable.getSelection();
|
|
||||||
MemorySearchAlgorithm algorithm =
|
|
||||||
searchInfo.createSearchAlgorithm(program, start, selection);
|
|
||||||
MemSearcherTask task = new MemSearcherTask(searchInfo, algorithm);
|
|
||||||
searchDialog.executeProgressTask(task, 500);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void disableSearchAgain() {
|
|
||||||
searchAgainAction.setEnabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Address getSearchStartAddress(SearchInfo localSearchInfo) {
|
|
||||||
ProgramLocation location = navigatable.getLocation();
|
|
||||||
Address startAddress = location == null ? null : location.getAddress();
|
|
||||||
if (startAddress == null) {
|
|
||||||
Program program = navigatable.getProgram();
|
|
||||||
if (program == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
startAddress = localSearchInfo.isSearchForward() ? program.getMinAddress()
|
|
||||||
: program.getMaxAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastMatchingAddress == null) {
|
|
||||||
return startAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
// start the search after the last matching search
|
|
||||||
CodeUnit cu = navigatable.getProgram().getListing().getCodeUnitContaining(startAddress);
|
|
||||||
if (cu.contains(lastMatchingAddress)) {
|
|
||||||
startAddress = localSearchInfo.isSearchForward() ? lastMatchingAddress.next()
|
|
||||||
: lastMatchingAddress.previous();
|
|
||||||
}
|
|
||||||
return startAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void updateNavigatable(ActionContext context) {
|
|
||||||
if (context instanceof NavigatableActionContext) {
|
|
||||||
NavigatableActionContext navContext = ((NavigatableActionContext) context);
|
|
||||||
setNavigatable(navContext.getNavigatable());
|
|
||||||
updateSelection(navContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void processEvent(PluginEvent event) {
|
|
||||||
|
|
||||||
if (event instanceof ProgramSelectionPluginEvent) {
|
|
||||||
ProgramSelection selection = ((ProgramSelectionPluginEvent) event).getSelection();
|
|
||||||
boolean hasSelection = !selection.isEmpty();
|
|
||||||
|
|
||||||
if (searchDialog != null) {
|
|
||||||
searchDialog.setHasSelection(hasSelection, autoRestrictSelection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public void setIsMnemonic(boolean isMnemonic) {
|
|
||||||
// // provides the dialog with the knowledge of whether or not
|
|
||||||
// // the action being performed is a MnemonicSearchPlugin
|
|
||||||
// this.isMnemonic = isMnemonic;
|
|
||||||
// }
|
|
||||||
|
|
||||||
private void setNavigatable(Navigatable newNavigatable) {
|
|
||||||
if (newNavigatable == navigatable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (navigatable != null) {
|
|
||||||
navigatable.removeNavigatableListener(this);
|
|
||||||
}
|
|
||||||
if (newNavigatable != null) {
|
|
||||||
newNavigatable.addNavigatableListener(this);
|
|
||||||
}
|
|
||||||
this.navigatable = newNavigatable;
|
|
||||||
|
|
||||||
lastMatchingAddress = null;
|
|
||||||
if (searchDialog != null) {
|
|
||||||
searchDialog.setSearchEnabled(newNavigatable != null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void init() {
|
|
||||||
goToService = tool.getService(GoToService.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void invokeSearchDialog(NavigatableActionContext context) {
|
|
||||||
if (searchDialog == null) {
|
|
||||||
boolean isBigEndian = navigatable.getProgram().getLanguage().isBigEndian();
|
|
||||||
searchDialog = new MemSearchDialog(this, isBigEndian, isMnemonic);
|
|
||||||
searchDialog.setShowAdvancedOptions(showAdvancedOptions);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
searchDialog.setEndianness(navigatable.getProgram().getLanguage().isBigEndian());
|
|
||||||
searchDialog.close(); // close it to make sure it gets parented to the current focused window.
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] searchBytes = getInitialSearchBytes(context);
|
|
||||||
if (searchBytes != null) {
|
|
||||||
searchDialog.setBytes(searchBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean hasSelection = context.hasSelection();
|
|
||||||
searchDialog.setHasSelection(hasSelection, autoRestrictSelection);
|
|
||||||
searchDialog.show(context.getComponentProvider());
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] getInitialSearchBytes(NavigatableActionContext context) {
|
|
||||||
if (!prepopulateSearch) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ProgramSelection selection = context.getSelection();
|
|
||||||
if (selection == null || selection.isEmpty() || hasBigSelection(context)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// safe cast as size has already been checked.
|
|
||||||
int numAddresses = (int) selection.getNumAddresses();
|
|
||||||
Address address = selection.getMinAddress();
|
|
||||||
Memory memory = context.getProgram().getMemory();
|
|
||||||
byte[] bytes = new byte[numAddresses];
|
|
||||||
try {
|
|
||||||
int count = memory.getBytes(address, bytes);
|
|
||||||
if (count == numAddresses) {
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (MemoryAccessException e) {
|
|
||||||
// fall through and return null
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private BytesFieldLocation getBytesFieldLocation(Address address) {
|
|
||||||
if (address == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Program program = navigatable.getProgram();
|
|
||||||
|
|
||||||
return new BytesFieldLocation(program, address);
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public void search(byte[] bytes, NavigatableActionContext context) {
|
|
||||||
// setNavigatable(context.getNavigatable());
|
|
||||||
// invokeSearchDialog(context);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void setSearchText(String maskedString) {
|
|
||||||
// searchDialog.setSearchText(maskedString);
|
|
||||||
// }
|
|
||||||
|
|
||||||
private void createActions() {
|
|
||||||
searchAction = new NavigatableContextAction("Search Memory", getName(), false) {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(NavigatableActionContext context) {
|
|
||||||
setNavigatable(context.getNavigatable());
|
|
||||||
invokeSearchDialog(context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
searchAction.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, searchAction.getName()));
|
|
||||||
String[] menuPath = new String[] { "&Search", "Memory (Deprecated)..." };
|
|
||||||
searchAction.setMenuBarData(new MenuData(menuPath, "search"));
|
|
||||||
searchAction.setDescription("Search Memory for byte sequence");
|
|
||||||
searchAction.addToWindowWhen(NavigatableActionContext.class);
|
|
||||||
tool.addAction(searchAction);
|
|
||||||
|
|
||||||
searchAgainAction = new NavigatableContextAction("Repeat Memory Search", getName(), false) {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(NavigatableActionContext context) {
|
|
||||||
setNavigatable(context.getNavigatable());
|
|
||||||
performSearch(searchInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isEnabledForContext(NavigatableActionContext context) {
|
|
||||||
return searchInfo != null && super.isEnabledForContext(context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
searchAgainAction
|
|
||||||
.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, searchAgainAction.getName()));
|
|
||||||
menuPath = new String[] { "&Search", "Repeat Memory Search" };
|
|
||||||
searchAgainAction.setMenuBarData(new MenuData(menuPath, "search"));
|
|
||||||
searchAgainAction.setDescription("Search Memory for byte sequence");
|
|
||||||
searchAgainAction.addToWindowWhen(NavigatableActionContext.class);
|
|
||||||
tool.addAction(searchAgainAction);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeOptionListeners() {
|
|
||||||
ToolOptions opt = tool.getOptions(SearchConstants.SEARCH_OPTION_NAME);
|
|
||||||
opt.registerOption(PRE_POPULATE_MEM_SEARCH, true, null,
|
|
||||||
"Initializes memory search byte sequence from " +
|
|
||||||
"the current selection provided the selection is less than 10 bytes.");
|
|
||||||
opt.registerOption(AUTO_RESTRICT_SELECTION, true, null,
|
|
||||||
"Automactically adjusts memory searches restricted" +
|
|
||||||
" to the current selection, as selections comes and goes");
|
|
||||||
opt.registerOption(SearchConstants.SEARCH_HIGHLIGHT_NAME, true, null,
|
|
||||||
"Toggles highlight search results");
|
|
||||||
|
|
||||||
opt.registerThemeColorBinding(SearchConstants.SEARCH_HIGHLIGHT_COLOR_OPTION_NAME,
|
|
||||||
SearchConstants.SEARCH_HIGHLIGHT_COLOR.getId(), null,
|
|
||||||
"The search result highlight color");
|
|
||||||
opt.registerThemeColorBinding(SearchConstants.SEARCH_HIGHLIGHT_CURRENT_COLOR_OPTION_NAME,
|
|
||||||
SearchConstants.SEARCH_HIGHLIGHT_CURRENT_ADDR_COLOR.getId(), null,
|
|
||||||
"The search result highlight color for the currently selected match");
|
|
||||||
|
|
||||||
opt.addOptionsChangeListener(this);
|
|
||||||
|
|
||||||
opt = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS);
|
|
||||||
opt.addOptionsChangeListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadOptions() {
|
|
||||||
|
|
||||||
Options opt = tool.getOptions(SearchConstants.SEARCH_OPTION_NAME);
|
|
||||||
int newSearchLimit =
|
|
||||||
opt.getInt(GhidraOptions.OPTION_SEARCH_LIMIT, SearchConstants.DEFAULT_SEARCH_LIMIT);
|
|
||||||
if (newSearchLimit <= 0) {
|
|
||||||
throw new OptionsVetoException("Search limit must be greater than 0");
|
|
||||||
}
|
|
||||||
searchLimit = newSearchLimit;
|
|
||||||
|
|
||||||
if (searchInfo != null) {
|
|
||||||
searchInfo.setSearchLimit(newSearchLimit);
|
|
||||||
}
|
|
||||||
|
|
||||||
prepopulateSearch = opt.getBoolean(PRE_POPULATE_MEM_SEARCH, true);
|
|
||||||
autoRestrictSelection = opt.getBoolean(AUTO_RESTRICT_SELECTION, true);
|
|
||||||
doHighlight = opt.getBoolean(SearchConstants.SEARCH_HIGHLIGHT_NAME, true);
|
|
||||||
|
|
||||||
opt = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS);
|
|
||||||
byteGroupSize = opt.getInt(BytesFieldFactory.BYTE_GROUP_SIZE_MSG, 1);
|
|
||||||
byteDelimiter = opt.getString(BytesFieldFactory.DELIMITER_MSG, " ");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
|
|
||||||
Object newValue) {
|
|
||||||
loadOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void updateSelection(NavigatableActionContext context) {
|
|
||||||
if (searchDialog != null) {
|
|
||||||
searchDialog.setHasSelection(context.hasSelection(), autoRestrictSelection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasBigSelection(NavigatableActionContext context) {
|
|
||||||
if (!context.hasSelection()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ProgramSelection selection = context.getSelection();
|
|
||||||
if (selection.getNumAddressRanges() > 1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return selection.getNumAddresses() > MAX_PRE_POPULTATE_BYTE_COUNT;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showIncrementalSearchResults(SearchInfo info) {
|
|
||||||
|
|
||||||
Program program = navigatable.getProgram();
|
|
||||||
|
|
||||||
TableService query = tool.getService(TableService.class);
|
|
||||||
|
|
||||||
searchDialog.setStatusText("Searching...");
|
|
||||||
|
|
||||||
MemSearchTableModel model = new MemSearchTableModel(tool, program, info,
|
|
||||||
getSearchStartAddress(info), navigatable.getSelection());
|
|
||||||
|
|
||||||
currentResultsTableProvider =
|
|
||||||
getTableResultsProvider(info.getSearchData(), program, model, query);
|
|
||||||
currentResultsTableProvider.installRemoveItemsAction();
|
|
||||||
|
|
||||||
currentTableListener = new TableLoadingListener(model);
|
|
||||||
model.addInitialLoadListener(currentTableListener);
|
|
||||||
|
|
||||||
GThreadedTablePanel<MemSearchResult> tablePanel =
|
|
||||||
currentResultsTableProvider.getThreadedTablePanel();
|
|
||||||
searchAllTaskMonitor = tablePanel.getTaskMonitor();
|
|
||||||
|
|
||||||
installHighlightProvider(model, currentResultsTableProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void searchFinished() {
|
|
||||||
searchDialog.searchCompleted();
|
|
||||||
currentResultsTableProvider = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean canCloseDomainObject(DomainObject dObj) {
|
|
||||||
if ((navigatable != null && navigatable.getProgram() == dObj) && isSearching()) {
|
|
||||||
tool.setStatusInfo("Can't close program while searching...", true);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*testing*/ boolean isSearching() {
|
|
||||||
if (waitingForSearchAll) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (searchDialog == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return searchDialog.getTaskScheduler().isBusy();
|
|
||||||
}
|
|
||||||
|
|
||||||
private TableComponentProvider<MemSearchResult> getTableResultsProvider(SearchData searchData,
|
|
||||||
Program program, GhidraProgramTableModel<MemSearchResult> model,
|
|
||||||
TableService tableService) {
|
|
||||||
|
|
||||||
String searchString = searchDialog.getSearchText();
|
|
||||||
String title = "Search Memory - \"" + searchString + "\"";
|
|
||||||
String type = "Search";
|
|
||||||
if (navigatable.supportsMarkers()) {
|
|
||||||
return tableService.showTableWithMarkers(title, type, model,
|
|
||||||
SearchConstants.SEARCH_HIGHLIGHT_COLOR, SEARCH_MARKER_ICON, type, navigatable);
|
|
||||||
}
|
|
||||||
return tableService.showTable(title, type, model, type, navigatable);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void installHighlightProvider(MemSearchTableModel model,
|
|
||||||
TableComponentProvider<MemSearchResult> provider) {
|
|
||||||
Program program = navigatable.getProgram();
|
|
||||||
new SearchTableHighlightHandler(navigatable, model, provider, program);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void installHighlightProvider(MemSearcherTask searcher,
|
|
||||||
TableComponentProvider<MemSearchResult> provider) {
|
|
||||||
Program program = navigatable.getProgram();
|
|
||||||
new TaskHighlightHandler(navigatable, searcher, provider, program);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void readConfigState(SaveState saveState) {
|
|
||||||
showAdvancedOptions = saveState.getBoolean(SHOW_ADVANCED_OPTIONS, false);
|
|
||||||
if (searchDialog != null) {
|
|
||||||
searchDialog.setShowAdvancedOptions(showAdvancedOptions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeConfigState(SaveState saveState) {
|
|
||||||
if (searchDialog != null) {
|
|
||||||
saveState.putBoolean(SHOW_ADVANCED_OPTIONS, searchDialog.getShowAdvancedOptions());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void navigatableRemoved(Navigatable removedNavigatable) {
|
|
||||||
setNavigatable(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void contextChanged(ActionContext context) {
|
|
||||||
updateNavigatable(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskListener createTaskListener() {
|
|
||||||
return new SearchOnceTaskListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
//==================================================================================================
|
|
||||||
// Inner Classes
|
|
||||||
//==================================================================================================
|
|
||||||
|
|
||||||
private class TableLoadingListener implements ThreadedTableModelListener {
|
|
||||||
|
|
||||||
private ThreadedTableModel<MemSearchResult, ?> model;
|
|
||||||
|
|
||||||
TableLoadingListener(ThreadedTableModel<MemSearchResult, ?> model) {
|
|
||||||
this.model = model;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadingFinished(boolean wasCancelled) {
|
|
||||||
if (isDisposed()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ComponentProvider provider = currentResultsTableProvider;
|
|
||||||
waitingForSearchAll = false;
|
|
||||||
searchFinished();
|
|
||||||
if (wasCancelled) {
|
|
||||||
searchDialog.setStatusText("Search Cancelled");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int matchCount = model.getRowCount();
|
|
||||||
if (matchCount == 0) {
|
|
||||||
searchDialog.setStatusText("No matches found.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchCount >= searchLimit) {
|
|
||||||
// use this when showing the dialog below so that the provider does not get
|
|
||||||
// hidden behind the tool
|
|
||||||
JComponent resultsTable = provider.getComponent();
|
|
||||||
Msg.showInfo(getClass(), resultsTable, "Search Limit Exceeded!",
|
|
||||||
"Stopped search after finding " + matchCount + " matches.\n" +
|
|
||||||
"The search limit can be changed at Edit->Tool Options, under Search.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// suggestion to not close search dialog. TODO remove next line in future versions.
|
|
||||||
// searchDialog.close();
|
|
||||||
searchDialog.setStatusText("Done");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadingStarted() {
|
|
||||||
// don't care
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadPending() {
|
|
||||||
// don't care
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private abstract class SearchResultsHighlighter
|
|
||||||
implements ListingHighlightProvider, ComponentProviderActivationListener {
|
|
||||||
|
|
||||||
private TableComponentProvider<MemSearchResult> provider;
|
|
||||||
private Program highlightProgram;
|
|
||||||
private final Navigatable highlightNavigatable;
|
|
||||||
|
|
||||||
SearchResultsHighlighter(Navigatable navigatable,
|
|
||||||
TableComponentProvider<MemSearchResult> provider, Program program) {
|
|
||||||
highlightNavigatable = navigatable;
|
|
||||||
this.provider = provider;
|
|
||||||
this.highlightProgram = program;
|
|
||||||
|
|
||||||
if (provider != null) {
|
|
||||||
provider.addActivationListener(this);
|
|
||||||
}
|
|
||||||
highlightNavigatable.setHighlightProvider(this, program);
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract List<MemSearchResult> getMatches();
|
|
||||||
|
|
||||||
abstract void cleanup();
|
|
||||||
|
|
||||||
private List<MemSearchResult> getAddressesFoundInRange(Address start, Address end) {
|
|
||||||
List<MemSearchResult> data = getMatches();
|
|
||||||
int startIndex = findFirstIndex(data, start, end);
|
|
||||||
if (startIndex < 0) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
int endIndex = findIndexAtOrGreater(data, end);
|
|
||||||
if (endIndex < data.size() && ((data.get(endIndex)).addressEquals(end))) {
|
|
||||||
endIndex++; // exact match on end, so include it
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MemSearchResult> resultList = data.subList(startIndex, endIndex);
|
|
||||||
return resultList;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int findFirstIndex(List<MemSearchResult> list, Address start, Address end) {
|
|
||||||
|
|
||||||
List<MemSearchResult> data = getMatches();
|
|
||||||
|
|
||||||
int startIndex = findIndexAtOrGreater(data, start);
|
|
||||||
if (startIndex > 0) { // see if address before extends into this range.
|
|
||||||
MemSearchResult resultBefore = data.get(startIndex - 1);
|
|
||||||
Address beforeAddr = resultBefore.getAddress();
|
|
||||||
int length = resultBefore.getLength();
|
|
||||||
if (start.hasSameAddressSpace(beforeAddr) && start.subtract(beforeAddr) < length) {
|
|
||||||
return startIndex - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startIndex == data.size()) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
MemSearchResult result = data.get(startIndex);
|
|
||||||
Address addr = result.getAddress();
|
|
||||||
if (end.compareTo(addr) >= 0) {
|
|
||||||
return startIndex;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int findIndexAtOrGreater(List<MemSearchResult> list, Address address) {
|
|
||||||
|
|
||||||
MemSearchResult key = new MemSearchResult(address, 1);
|
|
||||||
int index = Collections.binarySearch(list, key);
|
|
||||||
if (index < 0) {
|
|
||||||
index = -index - 1;
|
|
||||||
}
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Highlight[] createHighlights(String text, ListingField field, int cursorTextOffset) {
|
|
||||||
|
|
||||||
Program program = navigatable != null ? navigatable.getProgram() : null;
|
|
||||||
Class<? extends FieldFactory> fieldFactoryClass = field.getFieldFactory().getClass();
|
|
||||||
if (fieldFactoryClass != BytesFieldFactory.class) {
|
|
||||||
return NO_HIGHLIGHTS;
|
|
||||||
}
|
|
||||||
if (checkRemoveHighlights()) {
|
|
||||||
return NO_HIGHLIGHTS;
|
|
||||||
}
|
|
||||||
|
|
||||||
ProxyObj<?> proxy = field.getProxy();
|
|
||||||
Object obj = proxy.getObject();
|
|
||||||
if (!(obj instanceof CodeUnit cu)) {
|
|
||||||
return NO_HIGHLIGHTS;
|
|
||||||
}
|
|
||||||
if (!doHighlight) {
|
|
||||||
return NO_HIGHLIGHTS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (highlightProgram != program) {
|
|
||||||
return NO_HIGHLIGHTS;
|
|
||||||
}
|
|
||||||
|
|
||||||
Address minAddr = cu.getMinAddress();
|
|
||||||
Address maxAddr = cu.getMaxAddress();
|
|
||||||
List<MemSearchResult> results = getAddressesFoundInRange(minAddr, maxAddr);
|
|
||||||
|
|
||||||
Highlight[] highlights = new Highlight[results.size()];
|
|
||||||
for (int i = 0; i < highlights.length; i++) {
|
|
||||||
MemSearchResult result = results.get(i);
|
|
||||||
int highlightLength = result.getLength();
|
|
||||||
Address addr = result.getAddress();
|
|
||||||
Color highlightColor = getHighlightColor(addr, highlightLength);
|
|
||||||
int startByteOffset = (int) addr.subtract(minAddr);
|
|
||||||
int endByteOffset = startByteOffset + highlightLength - 1;
|
|
||||||
startByteOffset = Math.max(startByteOffset, 0);
|
|
||||||
highlights[i] = getHighlight(text, startByteOffset, endByteOffset, highlightColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
return highlights;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Highlight getHighlight(String text, int start, int end, Color color) {
|
|
||||||
int charStart = getCharPosition(text, start);
|
|
||||||
int charEnd = getCharPosition(text, end) + 1;
|
|
||||||
return new Highlight(charStart, charEnd, color);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getCharPosition(String text, int byteOffset) {
|
|
||||||
int groupSize = byteGroupSize * 2 + byteDelimiter.length();
|
|
||||||
int groupIndex = byteOffset / byteGroupSize;
|
|
||||||
int groupOffset = byteOffset % byteGroupSize;
|
|
||||||
|
|
||||||
int pos = groupIndex * groupSize + 2 * groupOffset;
|
|
||||||
return Math.min(text.length() - 1, pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Color getHighlightColor(Address highlightStart, int highlightLength) {
|
|
||||||
ProgramLocation location = navigatable != null ? navigatable.getLocation() : null;
|
|
||||||
if (!(location instanceof BytesFieldLocation)) {
|
|
||||||
return SearchConstants.SEARCH_HIGHLIGHT_COLOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
BytesFieldLocation byteLoc = (BytesFieldLocation) location;
|
|
||||||
Address byteAddress = byteLoc.getAddressForByte();
|
|
||||||
if (highlightStart.hasSameAddressSpace(byteAddress)) {
|
|
||||||
long diff = byteAddress.subtract(highlightStart);
|
|
||||||
if (diff >= 0 && diff < highlightLength) {
|
|
||||||
// the current location is in the highlight
|
|
||||||
return SearchConstants.SEARCH_HIGHLIGHT_CURRENT_ADDR_COLOR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return SearchConstants.SEARCH_HIGHLIGHT_COLOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean checkRemoveHighlights() {
|
|
||||||
if (provider != null) { // search all - remove highlights when
|
|
||||||
if (!tool.isVisible(provider)) { // results are no longer showing
|
|
||||||
highlightNavigatable.removeHighlightProvider(this, highlightProgram);
|
|
||||||
cleanup();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!searchDialog.isVisible()) {
|
|
||||||
// single search - remove highlights when search dialog no longer showing
|
|
||||||
highlightNavigatable.removeHighlightProvider(this, highlightProgram);
|
|
||||||
cleanup();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void componentProviderActivated(ComponentProvider componentProvider) {
|
|
||||||
// enable highlighting
|
|
||||||
highlightNavigatable.setHighlightProvider(this, highlightProgram);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void componentProviderDeactivated(ComponentProvider componentProvider) {
|
|
||||||
// only handle highlighting during activation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SearchTableHighlightHandler extends SearchResultsHighlighter {
|
|
||||||
private final MemSearchTableModel model;
|
|
||||||
private List<MemSearchResult> sortedResults;
|
|
||||||
|
|
||||||
SearchTableHighlightHandler(Navigatable navigatable, MemSearchTableModel model,
|
|
||||||
TableComponentProvider<MemSearchResult> provider, Program program) {
|
|
||||||
super(navigatable, provider, program);
|
|
||||||
this.model = model;
|
|
||||||
|
|
||||||
model.addThreadedTableModelListener(new ThreadedTableModelListener() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadingStarted() {
|
|
||||||
clearCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadingFinished(boolean wasCancelled) {
|
|
||||||
// stub
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadPending() {
|
|
||||||
clearCache();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
List<MemSearchResult> getMatches() {
|
|
||||||
|
|
||||||
if (sortedResults != null) {
|
|
||||||
return sortedResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (model.isBusy()) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MemSearchResult> modelData = model.getModelData();
|
|
||||||
if (model.isSortedOnAddress()) {
|
|
||||||
return modelData;
|
|
||||||
}
|
|
||||||
|
|
||||||
sortedResults = new ArrayList<>(modelData);
|
|
||||||
Collections.sort(sortedResults);
|
|
||||||
|
|
||||||
return sortedResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void cleanup() {
|
|
||||||
clearCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearCache() {
|
|
||||||
if (sortedResults != null) {
|
|
||||||
sortedResults.clear();
|
|
||||||
sortedResults = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TaskHighlightHandler extends SearchResultsHighlighter {
|
|
||||||
private final MemSearcherTask searchTask;
|
|
||||||
|
|
||||||
TaskHighlightHandler(Navigatable navigatable, MemSearcherTask searcher,
|
|
||||||
TableComponentProvider<MemSearchResult> provider, Program program) {
|
|
||||||
super(navigatable, provider, program);
|
|
||||||
this.searchTask = searcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
List<MemSearchResult> getMatches() {
|
|
||||||
return searchTask.getMatchingAddresses();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void cleanup() {
|
|
||||||
// nothing to do
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SearchOnceTaskListener implements TaskListener {
|
|
||||||
@Override
|
|
||||||
public void taskCompleted(Task task) {
|
|
||||||
if (isDisposed()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MemSearcherTask searcher = (MemSearcherTask) task;
|
|
||||||
List<MemSearchResult> results = searcher.getMatchingAddresses();
|
|
||||||
if (results.isEmpty()) {
|
|
||||||
searchDialog.setStatusText("Not Found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
searchDialog.setStatusText("Found");
|
|
||||||
MemSearchResult result = results.get(0);
|
|
||||||
Address addr = result.getAddress();
|
|
||||||
goToService.goTo(navigatable, getBytesFieldLocation(addr), navigatable.getProgram());
|
|
||||||
lastMatchingAddress = addr;
|
|
||||||
installHighlightProvider(searcher, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void taskCancelled(Task task) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.app.plugin.core.searchmem;
|
|
||||||
|
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
|
||||||
import ghidra.program.model.address.Address;
|
|
||||||
import ghidra.program.model.listing.Program;
|
|
||||||
import ghidra.util.search.memory.MemSearchResult;
|
|
||||||
import ghidra.util.table.ProgramLocationTableRowMapper;
|
|
||||||
|
|
||||||
public class MemSearchResultToAddressTableRowMapper
|
|
||||||
extends ProgramLocationTableRowMapper<MemSearchResult, Address> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Address map(MemSearchResult rowObject, Program data, ServiceProvider serviceProvider) {
|
|
||||||
return rowObject.getAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.app.plugin.core.searchmem;
|
|
||||||
|
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
|
||||||
import ghidra.program.model.listing.*;
|
|
||||||
import ghidra.util.search.memory.MemSearchResult;
|
|
||||||
import ghidra.util.table.ProgramLocationTableRowMapper;
|
|
||||||
|
|
||||||
public class MemSearchResultToFunctionTableRowMapper
|
|
||||||
extends ProgramLocationTableRowMapper<MemSearchResult, Function> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Function map(MemSearchResult rowObject, Program program,
|
|
||||||
ServiceProvider serviceProvider) {
|
|
||||||
FunctionManager functionManager = program.getFunctionManager();
|
|
||||||
return functionManager.getFunctionContaining(rowObject.getAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.app.plugin.core.searchmem;
|
|
||||||
|
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
|
||||||
import ghidra.program.model.listing.Program;
|
|
||||||
import ghidra.program.util.ProgramLocation;
|
|
||||||
import ghidra.util.search.memory.MemSearchResult;
|
|
||||||
import ghidra.util.table.ProgramLocationTableRowMapper;
|
|
||||||
|
|
||||||
public class MemSearchResultToProgramLocationTableRowMapper
|
|
||||||
extends ProgramLocationTableRowMapper<MemSearchResult, ProgramLocation> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ProgramLocation map(MemSearchResult rowObject, Program program,
|
|
||||||
ServiceProvider serviceProvider) {
|
|
||||||
return new ProgramLocation(program, rowObject.getAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,106 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.app.plugin.core.searchmem;
|
|
||||||
|
|
||||||
import docking.widgets.table.*;
|
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
|
||||||
import ghidra.program.model.address.*;
|
|
||||||
import ghidra.program.model.listing.Program;
|
|
||||||
import ghidra.program.util.*;
|
|
||||||
import ghidra.util.datastruct.Accumulator;
|
|
||||||
import ghidra.util.exception.CancelledException;
|
|
||||||
import ghidra.util.search.memory.*;
|
|
||||||
import ghidra.util.table.AddressBasedTableModel;
|
|
||||||
import ghidra.util.table.field.AddressTableColumn;
|
|
||||||
import ghidra.util.task.TaskMonitor;
|
|
||||||
|
|
||||||
public class MemSearchTableModel extends AddressBasedTableModel<MemSearchResult> {
|
|
||||||
|
|
||||||
private SearchInfo searchInfo;
|
|
||||||
private ProgramSelection selection;
|
|
||||||
private Address startAddress;
|
|
||||||
private MemorySearchAlgorithm algorithm;
|
|
||||||
|
|
||||||
MemSearchTableModel(ServiceProvider serviceProvider, Program program, SearchInfo searchInfo,
|
|
||||||
Address searchStartAddress, ProgramSelection programSelection) {
|
|
||||||
super("Memory Search", serviceProvider, program, null, true);
|
|
||||||
this.searchInfo = searchInfo;
|
|
||||||
this.startAddress = searchStartAddress;
|
|
||||||
this.selection = programSelection;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSortedOnAddress() {
|
|
||||||
TableSortState sortState = getTableSortState();
|
|
||||||
if (sortState.isUnsorted()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnSortState primaryState = sortState.getAllSortStates().get(0);
|
|
||||||
DynamicTableColumn<MemSearchResult, ?, ?> column =
|
|
||||||
getColumn(primaryState.getColumnModelIndex());
|
|
||||||
String name = column.getColumnName();
|
|
||||||
if (AddressTableColumn.NAME.equals(name)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doLoad(Accumulator<MemSearchResult> accumulator, TaskMonitor monitor)
|
|
||||||
throws CancelledException {
|
|
||||||
algorithm = searchInfo.createSearchAlgorithm(getProgram(), startAddress, selection);
|
|
||||||
algorithm.search(accumulator, monitor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ProgramLocation getProgramLocation(int row, int column) {
|
|
||||||
Program p = getProgram();
|
|
||||||
if (p == null) {
|
|
||||||
return null; // we've been disposed
|
|
||||||
}
|
|
||||||
|
|
||||||
ProgramLocation loc = super.getProgramLocation(row, column);
|
|
||||||
if (loc != null && p.getMemory().contains(loc.getByteAddress())) {
|
|
||||||
return new BytesFieldLocation(p, loc.getByteAddress());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Address getAddress(int row) {
|
|
||||||
MemSearchResult result = getRowObject(row);
|
|
||||||
return result.getAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ProgramSelection getProgramSelection(int[] rows) {
|
|
||||||
AddressSet addressSet = new AddressSet();
|
|
||||||
for (int row : rows) {
|
|
||||||
MemSearchResult result = getRowObject(row);
|
|
||||||
int addOn = result.getLength() - 1;
|
|
||||||
Address minAddr = getAddress(row);
|
|
||||||
Address maxAddr = minAddr;
|
|
||||||
try {
|
|
||||||
maxAddr = minAddr.addNoWrap(addOn);
|
|
||||||
addressSet.addRange(minAddr, maxAddr);
|
|
||||||
}
|
|
||||||
catch (AddressOverflowException e) {
|
|
||||||
// I guess we don't care--not sure why this is undocumented :(
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new ProgramSelection(addressSet);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
/* ###
|
|
||||||
* IP: GHIDRA
|
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.app.plugin.core.searchmem;
|
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.regex.PatternSyntaxException;
|
|
||||||
|
|
||||||
public class RegExSearchData extends SearchData {
|
|
||||||
private Pattern pattern;
|
|
||||||
|
|
||||||
public static RegExSearchData createRegExSearchData( String inputString ) {
|
|
||||||
RegExSearchData regExSearchData = new RegExSearchData( inputString );
|
|
||||||
if ( regExSearchData.errorMessage != null ) {
|
|
||||||
throw new IllegalArgumentException( "Problem creating search data: " +
|
|
||||||
regExSearchData.errorMessage );
|
|
||||||
}
|
|
||||||
return regExSearchData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RegExSearchData(String inputString) {
|
|
||||||
super(inputString, new byte[0], null);
|
|
||||||
try {
|
|
||||||
pattern = Pattern.compile(inputString, Pattern.DOTALL);
|
|
||||||
} catch (PatternSyntaxException pse) {
|
|
||||||
errorMessage = pse.getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isValidSearchData() {
|
|
||||||
return pattern != null;
|
|
||||||
}
|
|
||||||
public Pattern getRegExPattern() {
|
|
||||||
return pattern;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.app.plugin.core.searchmem;
|
|
||||||
|
|
||||||
import javax.swing.event.ChangeListener;
|
|
||||||
|
|
||||||
public class RegExSearchFormat extends SearchFormat {
|
|
||||||
|
|
||||||
public RegExSearchFormat(ChangeListener listener) {
|
|
||||||
super("Regular Expression", listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getToolTip() {
|
|
||||||
return "Interpret value as a regular expression.";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean usesEndieness() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public boolean supportsBackwardsSearch() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public SearchData getSearchData( String input ) {
|
|
||||||
return new RegExSearchData(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.app.plugin.core.searchmem;
|
|
||||||
|
|
||||||
import ghidra.program.model.address.Address;
|
|
||||||
import ghidra.program.model.address.AddressSetView;
|
|
||||||
import ghidra.program.model.listing.Program;
|
|
||||||
import ghidra.program.model.mem.Memory;
|
|
||||||
import ghidra.program.util.ProgramSelection;
|
|
||||||
import ghidra.util.search.memory.CodeUnitSearchInfo;
|
|
||||||
import ghidra.util.search.memory.SearchInfo;
|
|
||||||
|
|
||||||
class SearchAllSearchInfo extends SearchInfo {
|
|
||||||
|
|
||||||
public SearchAllSearchInfo(SearchData searchData, int matchLimit, boolean searchSelection,
|
|
||||||
boolean forwardSearch, int alignment, boolean includeNonLoadedBlocks,
|
|
||||||
CodeUnitSearchInfo codeUnitSearchInfo) {
|
|
||||||
super(searchData, matchLimit, searchSelection, forwardSearch, alignment,
|
|
||||||
includeNonLoadedBlocks, codeUnitSearchInfo, null /* search all uses a different listener mechanism */);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected AddressSetView getSearchableAddressSet(Program program, Address address,
|
|
||||||
ProgramSelection selection) {
|
|
||||||
|
|
||||||
// in the search all case, we don't care about the starting address.
|
|
||||||
|
|
||||||
Memory memory = program.getMemory();
|
|
||||||
AddressSetView set =
|
|
||||||
this.includeNonLoadedBlocks ? memory.getAllInitializedAddressSet()
|
|
||||||
: memory.getLoadedAndInitializedAddressSet();
|
|
||||||
if (searchSelection && selection != null && !selection.isEmpty()) {
|
|
||||||
set = set.intersect(selection);
|
|
||||||
}
|
|
||||||
return set;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSearchAll() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,92 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.app.plugin.core.searchmem;
|
|
||||||
|
|
||||||
public class SearchData {
|
|
||||||
|
|
||||||
private String inputString;
|
|
||||||
protected String errorMessage;
|
|
||||||
private byte[] bytes;
|
|
||||||
private byte[] mask;
|
|
||||||
private boolean isValidInputData;
|
|
||||||
private boolean isValidSearchData;
|
|
||||||
|
|
||||||
// valid input and search data with mask
|
|
||||||
protected SearchData(String inputString, byte[] searchBytes, byte[] mask) {
|
|
||||||
this.isValidInputData = true;
|
|
||||||
this.isValidSearchData = true;
|
|
||||||
this.inputString = inputString;
|
|
||||||
this.bytes = searchBytes == null ? new byte[0] : searchBytes;
|
|
||||||
this.mask = mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
// valid input, bad search data
|
|
||||||
private SearchData(String errorMessage, boolean isValidInputData) {
|
|
||||||
this.isValidInputData = isValidInputData;
|
|
||||||
this.isValidSearchData = false;
|
|
||||||
bytes = new byte[0];
|
|
||||||
this.errorMessage = errorMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SearchData createSearchData(String inputString, byte[] searchBytes, byte[] mask) {
|
|
||||||
return new SearchData(inputString, searchBytes, mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SearchData createIncompleteSearchData(String errorMessage) {
|
|
||||||
return new SearchData(errorMessage, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SearchData createInvalidInputSearchData(String errorMessage) {
|
|
||||||
return new SearchData(errorMessage, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getBytes() {
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getMask() {
|
|
||||||
return mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isValidInputData() {
|
|
||||||
return isValidInputData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isValidSearchData() {
|
|
||||||
return isValidSearchData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getInputString() {
|
|
||||||
return inputString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStatusMessage() {
|
|
||||||
return errorMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHexString() {
|
|
||||||
StringBuilder buf = new StringBuilder();
|
|
||||||
for (byte element : bytes) {
|
|
||||||
String hexString = Integer.toHexString(element & 0xff);
|
|
||||||
if (hexString.length() == 1) {
|
|
||||||
buf.append("0");
|
|
||||||
}
|
|
||||||
buf.append(hexString);
|
|
||||||
buf.append(" ");
|
|
||||||
}
|
|
||||||
return buf.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
/* ###
|
|
||||||
* IP: GHIDRA
|
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.app.plugin.core.searchmem;
|
|
||||||
|
|
||||||
import javax.swing.BorderFactory;
|
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.event.ChangeListener;
|
|
||||||
|
|
||||||
public abstract class SearchFormat {
|
|
||||||
private String name;
|
|
||||||
protected boolean isBigEndian;
|
|
||||||
protected ChangeListener changeListener;
|
|
||||||
|
|
||||||
protected SearchFormat(String name, ChangeListener listener) {
|
|
||||||
this.name = name;
|
|
||||||
this.changeListener = listener;
|
|
||||||
}
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JPanel getOptionsPanel() {
|
|
||||||
JPanel noOptionsPanel = new JPanel();
|
|
||||||
noOptionsPanel.setBorder(BorderFactory.createTitledBorder("Format Options"));
|
|
||||||
return noOptionsPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEndieness(boolean isBigEndian) {
|
|
||||||
this.isBigEndian = isBigEndian;
|
|
||||||
}
|
|
||||||
public boolean usesEndieness() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
public boolean supportsBackwardsSearch() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract String getToolTip();
|
|
||||||
|
|
||||||
public abstract SearchData getSearchData( String input );
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -56,7 +56,7 @@ public class MemorySearcher {
|
||||||
* @param searchLimit the max number of hits before stopping
|
* @param searchLimit the max number of hits before stopping
|
||||||
*/
|
*/
|
||||||
public MemorySearcher(AddressableByteSource byteSource, ByteMatcher matcher,
|
public MemorySearcher(AddressableByteSource byteSource, ByteMatcher matcher,
|
||||||
AddressSet addresses, int searchLimit) {
|
AddressSetView addresses, int searchLimit) {
|
||||||
this(byteSource, matcher, addresses, searchLimit, DEFAULT_CHUNK_SIZE);
|
this(byteSource, matcher, addresses, searchLimit, DEFAULT_CHUNK_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ public class MemorySearcher {
|
||||||
* @param chunkSize the maximum number of bytes to feed to the matcher at any one time.
|
* @param chunkSize the maximum number of bytes to feed to the matcher at any one time.
|
||||||
*/
|
*/
|
||||||
public MemorySearcher(AddressableByteSource byteSource, ByteMatcher matcher,
|
public MemorySearcher(AddressableByteSource byteSource, ByteMatcher matcher,
|
||||||
AddressSet addresses, int searchLimit, int chunkSize) {
|
AddressSetView addresses, int searchLimit, int chunkSize) {
|
||||||
this.matcher = matcher;
|
this.matcher = matcher;
|
||||||
this.searchSet = addresses;
|
this.searchSet = addresses;
|
||||||
this.searchLimit = searchLimit;
|
this.searchLimit = searchLimit;
|
||||||
|
|
|
@ -29,8 +29,14 @@ import ghidra.app.cmd.label.SetLabelPrimaryCmd;
|
||||||
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
|
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
|
||||||
import ghidra.app.plugin.core.clear.ClearCmd;
|
import ghidra.app.plugin.core.clear.ClearCmd;
|
||||||
import ghidra.app.plugin.core.clear.ClearOptions;
|
import ghidra.app.plugin.core.clear.ClearOptions;
|
||||||
import ghidra.app.plugin.core.searchmem.RegExSearchData;
|
|
||||||
import ghidra.app.script.GhidraScript;
|
import ghidra.app.script.GhidraScript;
|
||||||
|
import ghidra.features.base.memsearch.bytesource.AddressableByteSource;
|
||||||
|
import ghidra.features.base.memsearch.bytesource.ProgramByteSource;
|
||||||
|
import ghidra.features.base.memsearch.gui.SearchSettings;
|
||||||
|
import ghidra.features.base.memsearch.matcher.ByteMatcher;
|
||||||
|
import ghidra.features.base.memsearch.matcher.RegExByteMatcher;
|
||||||
|
import ghidra.features.base.memsearch.searcher.MemoryMatch;
|
||||||
|
import ghidra.features.base.memsearch.searcher.MemorySearcher;
|
||||||
import ghidra.framework.main.AppInfo;
|
import ghidra.framework.main.AppInfo;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
@ -49,7 +55,6 @@ import ghidra.util.ascii.AsciiCharSetRecognizer;
|
||||||
import ghidra.util.datastruct.Accumulator;
|
import ghidra.util.datastruct.Accumulator;
|
||||||
import ghidra.util.datastruct.ListAccumulator;
|
import ghidra.util.datastruct.ListAccumulator;
|
||||||
import ghidra.util.exception.*;
|
import ghidra.util.exception.*;
|
||||||
import ghidra.util.search.memory.*;
|
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -771,15 +776,38 @@ public class FlatProgramAPI {
|
||||||
public final Address[] findBytes(AddressSetView set, String byteString, int matchLimit,
|
public final Address[] findBytes(AddressSetView set, String byteString, int matchLimit,
|
||||||
int alignment) {
|
int alignment) {
|
||||||
|
|
||||||
return findBytes(set, byteString, matchLimit, alignment, false);
|
if (matchLimit <= 0) {
|
||||||
|
matchLimit = 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchSettings settings = new SearchSettings().withAlignment(alignment);
|
||||||
|
ByteMatcher matcher = new RegExByteMatcher(byteString, settings);
|
||||||
|
AddressableByteSource byteSource = new ProgramByteSource(currentProgram);
|
||||||
|
Memory memory = currentProgram.getMemory();
|
||||||
|
AddressSet intersection = memory.getLoadedAndInitializedAddressSet().intersect(set);
|
||||||
|
|
||||||
|
MemorySearcher searcher = new MemorySearcher(byteSource, matcher, intersection, matchLimit);
|
||||||
|
Accumulator<MemoryMatch> accumulator = new ListAccumulator<>();
|
||||||
|
searcher.findAll(accumulator, monitor);
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
List<Address> addresses =
|
||||||
|
accumulator.stream()
|
||||||
|
.map(r -> r.getAddress())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
//@formatter:on
|
||||||
|
return addresses.toArray(new Address[addresses.size()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* This method has been deprecated, use {@link #findBytes(Address, String, int, int)} instead.
|
||||||
|
* The concept of searching and finding matches that span gaps (address ranges where no memory
|
||||||
|
* blocks have been defined), is no longer supported. If this capability has value to anyone,
|
||||||
|
* please contact the Ghidra team and let us know.
|
||||||
|
* <P>
|
||||||
* Finds a byte pattern within an addressSet.
|
* Finds a byte pattern within an addressSet.
|
||||||
*
|
*
|
||||||
* Note: When searchAcrossAddressGaps is set to true, the ranges within the addressSet are
|
* Note: The ranges within the addressSet are NOT treated as a contiguous set when searching
|
||||||
* treated as a contiguous set when searching.
|
|
||||||
*
|
|
||||||
* <p>
|
* <p>
|
||||||
* The <code>byteString</code> may contain regular expressions. The following
|
* The <code>byteString</code> may contain regular expressions. The following
|
||||||
* highlights some example search strings (note the use of double backslashes ("\\")):
|
* highlights some example search strings (note the use of double backslashes ("\\")):
|
||||||
|
@ -794,49 +822,21 @@ public class FlatProgramAPI {
|
||||||
* @param byteString the byte pattern for which to search
|
* @param byteString the byte pattern for which to search
|
||||||
* @param matchLimit The number of matches to which the search should be restricted
|
* @param matchLimit The number of matches to which the search should be restricted
|
||||||
* @param alignment byte alignment to use for search starts. For example, a value of
|
* @param alignment byte alignment to use for search starts. For example, a value of
|
||||||
* 1 searches from every byte. A value of 2 only matches runs that begin on a even
|
* 1 searches from every byte. A value of 2 only matches runs that begin on a even
|
||||||
* address boundary.
|
* address boundary.
|
||||||
* @param searchAcrossAddressGaps when set to 'true' searches for matches across the gaps
|
* @param searchAcrossAddressGaps This parameter is no longer supported and its value is
|
||||||
* of each addressRange contained in the addresSet.
|
* ignored. Previously, if true, match results were allowed to span non-continguous memory
|
||||||
|
* ranges.
|
||||||
* @return the start addresses that contain byte patterns that match the given byteString
|
* @return the start addresses that contain byte patterns that match the given byteString
|
||||||
* @throws IllegalArgumentException if the byteString is not a valid regular expression
|
* @throws IllegalArgumentException if the byteString is not a valid regular expression
|
||||||
* @see #findBytes(Address, String)
|
* @see #findBytes(Address, String)
|
||||||
|
*
|
||||||
|
* @deprecated see description for details.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "11.3", forRemoval = true)
|
||||||
public final Address[] findBytes(AddressSetView set, String byteString, int matchLimit,
|
public final Address[] findBytes(AddressSetView set, String byteString, int matchLimit,
|
||||||
int alignment, boolean searchAcrossAddressGaps) {
|
int alignment, boolean searchAcrossAddressGaps) {
|
||||||
|
return findBytes(set, byteString, matchLimit, alignment);
|
||||||
if (matchLimit <= 0) {
|
|
||||||
matchLimit = 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
RegExSearchData searchData = RegExSearchData.createRegExSearchData(byteString);
|
|
||||||
|
|
||||||
//@formatter:off
|
|
||||||
SearchInfo searchInfo = new SearchInfo(searchData,
|
|
||||||
matchLimit,
|
|
||||||
false, // search selection
|
|
||||||
true, // search forward
|
|
||||||
alignment,
|
|
||||||
true, // include non-loaded blocks
|
|
||||||
null);
|
|
||||||
//@formatter:on
|
|
||||||
|
|
||||||
Memory memory = currentProgram.getMemory();
|
|
||||||
AddressSet intersection = memory.getLoadedAndInitializedAddressSet().intersect(set);
|
|
||||||
|
|
||||||
RegExMemSearcherAlgorithm searcher = new RegExMemSearcherAlgorithm(searchInfo, intersection,
|
|
||||||
currentProgram, searchAcrossAddressGaps);
|
|
||||||
|
|
||||||
Accumulator<MemSearchResult> accumulator = new ListAccumulator<>();
|
|
||||||
searcher.search(accumulator, monitor);
|
|
||||||
|
|
||||||
//@formatter:off
|
|
||||||
List<Address> addresses =
|
|
||||||
accumulator.stream()
|
|
||||||
.map(r -> r.getAddress())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
//@formatter:on
|
|
||||||
return addresses.toArray(new Address[addresses.size()]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1512,21 +1512,21 @@ public class FlatProgramAPI {
|
||||||
public final Namespace getNamespace(Namespace parent, String namespaceName) {
|
public final Namespace getNamespace(Namespace parent, String namespaceName) {
|
||||||
return currentProgram.getSymbolTable().getNamespace(namespaceName, parent);
|
return currentProgram.getSymbolTable().getNamespace(namespaceName, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link Namespace} with the given name contained inside the
|
* Creates a new {@link Namespace} with the given name contained inside the
|
||||||
* specified parent namespace.
|
* specified parent namespace.
|
||||||
* Pass <code>null</code> for parent to indicate the global namespace.
|
* Pass <code>null</code> for parent to indicate the global namespace.
|
||||||
* If a {@link Namespace} or {@link GhidraClass} with the given name already exists, the
|
* If a {@link Namespace} or {@link GhidraClass} with the given name already exists, the
|
||||||
* existing one will be returned.
|
* existing one will be returned.
|
||||||
* @param parent the parent namespace, or null for global namespace
|
* @param parent the parent namespace, or null for global namespace
|
||||||
* @param namespaceName the requested namespace's name
|
* @param namespaceName the requested namespace's name
|
||||||
* @return the namespace with the given name
|
* @return the namespace with the given name
|
||||||
* @throws DuplicateNameException if a {@link Library} symbol exists with the given name
|
* @throws DuplicateNameException if a {@link Library} symbol exists with the given name
|
||||||
* @throws InvalidInputException if the name is invalid
|
* @throws InvalidInputException if the name is invalid
|
||||||
* @throws IllegalArgumentException if parent Namespace does not correspond to
|
* @throws IllegalArgumentException if parent Namespace does not correspond to
|
||||||
* <code>currerntProgram</code>
|
* <code>currerntProgram</code>
|
||||||
*/
|
*/
|
||||||
public final Namespace createNamespace(Namespace parent, String namespaceName)
|
public final Namespace createNamespace(Namespace parent, String namespaceName)
|
||||||
throws DuplicateNameException, InvalidInputException {
|
throws DuplicateNameException, InvalidInputException {
|
||||||
SymbolTable symbolTable = currentProgram.getSymbolTable();
|
SymbolTable symbolTable = currentProgram.getSymbolTable();
|
||||||
|
@ -1539,7 +1539,7 @@ public class FlatProgramAPI {
|
||||||
}
|
}
|
||||||
return symbolTable.createNameSpace(parent, namespaceName, SourceType.USER_DEFINED);
|
return symbolTable.createNameSpace(parent, namespaceName, SourceType.USER_DEFINED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link GhidraClass} with the given name contained inside the
|
* Creates a new {@link GhidraClass} with the given name contained inside the
|
||||||
* specified parent namespace.
|
* specified parent namespace.
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.util.search.memory;
|
|
||||||
|
|
||||||
public class CodeUnitSearchInfo {
|
|
||||||
|
|
||||||
private final boolean searchInstructions;
|
|
||||||
private final boolean searchDefinedData;
|
|
||||||
private final boolean searchUndefinedData;
|
|
||||||
|
|
||||||
public CodeUnitSearchInfo( boolean searchInstructions, boolean searchDefinedData,
|
|
||||||
boolean searchUndefinedData ) {
|
|
||||||
this.searchInstructions = searchInstructions;
|
|
||||||
this.searchDefinedData = searchDefinedData;
|
|
||||||
this.searchUndefinedData = searchUndefinedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSearchInstructions() {
|
|
||||||
return searchInstructions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSearchDefinedData() {
|
|
||||||
return searchDefinedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSearchUndefinedData() {
|
|
||||||
return searchUndefinedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean searchAll() {
|
|
||||||
return searchInstructions && searchDefinedData && searchUndefinedData;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,104 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.util.search.memory;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import ghidra.program.model.address.Address;
|
|
||||||
import ghidra.util.SystemUtilities;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A class that represents a memory search hit at an address.
|
|
||||||
*/
|
|
||||||
public class MemSearchResult implements Comparable<MemSearchResult> {
|
|
||||||
|
|
||||||
private Address address;
|
|
||||||
private int length;
|
|
||||||
private byte[] bytes;
|
|
||||||
|
|
||||||
public MemSearchResult(Address address, int length) {
|
|
||||||
this.address = Objects.requireNonNull(address);
|
|
||||||
|
|
||||||
if (length <= 0) {
|
|
||||||
throw new IllegalArgumentException("Length must be greater than 0");
|
|
||||||
}
|
|
||||||
this.length = length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MemSearchResult(Address address, byte[] bytes) {
|
|
||||||
if (bytes == null || bytes.length < 1) {
|
|
||||||
throw new IllegalArgumentException("Must provide at least 1 byte");
|
|
||||||
}
|
|
||||||
this.address = Objects.requireNonNull(address);
|
|
||||||
this.bytes = bytes;
|
|
||||||
this.length = bytes.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Address getAddress() {
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLength() {
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getBytes() {
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(MemSearchResult o) {
|
|
||||||
return address.compareTo(o.address);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + ((address == null) ? 0 : address.hashCode());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
MemSearchResult other = (MemSearchResult) obj;
|
|
||||||
return SystemUtilities.isEqual(address, other.address);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return address.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the given address equals the address of this search result
|
|
||||||
* @param a the other address
|
|
||||||
* @return true if the given address equals the address of this search result
|
|
||||||
*/
|
|
||||||
public boolean addressEquals(Address a) {
|
|
||||||
return address.equals(a);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,138 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.util.search.memory;
|
|
||||||
|
|
||||||
import ghidra.app.plugin.core.searchmem.SearchData;
|
|
||||||
import ghidra.program.model.address.*;
|
|
||||||
import ghidra.program.model.listing.*;
|
|
||||||
import ghidra.program.model.mem.Memory;
|
|
||||||
import ghidra.util.datastruct.Accumulator;
|
|
||||||
import ghidra.util.task.TaskMonitor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search memory using the provided search text.
|
|
||||||
*/
|
|
||||||
public class MemSearcherAlgorithm implements MemorySearchAlgorithm {
|
|
||||||
|
|
||||||
private boolean forwardSearch;
|
|
||||||
private SearchData searchData;
|
|
||||||
private AddressSetView searchSet;
|
|
||||||
protected int searchLimit;
|
|
||||||
private Program program;
|
|
||||||
private int alignment;
|
|
||||||
private CodeUnitSearchInfo codeUnitSearchInfo;
|
|
||||||
|
|
||||||
MemSearcherAlgorithm(SearchInfo searchInfo, AddressSetView searchSet, Program program) {
|
|
||||||
|
|
||||||
this.searchData = searchInfo.getSearchData();
|
|
||||||
this.forwardSearch = searchInfo.isSearchForward();
|
|
||||||
this.alignment = searchInfo.getAlignment();
|
|
||||||
this.searchSet = searchSet;
|
|
||||||
this.searchLimit = searchInfo.getSearchLimit();
|
|
||||||
this.program = program;
|
|
||||||
this.codeUnitSearchInfo = searchInfo.getCodeUnitSearchInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void search(Accumulator<MemSearchResult> accumulator, TaskMonitor monitor) {
|
|
||||||
AddressRangeIterator addressRanges = searchSet.getAddressRanges(forwardSearch);
|
|
||||||
monitor.initialize(searchSet.getNumAddresses());
|
|
||||||
int progressCount = 0;
|
|
||||||
|
|
||||||
while (addressRanges.hasNext() && !monitor.isCancelled()) {
|
|
||||||
|
|
||||||
AddressRange range = addressRanges.next();
|
|
||||||
searchRange(accumulator, range, monitor, progressCount);
|
|
||||||
progressCount += range.getLength();
|
|
||||||
monitor.setProgress(progressCount);
|
|
||||||
if (accumulator.size() >= searchLimit) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void searchRange(Accumulator<MemSearchResult> accumulator, AddressRange range,
|
|
||||||
TaskMonitor monitor, int progressCount) {
|
|
||||||
|
|
||||||
Memory mem = program.getMemory();
|
|
||||||
Address startAddress = forwardSearch ? range.getMinAddress() : range.getMaxAddress();
|
|
||||||
Address endAddress = forwardSearch ? range.getMaxAddress() : range.getMinAddress();
|
|
||||||
int length = searchData.getBytes().length;
|
|
||||||
while (startAddress != null && !monitor.isCancelled()) {
|
|
||||||
Address matchAddress = mem.findBytes(startAddress, endAddress, searchData.getBytes(),
|
|
||||||
searchData.getMask(), forwardSearch, monitor);
|
|
||||||
|
|
||||||
if (isMatchingAddress(matchAddress)) {
|
|
||||||
MemSearchResult result = new MemSearchResult(matchAddress, length);
|
|
||||||
accumulator.add(result);
|
|
||||||
if (accumulator.size() >= searchLimit) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
monitor.setProgress(progressCount + getRangeDifference(range, matchAddress));
|
|
||||||
}
|
|
||||||
startAddress = getNextAddress(matchAddress, range);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isMatchingAddress(Address address) {
|
|
||||||
if (address == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((address.getOffset() % alignment) != 0) {
|
|
||||||
return false; // wrong alignment
|
|
||||||
}
|
|
||||||
|
|
||||||
if (codeUnitSearchInfo.searchAll()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Listing listing = program.getListing();
|
|
||||||
CodeUnit codeUnit = listing.getCodeUnitContaining(address);
|
|
||||||
if (codeUnit instanceof Instruction) {
|
|
||||||
return codeUnitSearchInfo.isSearchInstructions();
|
|
||||||
}
|
|
||||||
else if (codeUnit instanceof Data) {
|
|
||||||
Data data = (Data) codeUnit;
|
|
||||||
if (data.isDefined()) {
|
|
||||||
return codeUnitSearchInfo.isSearchDefinedData();
|
|
||||||
}
|
|
||||||
return codeUnitSearchInfo.isSearchUndefinedData();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getRangeDifference(AddressRange range, Address address) {
|
|
||||||
return (int) (forwardSearch ? address.subtract(range.getMinAddress())
|
|
||||||
: range.getMaxAddress().subtract(address));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Address getNextAddress(Address currentAddress, AddressRange range) {
|
|
||||||
if (currentAddress == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (forwardSearch) {
|
|
||||||
return currentAddress.equals(range.getMaxAddress()) ? null : currentAddress.next();
|
|
||||||
}
|
|
||||||
return currentAddress.equals(range.getMinAddress()) ? null : currentAddress.previous();
|
|
||||||
}
|
|
||||||
|
|
||||||
AddressSetView getSearchSet() {
|
|
||||||
return searchSet;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.util.search.memory;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import ghidra.util.datastruct.ListAccumulator;
|
|
||||||
import ghidra.util.task.Task;
|
|
||||||
import ghidra.util.task.TaskMonitor;
|
|
||||||
|
|
||||||
public class MemSearcherTask extends Task {
|
|
||||||
|
|
||||||
private MemorySearchAlgorithm algorithm;
|
|
||||||
private List<MemSearchResult> results;
|
|
||||||
|
|
||||||
public MemSearcherTask(SearchInfo searchInfo, MemorySearchAlgorithm algorithm) {
|
|
||||||
super("Search Memory", true, true, false);
|
|
||||||
|
|
||||||
this.algorithm = algorithm;
|
|
||||||
addTaskListener(searchInfo.getListener());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run(TaskMonitor monitor) {
|
|
||||||
|
|
||||||
ListAccumulator<MemSearchResult> accumulator = new ListAccumulator<>();
|
|
||||||
algorithm.search(accumulator, monitor);
|
|
||||||
this.results = accumulator.asList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<MemSearchResult> getMatchingAddresses() {
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,103 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.util.search.memory;
|
|
||||||
|
|
||||||
import ghidra.program.model.address.*;
|
|
||||||
import ghidra.program.model.mem.Memory;
|
|
||||||
import ghidra.program.model.mem.MemoryAccessException;
|
|
||||||
import ghidra.util.exception.AssertException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class implements the CharSequence interface using Memory and an AddressSet. The
|
|
||||||
* idea is that each byte in memory at the addresses specified in the AddressSet will form
|
|
||||||
* a contiguous sequence of characters.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class MemoryAddressSetCharSequence implements CharSequence {
|
|
||||||
|
|
||||||
private final Memory memory;
|
|
||||||
private final AddressSetView set;
|
|
||||||
private final AddressSetMapping mapping;
|
|
||||||
|
|
||||||
public MemoryAddressSetCharSequence(Memory memory, AddressSetView addressSet)
|
|
||||||
throws MemoryAccessException {
|
|
||||||
this.memory = memory;
|
|
||||||
this.set = addressSet;
|
|
||||||
|
|
||||||
if (addressSet.getNumAddresses() > Integer.MAX_VALUE) {
|
|
||||||
throw new AssertException(
|
|
||||||
"The MemAddressSetCharSequence class only supports address sets of size <= 0x7ffffffff byte addresses.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!memory.getAllInitializedAddressSet().contains(addressSet)) {
|
|
||||||
throw new MemoryAccessException(
|
|
||||||
"Not all addresses in given address set are in memory!");
|
|
||||||
}
|
|
||||||
|
|
||||||
mapping = new AddressSetMapping(addressSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MemoryAddressSetCharSequence(Memory memory, Address start, Address end)
|
|
||||||
throws MemoryAccessException {
|
|
||||||
this(memory, new AddressSet(start, end));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes an index and returns the matching Address
|
|
||||||
* @param index index to search on
|
|
||||||
* @return Address address matched to index
|
|
||||||
*/
|
|
||||||
public Address getAddressAtIndex(int index) {
|
|
||||||
return mapping.getAddress(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int length() {
|
|
||||||
return (int) set.getNumAddresses(); //safe cast because we check in constructor
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public char charAt(int index) {
|
|
||||||
Address address = getAddressAtIndex(index);
|
|
||||||
|
|
||||||
try {
|
|
||||||
byte b = memory.getByte(address);
|
|
||||||
return (char) (b & 0xff);
|
|
||||||
}
|
|
||||||
catch (MemoryAccessException e) {
|
|
||||||
throw new AssertException("Can't happen since we already checked in constructor");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CharSequence subSequence(int start, int end) {
|
|
||||||
if (start < 0 || start >= length() || end < 0 || end >= length()) {
|
|
||||||
throw new IndexOutOfBoundsException("Start and end must be in [0, " + (length() - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
Address startAddress = getAddressAtIndex(start);
|
|
||||||
Address endAddress = getAddressAtIndex(end);
|
|
||||||
AddressSet intersectSet = set.intersectRange(startAddress, endAddress);
|
|
||||||
|
|
||||||
try {
|
|
||||||
return new MemoryAddressSetCharSequence(memory, intersectSet);
|
|
||||||
}
|
|
||||||
catch (MemoryAccessException e) {
|
|
||||||
throw new AssertException("Can't happen since we already checked");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.util.search.memory;
|
|
||||||
|
|
||||||
import ghidra.util.datastruct.Accumulator;
|
|
||||||
import ghidra.util.task.TaskMonitor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An interface to unify the different methods for searching memory.
|
|
||||||
*/
|
|
||||||
public interface MemorySearchAlgorithm {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform the search
|
|
||||||
*
|
|
||||||
* @param accumulator the results accumulator
|
|
||||||
* @param monitor the monitor
|
|
||||||
*/
|
|
||||||
public void search(Accumulator<MemSearchResult> accumulator, TaskMonitor monitor);
|
|
||||||
}
|
|
|
@ -1,186 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.util.search.memory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import ghidra.app.plugin.core.searchmem.RegExSearchData;
|
|
||||||
import ghidra.app.plugin.core.searchmem.SearchData;
|
|
||||||
import ghidra.program.model.address.*;
|
|
||||||
import ghidra.program.model.listing.*;
|
|
||||||
import ghidra.program.model.mem.*;
|
|
||||||
import ghidra.util.Msg;
|
|
||||||
import ghidra.util.datastruct.Accumulator;
|
|
||||||
import ghidra.util.task.TaskMonitor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search memory using the provided regular expression.
|
|
||||||
*/
|
|
||||||
public class RegExMemSearcherAlgorithm implements MemorySearchAlgorithm {
|
|
||||||
|
|
||||||
private SearchInfo searchInfo;
|
|
||||||
private AddressSetView searchSet;
|
|
||||||
private Program program;
|
|
||||||
private boolean spanAddressGaps;
|
|
||||||
private int alignment;
|
|
||||||
private CodeUnitSearchInfo codeUnitSearchInfo;
|
|
||||||
|
|
||||||
public RegExMemSearcherAlgorithm(SearchInfo searchInfo, AddressSetView searchSet,
|
|
||||||
Program program, boolean searchAcrossAddressGaps) {
|
|
||||||
|
|
||||||
SearchData data = searchInfo.getSearchData();
|
|
||||||
if (!(data instanceof RegExSearchData)) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"The given SearchInfo does not contain a RegExSearchData");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.searchInfo = searchInfo;
|
|
||||||
this.searchSet = searchSet;
|
|
||||||
this.program = program;
|
|
||||||
this.spanAddressGaps = searchAcrossAddressGaps;
|
|
||||||
this.alignment = searchInfo.getAlignment();
|
|
||||||
this.codeUnitSearchInfo = searchInfo.getCodeUnitSearchInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void search(Accumulator<MemSearchResult> accumulator, TaskMonitor monitor) {
|
|
||||||
monitor.initialize(searchSet.getNumAddresses());
|
|
||||||
|
|
||||||
if (spanAddressGaps) {
|
|
||||||
searchAddressSet(searchSet, accumulator, monitor, 0);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
AddressRangeIterator rangeIterator = searchSet.getAddressRanges();
|
|
||||||
int progress = 0;
|
|
||||||
int searchLimit = searchInfo.getSearchLimit();
|
|
||||||
while (rangeIterator.hasNext()) {
|
|
||||||
AddressRange range = rangeIterator.next();
|
|
||||||
searchAddressSet(new AddressSet(range), accumulator, monitor, progress);
|
|
||||||
progress += (int) range.getLength();
|
|
||||||
monitor.setProgress(progress);
|
|
||||||
|
|
||||||
if (accumulator.size() >= searchLimit) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void searchAddressSet(AddressSetView addressSet,
|
|
||||||
Accumulator<MemSearchResult> accumulator, TaskMonitor monitor, int progressCount) {
|
|
||||||
|
|
||||||
if (addressSet.getNumAddresses() <= Integer.MAX_VALUE) {
|
|
||||||
searchSubAddressSet(addressSet, accumulator, monitor, progressCount);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
List<AddressSet> sets = breakSetsByMemoryBlock(addressSet);
|
|
||||||
int searchLimit = searchInfo.getSearchLimit();
|
|
||||||
for (AddressSet set : sets) {
|
|
||||||
searchSubAddressSet(set, accumulator, monitor, progressCount);
|
|
||||||
|
|
||||||
if (accumulator.size() >= searchLimit) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<AddressSet> breakSetsByMemoryBlock(AddressSetView addressSet) {
|
|
||||||
Memory mem = program.getMemory();
|
|
||||||
List<AddressSet> list = new ArrayList<>();
|
|
||||||
MemoryBlock[] blocks = mem.getBlocks();
|
|
||||||
for (MemoryBlock memoryBlock : blocks) {
|
|
||||||
|
|
||||||
AddressSet set =
|
|
||||||
addressSet.intersectRange(memoryBlock.getStart(), memoryBlock.getEnd());
|
|
||||||
if (!set.isEmpty()) {
|
|
||||||
list.add(set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void searchSubAddressSet(AddressSetView addressSet,
|
|
||||||
Accumulator<MemSearchResult> accumulator, TaskMonitor monitor, int progressCount) {
|
|
||||||
|
|
||||||
SearchData searchData = searchInfo.getSearchData();
|
|
||||||
Pattern pattern = ((RegExSearchData) searchData).getRegExPattern();
|
|
||||||
Memory memory = program.getMemory();
|
|
||||||
int searchLimit = searchInfo.getSearchLimit();
|
|
||||||
|
|
||||||
try {
|
|
||||||
MemoryAddressSetCharSequence charSet =
|
|
||||||
new MemoryAddressSetCharSequence(memory, addressSet);
|
|
||||||
Matcher matcher = pattern.matcher(charSet);
|
|
||||||
|
|
||||||
int searchFrom = 0;
|
|
||||||
while (matcher.find(searchFrom) && !monitor.isCancelled()) {
|
|
||||||
int startIndex = matcher.start();
|
|
||||||
int length = matcher.end() - startIndex;
|
|
||||||
Address address = charSet.getAddressAtIndex(startIndex);
|
|
||||||
if (isMatchingAddress(address, length)) {
|
|
||||||
|
|
||||||
MemSearchResult result = new MemSearchResult(address, length);
|
|
||||||
accumulator.add(result);
|
|
||||||
monitor.setProgress(progressCount + startIndex);
|
|
||||||
|
|
||||||
if (accumulator.size() >= searchLimit) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// move forward by one byte to check for matches within matches
|
|
||||||
searchFrom = startIndex + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (MemoryAccessException e) {
|
|
||||||
Msg.error(this, "Unexpected Exception: " + e.getMessage(), e);
|
|
||||||
monitor.setMessage("Error: Could not read memory");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isMatchingAddress(Address address, long matchSize) {
|
|
||||||
if (address == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((address.getOffset() % alignment) != 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (codeUnitSearchInfo.searchAll()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Listing listing = program.getListing();
|
|
||||||
CodeUnit codeUnit = listing.getCodeUnitContaining(address);
|
|
||||||
if (codeUnit instanceof Instruction) {
|
|
||||||
return codeUnitSearchInfo.isSearchInstructions();
|
|
||||||
}
|
|
||||||
else if (codeUnit instanceof Data) {
|
|
||||||
Data data = (Data) codeUnit;
|
|
||||||
if (data.isDefined()) {
|
|
||||||
return codeUnitSearchInfo.isSearchDefinedData();
|
|
||||||
}
|
|
||||||
return codeUnitSearchInfo.isSearchUndefinedData();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,133 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.util.search.memory;
|
|
||||||
|
|
||||||
import ghidra.app.plugin.core.searchmem.RegExSearchData;
|
|
||||||
import ghidra.app.plugin.core.searchmem.SearchData;
|
|
||||||
import ghidra.program.model.address.*;
|
|
||||||
import ghidra.program.model.listing.Program;
|
|
||||||
import ghidra.program.model.mem.Memory;
|
|
||||||
import ghidra.program.util.ProgramSelection;
|
|
||||||
import ghidra.util.task.TaskListener;
|
|
||||||
|
|
||||||
public class SearchInfo {
|
|
||||||
private final SearchData searchData;
|
|
||||||
private int searchLimit;
|
|
||||||
private final boolean forwardSearch;
|
|
||||||
protected final boolean searchSelection;
|
|
||||||
private final int alignment;
|
|
||||||
private final CodeUnitSearchInfo codeUnitSearchInfo;
|
|
||||||
private final TaskListener listener;
|
|
||||||
protected final boolean includeNonLoadedBlocks;
|
|
||||||
|
|
||||||
public SearchInfo(SearchData searchData, int matchLimit, boolean searchSelection,
|
|
||||||
boolean forwardSearch, int alignment, boolean includeNonLoadedBlocks,
|
|
||||||
TaskListener listener) {
|
|
||||||
this(searchData, matchLimit, searchSelection, forwardSearch, alignment,
|
|
||||||
includeNonLoadedBlocks, new CodeUnitSearchInfo(true, true, true), listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SearchInfo(SearchData searchData, int searchLimit, boolean searchSelection,
|
|
||||||
boolean forwardSearch, int alignment, boolean includeNonLoadedBlocks,
|
|
||||||
CodeUnitSearchInfo codeUnitSearchInfo, TaskListener listener) {
|
|
||||||
this.searchData = searchData;
|
|
||||||
this.searchLimit = searchLimit;
|
|
||||||
this.searchSelection = searchSelection;
|
|
||||||
this.forwardSearch = forwardSearch;
|
|
||||||
this.alignment = alignment;
|
|
||||||
this.listener = listener;
|
|
||||||
this.codeUnitSearchInfo = codeUnitSearchInfo;
|
|
||||||
this.includeNonLoadedBlocks = includeNonLoadedBlocks;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate an address set which only includes initialized memory
|
|
||||||
*
|
|
||||||
* @param program the program
|
|
||||||
* @param startAddress starting point for search or null to start from the top of memory
|
|
||||||
* @param selection addresses to be searched or null to search all memory
|
|
||||||
* @return searchable address set
|
|
||||||
*/
|
|
||||||
protected AddressSetView getSearchableAddressSet(Program program, Address startAddress,
|
|
||||||
ProgramSelection selection) {
|
|
||||||
|
|
||||||
if (startAddress == null) {
|
|
||||||
return new AddressSet(); // special case if we are at the first address going backwards
|
|
||||||
// or the last address going forwards
|
|
||||||
}
|
|
||||||
|
|
||||||
Memory memory = program.getMemory();
|
|
||||||
AddressSetView set = includeNonLoadedBlocks ? memory.getAllInitializedAddressSet()
|
|
||||||
: memory.getLoadedAndInitializedAddressSet();
|
|
||||||
if (searchSelection && selection != null && !selection.isEmpty()) {
|
|
||||||
set = set.intersect(selection);
|
|
||||||
}
|
|
||||||
Address start = forwardSearch ? startAddress : memory.getMinAddress();
|
|
||||||
Address end = forwardSearch ? memory.getMaxAddress() : startAddress;
|
|
||||||
if (start.compareTo(end) > 0) {
|
|
||||||
return new AddressSet();
|
|
||||||
}
|
|
||||||
AddressSet addressSet = program.getAddressFactory().getAddressSet(start, end);
|
|
||||||
return set.intersect(addressSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MemorySearchAlgorithm createSearchAlgorithm(Program p, Address start,
|
|
||||||
ProgramSelection selection) {
|
|
||||||
|
|
||||||
AddressSetView asView = getSearchableAddressSet(p, start, selection);
|
|
||||||
|
|
||||||
// note: this should probably be true--is there a reason not to do this?
|
|
||||||
// -also, shouldn't the non-regex searcher cross 'gaps' as well?
|
|
||||||
boolean searchAcrossGaps = false;
|
|
||||||
|
|
||||||
if (searchData instanceof RegExSearchData) {
|
|
||||||
return new RegExMemSearcherAlgorithm(this, asView, p, searchAcrossGaps);
|
|
||||||
}
|
|
||||||
return new MemSearcherAlgorithm(this, asView, p);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSearchForward() {
|
|
||||||
return forwardSearch;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSearchAll() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getAlignment() {
|
|
||||||
return alignment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TaskListener getListener() {
|
|
||||||
return listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SearchData getSearchData() {
|
|
||||||
return searchData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CodeUnitSearchInfo getCodeUnitSearchInfo() {
|
|
||||||
return codeUnitSearchInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSearchLimit() {
|
|
||||||
return searchLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSearchLimit(int searchLimit) {
|
|
||||||
this.searchLimit = searchLimit;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -160,25 +160,6 @@ public class GhidraScriptRealProgramTest extends AbstractGhidraHeadedIntegration
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFindBytesAcrossGap() throws Exception {
|
|
||||||
GhidraScript script = getScript();
|
|
||||||
|
|
||||||
AddressSet set = new AddressSet();
|
|
||||||
|
|
||||||
//Match charAt 0x010064db, 0x010064df
|
|
||||||
set.addRange(script.toAddr(0x10064d5), script.toAddr(0x010064db));
|
|
||||||
set.addRange(script.toAddr(0x010064df), script.toAddr(0x010064e3));
|
|
||||||
|
|
||||||
String byteString = "\\x51\\x52";
|
|
||||||
|
|
||||||
Address[] results = script.findBytes(set, byteString, 20, 1, true);
|
|
||||||
|
|
||||||
assertEquals(1, results.length);
|
|
||||||
assertEquals(script.toAddr(0x010064db), results[0]);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFindText() throws Exception {
|
public void testFindText() throws Exception {
|
||||||
GhidraScript script = getScript();
|
GhidraScript script = getScript();
|
||||||
|
|
|
@ -26,8 +26,8 @@ import ghidra.app.events.ProgramSelectionPluginEvent;
|
||||||
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
||||||
import ghidra.app.plugin.core.marker.MarkerManagerPlugin;
|
import ghidra.app.plugin.core.marker.MarkerManagerPlugin;
|
||||||
import ghidra.app.plugin.core.programtree.ProgramTreePlugin;
|
import ghidra.app.plugin.core.programtree.ProgramTreePlugin;
|
||||||
import ghidra.app.plugin.core.searchmem.MemSearchPlugin;
|
|
||||||
import ghidra.app.services.ProgramManager;
|
import ghidra.app.services.ProgramManager;
|
||||||
|
import ghidra.features.base.memsearch.gui.MemorySearchPlugin;
|
||||||
import ghidra.features.base.memsearch.gui.MemorySearchProvider;
|
import ghidra.features.base.memsearch.gui.MemorySearchProvider;
|
||||||
import ghidra.features.base.memsearch.mnemonic.MnemonicSearchPlugin;
|
import ghidra.features.base.memsearch.mnemonic.MnemonicSearchPlugin;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
@ -58,7 +58,7 @@ public class MnemonicSearchPluginTest extends AbstractGhidraHeadedIntegrationTes
|
||||||
tool.addPlugin(ProgramTreePlugin.class.getName());
|
tool.addPlugin(ProgramTreePlugin.class.getName());
|
||||||
tool.addPlugin(CodeBrowserPlugin.class.getName());
|
tool.addPlugin(CodeBrowserPlugin.class.getName());
|
||||||
tool.addPlugin(MarkerManagerPlugin.class.getName());
|
tool.addPlugin(MarkerManagerPlugin.class.getName());
|
||||||
tool.addPlugin(MemSearchPlugin.class.getName());
|
tool.addPlugin(MemorySearchPlugin.class.getName());
|
||||||
tool.addPlugin(MnemonicSearchPlugin.class.getName());
|
tool.addPlugin(MnemonicSearchPlugin.class.getName());
|
||||||
plugin = env.getPlugin(MnemonicSearchPlugin.class);
|
plugin = env.getPlugin(MnemonicSearchPlugin.class);
|
||||||
cb = env.getPlugin(CodeBrowserPlugin.class);
|
cb = env.getPlugin(CodeBrowserPlugin.class);
|
||||||
|
|
|
@ -1,97 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package ghidra.util.search;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import ghidra.app.plugin.core.searchmem.RegExSearchData;
|
|
||||||
import ghidra.program.database.ProgramBuilder;
|
|
||||||
import ghidra.program.model.address.AddressSetView;
|
|
||||||
import ghidra.program.model.listing.Program;
|
|
||||||
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
|
||||||
import ghidra.util.datastruct.ListAccumulator;
|
|
||||||
import ghidra.util.search.memory.*;
|
|
||||||
import ghidra.util.task.TaskMonitor;
|
|
||||||
|
|
||||||
public class RegExMemSearcherTaskTest extends AbstractGhidraHeadlessIntegrationTest {
|
|
||||||
|
|
||||||
private Program buildProgram() throws Exception {
|
|
||||||
ProgramBuilder builder = new ProgramBuilder("TestX86", ProgramBuilder._X86);
|
|
||||||
builder.createMemory(".text", Long.toHexString(0x1001000), 0x100);
|
|
||||||
builder.createMemory(".text1", Long.toHexString(0x1002000), 0x100);
|
|
||||||
return builder.getProgram();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Program buildLargeProgram() throws Exception {
|
|
||||||
ProgramBuilder builder = new ProgramBuilder("TestX86", ProgramBuilder._X86);
|
|
||||||
builder.createMemory(".text", Long.toHexString(0x1001000), Integer.MAX_VALUE);
|
|
||||||
builder.createMemory(".text1", Long.toHexString(0x1001000 + Integer.MAX_VALUE), 0x100);
|
|
||||||
return builder.getProgram();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFindMatchesWithinMatches() throws Exception {
|
|
||||||
|
|
||||||
Program p = buildProgram();
|
|
||||||
String regex = "\\x00\\x00\\x00\\x00";
|
|
||||||
RegExSearchData searchData = new RegExSearchData(regex);
|
|
||||||
int max = 50;
|
|
||||||
SearchInfo searchInfo = new SearchInfo(searchData, max, false, true, 1, false, null);
|
|
||||||
AddressSetView addrs = p.getMemory().getLoadedAndInitializedAddressSet();
|
|
||||||
|
|
||||||
RegExMemSearcherAlgorithm searcher =
|
|
||||||
new RegExMemSearcherAlgorithm(searchInfo, addrs, p, false);
|
|
||||||
|
|
||||||
ListAccumulator<MemSearchResult> accumulator = new ListAccumulator<>();
|
|
||||||
searcher.search(accumulator, TaskMonitor.DUMMY);
|
|
||||||
List<MemSearchResult> results = accumulator.asList();
|
|
||||||
|
|
||||||
assertEquals(max, results.size());
|
|
||||||
|
|
||||||
assertEquals(0x1001000, results.get(0).getAddress().getOffset());
|
|
||||||
assertEquals(0x1001001, results.get(1).getAddress().getOffset());
|
|
||||||
assertEquals(0x1001002, results.get(2).getAddress().getOffset());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFindMatchesWithinMatchesLargeProgram() throws Exception {
|
|
||||||
|
|
||||||
Program p = buildLargeProgram();
|
|
||||||
String regex = "\\x00\\x00\\x00\\x00";
|
|
||||||
RegExSearchData searchData = new RegExSearchData(regex);
|
|
||||||
int max = 50;
|
|
||||||
SearchInfo searchInfo = new SearchInfo(searchData, max, false, true, 1, false, null);
|
|
||||||
AddressSetView addrs = p.getMemory().getLoadedAndInitializedAddressSet();
|
|
||||||
|
|
||||||
RegExMemSearcherAlgorithm searcher =
|
|
||||||
new RegExMemSearcherAlgorithm(searchInfo, addrs, p, false);
|
|
||||||
|
|
||||||
ListAccumulator<MemSearchResult> accumulator = new ListAccumulator<>();
|
|
||||||
searcher.search(accumulator, TaskMonitor.DUMMY);
|
|
||||||
List<MemSearchResult> results = accumulator.asList();
|
|
||||||
|
|
||||||
assertEquals(max, results.size());
|
|
||||||
|
|
||||||
assertEquals(0x1001000, results.get(0).getAddress().getOffset());
|
|
||||||
assertEquals(0x1001001, results.get(1).getAddress().getOffset());
|
|
||||||
assertEquals(0x1001002, results.get(2).getAddress().getOffset());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -19,20 +19,26 @@ import java.util.List;
|
||||||
|
|
||||||
import ghidra.app.cmd.data.exceptionhandling.CreateEHFuncInfoBackgroundCmd;
|
import ghidra.app.cmd.data.exceptionhandling.CreateEHFuncInfoBackgroundCmd;
|
||||||
import ghidra.app.cmd.data.exceptionhandling.EHFunctionInfoModel;
|
import ghidra.app.cmd.data.exceptionhandling.EHFunctionInfoModel;
|
||||||
import ghidra.app.plugin.core.searchmem.RegExSearchData;
|
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.app.util.datatype.microsoft.DataApplyOptions;
|
import ghidra.app.util.datatype.microsoft.DataApplyOptions;
|
||||||
import ghidra.app.util.datatype.microsoft.DataValidationOptions;
|
import ghidra.app.util.datatype.microsoft.DataValidationOptions;
|
||||||
import ghidra.app.util.importer.MessageLog;
|
import ghidra.app.util.importer.MessageLog;
|
||||||
|
import ghidra.features.base.memsearch.bytesource.AddressableByteSource;
|
||||||
|
import ghidra.features.base.memsearch.bytesource.ProgramByteSource;
|
||||||
|
import ghidra.features.base.memsearch.gui.SearchSettings;
|
||||||
|
import ghidra.features.base.memsearch.matcher.ByteMatcher;
|
||||||
|
import ghidra.features.base.memsearch.matcher.RegExByteMatcher;
|
||||||
|
import ghidra.features.base.memsearch.searcher.MemoryMatch;
|
||||||
|
import ghidra.features.base.memsearch.searcher.MemorySearcher;
|
||||||
import ghidra.framework.cmd.Command;
|
import ghidra.framework.cmd.Command;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.data.InvalidDataTypeException;
|
import ghidra.program.model.data.InvalidDataTypeException;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.program.model.mem.Memory;
|
||||||
import ghidra.program.model.mem.MemoryBlock;
|
import ghidra.program.model.mem.MemoryBlock;
|
||||||
import ghidra.program.util.ProgramMemoryUtil;
|
import ghidra.program.util.ProgramMemoryUtil;
|
||||||
import ghidra.util.datastruct.ListAccumulator;
|
import ghidra.util.datastruct.ListAccumulator;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.search.memory.*;
|
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -81,24 +87,26 @@ public class PEExceptionAnalyzer extends AbstractAnalyzer {
|
||||||
"[\\x20,\\x21,\\x22]\\x05\\x93[\\x19,\\x39,\\x59,\\x79,\\x99,\\xb9,\\xd9,\\xf9]";
|
"[\\x20,\\x21,\\x22]\\x05\\x93[\\x19,\\x39,\\x59,\\x79,\\x99,\\xb9,\\xd9,\\xf9]";
|
||||||
String bePattern =
|
String bePattern =
|
||||||
"[\\x19,\\x39,\\x59,\\x79,\\x99,\\xb9,\\xd9,\\xf9]\\x93\\x05[\\x20,\\x21,\\x22]";
|
"[\\x19,\\x39,\\x59,\\x79,\\x99,\\xb9,\\xd9,\\xf9]\\x93\\x05[\\x20,\\x21,\\x22]";
|
||||||
RegExSearchData regExSearchData = RegExSearchData.createRegExSearchData(
|
|
||||||
program.getLanguage().isBigEndian() ? bePattern : lePattern);
|
String pattern = program.getLanguage().isBigEndian() ? bePattern : lePattern;
|
||||||
int alignment = 4;
|
int alignment = 4;
|
||||||
SearchInfo searchInfo = new SearchInfo(regExSearchData, MATCH_LIMIT, false, true, alignment,
|
|
||||||
false, new CodeUnitSearchInfo(false, true, true), null);
|
|
||||||
|
|
||||||
// Only want to search loaded and initialized addresses.
|
SearchSettings settings = new SearchSettings().withAlignment(alignment);
|
||||||
AddressSet intersection =
|
settings = settings.withIncludeInstructions(false); // only search data
|
||||||
program.getMemory().getLoadedAndInitializedAddressSet().intersect(set);
|
|
||||||
|
ByteMatcher matcher = new RegExByteMatcher(pattern, settings);
|
||||||
|
AddressableByteSource byteSource = new ProgramByteSource(program);
|
||||||
|
Memory memory = program.getMemory();
|
||||||
|
AddressSet addresses = memory.getLoadedAndInitializedAddressSet().intersect(set);
|
||||||
|
|
||||||
// Only want to search exception handling memory blocks.
|
// Only want to search exception handling memory blocks.
|
||||||
intersection = getAddressSet(ehBlocks).intersect(intersection);
|
addresses = getAddressSet(ehBlocks).intersect(addresses);
|
||||||
|
|
||||||
RegExMemSearcherAlgorithm searcher =
|
MemorySearcher searcher = new MemorySearcher(byteSource, matcher, addresses, MATCH_LIMIT);
|
||||||
new RegExMemSearcherAlgorithm(searchInfo, intersection, program, true);
|
|
||||||
|
|
||||||
ListAccumulator<MemSearchResult> accumulator = new ListAccumulator<>();
|
ListAccumulator<MemoryMatch> accumulator = new ListAccumulator<>();
|
||||||
searcher.search(accumulator, monitor);
|
searcher.findAll(accumulator, monitor);
|
||||||
List<MemSearchResult> results = accumulator.asList();
|
List<MemoryMatch> results = accumulator.asList();
|
||||||
|
|
||||||
// Establish the options to use when creating the exception handling data.
|
// Establish the options to use when creating the exception handling data.
|
||||||
// For now these are fixed. Later these may need to come from analysis options.
|
// For now these are fixed. Later these may need to come from analysis options.
|
||||||
|
@ -109,14 +117,14 @@ public class PEExceptionAnalyzer extends AbstractAnalyzer {
|
||||||
|
|
||||||
// Attempt to create data at each address if it appears to be valid for the data type.
|
// Attempt to create data at each address if it appears to be valid for the data type.
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (MemSearchResult result : results) {
|
for (MemoryMatch match : results) {
|
||||||
|
|
||||||
monitor.setProgress(count++);
|
monitor.setProgress(count++);
|
||||||
if (monitor.isCancelled()) {
|
if (monitor.isCancelled()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Address address = result.getAddress();
|
Address address = match.getAddress();
|
||||||
if (address.getOffset() % alignment != 0) {
|
if (address.getOffset() % alignment != 0) {
|
||||||
continue; // Skip non-aligned addresses.
|
continue; // Skip non-aligned addresses.
|
||||||
}
|
}
|
||||||
|
@ -130,7 +138,7 @@ public class PEExceptionAnalyzer extends AbstractAnalyzer {
|
||||||
|
|
||||||
// Create FuncInfo data at the address of the magic number, if the data appears valid.
|
// Create FuncInfo data at the address of the magic number, if the data appears valid.
|
||||||
// This can also create associated exception handling data based on the options.
|
// This can also create associated exception handling data based on the options.
|
||||||
Command cmd =
|
Command<Program> cmd =
|
||||||
new CreateEHFuncInfoBackgroundCmd(address, validationOptions, applyOptions);
|
new CreateEHFuncInfoBackgroundCmd(address, validationOptions, applyOptions);
|
||||||
cmd.applyTo(program);
|
cmd.applyTo(program);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user