diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchResultsPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchResultsPanel.java index ebdd6d227a..c69209954f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchResultsPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchResultsPanel.java @@ -227,7 +227,8 @@ public class MemorySearchResultsPanel extends JPanel { catch (Throwable t) { // Catch any runtime errors so that we exit task gracefully and don't leave // the provider in a stuck "busy" state. - Msg.showError(this, null, "Unexpected error refreshing bytes", t); + Msg.showError(this, null, "Error Searching Memory", + "Unexpected error searching for bytes", t); } Swing.runLater(() -> provider.searchOnceCompleted(null, monitor.isCancelled())); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/RegExByteMatcher.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/RegExByteMatcher.java index a7f9beff02..40af5da0a7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/RegExByteMatcher.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/RegExByteMatcher.java @@ -69,19 +69,24 @@ public class RegExByteMatcher extends UserInputByteMatcher { private class ByteCharSequence implements CharSequence { private ExtendedByteSequence byteSequence; + private int preLength; ByteCharSequence(ExtendedByteSequence byteSequence) { this.byteSequence = byteSequence; + preLength = byteSequence.getPreLength(); } @Override public int length() { - return byteSequence.getExtendedLength(); + return byteSequence.getExtendedLength() + preLength; } @Override public char charAt(int index) { - byte b = byteSequence.getByte(index); + // Our charSequence starts at the beginning of any pre-bytes. + // The iterator using this sequence will have to make the opposite translation when + // interpreting offsets into this sequence as reported by the pattern matcher. + byte b = byteSequence.getByte(index - preLength); return (char) (b & 0xff); } @@ -131,15 +136,22 @@ public class RegExByteMatcher extends UserInputByteMatcher { } private Match findNextMatch() { - if (!matcher.find()) { - return null; + int preLength = byteSequence.getPreLength(); + + // loop until we find a match that starts past the pre-bytes. + // we are scanning the pre-bytes in case the regEx has any look-behind, but we + // really only want matches that start in the main range. + while (matcher.find()) { + int start = matcher.start() - preLength; + int end = matcher.end() - preLength; + if (start >= 0) { + if (start >= byteSequence.getLength()) { + return null; // we are past the end of the main byte sequence, so done + } + return new Match<>(searchData, start, end - start); + } } - int start = matcher.start(); - int end = matcher.end(); - if (start >= byteSequence.getLength()) { - return null; - } - return new Match<>(searchData, start, end - start); + return null; // no matches so we are done with this buffer } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/SearchData.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/SearchData.java index 0e5f679a20..d490b5bb34 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/SearchData.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/SearchData.java @@ -42,6 +42,11 @@ public class SearchData { return settings; } + @Override + public String toString() { + return input; + } + @Override public int hashCode() { return Objects.hash(input, settings); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/MemorySearcher.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/MemorySearcher.java index b49ab947d7..522beb742b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/MemorySearcher.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/MemorySearcher.java @@ -39,8 +39,9 @@ import ghidra.util.task.TaskMonitor; public class MemorySearcher { private static final int DEFAULT_CHUNK_SIZE = 16 * 1024; private static final int OVERLAP_SIZE = 100; - private final AddressableByteSequence bytes1; - private final AddressableByteSequence bytes2; + private AddressableByteSequence preBytes; + private AddressableByteSequence mainBytes; + private AddressableByteSequence postBytes; private final ByteMatcher matcher; private final int chunkSize; @@ -75,8 +76,9 @@ public class MemorySearcher { this.searchLimit = searchLimit; this.chunkSize = chunkSize; - bytes1 = new AddressableByteSequence(byteSource, chunkSize); - bytes2 = new AddressableByteSequence(byteSource, chunkSize); + preBytes = new AddressableByteSequence(byteSource, chunkSize); + mainBytes = new AddressableByteSequence(byteSource, chunkSize); + postBytes = new AddressableByteSequence(byteSource, chunkSize); } /** @@ -172,40 +174,36 @@ public class MemorySearcher { } private MemoryMatch findFirst(AddressRange range, TaskMonitor monitor) { - AddressableByteSequence searchBytes = bytes1; - AddressableByteSequence extra = bytes2; + preBytes.clear(); + mainBytes.clear(); + postBytes.clear(); AddressRangeIterator it = new AddressRangeSplitter(range, chunkSize, true); AddressRange first = it.next(); - searchBytes.setRange(first); + mainBytes.setRange(first); while (it.hasNext()) { AddressRange next = it.next(); - extra.setRange(next); + postBytes.setRange(next); - MemoryMatch match = findFirst(searchBytes, extra, monitor); + MemoryMatch match = findFirst(monitor); if (match != null) { return match; } if (monitor.isCancelled()) { break; } - - // Flip flop the byte buffers, making the extended buffer become primary and preparing - // the primary buffer to be used to read the next chunk. See the - // ExtendedByteSequence class for an explanation of this approach. - searchBytes = extra; - extra = searchBytes == bytes1 ? bytes2 : bytes1; + rotateBuffers(); // main becomes pre, post becomes main, and pre becomes post } // last segment, no extra bytes to overlap, so just search the primary buffer - extra.clear(); - return findFirst(searchBytes, extra, monitor); + postBytes.clear(); + return findFirst(monitor); } private MemoryMatch findLast(AddressRange range, Address start, TaskMonitor monitor) { - AddressableByteSequence searchBytes = bytes1; - AddressableByteSequence extra = bytes2; - extra.clear(); + preBytes.clear(); + mainBytes.clear(); + postBytes.clear(); if (range.contains(start)) { Address min = range.getMinAddress(); @@ -213,40 +211,36 @@ public class MemorySearcher { range = new AddressRangeImpl(min, start); AddressRange remaining = new AddressRangeImpl(start.next(), max); AddressRange extraRange = new AddressRangeSplitter(remaining, chunkSize, true).next(); - extra.setRange(extraRange); + postBytes.setRange(extraRange); } AddressRangeIterator it = new AddressRangeSplitter(range, chunkSize, false); + mainBytes.setRange(it.next()); while (it.hasNext()) { AddressRange next = it.next(); - searchBytes.setRange(next); - MemoryMatch match = findLast(searchBytes, extra, monitor); + preBytes.setRange(next); + MemoryMatch match = findLast(monitor); if (match != null) { return match; } if (monitor.isCancelled()) { break; } - - // Flip flop the byte buffers, making the primary buffer the new extended buffer - // and refilling the primary buffer with new data going backwards. - extra = searchBytes; - searchBytes = extra == bytes1 ? bytes2 : bytes1; + rotateBuffersBackwards(); // main becomes post, pre becomes main, post becomes pre } - return null; + preBytes.clear(); + return findLast(monitor); } - private MemoryMatch findFirst(AddressableByteSequence searchBytes, ByteSequence extra, - TaskMonitor monitor) { + private MemoryMatch findFirst(TaskMonitor monitor) { - ExtendedByteSequence searchSequence = - new ExtendedByteSequence(searchBytes, extra, OVERLAP_SIZE); + ExtendedByteSequence sequence = + new ExtendedByteSequence(mainBytes, preBytes, postBytes, OVERLAP_SIZE); - for (Match byteMatch : matcher.match(searchSequence)) { - Address address = searchBytes.getAddress((int) byteMatch.getStart()); - byte[] bytes = - searchSequence.getBytes((int) byteMatch.getStart(), byteMatch.getLength()); + for (Match byteMatch : matcher.match(sequence)) { + Address address = mainBytes.getAddress((int) byteMatch.getStart()); + byte[] bytes = sequence.getBytes((int) byteMatch.getStart(), byteMatch.getLength()); MemoryMatch match = new MemoryMatch<>(address, bytes, byteMatch.getPattern()); if (filter.test(match)) { return match; @@ -255,20 +249,19 @@ public class MemorySearcher { break; } } - monitor.incrementProgress(searchBytes.getLength()); + monitor.incrementProgress(mainBytes.getLength()); return null; } - private MemoryMatch findLast(AddressableByteSequence searchBytes, ByteSequence extra, - TaskMonitor monitor) { + private MemoryMatch findLast(TaskMonitor monitor) { MemoryMatch last = null; ExtendedByteSequence searchSequence = - new ExtendedByteSequence(searchBytes, extra, OVERLAP_SIZE); + new ExtendedByteSequence(mainBytes, preBytes, postBytes, OVERLAP_SIZE); for (Match byteMatch : matcher.match(searchSequence)) { - Address address = searchBytes.getAddress((int) byteMatch.getStart()); + Address address = mainBytes.getAddress((int) byteMatch.getStart()); byte[] bytes = searchSequence.getBytes((int) byteMatch.getStart(), byteMatch.getLength()); MemoryMatch match = new MemoryMatch<>(address, bytes, byteMatch.getPattern()); @@ -279,44 +272,58 @@ public class MemorySearcher { return null; } } - monitor.incrementProgress(searchBytes.getLength()); + monitor.incrementProgress(mainBytes.getLength()); return last; } private boolean findAll(Accumulator> accumulator, AddressRange range, TaskMonitor monitor) { - AddressableByteSequence searchBytes = bytes1; - AddressableByteSequence extra = bytes2; + preBytes.clear(); + mainBytes.clear(); + postBytes.clear(); AddressRangeIterator it = new AddressRangeSplitter(range, chunkSize, true); AddressRange first = it.next(); - searchBytes.setRange(first); + mainBytes.setRange(first); while (it.hasNext()) { AddressRange next = it.next(); - extra.setRange(next); - if (!findAll(accumulator, searchBytes, extra, monitor)) { + postBytes.setRange(next); + if (!findAllInBuffers(accumulator, monitor)) { return false; } - searchBytes = extra; - extra = searchBytes == bytes1 ? bytes2 : bytes1; + rotateBuffers(); // main becomes pre, post becomes main, and pre becomes post } - extra.clear(); - return findAll(accumulator, searchBytes, extra, monitor); + postBytes.clear(); + return findAllInBuffers(accumulator, monitor); } - private boolean findAll(Accumulator> accumulator, - AddressableByteSequence searchBytes, ByteSequence extra, TaskMonitor monitor) { + private void rotateBuffers() { + AddressableByteSequence tmp = preBytes; + preBytes = mainBytes; + mainBytes = postBytes; + postBytes = tmp; + } + + private void rotateBuffersBackwards() { + // moving backwards through memory + AddressableByteSequence tmp = postBytes; + postBytes = mainBytes; + mainBytes = preBytes; + preBytes = tmp; + } + + private boolean findAllInBuffers(Accumulator> accumulator, TaskMonitor monitor) { if (monitor.isCancelled()) { return false; } ExtendedByteSequence searchSequence = - new ExtendedByteSequence(searchBytes, extra, OVERLAP_SIZE); + new ExtendedByteSequence(mainBytes, preBytes, postBytes, OVERLAP_SIZE); for (Match match : matcher.match(searchSequence)) { - Address address = searchBytes.getAddress((int) match.getStart()); + Address address = mainBytes.getAddress((int) match.getStart()); byte[] bytes = searchSequence.getBytes((int) match.getStart(), match.getLength()); MemoryMatch memMatch = new MemoryMatch<>(address, bytes, match.getPattern()); if (filter.test(memMatch)) { @@ -333,7 +340,7 @@ public class MemorySearcher { // Reset the monitor message, since clients may change the message (such as the // incremental table loader) monitor.setMessage("Searching..."); - monitor.incrementProgress(searchBytes.getLength()); + monitor.incrementProgress(mainBytes.getLength()); return true; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/BulkPatternSearcher.java b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/BulkPatternSearcher.java index 3ea2704f7b..7debf06a54 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/BulkPatternSearcher.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/BulkPatternSearcher.java @@ -197,34 +197,37 @@ public class BulkPatternSearcher { // patterns will matched in any data in the stream past that point, but data past that point // may be used to complete a pattern. - InputStreamBufferByteSequence bufA = new InputStreamBufferByteSequence(bufSize); - InputStreamBufferByteSequence bufB = new InputStreamBufferByteSequence(bufSize); - InputStreamBufferByteSequence buf1 = bufA; - InputStreamBufferByteSequence buf2 = bufB; - buf1.load(restrictedStream, bufSize); - buf2.load(restrictedStream, bufSize); + InputStreamBufferByteSequence pre = new InputStreamBufferByteSequence(bufSize); + InputStreamBufferByteSequence main = new InputStreamBufferByteSequence(bufSize); + InputStreamBufferByteSequence post = new InputStreamBufferByteSequence(bufSize); + main.load(restrictedStream, bufSize); + post.load(restrictedStream, bufSize); - while (buf1.getLength() > 0 && buf2.getLength() > 0) { + while (main.getLength() > 0 && post.getLength() > 0) { if (monitor.isCancelled()) { return; } - ExtendedByteSequence combined = new ExtendedByteSequence(buf1, buf2, maxPatternLength); + ExtendedByteSequence combined = + new ExtendedByteSequence(main, pre, post, maxPatternLength); search(combined, results, offset); - monitor.incrementProgress(buf1.getLength()); - offset += buf1.getLength(); + monitor.incrementProgress(main.getLength()); + offset += main.getLength(); - // swap buffers and load data into second buffer - buf1 = buf2; - buf2 = buf1 == bufA ? bufB : bufA; - buf2.load(restrictedStream, bufSize); + // rotate buffers and load data into second buffer + InputStreamBufferByteSequence tmp = pre; + pre = main; + main = post; + post = tmp; + post.load(restrictedStream, bufSize); } // just have to read a bit more to finish last pattern and we go beyond restricted maxRead // so use unrestricted stream - buf2.load(inputStream, maxPatternLength); - ExtendedByteSequence combined = new ExtendedByteSequence(buf1, buf2, maxPatternLength); + post.load(inputStream, maxPatternLength); + ExtendedByteSequence combined = + new ExtendedByteSequence(main, pre, post, maxPatternLength); search(combined, results, offset); - monitor.incrementProgress(buf1.getLength()); + monitor.incrementProgress(main.getLength()); } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/ExtendedByteSequence.java b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/ExtendedByteSequence.java index dc265e579e..b3f54869e8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/ExtendedByteSequence.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/ExtendedByteSequence.java @@ -15,41 +15,54 @@ */ package ghidra.util.bytesearch; +import java.util.Objects; + /** * A class for accessing a contiguous sequence of bytes from some underlying byte source to * be used for searching for a byte pattern within the byte source. This sequence of bytes - * consists of two parts; the primary sequence and an extended sequence. Search matches - * must begin in the primary sequence, but may extend into the extended sequence. + * consists of three parts; the primary sequence, a pre sequence, and an extended sequence. + * Search matches must begin in the primary sequence, but may extend into the extended sequence. The + * pre-sequence is used for searching that supports look-behind such as some regular expressions. *

* Searching large ranges of memory can be partitioned into searching smaller chunks. But - * to handle search sequences that span chunks, two chunks are presented at a time, with the second - * chunk being the extended bytes. On the next iteration of the search loop, the extended chunk - * will become the primary chunk, with the next chunk after that becoming the extended sequence - * and so on. + * to handle search sequences that span chunks, three chunks are presented at a time. Look-behind + * patterns can use the pre-chunk to see the bytes before the main chunk. Actual matches must start + * in the main chunk, but can extend into the extended chunk. On the next iteration of the search + * loop, the main chunk becomes the pre-chunk and the extended chunk becomes the main chunk and a + * new post-chunk is read from the input source. */ public class ExtendedByteSequence implements ByteSequence { - - private ByteSequence main; - private ByteSequence extended; + private static final ByteSequence EMPTY = new EmptyByteSequence(); + private ByteSequence mainSequence; + private ByteSequence postSequence; + private ByteSequence preSequence; + private int mainLength; private int extendedLength; + private int preLength; /** * Constructs an extended byte sequence from two {@link ByteSequence}s. * @param main the byte sequence where search matches may start - * @param extended the byte sequence where search matches may extend into - * @param extendedLimit specifies how much of the extended byte sequence to allow search + * @param pre the byte sequence bytes before the main byte sequence used by searchers + * that support "look behind" + * @param post the byte sequence where search matches may extend into + * @param overlap specifies how much of the extended byte sequence to allow search * matches to extend into. (The extended buffer will be the primary buffer next time, so * it is a full size buffer, but we only need to use a portion of it to support overlap. */ - public ExtendedByteSequence(ByteSequence main, ByteSequence extended, int extendedLimit) { - this.main = main; - this.extended = extended; - this.extendedLength = main.getLength() + Math.min(extendedLimit, extended.getLength()); + public ExtendedByteSequence(ByteSequence main, ByteSequence pre, + ByteSequence post, int overlap) { + this.mainSequence = Objects.requireNonNull(main); + this.preSequence = Objects.requireNonNullElse(pre, EMPTY); + this.postSequence = Objects.requireNonNullElse(post, EMPTY); + this.mainLength = mainSequence.getLength(); + this.extendedLength = main.getLength() + Math.min(overlap, postSequence.getLength()); + this.preLength = Math.min(overlap, preSequence.getLength()); } @Override public int getLength() { - return main.getLength(); + return mainSequence.getLength(); } /** @@ -62,26 +75,34 @@ public class ExtendedByteSequence implements ByteSequence { return extendedLength; } + public int getPreLength() { + return preLength; + } + @Override public byte getByte(int i) { - int mainLength = main.getLength(); - if (i >= mainLength) { - return extended.getByte(i - mainLength); + if (i < 0) { + return preSequence.getByte(i + preLength); } - return main.getByte(i); + if (i >= mainLength) { + return postSequence.getByte(i - mainLength); + } + return mainSequence.getByte(i); } @Override public byte[] getBytes(int index, int size) { - if (index < 0 || index + size > extendedLength) { + if (index < -preLength || index + size > extendedLength) { throw new IndexOutOfBoundsException(); } - int length = main.getLength(); - if (index + size < length) { - return main.getBytes(index, size); + if (index < 0 && index + size <= 0) { + return preSequence.getBytes(index + preLength, size); } - if (index >= length) { - return extended.getBytes(index - length, size); + if (index + size < mainLength) { + return mainSequence.getBytes(index, size); + } + if (index >= mainLength) { + return postSequence.getBytes(index - mainLength, size); } // otherwise it spans byte[] results = new byte[size]; @@ -93,6 +114,30 @@ public class ExtendedByteSequence implements ByteSequence { @Override public boolean hasAvailableBytes(int index, int length) { - return index >= 0 && index + length <= getExtendedLength(); + return index >= -preLength && index + length <= extendedLength; + } + + private static class EmptyByteSequence implements ByteSequence { + + @Override + public int getLength() { + return 0; + } + + @Override + public byte getByte(int index) { + return 0; + } + + @Override + public boolean hasAvailableBytes(int index, int length) { + return false; + } + + @Override + public byte[] getBytes(int start, int length) { + return new byte[0]; + } + } } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/ByteArrayByteSequenceTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/ByteArrayByteSequenceTest.java index adfee49544..cb6bf53d81 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/ByteArrayByteSequenceTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/ByteArrayByteSequenceTest.java @@ -23,9 +23,10 @@ import ghidra.util.bytesearch.ByteSequence; import ghidra.util.bytesearch.ExtendedByteSequence; public class ByteArrayByteSequenceTest { + private ByteSequence pre = new ByteArrayByteSequence((byte) 0, (byte) 1, (byte) 2, (byte) 3); private ByteSequence main = new ByteArrayByteSequence((byte) 0, (byte) 1, (byte) 2, (byte) 3); private ByteSequence extra = new ByteArrayByteSequence((byte) 4, (byte) 5); - private ByteSequence extended = new ExtendedByteSequence(main, extra, 100); + private ByteSequence extended = new ExtendedByteSequence(main, pre, extra, 100); @Test public void testSimpleByteSeqeunce() { diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/MaskedBytesSequenceByteMatcherTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/MaskedBytesSequenceByteMatcherTest.java index 1cb14e6c0e..853874ffe7 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/MaskedBytesSequenceByteMatcherTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/MaskedBytesSequenceByteMatcherTest.java @@ -34,8 +34,9 @@ public class MaskedBytesSequenceByteMatcherTest { ByteSequence main = new ByteArrayByteSequence(makeBytes(1, 2, 3, 2, 4, 5, 2, 6, 2, 3, 2)); ByteSequence extra = new ByteArrayByteSequence(makeBytes(4, 1, 1, 3, 2, 4)); + ByteSequence pre = new ByteArrayByteSequence(makeBytes(4, 1, 1, 3, 2, 4)); - byteSequence = new ExtendedByteSequence(main, extra, 100); + byteSequence = new ExtendedByteSequence(main, pre, extra, 100); } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/RegExByteMatcherTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/RegExByteMatcherTest.java index b76ffb86b4..9d7668a720 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/RegExByteMatcherTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/RegExByteMatcherTest.java @@ -30,9 +30,10 @@ public class RegExByteMatcherTest { @Before public void setUp() { + ByteSequence pre = new ByteArrayByteSequence(makeBytes("")); ByteSequence main = new ByteArrayByteSequence(makeBytes("one two three tw")); ByteSequence extra = new ByteArrayByteSequence(makeBytes("o four two five")); - byteSequence = new ExtendedByteSequence(main, extra, 100); + byteSequence = new ExtendedByteSequence(main, pre, extra, 100); } @@ -76,6 +77,66 @@ public class RegExByteMatcherTest { assertFalse(it.hasNext()); } + @Test + public void testPositiveLookBehindAcrossBuffers() { + ByteSequence pre = new ByteArrayByteSequence(makeBytes("bbb bob bob aaa")); + ByteSequence main = new ByteArrayByteSequence(makeBytes(" bob bob aaa bob")); + ByteSequence post = null; + + byteSequence = new ExtendedByteSequence(main, pre, post, 100); + RegExByteMatcher byteMatcher = new RegExByteMatcher("(?<=aaa )bob", null); + SearchData searchData = byteMatcher.getSearchData(); + + Iterator> it = byteMatcher.match(byteSequence).iterator(); + assertTrue(it.hasNext()); + assertEquals(new Match<>(searchData, 1, 3), it.next()); + assertEquals(new Match<>(searchData, 13, 3), it.next()); + assertFalse(it.hasNext()); + + } + + @Test + public void testPositiveLookBehindAcrossBuffers_ThatStartBeforeMainAreIgnored() { + ByteSequence pre = new ByteArrayByteSequence(makeBytes("aaa joe aaa bo")); + ByteSequence main = new ByteArrayByteSequence(makeBytes("b bob bob bob")); + ByteSequence post = null; + + byteSequence = new ExtendedByteSequence(main, pre, post, 100); + RegExByteMatcher byteMatcher = new RegExByteMatcher("(?<=aaa )bob", null); + + Iterator> it = byteMatcher.match(byteSequence).iterator(); + assertFalse(it.hasNext()); + } + + @Test + public void testPositiveLookBehindAcrossBuffers_ThatStartInPostAreIgnored() { + ByteSequence pre = null; + ByteSequence main = new ByteArrayByteSequence(makeBytes("bbb bob bob aaa ")); + ByteSequence post = new ByteArrayByteSequence(makeBytes("bob bob aaa bob")); + + byteSequence = new ExtendedByteSequence(main, pre, post, 100); + RegExByteMatcher byteMatcher = new RegExByteMatcher("(?<=aaa )bob", null); + + Iterator> it = byteMatcher.match(byteSequence).iterator(); + assertFalse(it.hasNext()); + } + + @Test + public void testNegativeLookBehindAcrossBuffers() { + ByteSequence pre = new ByteArrayByteSequence(makeBytes("aaa bob bob aaa")); + ByteSequence main = new ByteArrayByteSequence(makeBytes(" bob bob aaa bob")); + ByteSequence post = null; + + byteSequence = new ExtendedByteSequence(main, pre, post, 100); + RegExByteMatcher byteMatcher = new RegExByteMatcher("(?> it = byteMatcher.match(byteSequence).iterator(); + assertTrue(it.hasNext()); + assertEquals(new Match<>(searchData, 5, 3), it.next()); + assertFalse(it.hasNext()); + } + private byte[] makeBytes(String string) { byte[] bytes = new byte[string.length()]; for (int i = 0; i < bytes.length; i++) { diff --git a/Ghidra/Features/MicrosoftCodeAnalyzer/src/main/java/ghidra/app/plugin/prototype/MicrosoftCodeAnalyzerPlugin/PEExceptionAnalyzer.java b/Ghidra/Features/MicrosoftCodeAnalyzer/src/main/java/ghidra/app/plugin/prototype/MicrosoftCodeAnalyzerPlugin/PEExceptionAnalyzer.java index 8929cf7027..379ffaf218 100644 --- a/Ghidra/Features/MicrosoftCodeAnalyzer/src/main/java/ghidra/app/plugin/prototype/MicrosoftCodeAnalyzerPlugin/PEExceptionAnalyzer.java +++ b/Ghidra/Features/MicrosoftCodeAnalyzer/src/main/java/ghidra/app/plugin/prototype/MicrosoftCodeAnalyzerPlugin/PEExceptionAnalyzer.java @@ -26,8 +26,7 @@ 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.matcher.*; import ghidra.features.base.memsearch.searcher.MemoryMatch; import ghidra.features.base.memsearch.searcher.MemorySearcher; import ghidra.framework.cmd.Command; @@ -94,7 +93,7 @@ public class PEExceptionAnalyzer extends AbstractAnalyzer { SearchSettings settings = new SearchSettings().withAlignment(alignment); settings = settings.withIncludeInstructions(false); // only search data - ByteMatcher matcher = new RegExByteMatcher(pattern, settings); + ByteMatcher matcher = new RegExByteMatcher(pattern, settings); AddressableByteSource byteSource = new ProgramByteSource(program); Memory memory = program.getMemory(); AddressSet addresses = memory.getLoadedAndInitializedAddressSet().intersect(set); @@ -102,11 +101,12 @@ public class PEExceptionAnalyzer extends AbstractAnalyzer { // Only want to search exception handling memory blocks. addresses = getAddressSet(ehBlocks).intersect(addresses); - MemorySearcher searcher = new MemorySearcher(byteSource, matcher, addresses, MATCH_LIMIT); + MemorySearcher searcher = + new MemorySearcher<>(byteSource, matcher, addresses, MATCH_LIMIT); - ListAccumulator accumulator = new ListAccumulator<>(); + ListAccumulator> accumulator = new ListAccumulator<>(); searcher.findAll(accumulator, monitor); - List results = accumulator.asList(); + List> results = accumulator.asList(); // 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.