Merge remote-tracking branch 'origin/GP-5933_ghidragon_update_mem_searrch_to_support_mutliple_patterns--SQUASHED'

This commit is contained in:
Ryan Kurtz
2025-09-09 07:52:29 -04:00
9 changed files with 228 additions and 21 deletions

View File

@@ -27,14 +27,23 @@ import ghidra.features.base.memsearch.gui.SearchSettings;
*/
public abstract class ByteMatcher {
private final String name;
private final String input;
private final SearchSettings settings;
protected ByteMatcher(String input, SearchSettings settings) {
protected ByteMatcher(String name, String input, SearchSettings settings) {
this.name = name;
this.input = input;
this.settings = settings;
}
/**
* {@return the name of this byte matcher.}
*/
public String getName() {
return name;
}
/**
* Returns the original input text that generated this ByteMatacher.
* @return the original input text that generated this BytesMatcher
@@ -120,7 +129,10 @@ public abstract class ByteMatcher {
/**
* Record class to contain a match specification.
* @param start the offset in the buffer where the match starts
* @param length the length of the match
* @param matcher the matcher the found the match
*/
public record ByteMatch(int start, int length) {}
public record ByteMatch(int start, int length, ByteMatcher matcher) {}
}

View File

@@ -0,0 +1,98 @@
/* ###
* 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.features.base.memsearch.matcher;
import java.util.Iterator;
import java.util.List;
import ghidra.features.base.memsearch.bytesequence.ExtendedByteSequence;
import ghidra.features.base.memsearch.gui.SearchSettings;
import ghidra.features.base.memsearch.searcher.MemorySearcher;
/**
* A ByteMatcher that searches an input sequence for matches from multiple patterns. This is
* useful for using with the {@link MemorySearcher} so that multiple patterns can be searched with
* only one pass through memory, thus paying the memory I/O costs only once. The resulting matches
* will contain the sub ByteMatcher that matched so that it is easy to know which of the multiple
* patterns matched.
*/
public class CombinedByteMatcher extends ByteMatcher {
private List<ByteMatcher> matchers;
public CombinedByteMatcher(List<ByteMatcher> matchers, SearchSettings settings) {
super("Multi-Pattern Matcher", null, settings);
this.matchers = matchers;
}
@Override
public Iterable<ByteMatch> match(ExtendedByteSequence bytes) {
return new MultiMatcherIterator(bytes);
}
@Override
public String getDescription() {
return getName();
}
@Override
public String getToolTip() {
return null;
}
private class MultiMatcherIterator implements Iterable<ByteMatch>, Iterator<ByteMatch> {
private Iterator<ByteMatcher> matcherIterator;
private Iterator<ByteMatch> currentMatchIterator;
private ExtendedByteSequence bytes;
MultiMatcherIterator(ExtendedByteSequence bytes) {
this.bytes = bytes;
matcherIterator = matchers.iterator();
currentMatchIterator = getNextMatchIterator();
}
@Override
public boolean hasNext() {
while (currentMatchIterator != null && !currentMatchIterator.hasNext()) {
currentMatchIterator = getNextMatchIterator();
}
return currentMatchIterator != null;
}
private Iterator<ByteMatch> getNextMatchIterator() {
if (matcherIterator.hasNext()) {
ByteMatcher matcher = matcherIterator.next();
return matcher.match(bytes).iterator();
}
return null;
}
@Override
public ByteMatch next() {
if (hasNext()) {
return currentMatchIterator.next();
}
return null;
}
@Override
public Iterator<ByteMatch> iterator() {
return this;
}
}
}

View File

@@ -50,7 +50,7 @@ public class InvalidByteMatcher extends ByteMatcher {
* a negative number.
*/
public InvalidByteMatcher(String errorMessage, boolean isValidInput) {
super(null, null);
super("Invalid", null, null);
this.errorMessage = errorMessage;
this.isValidInput = isValidInput;
}

View File

@@ -52,7 +52,7 @@ public class MaskedByteSequenceByteMatcher extends ByteMatcher {
*/
public MaskedByteSequenceByteMatcher(String input, byte[] bytes, byte[] masks,
SearchSettings settings) {
super(input, settings);
super("Masked Byte Sequence Matcher", input, settings);
if (masks == null) {
masks = new byte[bytes.length];
@@ -145,7 +145,8 @@ public class MaskedByteSequenceByteMatcher extends ByteMatcher {
while (nextPossibleStart >= 0) {
startIndex = nextPossibleStart + 1;
if (isValidMatch(nextPossibleStart)) {
return new ByteMatch(nextPossibleStart, searchBytes.length);
return new ByteMatch(nextPossibleStart, searchBytes.length,
MaskedByteSequenceByteMatcher.this);
}
nextPossibleStart = findNextPossibleStart(startIndex);
}

View File

@@ -32,7 +32,11 @@ public class RegExByteMatcher extends ByteMatcher {
private final Pattern pattern;
public RegExByteMatcher(String input, SearchSettings settings) {
super(input, settings);
this("Regex Matcher", input, settings);
}
public RegExByteMatcher(String name, String input, SearchSettings settings) {
super(name, input, settings);
// without DOTALL mode, bytes that match line terminator characters will cause
// the regular expression pattern to not match.
this.pattern = Pattern.compile(input, Pattern.DOTALL);
@@ -133,7 +137,7 @@ public class RegExByteMatcher extends ByteMatcher {
if (start >= byteSequence.getLength()) {
return null;
}
return new ByteMatch(start, end - start);
return new ByteMatch(start, end - start, RegExByteMatcher.this);
}
}

View File

@@ -246,7 +246,7 @@ public class MemorySearcher {
for (ByteMatch byteMatch : matcher.match(searchSequence)) {
Address address = searchBytes.getAddress(byteMatch.start());
byte[] bytes = searchSequence.getBytes(byteMatch.start(), byteMatch.length());
MemoryMatch match = new MemoryMatch(address, bytes, matcher);
MemoryMatch match = new MemoryMatch(address, bytes, byteMatch.matcher());
if (filter.test(match)) {
return match;
}
@@ -269,7 +269,7 @@ public class MemorySearcher {
for (ByteMatch byteMatch : matcher.match(searchSequence)) {
Address address = searchBytes.getAddress(byteMatch.start());
byte[] bytes = searchSequence.getBytes(byteMatch.start(), byteMatch.length());
MemoryMatch match = new MemoryMatch(address, bytes, matcher);
MemoryMatch match = new MemoryMatch(address, bytes, byteMatch.matcher());
if (filter.test(match)) {
last = match;
}
@@ -316,7 +316,7 @@ public class MemorySearcher {
for (ByteMatch byteMatch : matcher.match(searchSequence)) {
Address address = searchBytes.getAddress(byteMatch.start());
byte[] bytes = searchSequence.getBytes(byteMatch.start(), byteMatch.length());
MemoryMatch match = new MemoryMatch(address, bytes, matcher);
MemoryMatch match = new MemoryMatch(address, bytes, byteMatch.matcher());
if (filter.test(match)) {
if (accumulator.size() >= searchLimit) {
return false;

View File

@@ -0,0 +1,92 @@
/* ###
* 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.features.base.memsearch.bytesequence;
import static org.junit.Assert.*;
import java.util.List;
import java.util.stream.StreamSupport;
import org.junit.Before;
import org.junit.Test;
import ghidra.features.base.memsearch.matcher.*;
import ghidra.features.base.memsearch.matcher.ByteMatcher.ByteMatch;
public class CombinedByteMatcherTest {
private ByteMatcher xxxByteMatcher;
private ByteMatcher yyyByteMatcher;
private ByteMatcher zzzByteMatcher;
private CombinedByteMatcher multiMatcher;
@Before
public void setUp() {
xxxByteMatcher = new RegExByteMatcher("xxx", null);
yyyByteMatcher = new RegExByteMatcher("yyy", null);
zzzByteMatcher = new RegExByteMatcher("zzz", null);
multiMatcher =
new CombinedByteMatcher(List.of(xxxByteMatcher, yyyByteMatcher, zzzByteMatcher), null);
}
@Test
public void textFindsOneEachPatterns() {
List<ByteMatch> results = match("fooxxxbar, fooyyybar, foozzzbar");
assertEquals(3, results.size());
assertEquals(new ByteMatch(3, 3, xxxByteMatcher), results.get(0));
assertEquals(new ByteMatch(15, 3, yyyByteMatcher), results.get(1));
assertEquals(new ByteMatch(26, 3, zzzByteMatcher), results.get(2));
}
@Test
public void textFindsMutliplePatterns() {
List<ByteMatch> results = match("xxxyyyzzzxxxyyyzzz");
assertEquals(6, results.size());
assertEquals(new ByteMatch(0, 3, xxxByteMatcher), results.get(0));
assertEquals(new ByteMatch(9, 3, xxxByteMatcher), results.get(1));
assertEquals(new ByteMatch(3, 3, yyyByteMatcher), results.get(2));
assertEquals(new ByteMatch(12, 3, yyyByteMatcher), results.get(3));
assertEquals(new ByteMatch(6, 3, zzzByteMatcher), results.get(4));
assertEquals(new ByteMatch(15, 3, zzzByteMatcher), results.get(5));
}
@Test
public void testNoMatches() {
List<ByteMatch> results = match("There are no matches here!");
assertEquals(0, results.size());
}
private List<ByteMatch> match(String s) {
ExtendedByteSequence sequence = createByteSequence(s);
Iterable<ByteMatch> match = multiMatcher.match(sequence);
return StreamSupport.stream(match.spliterator(), false).toList();
}
private ExtendedByteSequence createByteSequence(String s) {
ByteSequence main = new ByteArrayByteSequence(makeBytes(s));
ByteSequence extra = new ByteArrayByteSequence(makeBytes(""));
return new ExtendedByteSequence(main, extra, 0);
}
private byte[] makeBytes(String string) {
byte[] bytes = new byte[string.length()];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) string.charAt(i);
}
return bytes;
}
}

View File

@@ -49,10 +49,10 @@ public class MaskedBytesSequenceByteMatcherTest {
Iterator<ByteMatch> it = byteMatcher.match(byteSequence).iterator();
assertTrue(it.hasNext());
assertEquals(new ByteMatch(2, 3), it.next());
assertEquals(new ByteMatch(2, 3, byteMatcher), it.next());
assertTrue(it.hasNext());
assertEquals(new ByteMatch(9, 3), it.next());
assertEquals(new ByteMatch(9, 3, byteMatcher), it.next());
assertFalse(it.hasNext());
@@ -66,8 +66,8 @@ public class MaskedBytesSequenceByteMatcherTest {
Iterator<ByteMatch> it = byteMatcher.match(byteSequence).iterator();
assertEquals(new ByteMatch(2, 3), it.next());
assertEquals(new ByteMatch(9, 3), it.next());
assertEquals(new ByteMatch(2, 3, byteMatcher), it.next());
assertEquals(new ByteMatch(9, 3, byteMatcher), it.next());
assertNull(it.next());
}
@@ -81,9 +81,9 @@ public class MaskedBytesSequenceByteMatcherTest {
Iterator<ByteMatch> it = byteMatcher.match(byteSequence).iterator();
assertEquals(new ByteMatch(1, 3), it.next());
assertEquals(new ByteMatch(6, 3), it.next());
assertEquals(new ByteMatch(8, 3), it.next());
assertEquals(new ByteMatch(1, 3, byteMatcher), it.next());
assertEquals(new ByteMatch(6, 3, byteMatcher), it.next());
assertEquals(new ByteMatch(8, 3, byteMatcher), it.next());
assertNull(it.next());
}

View File

@@ -45,10 +45,10 @@ public class RegExByteMatcherTest {
Iterator<ByteMatch> it = byteMatcher.match(byteSequence).iterator();
assertTrue(it.hasNext());
assertEquals(new ByteMatch(4, 3), it.next());
assertEquals(new ByteMatch(4, 3, byteMatcher), it.next());
assertTrue(it.hasNext());
assertEquals(new ByteMatch(14, 3), it.next());
assertEquals(new ByteMatch(14, 3, byteMatcher), it.next());
assertFalse(it.hasNext());
@@ -61,8 +61,8 @@ public class RegExByteMatcherTest {
Iterator<ByteMatch> it = byteMatcher.match(byteSequence).iterator();
assertEquals(new ByteMatch(4, 3), it.next());
assertEquals(new ByteMatch(14, 3), it.next());
assertEquals(new ByteMatch(4, 3, byteMatcher), it.next());
assertEquals(new ByteMatch(14, 3, byteMatcher), it.next());
assertNull(it.next());
}