GP-1358 - Updated the Search Memory dialog to treat a single wildcard character as if 2 wildcards had been entered

Closes #3351
This commit is contained in:
dragonmacher
2021-10-06 17:04:33 -04:00
parent 7e2cd85eac
commit cd41a43ab8
4 changed files with 112 additions and 52 deletions
@@ -137,6 +137,19 @@
</TR> </TR>
</TBODY> </TBODY>
</TABLE> </TABLE>
<P><IMG alt="" border="0" src="../../shared/tip.png">
As a convenience, if a user enters a single wildcard value within the search text, then
the search string will be interpreted as if 2 consecutive wildcard characters were
entered, meaning to match any byte value.
</P>
<P>
Similarly, if the search string contains an odd number of characters, then a 0 is prepended
to the search string, based on the assumption that a single hex digit implies a leading
0 value.
</P>
</BLOCKQUOTE> </BLOCKQUOTE>
</BLOCKQUOTE> </BLOCKQUOTE>
@@ -15,12 +15,12 @@
*/ */
package ghidra.app.plugin.core.searchmem; package ghidra.app.plugin.core.searchmem;
import ghidra.util.HTMLUtilities;
import java.util.*; import java.util.*;
import javax.swing.event.ChangeListener; import javax.swing.event.ChangeListener;
import ghidra.util.HTMLUtilities;
public class HexSearchFormat extends SearchFormat { public class HexSearchFormat extends SearchFormat {
private static final String WILD_CARDS = ".?"; private static final String WILD_CARDS = ".?";
@@ -33,8 +33,8 @@ public class HexSearchFormat extends SearchFormat {
@Override @Override
public String getToolTip() { public String getToolTip() {
return HTMLUtilities.toHTML("Interpret value as a sequence of\n" return HTMLUtilities.toHTML("Interpret value as a sequence of\n" +
+ "hex numbers, separated by spaces.\n" + "Enter '*' or '?' for a wildcard match"); "hex numbers, separated by spaces.\n" + "Enter '.' or '?' for a wildcard match");
} }
@Override @Override
@@ -65,18 +65,32 @@ public class HexSearchFormat extends SearchFormat {
} }
private List<String> getByteStrings(String token) { private List<String> getByteStrings(String token) {
if (token.length() % 2 != 0) {
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; token = "0" + token;
} }
int n = token.length() / 2; int n = token.length() / 2;
List<String> list = new ArrayList<String>(n); List<String> list = new ArrayList<String>(n);
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
list.add(token.substring(i * 2, i * 2 + 2)); list.add(token.substring(i * 2, i * 2 + 2));
} }
return list; 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) { private boolean isValidHex(String str) {
if (str.length() > 16) { if (str.length() > 16) {
statusText = "Max group size exceeded. Enter <space> to add more."; statusText = "Max group size exceeded. Enter <space> to add more.";
@@ -92,19 +106,13 @@ public class HexSearchFormat extends SearchFormat {
} }
/** /**
* Returns the byte value to be used for the given hex bytes. Handles wildcard * Returns the byte value to be used for the given hex bytes. Handles wildcard characters by
* characters by return treating them as 0s. * return treating them as 0s.
*/ */
private byte getByte(String tok) { private byte getByte(String tok) {
char c1 = tok.charAt(0); char c1 = tok.charAt(0);
char c2 = tok.charAt(1); char c2 = tok.charAt(1);
if (WILD_CARDS.indexOf(c1) > 0) { // note: the hexValueOf() method will turn wildcard chars into 0s
c1 = '0';
}
if (WILD_CARDS.indexOf(c2) > 0) {
c2 = '0';
}
return (byte) (hexValueOf(c1) * 16 + hexValueOf(c2)); return (byte) (hexValueOf(c1) * 16 + hexValueOf(c2));
} }
@@ -117,13 +125,13 @@ public class HexSearchFormat extends SearchFormat {
char c2 = tok.charAt(1); char c2 = tok.charAt(1);
int index1 = WILD_CARDS.indexOf(c1); int index1 = WILD_CARDS.indexOf(c1);
int index2 = WILD_CARDS.indexOf(c2); int index2 = WILD_CARDS.indexOf(c2);
if ((index1 >= 0) && (index2 >= 0)) { if (index1 >= 0 && index2 >= 0) {
return (byte) 0x00; return (byte) 0x00;
} }
if ((index1 >= 0) && (index2 < 0)) { if (index1 >= 0 && index2 < 0) {
return (byte) 0x0F; return (byte) 0x0F;
} }
if ((index1 < 0) && (index2 >= 0)) { if (index1 < 0 && index2 >= 0) {
return (byte) 0xF0; return (byte) 0xF0;
} }
return (byte) 0xFF; return (byte) 0xFF;
@@ -142,8 +150,9 @@ public class HexSearchFormat extends SearchFormat {
else if ((c >= 'A') && (c <= 'F')) { else if ((c >= 'A') && (c <= 'F')) {
return c - 'A' + 10; return c - 'A' + 10;
} }
else else {
return 0; return 0;
}
} }
} }
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* 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.
@@ -26,24 +25,23 @@ public class SearchData {
private boolean isValidSearchData; private boolean isValidSearchData;
// valid input and search data with mask // valid input and search data with mask
protected SearchData( String inputString, byte[] searchBytes, byte[] mask ) { protected SearchData(String inputString, byte[] searchBytes, byte[] mask) {
this.isValidInputData = true; this.isValidInputData = true;
this.isValidSearchData = true; this.isValidSearchData = true;
this.inputString = inputString; this.inputString = inputString;
this.bytes = searchBytes == null ? new byte[0] : searchBytes; this.bytes = searchBytes == null ? new byte[0] : searchBytes;
this.mask = mask; this.mask = mask;
} }
// valid input, bad search data // valid input, bad search data
private SearchData( String errorMessage, boolean isValidInputData ) { private SearchData(String errorMessage, boolean isValidInputData) {
this.isValidInputData = isValidInputData; this.isValidInputData = isValidInputData;
this.isValidSearchData = false; this.isValidSearchData = false;
bytes = new byte[0]; bytes = new byte[0];
this.errorMessage = errorMessage; this.errorMessage = errorMessage;
} }
public static SearchData createSearchData(String inputString, public static SearchData createSearchData(String inputString, byte[] searchBytes, byte[] mask) {
byte[] searchBytes, byte[] mask) {
return new SearchData(inputString, searchBytes, mask); return new SearchData(inputString, searchBytes, mask);
} }
@@ -54,33 +52,39 @@ public class SearchData {
public static SearchData createInvalidInputSearchData(String errorMessage) { public static SearchData createInvalidInputSearchData(String errorMessage) {
return new SearchData(errorMessage, false); return new SearchData(errorMessage, false);
} }
public byte[] getBytes() { public byte[] getBytes() {
return bytes; return bytes;
} }
public byte[] getMask() { public byte[] getMask() {
return mask; return mask;
} }
public boolean isValidInputData() { public boolean isValidInputData() {
return isValidInputData; return isValidInputData;
} }
public boolean isValidSearchData() { public boolean isValidSearchData() {
return isValidSearchData; return isValidSearchData;
} }
public String getInputString() { public String getInputString() {
return inputString; return inputString;
} }
public String getStatusMessage() { public String getStatusMessage() {
return errorMessage; return errorMessage;
} }
public String getHexString() { public String getHexString() {
StringBuffer buf = new StringBuffer(); StringBuilder buf = new StringBuilder();
for(int i=0;i<bytes.length;i++) { for (byte element : bytes) {
String hexString = Integer.toHexString(bytes[i] & 0xff); String hexString = Integer.toHexString(element & 0xff);
if ( hexString.length() == 1 ) { if (hexString.length() == 1) {
buf.append( "0" ); buf.append("0");
} }
buf.append( hexString ); buf.append(hexString);
buf.append(" "); buf.append(" ");
} }
return buf.toString(); return buf.toString();
@@ -68,8 +68,7 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
MemoryBlock overlayBlock = builder.createOverlayMemory("otherOverlay", "OTHER:0", 100); MemoryBlock overlayBlock = builder.createOverlayMemory("otherOverlay", "OTHER:0", 100);
//create and disassemble a function //create and disassemble a function
builder.setBytes( builder.setBytes("0x01002cf5",
"0x01002cf5",
"55 8b ec 83 7d 14 00 56 8b 35 e0 10 00 01 57 74 09 ff 75 14 ff d6 8b f8 eb 02 33 " + "55 8b ec 83 7d 14 00 56 8b 35 e0 10 00 01 57 74 09 ff 75 14 ff d6 8b f8 eb 02 33 " +
"ff ff 75 10 ff d6 03 c7 8d 44 00 02 50 6a 40 ff 15 dc 10 00 01 8b f0 85 f6 74 27 " + "ff ff 75 10 ff d6 03 c7 8d 44 00 02 50 6a 40 ff 15 dc 10 00 01 8b f0 85 f6 74 27 " +
"56 ff 75 14 ff 75 10 e8 5c ff ff ff ff 75 18 ff 75 0c 56 ff 75 08 ff 15 04 12 00 " + "56 ff 75 14 ff 75 10 e8 5c ff ff ff ff 75 18 ff 75 0c 56 ff 75 08 ff 15 04 12 00 " +
@@ -93,7 +92,8 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
builder.setBytes("0x1001004", "85 4f dc 77"); builder.setBytes("0x1001004", "85 4f dc 77");
builder.applyDataType("0x1001004", new Pointer32DataType(), 1); builder.applyDataType("0x1001004", new Pointer32DataType(), 1);
builder.createEncodedString("0x01001708", "Notepad", StandardCharsets.UTF_16BE, true); builder.createEncodedString("0x01001708", "Notepad", StandardCharsets.UTF_16BE, true);
builder.createEncodedString("0x01001740", "something else", StandardCharsets.UTF_16BE, true); builder.createEncodedString("0x01001740", "something else", StandardCharsets.UTF_16BE,
true);
builder.createEncodedString("0x010013cc", "notepad.exe", StandardCharsets.US_ASCII, true); builder.createEncodedString("0x010013cc", "notepad.exe", StandardCharsets.US_ASCII, true);
//create some undefined data //create some undefined data
@@ -117,7 +117,7 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
assertTrue(searchAction.isEnabled()); assertTrue(searchAction.isEnabled());
// dig up the components of the dialog // dig up the components of the dialog
dialog = waitForDialogComponent(tool.getToolFrame(), MemSearchDialog.class, 2000); dialog = waitForDialogComponent(MemSearchDialog.class);
assertNotNull(dialog); assertNotNull(dialog);
assertNotNull(valueComboBox); assertNotNull(valueComboBox);
assertNotNull(hexLabel); assertNotNull(hexLabel);
@@ -209,20 +209,20 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
//@formatter:off //@formatter:off
List<Address> addrs = addrs( List<Address> addrs = addrs(
0x01002d06, 0x01002d06,
0x01002d11, 0x01002d11,
0x01002d2c, 0x01002d2c,
0x01002d2f, 0x01002d2f,
0x01002d37, 0x01002d37,
0x01002d3a, 0x01002d3a,
0x01002d3e, 0x01002d3e,
0x01002d52, 0x01002d52,
0x01002d55, 0x01002d55,
0x01002d58, 0x01002d58,
0x01002d5b, 0x01002d5b,
0x010035fd, 0x010035fd,
0x01004122, 0x01004122,
0x01004202, 0x01004202,
0x01004249 0x01004249
); );
//@formatter:on //@formatter:on
@@ -353,7 +353,7 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
Highlight[] h = getByteHighlights(addr(0x10029bd), "ff 15 d4 10 00 01"); Highlight[] h = getByteHighlights(addr(0x10029bd), "ff 15 d4 10 00 01");
assertEquals(1, h.length); assertEquals(1, h.length);
assertEquals(15, h[0].getStart()); assertEquals(15, h[0].getStart());
// end is not important since the match crosses code units // end is not important since the match crosses code units
} }
@Test @Test
@@ -366,7 +366,7 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
Highlight[] h = getByteHighlights(addr(0x10029c3), "8b d8"); Highlight[] h = getByteHighlights(addr(0x10029c3), "8b d8");
assertEquals(1, h.length); assertEquals(1, h.length);
assertEquals(3, h[0].getStart()); assertEquals(3, h[0].getStart());
// end is not important since the match crosses code units // end is not important since the match crosses code units
} }
@@ -506,6 +506,40 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
performSearchTest(addrs, "Next"); performSearchTest(addrs, "Next");
} }
@Test
public void testHexWildcardSearch_SingleWildcardCharacter_QuestionMark() throws Exception {
//
// This tests that a single wildcard character will get converted to a value of '00' and
// a mast of 'FF'. This allows a single '?' character to be used in place of '??'.
//
goTo(0x01001000);
List<Address> addrs = addrs(0x01001004, 0x01002d27);
setValueText("85 ?");
performSearchTest(addrs, "Next");
}
@Test
public void testHexWildcardSearch_SingleWildcardCharacter_Dot() throws Exception {
//
// This tests that a single wildcard character will get converted to a value of '00' and
// a mast of 'FF'. This allows a single '.' character to be used in place of '..'.
//
goTo(0x01001000);
List<Address> addrs = addrs(0x01001004, 0x01002d27);
setValueText("85 .");
performSearchTest(addrs, "Next");
}
@Test @Test
public void testHexWildcardSearchBackwards() throws Exception { public void testHexWildcardSearchBackwards() throws Exception {