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

View file

@ -137,6 +137,19 @@
</TR>
</TBODY>
</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>

View file

@ -15,12 +15,12 @@
*/
package ghidra.app.plugin.core.searchmem;
import ghidra.util.HTMLUtilities;
import java.util.*;
import javax.swing.event.ChangeListener;
import ghidra.util.HTMLUtilities;
public class HexSearchFormat extends SearchFormat {
private static final String WILD_CARDS = ".?";
@ -33,8 +33,8 @@ public class HexSearchFormat extends SearchFormat {
@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");
return HTMLUtilities.toHTML("Interpret value as a sequence of\n" +
"hex numbers, separated by spaces.\n" + "Enter '.' or '?' for a wildcard match");
}
@Override
@ -65,18 +65,32 @@ public class HexSearchFormat extends SearchFormat {
}
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;
}
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.";
@ -92,19 +106,13 @@ public class HexSearchFormat extends SearchFormat {
}
/**
* Returns the byte value to be used for the given hex bytes. Handles wildcard
* characters by return treating them as 0s.
* 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);
if (WILD_CARDS.indexOf(c1) > 0) {
c1 = '0';
}
if (WILD_CARDS.indexOf(c2) > 0) {
c2 = '0';
}
// note: the hexValueOf() method will turn wildcard chars into 0s
return (byte) (hexValueOf(c1) * 16 + hexValueOf(c2));
}
@ -117,13 +125,13 @@ public class HexSearchFormat extends SearchFormat {
char c2 = tok.charAt(1);
int index1 = WILD_CARDS.indexOf(c1);
int index2 = WILD_CARDS.indexOf(c2);
if ((index1 >= 0) && (index2 >= 0)) {
if (index1 >= 0 && index2 >= 0) {
return (byte) 0x00;
}
if ((index1 >= 0) && (index2 < 0)) {
if (index1 >= 0 && index2 < 0) {
return (byte) 0x0F;
}
if ((index1 < 0) && (index2 >= 0)) {
if (index1 < 0 && index2 >= 0) {
return (byte) 0xF0;
}
return (byte) 0xFF;
@ -142,8 +150,9 @@ public class HexSearchFormat extends SearchFormat {
else if ((c >= 'A') && (c <= 'F')) {
return c - 'A' + 10;
}
else
else {
return 0;
}
}
}

View file

@ -1,6 +1,5 @@
/* ###
* 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.
@ -26,24 +25,23 @@ public class SearchData {
private boolean isValidSearchData;
// 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.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 ) {
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) {
public static SearchData createSearchData(String inputString, byte[] searchBytes, byte[] mask) {
return new SearchData(inputString, searchBytes, mask);
}
@ -54,33 +52,39 @@ public class SearchData {
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() {
StringBuffer buf = new StringBuffer();
for(int i=0;i<bytes.length;i++) {
String hexString = Integer.toHexString(bytes[i] & 0xff);
if ( hexString.length() == 1 ) {
buf.append( "0" );
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(hexString);
buf.append(" ");
}
return buf.toString();

View file

@ -68,8 +68,7 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
MemoryBlock overlayBlock = builder.createOverlayMemory("otherOverlay", "OTHER:0", 100);
//create and disassemble a function
builder.setBytes(
"0x01002cf5",
builder.setBytes("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 " +
"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 " +
@ -93,7 +92,8 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
builder.setBytes("0x1001004", "85 4f dc 77");
builder.applyDataType("0x1001004", new Pointer32DataType(), 1);
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);
//create some undefined data
@ -117,7 +117,7 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
assertTrue(searchAction.isEnabled());
// dig up the components of the dialog
dialog = waitForDialogComponent(tool.getToolFrame(), MemSearchDialog.class, 2000);
dialog = waitForDialogComponent(MemSearchDialog.class);
assertNotNull(dialog);
assertNotNull(valueComboBox);
assertNotNull(hexLabel);
@ -209,20 +209,20 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
//@formatter:off
List<Address> addrs = addrs(
0x01002d06,
0x01002d11,
0x01002d2c,
0x01002d2f,
0x01002d37,
0x01002d3a,
0x01002d3e,
0x01002d52,
0x01002d55,
0x01002d58,
0x01002d5b,
0x010035fd,
0x01004122,
0x01004202,
0x01002d06,
0x01002d11,
0x01002d2c,
0x01002d2f,
0x01002d37,
0x01002d3a,
0x01002d3e,
0x01002d52,
0x01002d55,
0x01002d58,
0x01002d5b,
0x010035fd,
0x01004122,
0x01004202,
0x01004249
);
//@formatter:on
@ -353,7 +353,7 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
Highlight[] h = getByteHighlights(addr(0x10029bd), "ff 15 d4 10 00 01");
assertEquals(1, h.length);
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
@ -366,7 +366,7 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
Highlight[] h = getByteHighlights(addr(0x10029c3), "8b d8");
assertEquals(1, h.length);
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");
}
@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
public void testHexWildcardSearchBackwards() throws Exception {