mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-27 16:46:44 +08:00
Merge remote-tracking branch 'origin/GP-6308_ghidragon_handle_mapped_blocks--SQUASHED'
This commit is contained in:
+1
-1
@@ -41,7 +41,7 @@ public class BulkPatternByteMatcher<T extends BytePattern> implements ByteMatche
|
|||||||
@Override
|
@Override
|
||||||
public Iterable<Match<T>> match(ExtendedByteSequence bytes) {
|
public Iterable<Match<T>> match(ExtendedByteSequence bytes) {
|
||||||
List<Match<T>> matches = new ArrayList<>();
|
List<Match<T>> matches = new ArrayList<>();
|
||||||
matcher.search(bytes, matches, 0);
|
matcher.search(bytes, matches);
|
||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -316,6 +316,19 @@ public class ProgramBuilder {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MemoryBlock createMappedMemory(String name, String address, int size,
|
||||||
|
String mappedAddress) {
|
||||||
|
return tx(() -> {
|
||||||
|
Address blockAddress = addr(address);
|
||||||
|
Address mapAddress = addr(mappedAddress);
|
||||||
|
Memory memory = program.getMemory();
|
||||||
|
MemoryBlock block =
|
||||||
|
memory.createByteMappedBlock(name, blockAddress, mapAddress, size, false);
|
||||||
|
return block;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public MemoryBlock createUninitializedMemory(String name, String address, int size) {
|
public MemoryBlock createUninitializedMemory(String name, String address, int size) {
|
||||||
|
|
||||||
return tx(() -> {
|
return tx(() -> {
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/* ###
|
||||||
|
* 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.bytesearch;
|
||||||
|
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a match of a pattern at a given address in program memory.
|
||||||
|
*
|
||||||
|
* @param <T> The specific implementation of the pattern that was used to create this match
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class AddressMatch<T> extends Match<T> {
|
||||||
|
private Address address;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param pattern the byte pattern that matched
|
||||||
|
* @param offset offset within a searched buffer
|
||||||
|
* @param length the length of the matching sequence
|
||||||
|
* @param address the address in the program where the match occurred
|
||||||
|
*/
|
||||||
|
public AddressMatch(T pattern, long offset, int length, Address address) {
|
||||||
|
super(pattern, offset, length);
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the address where this match occurred
|
||||||
|
*/
|
||||||
|
public Address getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getPattern().toString() + " @ " + address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return super.hashCode() + address.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (!super.equals(obj)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
AddressMatch<?> other = (AddressMatch<?>) obj;
|
||||||
|
return address.equals(other.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+15
-10
@@ -70,6 +70,21 @@ public class AddressableByteSequence implements ByteSequence {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the range of bytes that this object will buffer. This immediately will read the bytes
|
||||||
|
* from the byte source into it's internal byte array buffer.
|
||||||
|
* @param start the address to start reading bytes
|
||||||
|
* @param length the number of bytes to read
|
||||||
|
*/
|
||||||
|
public void setRange(Address start, int length) {
|
||||||
|
if (length > capacity) {
|
||||||
|
throw new IllegalArgumentException("Length exceeds capacity");
|
||||||
|
}
|
||||||
|
this.startAddress = start;
|
||||||
|
this.length = length;
|
||||||
|
byteSource.getBytes(start, bytes, length);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the address of the byte represented by the given index into this buffer.
|
* Returns the address of the byte represented by the given index into this buffer.
|
||||||
* @param index the index into the buffer to get its associated address
|
* @param index the index into the buffer to get its associated address
|
||||||
@@ -112,14 +127,4 @@ public class AddressableByteSequence implements ByteSequence {
|
|||||||
public boolean hasAvailableBytes(int index, int length) {
|
public boolean hasAvailableBytes(int index, int length) {
|
||||||
return index >= 0 && index + length <= getLength();
|
return index >= 0 && index + length <= getLength();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setRange(Address start, int length) {
|
|
||||||
if (length > capacity) {
|
|
||||||
throw new IllegalArgumentException("Length exceeds capacity");
|
|
||||||
}
|
|
||||||
this.startAddress = start;
|
|
||||||
this.length = length;
|
|
||||||
byteSource.getBytes(start, bytes, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-25
@@ -48,6 +48,7 @@ public class BulkPatternSearcher<T extends BytePattern> {
|
|||||||
private SearchState<T> startState;
|
private SearchState<T> startState;
|
||||||
private int bufferSize = DEFAULT_BUFFER_SIZE;
|
private int bufferSize = DEFAULT_BUFFER_SIZE;
|
||||||
private int uniqueStateCount;
|
private int uniqueStateCount;
|
||||||
|
private int maxPatternLength;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
@@ -56,9 +57,18 @@ public class BulkPatternSearcher<T extends BytePattern> {
|
|||||||
*/
|
*/
|
||||||
public BulkPatternSearcher(List<T> patterns) {
|
public BulkPatternSearcher(List<T> patterns) {
|
||||||
this.patterns = patterns;
|
this.patterns = patterns;
|
||||||
|
maxPatternLength = computeMaxPatternLength();
|
||||||
startState = buildStateMachine();
|
startState = buildStateMachine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int computeMaxPatternLength() {
|
||||||
|
int max = 0;
|
||||||
|
for (T pattern : patterns) {
|
||||||
|
max = Math.max(max, pattern.getSize());
|
||||||
|
}
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search the given byte buffer for any of this searcher's patterns.
|
* Search the given byte buffer for any of this searcher's patterns.
|
||||||
* @param input the byte buffer to search
|
* @param input the byte buffer to search
|
||||||
@@ -104,7 +114,7 @@ public class BulkPatternSearcher<T extends BytePattern> {
|
|||||||
if (nextState == null) {
|
if (nextState == null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
nextState.addMatchesForCompletedPatterns(results, patternStart);
|
nextState.addMatches(results, patternStart);
|
||||||
state = nextState;
|
state = nextState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,7 +136,7 @@ public class BulkPatternSearcher<T extends BytePattern> {
|
|||||||
if (nextState == null) {
|
if (nextState == null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
nextState.addMatchesForCompletedPatterns(results, 0);
|
nextState.addMatches(results, 0);
|
||||||
state = nextState;
|
state = nextState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,20 +146,24 @@ public class BulkPatternSearcher<T extends BytePattern> {
|
|||||||
* to the given list of results.
|
* to the given list of results.
|
||||||
* @param bytes the extended byte sequence to search
|
* @param bytes the extended byte sequence to search
|
||||||
* @param results the list of match results to populate
|
* @param results the list of match results to populate
|
||||||
* @param chunkOffset a constant offset to add to the pattern starts found in this buffer.
|
|
||||||
* Users of this method may have split a larger byte sequence into chunks and the final match
|
* Users of this method may have split a larger byte sequence into chunks and the final match
|
||||||
* position needs to be the sum of the chunk offset plus the offset within this chunk.
|
* position needs to be the sum of the chunk offset plus the offset within this chunk.
|
||||||
*/
|
*/
|
||||||
public void search(ExtendedByteSequence bytes, List<Match<T>> results, int chunkOffset) {
|
public void search(ExtendedByteSequence bytes, List<Match<T>> results) {
|
||||||
for (int patternStart = 0; patternStart < bytes.getLength(); ++patternStart) {
|
search(bytes, results, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void search(ExtendedByteSequence bytes, List<Match<T>> results, long streamOffset) {
|
||||||
|
for (int start = -bytes.getPreLength(); start < bytes.getLength(); start++) {
|
||||||
SearchState<T> state = startState;
|
SearchState<T> state = startState;
|
||||||
for (int j = patternStart; j < bytes.getExtendedLength(); j++) {
|
for (int j = start; j < bytes.getExtendedLength(); j++) {
|
||||||
int index = bytes.getByte(j) & 0xff;
|
int index = bytes.getByte(j) & 0xff;
|
||||||
SearchState<T> nextState = state.nextStates[index];
|
SearchState<T> nextState = state.nextStates[index];
|
||||||
if (nextState == null) {
|
if (nextState == null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
nextState.addMatchesForCompletedPatterns(results, patternStart + chunkOffset);
|
nextState.addMatchesFilteredByEffectiveStart(results, start, 0,
|
||||||
|
bytes.getLength() - 1, streamOffset);
|
||||||
state = nextState;
|
state = nextState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,9 +196,8 @@ public class BulkPatternSearcher<T extends BytePattern> {
|
|||||||
public void search(InputStream inputStream, long maxRead, List<Match<T>> results,
|
public void search(InputStream inputStream, long maxRead, List<Match<T>> results,
|
||||||
TaskMonitor monitor) throws IOException {
|
TaskMonitor monitor) throws IOException {
|
||||||
RestrictedStream restrictedStream = new RestrictedStream(inputStream, maxRead);
|
RestrictedStream restrictedStream = new RestrictedStream(inputStream, maxRead);
|
||||||
int maxPatternLength = getLongestPatternLength();
|
|
||||||
int bufSize = Math.max(maxPatternLength, bufferSize);
|
int bufSize = Math.max(maxPatternLength, bufferSize);
|
||||||
int offset = 0;
|
long streamOffset = 0;
|
||||||
|
|
||||||
// The basic strategy is to use two byte buffers and create a virtual buffer with those two
|
// The basic strategy is to use two byte buffers and create a virtual buffer with those two
|
||||||
// buffers. The first pass will look for patterns that start in the 1st buffer but can
|
// buffers. The first pass will look for patterns that start in the 1st buffer but can
|
||||||
@@ -210,9 +223,9 @@ public class BulkPatternSearcher<T extends BytePattern> {
|
|||||||
|
|
||||||
ExtendedByteSequence combined =
|
ExtendedByteSequence combined =
|
||||||
new ExtendedByteSequence(main, pre, post, maxPatternLength);
|
new ExtendedByteSequence(main, pre, post, maxPatternLength);
|
||||||
search(combined, results, offset);
|
search(combined, results, streamOffset);
|
||||||
monitor.incrementProgress(main.getLength());
|
monitor.incrementProgress(main.getLength());
|
||||||
offset += main.getLength();
|
streamOffset += main.getLength();
|
||||||
|
|
||||||
// rotate buffers and load data into second buffer
|
// rotate buffers and load data into second buffer
|
||||||
InputStreamBufferByteSequence tmp = pre;
|
InputStreamBufferByteSequence tmp = pre;
|
||||||
@@ -226,7 +239,7 @@ public class BulkPatternSearcher<T extends BytePattern> {
|
|||||||
post.load(inputStream, maxPatternLength);
|
post.load(inputStream, maxPatternLength);
|
||||||
ExtendedByteSequence combined =
|
ExtendedByteSequence combined =
|
||||||
new ExtendedByteSequence(main, pre, post, maxPatternLength);
|
new ExtendedByteSequence(main, pre, post, maxPatternLength);
|
||||||
search(combined, results, offset);
|
search(combined, results, streamOffset);
|
||||||
monitor.incrementProgress(main.getLength());
|
monitor.incrementProgress(main.getLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,6 +252,13 @@ public class BulkPatternSearcher<T extends BytePattern> {
|
|||||||
this.bufferSize = bufferSize;
|
this.bufferSize = bufferSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@return the length of the longest pattern}
|
||||||
|
*/
|
||||||
|
public int getMaxPatternLength() {
|
||||||
|
return maxPatternLength;
|
||||||
|
}
|
||||||
|
|
||||||
private SearchState<T> buildStateMachine() {
|
private SearchState<T> buildStateMachine() {
|
||||||
Queue<SearchState<T>> unprocessed = new ArrayDeque<>();
|
Queue<SearchState<T>> unprocessed = new ArrayDeque<>();
|
||||||
|
|
||||||
@@ -257,14 +277,6 @@ public class BulkPatternSearcher<T extends BytePattern> {
|
|||||||
return start;
|
return start;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getLongestPatternLength() {
|
|
||||||
int maxLength = 0;
|
|
||||||
for (T t : patterns) {
|
|
||||||
maxLength = Math.max(maxLength, t.getSize());
|
|
||||||
}
|
|
||||||
return maxLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@return the number of unique states generated. Used for testing.}
|
* {@return the number of unique states generated. Used for testing.}
|
||||||
*/
|
*/
|
||||||
@@ -297,7 +309,7 @@ public class BulkPatternSearcher<T extends BytePattern> {
|
|||||||
if (state == null) {
|
if (state == null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
state.addMatchesForCompletedPatterns(resultBuffer, patternStart);
|
state.addMatches(resultBuffer, patternStart);
|
||||||
}
|
}
|
||||||
patternStart++;
|
patternStart++;
|
||||||
}
|
}
|
||||||
@@ -346,7 +358,7 @@ public class BulkPatternSearcher<T extends BytePattern> {
|
|||||||
return; // we are a terminal state
|
return; // we are a terminal state
|
||||||
}
|
}
|
||||||
for (int inputValue = 0; inputValue < 256; inputValue++) {
|
for (int inputValue = 0; inputValue < 256; inputValue++) {
|
||||||
List<T> matchedPatterns = getMatchedPatterns(inputValue);
|
List<T> matchedPatterns = getMatchingPatternsForTransitionValue(inputValue);
|
||||||
if (!matchedPatterns.isEmpty()) {
|
if (!matchedPatterns.isEmpty()) {
|
||||||
nextStates[inputValue] = getSearchState(matchedPatterns, cache, unresolved);
|
nextStates[inputValue] = getSearchState(matchedPatterns, cache, unresolved);
|
||||||
}
|
}
|
||||||
@@ -393,7 +405,7 @@ public class BulkPatternSearcher<T extends BytePattern> {
|
|||||||
return newState;
|
return newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<T> getMatchedPatterns(int inputValue) {
|
private List<T> getMatchingPatternsForTransitionValue(int inputValue) {
|
||||||
List<T> matchedPatterns = new ArrayList<>();
|
List<T> matchedPatterns = new ArrayList<>();
|
||||||
for (T pattern : activePatterns) {
|
for (T pattern : activePatterns) {
|
||||||
if (pattern.isMatch(level, inputValue)) {
|
if (pattern.isMatch(level, inputValue)) {
|
||||||
@@ -403,15 +415,29 @@ public class BulkPatternSearcher<T extends BytePattern> {
|
|||||||
return matchedPatterns;
|
return matchedPatterns;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addMatchesForCompletedPatterns(Collection<Match<T>> results, int i) {
|
private void addMatches(Collection<Match<T>> results, int start) {
|
||||||
if (completedPatterns == null) {
|
if (completedPatterns == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (T pattern : completedPatterns) {
|
for (T pattern : completedPatterns) {
|
||||||
results.add(new Match<T>(pattern, i, pattern.getSize()));
|
results.add(new Match<T>(pattern, start, pattern.getSize()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addMatchesFilteredByEffectiveStart(Collection<Match<T>> results, int start,
|
||||||
|
int min, int max, long streamOffset) {
|
||||||
|
if (completedPatterns == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (T pattern : completedPatterns) {
|
||||||
|
int actualStart = start + pattern.getPreSequenceLength();
|
||||||
|
if (actualStart >= min && actualStart <= max) {
|
||||||
|
results.add(new Match<T>(pattern, streamOffset + start, pattern.getSize()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private List<T> buildFullyMatchedPatternsList() {
|
private List<T> buildFullyMatchedPatternsList() {
|
||||||
List<T> list = new ArrayList<>();
|
List<T> list = new ArrayList<>();
|
||||||
for (T pattern : activePatterns) {
|
for (T pattern : activePatterns) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
package ghidra.util.bytesearch;
|
package ghidra.util.bytesearch;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for Patterns that can be combined into a single state machine that can be
|
* Interface for fixed length patterns that can be combined into a single state machine that can be
|
||||||
* simultaneously searched for in a byte sequence.
|
* simultaneously searched for in a byte sequence.
|
||||||
*/
|
*/
|
||||||
public interface BytePattern {
|
public interface BytePattern {
|
||||||
@@ -35,4 +35,16 @@ public interface BytePattern {
|
|||||||
*/
|
*/
|
||||||
public boolean isMatch(int patternOffset, int byteValue);
|
public boolean isMatch(int patternOffset, int byteValue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of bytes in this pattern that represent a pre-sequence that must match
|
||||||
|
* before the official start of the matching pattern. For example if looking for a pattern of
|
||||||
|
* "abcd", but only if it follows "xyz", then the pattern would be "xyzabcd", with a pre
|
||||||
|
* sequence length of 3. So when this pattern matches, we want the "match to be at the position
|
||||||
|
* where the "a" is and not the "x". This is know as "look behind" when using regular
|
||||||
|
* expressions.
|
||||||
|
* @return the number of bytes in the pattern that represent a required pre sequence before the
|
||||||
|
* actual pattern we want to find the position of
|
||||||
|
*/
|
||||||
|
public int getPreSequenceLength();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -482,4 +482,9 @@ public class DittedBitSequence implements BytePattern {
|
|||||||
}
|
}
|
||||||
return popcnt;
|
return popcnt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPreSequenceLength() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,9 @@ public class ExtendedByteSequence implements ByteSequence {
|
|||||||
return extendedLength;
|
return extendedLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@eturn the length of the pre sequence that is available}
|
||||||
|
*/
|
||||||
public int getPreLength() {
|
public int getPreLength() {
|
||||||
return preLength;
|
return preLength;
|
||||||
}
|
}
|
||||||
@@ -82,7 +85,7 @@ public class ExtendedByteSequence implements ByteSequence {
|
|||||||
@Override
|
@Override
|
||||||
public byte getByte(int i) {
|
public byte getByte(int i) {
|
||||||
if (i < 0) {
|
if (i < 0) {
|
||||||
return preSequence.getByte(i + preLength);
|
return preSequence.getByte(i + preSequence.getLength());
|
||||||
}
|
}
|
||||||
if (i >= mainLength) {
|
if (i >= mainLength) {
|
||||||
return postSequence.getByte(i - mainLength);
|
return postSequence.getByte(i - mainLength);
|
||||||
@@ -96,9 +99,9 @@ public class ExtendedByteSequence implements ByteSequence {
|
|||||||
throw new IndexOutOfBoundsException();
|
throw new IndexOutOfBoundsException();
|
||||||
}
|
}
|
||||||
if (index < 0 && index + size <= 0) {
|
if (index < 0 && index + size <= 0) {
|
||||||
return preSequence.getBytes(index + preLength, size);
|
return preSequence.getBytes(index + preSequence.getLength(), size);
|
||||||
}
|
}
|
||||||
if (index + size < mainLength) {
|
if (index >= 0 && index + size < mainLength) {
|
||||||
return mainSequence.getBytes(index, size);
|
return mainSequence.getBytes(index, size);
|
||||||
}
|
}
|
||||||
if (index >= mainLength) {
|
if (index >= mainLength) {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ package ghidra.util.bytesearch;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a match of a BytePattern at a given offset in a byte sequence.
|
* Represents a match of a pattern at a given offset in a byte sequence.
|
||||||
*
|
*
|
||||||
* @param <T> The specific implementation of the BytePattern that was used to create this match
|
* @param <T> The specific implementation of the BytePattern that was used to create this match
|
||||||
*
|
*
|
||||||
|
|||||||
+66
-165
@@ -15,44 +15,44 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.util.bytesearch;
|
package ghidra.util.bytesearch;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.address.AddressSetView;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.model.mem.MemoryBlock;
|
|
||||||
import ghidra.util.Msg;
|
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Multi pattern/mask/action memory searcher
|
* Multi pattern/mask/action memory searcher. This is the legacy memory searcher that specifically
|
||||||
* Patterns must be supplied/added, or a pre-initialized searchState supplied
|
* uses {@link Pattern} objects which relies on patterns having actions that get invoked as the
|
||||||
*
|
* pattern is found in memory. If you want a simpler, more generic way to search for bulk patterns
|
||||||
* Preload search patterns and actions, then call search method.
|
* in memory, you can use the {@link ProgramMemorySearcher}, . If you want an even more generic
|
||||||
|
* searcher that isn't restricted to just searching program memory, you can directly use a
|
||||||
|
* {@link BulkPatternSearcher}.
|
||||||
|
* <P>
|
||||||
|
* In this class, patterns can be given at construction time or added one at a time. Optionally,
|
||||||
|
* this class can be called with a pre-built BulkPatternSearcher, which is a bit awkward since
|
||||||
|
* it is not compatible with adding patterns later. In that case, a new BulkPatternSearcher will be
|
||||||
|
* created with only the patterns that were added after construction.
|
||||||
|
* <P>
|
||||||
|
* Once patterns have been added, simply call the search or searchAll methods to perform a search.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class MemoryBytePatternSearcher {
|
public class MemoryBytePatternSearcher {
|
||||||
private static final long RESTRICTED_PATTERN_BYTE_RANGE = 32;
|
|
||||||
|
|
||||||
BulkPatternSearcher<Pattern> patternSearcher = null;
|
BulkPatternSearcher<Pattern> patternSearcher = null;
|
||||||
|
|
||||||
ArrayList<Pattern> patternList;
|
ArrayList<Pattern> patternList;
|
||||||
|
private String searchName = "Searching";
|
||||||
private String searchName = "";
|
|
||||||
|
|
||||||
private boolean doExecutableBlocksOnly = false; // only search executable blocks
|
private boolean doExecutableBlocksOnly = false; // only search executable blocks
|
||||||
|
|
||||||
private long numToSearch = 0;
|
|
||||||
private long numSearched = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create with pre-created patternList
|
* Create with pre-created patternList
|
||||||
* @param searchName name of search
|
* @param searchName name of search
|
||||||
* @param patternList - list of patterns(bytes/mask/action)
|
* @param patternList - list of patterns(bytes/mask/action)
|
||||||
*/
|
*/
|
||||||
public MemoryBytePatternSearcher(String searchName, ArrayList<Pattern> patternList) {
|
public MemoryBytePatternSearcher(String searchName, List<Pattern> patternList) {
|
||||||
this.searchName = searchName;
|
this.searchName = searchName;
|
||||||
this.patternList = new ArrayList<Pattern>(patternList);
|
this.patternList = new ArrayList<Pattern>(patternList);
|
||||||
}
|
}
|
||||||
@@ -83,12 +83,25 @@ public class MemoryBytePatternSearcher {
|
|||||||
*/
|
*/
|
||||||
public void addPattern(Pattern pattern) {
|
public void addPattern(Pattern pattern) {
|
||||||
patternList.add(pattern);
|
patternList.add(pattern);
|
||||||
|
patternSearcher = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSearchExecutableOnly(boolean doExecutableBlocksOnly) {
|
public void setSearchExecutableOnly(boolean doExecutableBlocksOnly) {
|
||||||
this.doExecutableBlocksOnly = doExecutableBlocksOnly;
|
this.doExecutableBlocksOnly = doExecutableBlocksOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search all initialized memory blocks and associated actions on matches
|
||||||
|
*
|
||||||
|
* @param program to be searched
|
||||||
|
* @param monitor allow canceling and reporting of progress
|
||||||
|
*
|
||||||
|
* @throws CancelledException if canceled
|
||||||
|
*/
|
||||||
|
public void searchAll(Program program, TaskMonitor monitor) throws CancelledException {
|
||||||
|
search(program, null, monitor);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search initialized memory blocks for all patterns(bytes/mask/action).
|
* Search initialized memory blocks for all patterns(bytes/mask/action).
|
||||||
* Call associated action for each pattern matched.
|
* Call associated action for each pattern matched.
|
||||||
@@ -105,167 +118,55 @@ public class MemoryBytePatternSearcher {
|
|||||||
patternSearcher = new BulkPatternSearcher<>(patternList);
|
patternSearcher = new BulkPatternSearcher<>(patternList);
|
||||||
}
|
}
|
||||||
|
|
||||||
numToSearch = getNumToSearch(program, searchSet);
|
ProgramMemorySearcher<Pattern> searcher =
|
||||||
monitor.setMessage(searchName + " Search");
|
new ProgramMemorySearcher<>(searchName + " Search", program, patternSearcher);
|
||||||
monitor.initialize(numToSearch);
|
|
||||||
|
|
||||||
MemoryBlock[] blocks = program.getMemory().getBlocks();
|
if (searchSet == null) {
|
||||||
for (MemoryBlock block : blocks) {
|
searchSet = program.getMemory().getAllInitializedAddressSet();
|
||||||
monitor.setProgress(numSearched);
|
|
||||||
// check if entire block has anything that is searchable
|
|
||||||
if (!block.isInitialized()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (doExecutableBlocksOnly && !block.isExecute()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (searchSet != null && !searchSet.isEmpty() &&
|
|
||||||
!searchSet.intersects(block.getStart(), block.getEnd())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
searchBlock(patternSearcher, program, block, searchSet, monitor);
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
Msg.error(this, "Unable to scan block " + block.getName() + " for " + searchName);
|
|
||||||
}
|
|
||||||
numSearched += block.getSize();
|
|
||||||
}
|
}
|
||||||
|
if (doExecutableBlocksOnly) {
|
||||||
|
searchSet = searchSet.intersect(program.getMemory().getExecuteSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
searcher.search(searchSet, m -> processMatch(program, m), monitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getNumToSearch(Program program, AddressSetView searchSet) {
|
private void processMatch(Program program, AddressMatch<Pattern> match) {
|
||||||
long numAddresses = 0;
|
Pattern pattern = match.getPattern();
|
||||||
MemoryBlock[] blocks = program.getMemory().getBlocks();
|
Address addr = match.getAddress();
|
||||||
for (MemoryBlock block : blocks) {
|
// the post rules seem to work off the offset were the first pattern matched, not where
|
||||||
// check if entire block has anything that is searchable
|
// its mark start is. Since the address is at the mark offset, we want to subtract that
|
||||||
if (!block.isInitialized()) {
|
// back out
|
||||||
continue;
|
long rawPatternOffset = addr.getOffset() - pattern.getMarkOffset();
|
||||||
}
|
if (!pattern.checkPostRules(rawPatternOffset)) {
|
||||||
if (doExecutableBlocksOnly && !block.isExecute()) {
|
return;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (searchSet != null && !searchSet.isEmpty() &&
|
|
||||||
!searchSet.intersects(block.getStart(), block.getEnd())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
numAddresses += block.getSize();
|
|
||||||
}
|
}
|
||||||
return numAddresses;
|
|
||||||
|
MatchAction[] matchactions = pattern.getMatchActions();
|
||||||
|
preMatchApply(matchactions, addr);
|
||||||
|
for (MatchAction matchaction : matchactions) {
|
||||||
|
matchaction.apply(program, addr, match);
|
||||||
|
}
|
||||||
|
|
||||||
|
postMatchApply(matchactions, addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search through bytes of a memory block using the finite state machine (BulkPatterMatcher)
|
* Called just before any match rules are applied.
|
||||||
* Apply any additional rules for matching patterns.
|
* @param actions the actions from the pattern that matched
|
||||||
*
|
* @param address address of match
|
||||||
* @param program is the Program being searched
|
|
||||||
* @param block is the specific block of bytes being searched
|
|
||||||
*
|
|
||||||
* @throws IOException exception during read of memory
|
|
||||||
* @throws CancelledException canceled search
|
|
||||||
*/
|
*/
|
||||||
private void searchBlock(BulkPatternSearcher<Pattern> searcher, Program program,
|
public void preMatchApply(MatchAction[] actions, Address address) {
|
||||||
MemoryBlock block, AddressSetView restrictSet, TaskMonitor monitor)
|
|
||||||
throws IOException, CancelledException {
|
|
||||||
|
|
||||||
// if no restricted set, make restrict set the full block
|
|
||||||
AddressSet doneSet;
|
|
||||||
if (restrictSet == null || restrictSet.isEmpty()) {
|
|
||||||
doneSet = new AddressSet(block.getStart(), block.getEnd());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
doneSet = restrictSet.intersectRange(block.getStart(), block.getEnd());
|
|
||||||
}
|
|
||||||
|
|
||||||
long numInDoneSet = doneSet.getNumAddresses();
|
|
||||||
long numInBlock = block.getSize();
|
|
||||||
|
|
||||||
Address blockStartAddr = block.getStart();
|
|
||||||
|
|
||||||
// pull each range off the restricted set
|
|
||||||
long progress = monitor.getProgress();
|
|
||||||
AddressRangeIterator addressRanges = doneSet.getAddressRanges();
|
|
||||||
long numDone = 0;
|
|
||||||
while (addressRanges.hasNext()) {
|
|
||||||
monitor.checkCancelled();
|
|
||||||
monitor.setMessage(searchName + " Search");
|
|
||||||
monitor.setProgress(progress + (long) (numInBlock * ((float) numDone / numInDoneSet)));
|
|
||||||
AddressRange addressRange = addressRanges.next();
|
|
||||||
long numAddressesInRange = addressRange.getLength();
|
|
||||||
|
|
||||||
ArrayList<Match<Pattern>> mymatches = new ArrayList<>();
|
|
||||||
|
|
||||||
long streamoffset = blockStartAddr.getOffset();
|
|
||||||
|
|
||||||
// Give block a starting/ending point before this address to search
|
|
||||||
// patterns might start before, since they have a pre-pattern
|
|
||||||
// TODO: this is dangerous, since pattern might be very big, but the set should be restricted
|
|
||||||
// normally only when we are searching for more matching patterns that had a postrule that didn't satisfy
|
|
||||||
// normally the whole memory blocks will get searched.
|
|
||||||
long blockOffset = addressRange.getMinAddress().subtract(blockStartAddr);
|
|
||||||
blockOffset = blockOffset - RESTRICTED_PATTERN_BYTE_RANGE;
|
|
||||||
if (blockOffset <= 0) {
|
|
||||||
// don't go before the block start
|
|
||||||
blockOffset = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// compute number of bytes in the range + 1, and don't search more than that.
|
|
||||||
long maxBlockSearchLength =
|
|
||||||
addressRange.getMaxAddress().subtract(blockStartAddr) - blockOffset + 1;
|
|
||||||
|
|
||||||
InputStream data = block.getData();
|
|
||||||
data.skip(blockOffset);
|
|
||||||
|
|
||||||
searcher.search(data, maxBlockSearchLength, mymatches, monitor);
|
|
||||||
monitor.checkCancelled();
|
|
||||||
|
|
||||||
monitor.setMessage(searchName + " (Examine Matches)");
|
|
||||||
|
|
||||||
// TODO: DANGER there is much offset<-->address calculation here
|
|
||||||
// should be OK, since they are all relative to the block.
|
|
||||||
long matchProgress = progress + (long) (numInBlock * ((float) numDone / numInDoneSet));
|
|
||||||
for (int i = 0; i < mymatches.size(); ++i) {
|
|
||||||
monitor.checkCancelled();
|
|
||||||
monitor.setProgress(
|
|
||||||
matchProgress + (long) (numAddressesInRange * ((float) i / mymatches.size())));
|
|
||||||
Match<Pattern> match = mymatches.get(i);
|
|
||||||
Pattern pattern = match.getPattern();
|
|
||||||
Address addr =
|
|
||||||
blockStartAddr.add(pattern.getMarkOffset() + match.getStart() + blockOffset);
|
|
||||||
long totalOffset = streamoffset + blockOffset + match.getStart();
|
|
||||||
if (!pattern.checkPostRules(totalOffset)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
MatchAction[] matchactions = pattern.getMatchActions();
|
|
||||||
preMatchApply(matchactions, addr);
|
|
||||||
for (MatchAction matchaction : matchactions) {
|
|
||||||
matchaction.apply(program, addr, match);
|
|
||||||
}
|
|
||||||
|
|
||||||
postMatchApply(matchactions, addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
numDone += numAddressesInRange;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called before any match rules are applied
|
|
||||||
* @param matchactions actions that matched
|
|
||||||
* @param addr address of match
|
|
||||||
*/
|
|
||||||
public void preMatchApply(MatchAction[] matchactions, Address addr) {
|
|
||||||
// override if any initialization needs to be done before rule application
|
// override if any initialization needs to be done before rule application
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after any match rules are applied
|
* Called just after any match rules are applied.
|
||||||
* Can use for cross post rule matching state application and cleanup.
|
* Can be used for cross post rule matching state application and cleanup.
|
||||||
* @param matchactions actions that matched
|
* @param actions the actions from the pattern that matched
|
||||||
* @param addr adress of match
|
* @param address the address of match
|
||||||
*/
|
*/
|
||||||
public void postMatchApply(MatchAction[] matchactions, Address addr) {
|
public void postMatchApply(MatchAction[] actions, Address address) {
|
||||||
// override if any cleanup from rule match application is needed
|
// override if any cleanup from rule match application is needed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -243,4 +243,9 @@ public class Pattern extends DittedBitSequence {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPreSequenceLength() {
|
||||||
|
return markOffset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,198 @@
|
|||||||
|
/* ###
|
||||||
|
* 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.bytesearch;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import ghidra.features.base.memsearch.bytesource.ProgramByteSource;
|
||||||
|
import ghidra.features.base.memsearch.searcher.MemorySearcher;
|
||||||
|
import ghidra.program.model.address.*;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.program.model.mem.Memory;
|
||||||
|
import ghidra.program.model.mem.MemoryBlock;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for efficiently searching for one or more patterns in memory. Patterns used by this
|
||||||
|
* class can be any class that implements {@link BytePattern}, so clients are free to create
|
||||||
|
* their own custom pattern classes.
|
||||||
|
* <P>
|
||||||
|
* Note: this searcher searches each memory block individually. It intentionally does not find
|
||||||
|
* patterns that span memory blocks (even if the memory blocks are adjacent). If you want patterns
|
||||||
|
* to span memory blocks, you can use the {@link MemorySearcher} class, which is not block
|
||||||
|
* oriented.
|
||||||
|
*
|
||||||
|
* @param <T> The specific pattern class type
|
||||||
|
*/
|
||||||
|
public class ProgramMemorySearcher<T extends BytePattern> {
|
||||||
|
private static final int BUF_SIZE = 4096;
|
||||||
|
private BulkPatternSearcher<T> patternSearcher;
|
||||||
|
private Program program;
|
||||||
|
private String name;
|
||||||
|
private int maxPatternLength;
|
||||||
|
|
||||||
|
private AddressableByteSequence pre;
|
||||||
|
private AddressableByteSequence main;
|
||||||
|
private AddressableByteSequence post;
|
||||||
|
private List<Match<T>> intermediateResults = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param name the name of the searcher. (Used by the task monitor messages)
|
||||||
|
* @param program The program whose memory is to be searched
|
||||||
|
* @param patterns the list of pattern objects to search for
|
||||||
|
*/
|
||||||
|
public ProgramMemorySearcher(String name, Program program, List<T> patterns) {
|
||||||
|
this(name, program, new BulkPatternSearcher<T>(patterns));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param name the name of the searcher. (Used by the task monitor messages)
|
||||||
|
* @param program The program whose memory is to be searched
|
||||||
|
* @param patternSearcher the pre-constructed pattern searcher which is state-less and be
|
||||||
|
* reused, saving the time of building the state machine for the patterns.
|
||||||
|
*/
|
||||||
|
public ProgramMemorySearcher(String name, Program program,
|
||||||
|
BulkPatternSearcher<T> patternSearcher) {
|
||||||
|
this.name = name;
|
||||||
|
this.program = program;
|
||||||
|
this.patternSearcher = patternSearcher;
|
||||||
|
this.maxPatternLength = patternSearcher.getMaxPatternLength();
|
||||||
|
ProgramByteSource programByteSource = new ProgramByteSource(program);
|
||||||
|
pre = new AddressableByteSequence(programByteSource, BUF_SIZE);
|
||||||
|
main = new AddressableByteSequence(programByteSource, BUF_SIZE);
|
||||||
|
post = new AddressableByteSequence(programByteSource, BUF_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches all initialized memory in the program for the patterns given to this searcher.
|
||||||
|
* @param consumer the consumer to be called back when a match is found
|
||||||
|
* @param monitor the task monitor for reporting progress and allowing for cancellation
|
||||||
|
* @throws CancelledException thrown if the search is cancelled via the task monitor
|
||||||
|
*/
|
||||||
|
public void searchAll(Consumer<AddressMatch<T>> consumer, TaskMonitor monitor)
|
||||||
|
throws CancelledException {
|
||||||
|
search(program.getMemory().getLoadedAndInitializedAddressSet(), consumer, monitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the given address set within initialized memory for the patterns given to this
|
||||||
|
* searcher.
|
||||||
|
* @param addresses The address within the program to search. This address set will be further
|
||||||
|
* restricted to initialized program memory
|
||||||
|
* @param consumer the consumer to be called back when a match is found
|
||||||
|
* @param monitor the task monitor for reporting progress and allowing for cancellation
|
||||||
|
* @throws CancelledException thrown if the search is cancelled via the task monitor
|
||||||
|
*/
|
||||||
|
public void search(AddressSetView addresses, Consumer<AddressMatch<T>> consumer,
|
||||||
|
TaskMonitor monitor) throws CancelledException {
|
||||||
|
|
||||||
|
Memory memory = program.getMemory();
|
||||||
|
|
||||||
|
// we can't search in uninitialized memory, so exclude those addresses
|
||||||
|
AddressSet initializedAddresses = addresses.intersect(memory.getAllInitializedAddressSet());
|
||||||
|
monitor.setMessage(name);
|
||||||
|
monitor.initialize(initializedAddresses.getNumAddresses());
|
||||||
|
|
||||||
|
MemoryBlock[] blocks = memory.getBlocks();
|
||||||
|
for (MemoryBlock memoryBlock : blocks) {
|
||||||
|
monitor.checkCancelled();
|
||||||
|
searchBlock(memoryBlock, initializedAddresses, consumer, monitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void searchBlock(MemoryBlock block, AddressSet addresses,
|
||||||
|
Consumer<AddressMatch<T>> consumer, TaskMonitor monitor) throws CancelledException {
|
||||||
|
|
||||||
|
AddressSet blockSet = addresses.intersectRange(block.getStart(), block.getEnd());
|
||||||
|
for (AddressRange range : blockSet) {
|
||||||
|
searchRange(block, range, consumer, monitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void searchRange(MemoryBlock block, AddressRange range,
|
||||||
|
Consumer<AddressMatch<T>> consumer, TaskMonitor monitor) throws CancelledException {
|
||||||
|
pre.clear();
|
||||||
|
main.clear();
|
||||||
|
post.clear();
|
||||||
|
|
||||||
|
// load data before range to allow for pre sequence patterns to match
|
||||||
|
populatePreSequenceForLookBehindPatterns(block, range);
|
||||||
|
|
||||||
|
AddressRangeSplitter splitter = new AddressRangeSplitter(range, BUF_SIZE, true);
|
||||||
|
main.setRange(splitter.next());
|
||||||
|
while (splitter.hasNext()) {
|
||||||
|
monitor.checkCancelled();
|
||||||
|
post.setRange(splitter.next());
|
||||||
|
performSearch(consumer);
|
||||||
|
monitor.incrementProgress(range.getLength());
|
||||||
|
rotateBuffers();
|
||||||
|
}
|
||||||
|
// load data some past end of range to allow pattern to complete
|
||||||
|
populatePostSequenceForPatternCompletion(block, range);
|
||||||
|
performSearch(consumer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performSearch(Consumer<AddressMatch<T>> consumer) {
|
||||||
|
intermediateResults.clear();
|
||||||
|
int overlapSize = maxPatternLength; // number of bytes in pre/post that need to be used
|
||||||
|
ExtendedByteSequence sequence = new ExtendedByteSequence(main, pre, post, overlapSize);
|
||||||
|
patternSearcher.search(sequence, intermediateResults);
|
||||||
|
for (Match<T> match : intermediateResults) {
|
||||||
|
long start = match.getStart() + match.getPattern().getPreSequenceLength();
|
||||||
|
Address address = main.getAddress((int) start);
|
||||||
|
int length = match.getLength();
|
||||||
|
T pattern = match.getPattern();
|
||||||
|
AddressMatch<T> addressMatch = new AddressMatch<>(pattern, start, length, address);
|
||||||
|
consumer.accept(addressMatch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rotateBuffers() {
|
||||||
|
AddressableByteSequence tmp = pre;
|
||||||
|
pre = main;
|
||||||
|
main = post;
|
||||||
|
post = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void populatePreSequenceForLookBehindPatterns(MemoryBlock block, AddressRange range) {
|
||||||
|
Address blockStart = block.getStart();
|
||||||
|
Address rangeStart = range.getMinAddress();
|
||||||
|
if (rangeStart.equals(blockStart)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// We don't go back beyond block start for pre bytes
|
||||||
|
int availablePreBytes = (int) rangeStart.subtract(blockStart);
|
||||||
|
int preSize = Math.min(availablePreBytes, maxPatternLength);
|
||||||
|
Address preStartAddress = rangeStart.subtract(preSize);
|
||||||
|
pre.setRange(preStartAddress, preSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void populatePostSequenceForPatternCompletion(MemoryBlock block, AddressRange range) {
|
||||||
|
Address blockEnd = block.getEnd();
|
||||||
|
Address rangeEnd = range.getMaxAddress();
|
||||||
|
if (rangeEnd.equals(blockEnd)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int availablePostBytes = (int) blockEnd.subtract(rangeEnd);
|
||||||
|
int postSize = Math.min(availablePostBytes, maxPatternLength);
|
||||||
|
post.setRange(rangeEnd.next(), postSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
@@ -25,6 +25,10 @@ public class ByteArrayByteSequence implements ByteSequence {
|
|||||||
this.bytes = bytes;
|
this.bytes = bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ByteArrayByteSequence(String data) {
|
||||||
|
this(data.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getLength() {
|
public int getLength() {
|
||||||
return bytes.length;
|
return bytes.length;
|
||||||
|
|||||||
+77
-8
@@ -23,6 +23,7 @@ import java.util.*;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import ghidra.features.base.memsearch.bytesequence.ByteArrayByteSequence;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class BulkPatternSearcherTest {
|
public class BulkPatternSearcherTest {
|
||||||
@@ -35,6 +36,7 @@ public class BulkPatternSearcherTest {
|
|||||||
private TestPattern bcc = new TestPattern("bcc");
|
private TestPattern bcc = new TestPattern("bcc");
|
||||||
|
|
||||||
private BulkPatternSearcher<TestPattern> searcher;
|
private BulkPatternSearcher<TestPattern> searcher;
|
||||||
|
private List<Match<TestPattern>> results = new ArrayList<Match<TestPattern>>();
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
@@ -44,7 +46,6 @@ public class BulkPatternSearcherTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMatchWithIntoList() {
|
public void testMatchWithIntoList() {
|
||||||
List<Match<TestPattern>> results = new ArrayList<Match<TestPattern>>();
|
|
||||||
searcher.search(data.getBytes(), results);
|
searcher.search(data.getBytes(), results);
|
||||||
Iterator<Match<TestPattern>> it = results.iterator();
|
Iterator<Match<TestPattern>> it = results.iterator();
|
||||||
assertTrue(it.hasNext());
|
assertTrue(it.hasNext());
|
||||||
@@ -64,7 +65,6 @@ public class BulkPatternSearcherTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMatchWithIntoListWithBufferLimit() {
|
public void testMatchWithIntoListWithBufferLimit() {
|
||||||
List<Match<TestPattern>> results = new ArrayList<Match<TestPattern>>();
|
|
||||||
searcher.search(data.getBytes(), 5, results);
|
searcher.search(data.getBytes(), 5, results);
|
||||||
Iterator<Match<TestPattern>> it = results.iterator();
|
Iterator<Match<TestPattern>> it = results.iterator();
|
||||||
assertTrue(it.hasNext());
|
assertTrue(it.hasNext());
|
||||||
@@ -113,7 +113,6 @@ public class BulkPatternSearcherTest {
|
|||||||
String input = "This is a test of the input stream";
|
String input = "This is a test of the input stream";
|
||||||
InputStream is = new ByteArrayInputStream(input.getBytes());
|
InputStream is = new ByteArrayInputStream(input.getBytes());
|
||||||
|
|
||||||
List<Match<TestPattern>> results = new ArrayList<>();
|
|
||||||
Matcher.search(is, results, TaskMonitor.DUMMY);
|
Matcher.search(is, results, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
assertEquals(3, results.size());
|
assertEquals(3, results.size());
|
||||||
@@ -132,7 +131,6 @@ public class BulkPatternSearcherTest {
|
|||||||
String input = "This is a test of the input stream";
|
String input = "This is a test of the input stream";
|
||||||
InputStream is = new ByteArrayInputStream(input.getBytes());
|
InputStream is = new ByteArrayInputStream(input.getBytes());
|
||||||
|
|
||||||
List<Match<TestPattern>> results = new ArrayList<>();
|
|
||||||
matcher.search(is, -1, results, TaskMonitor.DUMMY);
|
matcher.search(is, -1, results, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
assertEquals(3, results.size());
|
assertEquals(3, results.size());
|
||||||
@@ -152,7 +150,6 @@ public class BulkPatternSearcherTest {
|
|||||||
String input = "This is a test of the input stream";
|
String input = "This is a test of the input stream";
|
||||||
InputStream is = new ByteArrayInputStream(input.getBytes());
|
InputStream is = new ByteArrayInputStream(input.getBytes());
|
||||||
|
|
||||||
List<Match<TestPattern>> results = new ArrayList<>();
|
|
||||||
matcher.search(is, 24, results, TaskMonitor.DUMMY);
|
matcher.search(is, 24, results, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
assertEquals(2, results.size());
|
assertEquals(2, results.size());
|
||||||
@@ -219,7 +216,6 @@ public class BulkPatternSearcherTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSearchBeginningOnly() {
|
public void testSearchBeginningOnly() {
|
||||||
List<Match<TestPattern>> results = new ArrayList<Match<TestPattern>>();
|
|
||||||
searcher.matches(data.getBytes(), data.length(), results);
|
searcher.matches(data.getBytes(), data.length(), results);
|
||||||
Iterator<Match<TestPattern>> it = results.iterator();
|
Iterator<Match<TestPattern>> it = results.iterator();
|
||||||
assertTrue(it.hasNext());
|
assertTrue(it.hasNext());
|
||||||
@@ -228,6 +224,68 @@ public class BulkPatternSearcherTest {
|
|||||||
assertFalse(it.hasNext());
|
assertFalse(it.hasNext());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testByteSequenceStartsInMainEndsInPost() {
|
||||||
|
TestPattern p = new TestPattern("joebob");
|
||||||
|
search("xxxxjoexbob", "xxxjoe", "bob", p);
|
||||||
|
assertEquals(1, results.size());
|
||||||
|
assertMatch(results.get(0), p, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPreSequencePatterns_patternStartsInPre_effectivelyStartInPre() {
|
||||||
|
TestPattern p = new TestPattern("joe", "bob");
|
||||||
|
|
||||||
|
// pre-pattern and effective start are both in pre sequence, so no match
|
||||||
|
search("xxjoeb", "obxxx", "xxxx", p);
|
||||||
|
assertTrue(results.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPreSequencePatterns_patternStartInPre_effectivelyStartsInMain() {
|
||||||
|
TestPattern p = new TestPattern("joe", "bob");
|
||||||
|
|
||||||
|
// pre-patterns starts in pre sequence, effective match start is in main, so this
|
||||||
|
// is a match
|
||||||
|
search("xxxxjoe", "bobxxx", "xxxx", p);
|
||||||
|
assertEquals(1, results.size());
|
||||||
|
assertMatch(results.get(0), p, -3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPreSequencePatterns_patternStartsInMainEndsInPost() {
|
||||||
|
TestPattern p = new TestPattern("joe", "bob");
|
||||||
|
|
||||||
|
// create input such that pre-sequence and main sequence start in main, but pattern
|
||||||
|
// ends in post sequence, so this is a match
|
||||||
|
search("xxx", "xxjoeb", "obxx", p);
|
||||||
|
assertEquals(1, results.size());
|
||||||
|
assertMatch(results.get(0), p, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPreSequencePattern_patternStartsInMain_effectStartInPost() {
|
||||||
|
TestPattern p = new TestPattern("joe", "bob");
|
||||||
|
|
||||||
|
// create input such that the pre-sequence starts in the main, but the actual pattern
|
||||||
|
// match start is in the post sequence, so this in not a match
|
||||||
|
|
||||||
|
search("xxx", "xxxjoe", "bob", p);
|
||||||
|
assertTrue(results.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void search(String preData, String mainData, String postData, TestPattern p) {
|
||||||
|
ByteSequence pre = new ByteArrayByteSequence(preData);
|
||||||
|
ByteSequence main = new ByteArrayByteSequence(mainData);
|
||||||
|
ByteSequence post = new ByteArrayByteSequence(postData);
|
||||||
|
ExtendedByteSequence sequence = new ExtendedByteSequence(main, pre, post, 10);
|
||||||
|
|
||||||
|
BulkPatternSearcher<TestPattern> patternSearcher =
|
||||||
|
new BulkPatternSearcher<TestPattern>(List.of(p));
|
||||||
|
patternSearcher.search(sequence, results);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void assertMatch(Match<TestPattern> match, TestPattern expectedPattern, int start) {
|
private void assertMatch(Match<TestPattern> match, TestPattern expectedPattern, int start) {
|
||||||
assertEquals(new Match<>(expectedPattern, start, expectedPattern.getSize()), match);
|
assertEquals(new Match<>(expectedPattern, start, expectedPattern.getSize()), match);
|
||||||
}
|
}
|
||||||
@@ -235,10 +293,16 @@ public class BulkPatternSearcherTest {
|
|||||||
private class TestPattern extends DittedBitSequence {
|
private class TestPattern extends DittedBitSequence {
|
||||||
|
|
||||||
private String inputString;
|
private String inputString;
|
||||||
|
private int preSequenceLength;
|
||||||
|
|
||||||
public TestPattern(String inputString) {
|
public TestPattern(String inputString) {
|
||||||
super(getBytes(inputString), getMask(inputString));
|
this("", inputString);
|
||||||
this.inputString = inputString;
|
}
|
||||||
|
|
||||||
|
public TestPattern(String preSequence, String matchSequence) {
|
||||||
|
super(getBytes(preSequence + matchSequence), getMask(preSequence + matchSequence));
|
||||||
|
this.inputString = preSequence + matchSequence;
|
||||||
|
this.preSequenceLength = preSequence.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] getMask(String inputString) {
|
private static byte[] getMask(String inputString) {
|
||||||
@@ -269,5 +333,10 @@ public class BulkPatternSearcherTest {
|
|||||||
return inputString;
|
return inputString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPreSequenceLength() {
|
||||||
|
return preSequenceLength;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+285
@@ -0,0 +1,285 @@
|
|||||||
|
/* ###
|
||||||
|
* 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.bytesearch;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import ghidra.program.database.ProgramBuilder;
|
||||||
|
import ghidra.program.database.ProgramDB;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.address.AddressSet;
|
||||||
|
import ghidra.program.model.data.CategoryPath;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.program.model.mem.MemoryBlock;
|
||||||
|
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
import ghidra.xml.XmlPullParser;
|
||||||
|
|
||||||
|
public class MemoryBytePatternSearcherTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
|
|
||||||
|
private ProgramBuilder builder;
|
||||||
|
private ProgramDB program;
|
||||||
|
List<AddressMatch<Pattern>> results = new ArrayList<>();
|
||||||
|
private MatchAction[] matchActions;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() throws Exception {
|
||||||
|
program = buildProgram();
|
||||||
|
matchActions = new MatchAction[1];
|
||||||
|
matchActions[0] = new TestMatchAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMatchesDontSpanBlocks() throws Exception {
|
||||||
|
builder.setString("0x1001010", "Hello");
|
||||||
|
builder.setString("0x10010ff", "Hello"); // crosses block boundary, shouldn't be found
|
||||||
|
builder.setString("0x1001520", "There");
|
||||||
|
TestPattern p1 = new TestPattern("Hello");
|
||||||
|
TestPattern p2 = new TestPattern("There");
|
||||||
|
MemoryBytePatternSearcher searcher = createSearcher(p1, p2);
|
||||||
|
|
||||||
|
searcher.search(program, null, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
assertEquals(2, results.size());
|
||||||
|
assertMatch(results.get(0), addr(0x1001010));
|
||||||
|
assertMatch(results.get(1), addr(0x1001520));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMatchInAddressSet() throws Exception {
|
||||||
|
builder.setString("0x1001010", "Hello");
|
||||||
|
TestPattern p1 = new TestPattern("Hello");
|
||||||
|
MemoryBytePatternSearcher searcher = createSearcher(p1);
|
||||||
|
AddressSet searchSet = addrSet(0x1001005, 0x1001030);
|
||||||
|
|
||||||
|
searcher.search(program, searchSet, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
assertEquals(1, results.size());
|
||||||
|
assertMatch(results.get(0), addr(0x1001010));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMatchStartsOutsideRange() throws Exception {
|
||||||
|
builder.setString("0x1001010", "Hello");
|
||||||
|
TestPattern p1 = new TestPattern("Hello");
|
||||||
|
MemoryBytePatternSearcher searcher = createSearcher(p1);
|
||||||
|
AddressSet searchSet = addrSet(0x1001011, 0x1001030);
|
||||||
|
|
||||||
|
searcher.search(program, searchSet, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
assertTrue(results.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMatchStartsInRangeAndExtendsOut() throws Exception {
|
||||||
|
builder.setString("0x1001010", "Hello");
|
||||||
|
TestPattern p1 = new TestPattern("Hello");
|
||||||
|
MemoryBytePatternSearcher searcher = createSearcher(p1);
|
||||||
|
AddressSet searchSet = addrSet(0x1001010, 0x1001012);
|
||||||
|
|
||||||
|
searcher.search(program, searchSet, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
assertEquals(1, results.size());
|
||||||
|
assertMatch(results.get(0), addr(0x1001010));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPreSequencePattern() throws Exception {
|
||||||
|
builder.setString("0x1001010", "HelloThere");
|
||||||
|
TestPattern p1 = new TestPattern("HelloThere", 5);
|
||||||
|
MemoryBytePatternSearcher searcher = createSearcher(p1);
|
||||||
|
AddressSet searchSet = addrSet(0x1001000, 0x1001040);
|
||||||
|
|
||||||
|
searcher.search(program, searchSet, TaskMonitor.DUMMY);
|
||||||
|
assertEquals(1, results.size());
|
||||||
|
assertMatch(results.get(0), addr(0x1001015));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPreSequencePattern_preStartsBeforeRange() throws Exception {
|
||||||
|
builder.setString("0x1001010", "HelloThere");
|
||||||
|
TestPattern p1 = new TestPattern("HelloThere", 5);
|
||||||
|
MemoryBytePatternSearcher searcher = createSearcher(p1);
|
||||||
|
AddressSet searchSet = addrSet(0x1001012, 0x1001040);
|
||||||
|
|
||||||
|
searcher.search(program, searchSet, TaskMonitor.DUMMY);
|
||||||
|
assertEquals(1, results.size());
|
||||||
|
assertMatch(results.get(0), addr(0x1001015));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPreSequencePattern_primaryStartsBeforeRange() throws Exception {
|
||||||
|
builder.setString("0x1001010", "HelloThere");
|
||||||
|
TestPattern p1 = new TestPattern("HelloThere", 5);
|
||||||
|
MemoryBytePatternSearcher searcher = createSearcher(p1);
|
||||||
|
AddressSet searchSet = addrSet(0x1001016, 0x1001040);
|
||||||
|
|
||||||
|
searcher.search(program, searchSet, TaskMonitor.DUMMY);
|
||||||
|
assertTrue(results.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchExecuteBlocksOnly() throws Exception {
|
||||||
|
MemoryBlock block = builder.createMemory("execute", "0x2000000", 0x100);
|
||||||
|
builder.setExecute(block, true);
|
||||||
|
|
||||||
|
builder.setString("0x1001010", "Hello");
|
||||||
|
builder.setString("0x2000010", "Hello");
|
||||||
|
TestPattern p1 = new TestPattern("Hello");
|
||||||
|
MemoryBytePatternSearcher searcher = createSearcher(p1);
|
||||||
|
searcher.setSearchExecutableOnly(true);
|
||||||
|
searcher.searchAll(program, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
assertEquals(1, results.size());
|
||||||
|
assertMatch(results.get(0), addr(0x2000010));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddPatternsLater() throws Exception {
|
||||||
|
builder.setString("0x1001010", "Hello");
|
||||||
|
builder.setString("0x1001030", "There");
|
||||||
|
TestPattern p1 = new TestPattern("Hello");
|
||||||
|
MemoryBytePatternSearcher searcher = createSearcher(p1);
|
||||||
|
searcher.searchAll(program, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
assertEquals(1, results.size());
|
||||||
|
assertMatch(results.get(0), addr(0x1001010));
|
||||||
|
|
||||||
|
results.clear();
|
||||||
|
searcher.addPattern(new TestPattern("There"));
|
||||||
|
searcher.searchAll(program, TaskMonitor.DUMMY);
|
||||||
|
assertEquals(2, results.size());
|
||||||
|
assertMatch(results.get(0), addr(0x1001010));
|
||||||
|
assertMatch(results.get(1), addr(0x1001030));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMappedBlocks() throws Exception {
|
||||||
|
builder.createMappedMemory("mapped", "0x2000000", 0x10000, "0x1000000");
|
||||||
|
|
||||||
|
builder.setString("0x1001100", "Hello");
|
||||||
|
builder.setString("0x1001510", "Hello");
|
||||||
|
TestPattern p1 = new TestPattern("Hello");
|
||||||
|
MemoryBytePatternSearcher searcher = createSearcher(p1);
|
||||||
|
searcher.searchAll(program, TaskMonitor.DUMMY);
|
||||||
|
assertEquals(4, results.size());
|
||||||
|
assertMatch(results.get(0), addr(0x1001100));
|
||||||
|
assertMatch(results.get(1), addr(0x1001510));
|
||||||
|
assertMatch(results.get(2), addr(0x2001100));
|
||||||
|
assertMatch(results.get(3), addr(0x2001510));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Address addr(long offset) {
|
||||||
|
return builder.addr(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AddressSet addrSet(long start, long end) {
|
||||||
|
return new AddressSet(addr(start), addr(end));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProgramDB buildProgram() throws Exception {
|
||||||
|
builder = new ProgramBuilder("Program1", ProgramBuilder._TOY, this);
|
||||||
|
|
||||||
|
builder.createMemory("b1", "0x1001000", 0x100);
|
||||||
|
builder.createMemory("b2", "0x1001100", 0x100);
|
||||||
|
builder.createMemory("b3", "0x1001500", 0x100);
|
||||||
|
CategoryPath miscPath = new CategoryPath("/MISC");
|
||||||
|
builder.addCategory(miscPath);
|
||||||
|
return builder.getProgram();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MemoryBytePatternSearcher createSearcher(Pattern... patterns) {
|
||||||
|
List<Pattern> patternList = Arrays.asList(patterns);
|
||||||
|
return new MemoryBytePatternSearcher("Test", patternList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertMatch(AddressMatch<Pattern> addressMatch, Address addr) {
|
||||||
|
assertEquals("Match address", addr, addressMatch.getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestPattern extends Pattern {
|
||||||
|
|
||||||
|
private String inputString;
|
||||||
|
private int preSequenceLength;
|
||||||
|
|
||||||
|
public TestPattern(String inputString) {
|
||||||
|
this(inputString, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestPattern(String inputString, int preSequenceLength) {
|
||||||
|
super(new DittedBitSequence(getBytes(inputString), getMask(inputString)),
|
||||||
|
preSequenceLength, new PostRule[0], matchActions);
|
||||||
|
this.inputString = inputString;
|
||||||
|
this.preSequenceLength = preSequenceLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] getMask(String inputString) {
|
||||||
|
byte[] mask = new byte[inputString.length()];
|
||||||
|
for (int i = 0; i < inputString.length(); i++) {
|
||||||
|
if (inputString.charAt(i) == '.') {
|
||||||
|
mask[i] = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mask[i] = (byte) 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] getBytes(String inputString) {
|
||||||
|
byte[] bytes = inputString.getBytes();
|
||||||
|
for (int i = 0; i < inputString.length(); i++) {
|
||||||
|
if (inputString.charAt(i) == '.') {
|
||||||
|
bytes[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return inputString;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPreSequenceLength() {
|
||||||
|
return preSequenceLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestMatchAction implements MatchAction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(Program program, Address addr, Match<Pattern> match) {
|
||||||
|
results.add(new AddressMatch<Pattern>(match.getPattern(), match.getStart(),
|
||||||
|
match.getLength(), addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void restoreXml(XmlPullParser parser) {
|
||||||
|
// not used
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+230
@@ -0,0 +1,230 @@
|
|||||||
|
/* ###
|
||||||
|
* 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.bytesearch;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import ghidra.program.database.ProgramBuilder;
|
||||||
|
import ghidra.program.database.ProgramDB;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.address.AddressSet;
|
||||||
|
import ghidra.program.model.data.CategoryPath;
|
||||||
|
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
public class ProgramMemorySearcherTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
|
|
||||||
|
private ProgramBuilder builder;
|
||||||
|
private ProgramDB program;
|
||||||
|
List<AddressMatch<TestPattern>> results = new ArrayList<>();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() throws Exception {
|
||||||
|
program = buildProgram();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMatchesDontSpanBlocks() throws Exception {
|
||||||
|
builder.setString("0x1001010", "Hello");
|
||||||
|
builder.setString("0x10010ff", "Hello"); // crosses block boundary, shouldn't be found
|
||||||
|
builder.setString("0x1001520", "There");
|
||||||
|
TestPattern p1 = new TestPattern("Hello");
|
||||||
|
TestPattern p2 = new TestPattern("There");
|
||||||
|
ProgramMemorySearcher<TestPattern> searcher = createSearcher(p1, p2);
|
||||||
|
|
||||||
|
searcher.searchAll(m -> results.add(m), TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
assertEquals(2, results.size());
|
||||||
|
assertMatch(results.get(0), addr(0x1001010));
|
||||||
|
assertMatch(results.get(1), addr(0x1001520));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMatchInAddressSet() throws Exception {
|
||||||
|
builder.setString("0x1001010", "Hello");
|
||||||
|
TestPattern p1 = new TestPattern("Hello");
|
||||||
|
ProgramMemorySearcher<TestPattern> searcher = createSearcher(p1);
|
||||||
|
AddressSet searchSet = addrSet(0x1001005, 0x1001030);
|
||||||
|
|
||||||
|
searcher.search(searchSet, m -> results.add(m), TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
assertEquals(1, results.size());
|
||||||
|
assertMatch(results.get(0), addr(0x1001010));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMatchStartsOutsideRange() throws Exception {
|
||||||
|
builder.setString("0x1001010", "Hello");
|
||||||
|
TestPattern p1 = new TestPattern("Hello");
|
||||||
|
ProgramMemorySearcher<TestPattern> searcher = createSearcher(p1);
|
||||||
|
AddressSet searchSet = addrSet(0x1001011, 0x1001030);
|
||||||
|
|
||||||
|
searcher.search(searchSet, m -> results.add(m), TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
assertTrue(results.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMatchStartsInRangeAndExtendsOut() throws Exception {
|
||||||
|
builder.setString("0x1001010", "Hello");
|
||||||
|
TestPattern p1 = new TestPattern("Hello");
|
||||||
|
ProgramMemorySearcher<TestPattern> searcher = createSearcher(p1);
|
||||||
|
AddressSet searchSet = addrSet(0x1001010, 0x1001012);
|
||||||
|
|
||||||
|
searcher.search(searchSet, m -> results.add(m), TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
assertEquals(1, results.size());
|
||||||
|
assertMatch(results.get(0), addr(0x1001010));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPreSequencePattern() throws Exception {
|
||||||
|
builder.setString("0x1001010", "HelloThere");
|
||||||
|
TestPattern p1 = new TestPattern("HelloThere", 5);
|
||||||
|
ProgramMemorySearcher<TestPattern> searcher = createSearcher(p1);
|
||||||
|
AddressSet searchSet = addrSet(0x1001000, 0x1001040);
|
||||||
|
|
||||||
|
searcher.search(searchSet, m -> results.add(m), TaskMonitor.DUMMY);
|
||||||
|
assertEquals(1, results.size());
|
||||||
|
assertMatch(results.get(0), addr(0x1001015));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPreSequencePattern_preStartsBeforeRange() throws Exception {
|
||||||
|
builder.setString("0x1001010", "HelloThere");
|
||||||
|
TestPattern p1 = new TestPattern("HelloThere", 5);
|
||||||
|
ProgramMemorySearcher<TestPattern> searcher = createSearcher(p1);
|
||||||
|
AddressSet searchSet = addrSet(0x1001012, 0x1001040);
|
||||||
|
|
||||||
|
searcher.search(searchSet, m -> results.add(m), TaskMonitor.DUMMY);
|
||||||
|
assertEquals(1, results.size());
|
||||||
|
assertMatch(results.get(0), addr(0x1001015));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPreSequencePattern_primaryStartsBeforeRange() throws Exception {
|
||||||
|
builder.setString("0x1001010", "HelloThere");
|
||||||
|
TestPattern p1 = new TestPattern("HelloThere", 5);
|
||||||
|
ProgramMemorySearcher<TestPattern> searcher = createSearcher(p1);
|
||||||
|
AddressSet searchSet = addrSet(0x1001016, 0x1001040);
|
||||||
|
|
||||||
|
searcher.search(searchSet, m -> results.add(m), TaskMonitor.DUMMY);
|
||||||
|
assertTrue(results.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBigBlock() throws Exception {
|
||||||
|
builder.createMemory("big", "0x2000000", 0x10000);
|
||||||
|
builder.setString("0x2000000", "Hello");
|
||||||
|
builder.setString("0x2123456", "Hello");
|
||||||
|
builder.setString("0x2fffff0", "Hello");
|
||||||
|
TestPattern p1 = new TestPattern("Hello");
|
||||||
|
ProgramMemorySearcher<TestPattern> searcher = createSearcher(p1);
|
||||||
|
|
||||||
|
searcher.searchAll(m -> results.add(m), TaskMonitor.DUMMY);
|
||||||
|
assertEquals(3, results.size());
|
||||||
|
assertMatch(results.get(0), addr(0x2000000));
|
||||||
|
assertMatch(results.get(1), addr(0x2123456));
|
||||||
|
assertMatch(results.get(2), addr(0x2fffff0));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Address addr(long offset) {
|
||||||
|
return builder.addr(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AddressSet addrSet(long start, long end) {
|
||||||
|
return new AddressSet(addr(start), addr(end));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProgramDB buildProgram() throws Exception {
|
||||||
|
builder = new ProgramBuilder("Program1", ProgramBuilder._TOY, this);
|
||||||
|
|
||||||
|
builder.createMemory("b1", "0x1001000", 0x100);
|
||||||
|
builder.createMemory("b2", "0x1001100", 0x100);
|
||||||
|
builder.createMemory("b3", "0x1001500", 0x100);
|
||||||
|
CategoryPath miscPath = new CategoryPath("/MISC");
|
||||||
|
builder.addCategory(miscPath);
|
||||||
|
return builder.getProgram();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProgramMemorySearcher<TestPattern> createSearcher(TestPattern... patterns) {
|
||||||
|
List<TestPattern> patternList = Arrays.asList(patterns);
|
||||||
|
ProgramMemorySearcher<TestPattern> searcher =
|
||||||
|
new ProgramMemorySearcher<TestPattern>("Test", program, patternList);
|
||||||
|
return searcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertMatch(AddressMatch<TestPattern> addressMatch, Address addr) {
|
||||||
|
assertEquals("Match address", addr, addressMatch.getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestPattern extends DittedBitSequence {
|
||||||
|
|
||||||
|
private String inputString;
|
||||||
|
private int preSequenceLength;
|
||||||
|
|
||||||
|
public TestPattern(String inputString) {
|
||||||
|
this(inputString, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestPattern(String inputString, int preSequenceLength) {
|
||||||
|
super(getBytes(inputString), getMask(inputString));
|
||||||
|
this.inputString = inputString;
|
||||||
|
this.preSequenceLength = preSequenceLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] getMask(String inputString) {
|
||||||
|
byte[] mask = new byte[inputString.length()];
|
||||||
|
for (int i = 0; i < inputString.length(); i++) {
|
||||||
|
if (inputString.charAt(i) == '.') {
|
||||||
|
mask[i] = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mask[i] = (byte) 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] getBytes(String inputString) {
|
||||||
|
byte[] bytes = inputString.getBytes();
|
||||||
|
for (int i = 0; i < inputString.length(); i++) {
|
||||||
|
if (inputString.charAt(i) == '.') {
|
||||||
|
bytes[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return inputString;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPreSequenceLength() {
|
||||||
|
return preSequenceLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user