diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/EmbeddedMediaAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/EmbeddedMediaAnalyzer.java
index 3bfeeb886a..27c3157d1b 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/EmbeddedMediaAnalyzer.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/EmbeddedMediaAnalyzer.java
@@ -15,23 +15,27 @@
*/
package ghidra.app.plugin.core.analysis;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
import ghidra.app.cmd.data.CreateDataCmd;
import ghidra.app.services.*;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.options.Options;
-import ghidra.program.model.address.*;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.data.*;
-import ghidra.program.model.listing.*;
+import ghidra.program.model.listing.BookmarkType;
+import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
+import ghidra.util.bytesearch.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class EmbeddedMediaAnalyzer extends AbstractAnalyzer {
private static final String NAME = "Embedded Media";
private static final String DESCRIPTION =
- "Finds and tries to apply embedded media data types (ie png, gif, jpeg, wav) in current program.";
+ "Finds embedded media data types (ie png, gif, jpeg, wav)";
private static final String OPTION_NAME_CREATE_BOOKMARKS = "Create Analysis Bookmarks";
private static final String OPTION_DESCRIPTION_CREATE_BOOKMARKS =
@@ -52,92 +56,78 @@ public class EmbeddedMediaAnalyzer extends AbstractAnalyzer {
throws CancelledException {
Memory memory = program.getMemory();
- AddressSetView initializedAddressSet = memory.getLoadedAndInitializedAddressSet();
- AddressSet initialedSearchSet = set.intersect(initializedAddressSet);
+ AddressSetView validMemorySet = memory.getLoadedAndInitializedAddressSet();
+ AddressSetView searchSet = set.intersect(validMemorySet);
+ if (searchSet.isEmpty()) {
+ return false; // no valid addresses to search
+ }
+
+ MemoryBytePatternSearcher searcher = new MemoryBytePatternSearcher("Embedded Media");
List
foundMedia = new ArrayList<>();
- foundMedia = scanForMedia(program, new GifDataType(), "GIF 87", GifDataType.MAGIC_87,
- GifDataType.GIFMASK, initialedSearchSet, memory, monitor);
+ addByteSearchPattern(searcher, program, foundMedia, new GifDataType(), "GIF 87",
+ GifDataType.MAGIC_87, GifDataType.GIFMASK);
- foundMedia.addAll(scanForMedia(program, new GifDataType(), "GIF 89", GifDataType.MAGIC_89,
- GifDataType.GIFMASK, initialedSearchSet, memory, monitor));
+ addByteSearchPattern(searcher, program, foundMedia, new GifDataType(), "GIF 89",
+ GifDataType.MAGIC_89, GifDataType.GIFMASK);
- foundMedia.addAll(scanForMedia(program, new PngDataType(), "PNG", PngDataType.MAGIC,
- PngDataType.MASK, initialedSearchSet, memory, monitor));
+ addByteSearchPattern(searcher, program, foundMedia, new PngDataType(), "PNG",
+ PngDataType.MAGIC, PngDataType.MASK);
- foundMedia.addAll(scanForMedia(program, new JPEGDataType(), "JPEG", JPEGDataType.MAGIC,
- JPEGDataType.MAGIC_MASK, initialedSearchSet, memory, monitor));
+ addByteSearchPattern(searcher, program, foundMedia, new JPEGDataType(), "JPEG",
+ JPEGDataType.MAGIC, JPEGDataType.MAGIC_MASK);
- foundMedia.addAll(scanForMedia(program, new WAVEDataType(), "WAVE", WAVEDataType.MAGIC,
- WAVEDataType.MAGIC_MASK, initialedSearchSet, memory, monitor));
+ addByteSearchPattern(searcher, program, foundMedia, new WAVEDataType(), "WAVE",
+ WAVEDataType.MAGIC, WAVEDataType.MAGIC_MASK);
- foundMedia.addAll(scanForMedia(program, new AUDataType(), "AU", AUDataType.MAGIC,
- AUDataType.MAGIC_MASK, initialedSearchSet, memory, monitor));
+ addByteSearchPattern(searcher, program, foundMedia, new AUDataType(), "AU",
+ AUDataType.MAGIC, AUDataType.MAGIC_MASK);
- foundMedia.addAll(scanForMedia(program, new AIFFDataType(), "AIFF", AIFFDataType.MAGIC,
- AIFFDataType.MAGIC_MASK, initialedSearchSet, memory, monitor));
+ addByteSearchPattern(searcher, program, foundMedia, new AIFFDataType(), "AIFF",
+ AIFFDataType.MAGIC, AIFFDataType.MAGIC_MASK);
- return true;
+ searcher.search(program, searchSet, monitor);
+
+ return foundMedia.size() > 0;
}
- private List scanForMedia(Program program, DataType dt, String mediaName,
- byte[] mediaBytes, byte[] mask, AddressSetView addresses, Memory memory,
- TaskMonitor monitor) {
-
- monitor.setMessage("Scanning for " + mediaName + " Embedded Media");
- monitor.initialize(addresses.getNumAddresses());
-
- List foundMediaAddresses = new ArrayList<>();
-
- Iterator iterator = addresses.iterator();
- while (iterator.hasNext()) {
- if (monitor.isCancelled()) {
- return foundMediaAddresses;
- }
-
- AddressRange range = iterator.next();
- Address start = range.getMinAddress();
- Address end = range.getMaxAddress();
-
- Address found = memory.findBytes(start, end, mediaBytes, mask, true, monitor);
- while (found != null && !monitor.isCancelled()) {
- //See if it is already an applied media data type
- Data data = program.getListing().getDefinedDataAt(found);
- int skipLen = 1;
- if (data == null) {
- try {
- CreateDataCmd cmd = new CreateDataCmd(found, dt);
- if (cmd.applyTo(program)) {
- if (createBookmarksEnabled) {
- program.getBookmarkManager().setBookmark(found,
- BookmarkType.ANALYSIS, "Embedded Media",
- "Found " + mediaName + " Embedded Media");
- }
- foundMediaAddresses.add(found);
- //have to get the actual applied data to find the actual length to skip because until then it can't compute the length due to the data type being dynamic
- skipLen = program.getListing().getDataAt(found).getLength();
- }
- }
- //If media does not apply correctly then it is not really a that media data type or there is other data in the way
- catch (Exception e) {
- // Not a valid embedded media or no room to apply it so just ignore it and skip it
- }
- }
- // skip either the valid data that was found or skip one byte
- // then do the next search
- try {
- start = found.add(skipLen);
- found = memory.findBytes(start, end, mediaBytes, mask, true, monitor);
- }
- catch (AddressOutOfBoundsException e) {
- // If media was at the very end of the address space, we will end up here
- break;
- }
- }
+ private void addByteSearchPattern(MemoryBytePatternSearcher searcher, Program program,
+ List foundMedia, DataType mediaDT, String mediaName, byte[] bytes,
+ byte[] mask) {
+ if (bytes == null) {
+ return;
}
- return foundMediaAddresses;
+ GenericMatchAction action = new GenericMatchAction(mediaDT) {
+ @Override
+ public void apply(Program prog, Address addr, Match match) {
+ //See if it is already an applied media data type
+ if (!program.getListing().isUndefined(addr, addr)) {
+ return;
+ }
+
+ try {
+ CreateDataCmd cmd = new CreateDataCmd(addr, mediaDT);
+ if (cmd.applyTo(program)) {
+ if (createBookmarksEnabled) {
+ program.getBookmarkManager().setBookmark(addr, BookmarkType.ANALYSIS,
+ "Embedded Media", "Found " + mediaName + " Embedded Media");
+ }
+ foundMedia.add(addr);
+ }
+ }
+ //If media does not apply correctly then it is not really a that media data type or there is other data in the way
+ catch (Exception e) {
+ // Not a valid embedded media or no room to apply it so just ignore it and skip it
+ }
+ }
+ };
+
+ GenericByteSequencePattern genericByteMatchPattern =
+ new GenericByteSequencePattern(bytes, mask, action);
+
+ searcher.addPattern(genericByteMatchPattern);
}
@Override
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramMemoryUtil.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramMemoryUtil.java
index f87cc044b4..fb4e1e4276 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramMemoryUtil.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramMemoryUtil.java
@@ -177,8 +177,7 @@ public class ProgramMemoryUtil {
MemoryBlock[] tmpBlocks = new MemoryBlock[blocks.length];
int j = 0;
for (MemoryBlock block : blocks) {
- if ((block.isInitialized() && withBytes) ||
- (!block.isInitialized() && !withBytes)) {
+ if ((block.isInitialized() && withBytes) || (!block.isInitialized() && !withBytes)) {
tmpBlocks[j++] = block;
}
}
@@ -493,6 +492,30 @@ public class ProgramMemoryUtil {
monitor = TaskMonitorAdapter.DUMMY_MONITOR;
}
+ byte[] addressBytes = getDirectAddressBytes(program, toAddress);
+
+ byte[] shiftedAddressBytes = getShiftedDirectAddressBytes(program, toAddress);
+
+ Memory memory = program.getMemory();
+ Set dirRefsAddrs = new TreeSet<>();
+ findBytePattern(memory, blocks, addressBytes, alignment, dirRefsAddrs, monitor);
+
+ if (shiftedAddressBytes != null) { // assume shifted address not supported with segmented memory
+ findBytePattern(memory, blocks, shiftedAddressBytes, alignment, dirRefsAddrs, monitor);
+ }
+
+ return dirRefsAddrs;
+ }
+
+ /**
+ * Get a representation of an address as it would appear in bytes in memory.
+ *
+ * @param program program
+ * @param toAddress target address
+ * @return byte representation of toAddress
+ */
+ public static byte[] getDirectAddressBytes(Program program, Address toAddress) {
+
Memory memory = program.getMemory();
boolean isBigEndian = memory.isBigEndian();
@@ -536,6 +559,31 @@ public class ProgramMemoryUtil {
addressBytes, 0, addressBytes.length);
}
+ return addressBytes;
+ }
+
+ /**
+ * returns shifted address bytes if they are different than un-shifted
+ *
+ * @param program program
+ * @param toAddress target address
+ * @return shifted bytes, null if same as un-shifted
+ */
+ public static byte[] getShiftedDirectAddressBytes(Program program, Address toAddress) {
+
+ byte[] addressBytes = getDirectAddressBytes(program, toAddress);
+
+ Memory memory = program.getMemory();
+ boolean isBigEndian = memory.isBigEndian();
+
+ DataConverter dataConverter;
+ if (isBigEndian) {
+ dataConverter = new BigEndianDataConverter();
+ }
+ else {
+ dataConverter = new LittleEndianDataConverter();
+ }
+
byte[] shiftedAddressBytes = null;
DataTypeManager dataTypeManager = program.getDataTypeManager();
DataOrganization dataOrganization = dataTypeManager.getDataOrganization();
@@ -554,24 +602,23 @@ public class ProgramMemoryUtil {
}
}
- // don't need this anymore - finding all 16 bit addrs in whole prog
-// AddressRange segmentRange = null;
-// if (toAddress instanceof SegmentedAddress) {
-// // Restrict search to currentSegment range
-// SegmentedAddressSpace segSpace = (SegmentedAddressSpace) toAddress.getAddressSpace();
-// segmentRange =
-// new AddressRangeImpl(segSpace.getAddress(currentSegment, 0), segSpace.getAddress(
-// currentSegment, 0xffff));
-// }
+ return shiftedAddressBytes;
+ }
- Set dirRefsAddrs = new TreeSet<>();
- findBytePattern(memory, blocks, addressBytes, alignment, dirRefsAddrs, monitor);
+ public static byte[] getImageBaseOffsets32Bytes(Program program, int alignment,
+ Address toAddress) {
- if (shiftedAddressBytes != null) { // assume shifted address not supported with segmented memory
- findBytePattern(memory, blocks, shiftedAddressBytes, alignment, dirRefsAddrs, monitor);
+ Address imageBase = program.getImageBase();
+
+ long offsetValue = toAddress.subtract(imageBase);
+ int offsetSize = 4; // 32 bit offset
+ byte[] bytes = new byte[offsetSize];
+ for (int i = 0; i < offsetSize; i++) {
+ bytes[i] = (byte) offsetValue;
+ offsetValue >>= 8; // Shift by a single byte.
}
- return dirRefsAddrs;
+ return bytes;
}
/**
@@ -627,8 +674,7 @@ public class ProgramMemoryUtil {
if (!block.isInitialized()) {
continue;
}
- if (memoryRange != null &&
- !memoryRange.intersects(block.getStart(), block.getEnd())) {
+ if (memoryRange != null && !memoryRange.intersects(block.getStart(), block.getEnd())) {
// skip blocks which do not correspond to currentSeg
continue;
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/AlignRule.java b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/AlignRule.java
new file mode 100644
index 0000000000..b6606476f8
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/AlignRule.java
@@ -0,0 +1,82 @@
+/* ###
+ * 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.util.xml.SpecXmlUtils;
+import ghidra.xml.XmlElement;
+import ghidra.xml.XmlPullParser;
+
+/**
+ * ByteSearch post search rule when a pattern is found. Used when a pattern must have a certain
+ * alignment at an offset from the location the pattern matches.
+ *
+ * The pattern can be constructed or restored from XML of the form,
+ * where alignOffset=mark, alignmask=bits
+ *
+ *
+ *
+ */
+
+public class AlignRule implements PostRule {
+
+ private int alignOffset; // Position, relative to start of pattern, to check alignment at
+ private int alignmask; // Mask of bits that must be zero
+
+ public AlignRule() {
+ }
+
+ /**
+ * ByteSearch post search rule when a pattern is found. Used when a pattern must have a certain
+ * alignment at an offset from the location the pattern matches. The alignment is
+ * specified by the alignmask bits that must be zero.
+ *
+ * Normally alignOffset is 0, since most patterns will match at the address that must be aligned
+ * To align a match, use the following
+ *
+ * align to 2 = alignmask 0x1 - lower bit must be zero
+ * align to 4 = alignmask 0x3 - lower two bits must be zero
+ * align to 8 = alignmask 0x7 - lower three bits must be zero
+ * align to 16 = alignmask 0xF - lower four bits must be zero
+ * ....
+ * Other strange alignments could be specified, but most likely the above suffice.
+ * @param alignOffset - bytes offset from pattern to check for alignment
+ * @param alignmask - the mask where a 1 bit must be zero
+ */
+ public AlignRule(int alignOffset, int alignmask) {
+ this.alignOffset = alignOffset;
+ this.alignmask = alignmask;
+ }
+
+ @Override
+ public boolean apply(Pattern pat, long matchoffset) {
+ int off = (int) matchoffset;
+ return (((off + alignOffset) & alignmask) == 0);
+ }
+
+ @Override
+ public void restoreXml(XmlPullParser parser) {
+ XmlElement el = parser.start("align");
+ alignOffset = SpecXmlUtils.decodeInt(el.getAttribute("mark"));
+ int bits = SpecXmlUtils.decodeInt(el.getAttribute("bits"));
+ alignmask = (1 << bits) - 1;
+ parser.end();
+ }
+
+ public int getAlignMask() {
+ return alignmask;
+ }
+
+}
diff --git a/Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/DittedBitSequence.java b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/DittedBitSequence.java
similarity index 74%
rename from Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/DittedBitSequence.java
rename to Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/DittedBitSequence.java
index 77a17295a7..bc85483788 100644
--- a/Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/DittedBitSequence.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/DittedBitSequence.java
@@ -21,6 +21,19 @@ import java.util.zip.CRC32;
import ghidra.xml.XmlPullParser;
+/**
+ * A pattern of bits/mask to match to a stream of bytes. The bits/mask can be of any length.
+ * The sequence can be initialized by:
+ *
+ * a string
+ * an array of bytes (no mask)
+ * an array of bytes and for mask
+ *
+ * The dits represent bits(binary) or nibbles(hex) that are don't care, for example:
+ * 0x..d.4de2 ....0000 .1...... 00101101 11101001
+ * where 0x starts a hex number and '.' is a don't care nibble (hex) or bit (binary)
+ */
+
public class DittedBitSequence {
//Given a byte 0-255 (NOT a signed byte), retrieves its popcount.
@@ -42,7 +55,7 @@ public class DittedBitSequence {
4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8 //240-255
};
- protected int index; // Unique index assigned to this sequence
+ private int index; // Unique index assigned to this sequence
private byte[] bits; // value bits contained in the sequence
private byte[] dits; // a 1 indicates the bit is not ditted
@@ -53,7 +66,9 @@ public class DittedBitSequence {
/**
* Constructor from a ditted-bit-sequence string where white space is ignored (e.g., "10..11.0");
- * @param dittedBitData
+ *
+ * @param dittedBitData ditted sequence specified as a string
+ *
* @throws IllegalArgumentException if invalid dittedBitData specified
*/
public DittedBitSequence(String dittedBitData) {
@@ -64,8 +79,8 @@ public class DittedBitSequence {
* Constructor from a ditted-bit string where white space is ignored. If there are no dits,
* {@code hex} is true, and {@code hex} does not begin with {code 0x}, {@code 0x} will be
* prepended to the string before constructing the {@link DittedBitSequence}.
- * @param dittedBitData
- * @param hex
+ * @param dittedBitData string of bits and dits or hex numbers and dits (e.g., 0.1..0, 0xAB..)
+ * @param hex true to force hex on the sequence
*/
public DittedBitSequence(String dittedBitData, boolean hex) {
if (hex && !dittedBitData.contains(".")) {
@@ -101,7 +116,8 @@ public class DittedBitSequence {
/**
* Construct a sequence of bytes to search for. No bits are masked off.
- * @param bytes
+ *
+ * @param bytes byte values that must match
*/
public DittedBitSequence(byte[] bytes) {
bits = bytes;
@@ -164,22 +180,40 @@ public class DittedBitSequence {
return true;
}
- public DittedBitSequence concatenate(DittedBitSequence op2) {
+ /**
+ * Concatenates a sequence to the end of another sequence and
+ * returns a new sequence.
+ *
+ * @param toConat sequence to concatenate to this sequence
+ *
+ * @return a new sequence that is the concat of this and toConcat
+ */
+ public DittedBitSequence concatenate(DittedBitSequence toConat) {
DittedBitSequence res = new DittedBitSequence();
- res.bits = new byte[bits.length + op2.bits.length];
+ res.bits = new byte[bits.length + toConat.bits.length];
res.dits = new byte[res.bits.length];
for (int i = 0; i < bits.length; ++i) {
res.bits[i] = bits[i];
res.dits[i] = dits[i];
}
- for (int i = 0; i < op2.bits.length; ++i) {
- res.bits[bits.length + i] = op2.bits[i];
- res.dits[bits.length + i] = op2.dits[i];
+ for (int i = 0; i < toConat.bits.length; ++i) {
+ res.bits[bits.length + i] = toConat.bits[i];
+ res.dits[bits.length + i] = toConat.dits[i];
}
return res;
}
+ /**
+ * Check for a match of a value at a certain offset in the pattern.
+ * An outside matcher will keep track of the match position within this
+ * ditted bit sequence. Then call this method to match.
+ *
+ * @param pos position in the pattern to match
+ * @param val a byte to be match at the given byte offset in the pattern
+ *
+ * @return true if the byte matches the sequence mask/value
+ */
public boolean isMatch(int pos, int val) {
if (pos >= bits.length) {
return false;
@@ -187,14 +221,38 @@ public class DittedBitSequence {
return ((byte) (val & dits[pos])) == bits[pos];
}
+ /**
+ * Set a an index in a larger sequence, or identifing id on this pattern
+ *
+ * @param index - index in match sequence, or unique id
+ */
+ public void setIndex(int index) {
+ this.index = index;
+ }
+
+ /**
+ * Get the index or identifying id attached to this pattern
+ *
+ * @return index or unique id attached to this sequence
+ */
public int getIndex() {
return index;
}
+ /**
+ * get the size of this sequence in bytes
+ *
+ * @return size in bytes
+ */
public int getSize() {
return bits.length;
}
+ /**
+ * Get number of bits that must be 0/1
+ *
+ * @return number of bits that are not don't care (ditted)
+ */
public int getNumFixedBits() {
int popcnt = 0;
for (byte dit : dits) {
@@ -203,7 +261,11 @@ public class DittedBitSequence {
return popcnt;
}
- //Return the number of dits.
+ /**
+ * Get number of bits that are ditted (don't care)
+ *
+ * @return number of ditted bits (don't care)
+ */
public int getNumUncertainBits() {
int popcnt = 0;
for (byte dit : dits) {
@@ -235,6 +297,11 @@ public class DittedBitSequence {
return buf.toString();
}
+ /**
+ * get a ditted hex string representing this sequence
+ *
+ * @return ditted hex string
+ */
public String getHexString() {
String uncompressed = this.toString();
String[] parts = uncompressed.trim().split(" ");
@@ -260,6 +327,17 @@ public class DittedBitSequence {
return sb.toString();
}
+ /**
+ * restore ditted string from XML stream with hex/binary ditted sequences in the form:
+ * 0x..d.4de2 ....0000 .1...... 00101101 11101001
+ * where 0x starts a hex number and '.' is a don't care nibble (hex) or bit (binary)
+ *
+ * @param parser XML pull parser stream
+ *
+ * @return number of bytes read from XML tag
+ *
+ * @throws IOException if XML read has an error
+ */
protected int restoreXmlData(XmlPullParser parser) throws IOException {
parser.start("data");
String text = parser.end().getText();
@@ -272,6 +350,16 @@ public class DittedBitSequence {
}
}
+ /**
+ * Initialize this sequence with a ditted sequence from a string in the form
+ * (e.g. - 011...1., 0x.F, 01110011 0xAB)
+ *
+ * @param text ditted sequence
+ *
+ * @return number of bytes in the ditted sequence
+ *
+ * @throws IllegalArgumentException if string is malformed
+ */
private int initFromDittedStringData(String text) throws IllegalArgumentException {
int markOffset = -1;
int mode = -1; // -1: looking for start, -2: skip to EOL, 0: hex mode, 1: binary mode
@@ -372,6 +460,13 @@ public class DittedBitSequence {
return markOffset;
}
+ /**
+ * Get the number of bits that are fixed, not ditted (don't care)
+ *
+ * @param marked number of bytes in the pattern to check
+ *
+ * @return number of initial fixed bits
+ */
public int getNumInitialFixedBits(int marked) {
if (dits == null) {
return 0;
@@ -385,5 +480,4 @@ public class DittedBitSequence {
}
return popcnt;
}
-
}
diff --git a/Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/DummyMatchAction.java b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/DummyMatchAction.java
similarity index 91%
rename from Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/DummyMatchAction.java
rename to Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/DummyMatchAction.java
index 29aa9689a0..e8e58e4c37 100644
--- a/Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/DummyMatchAction.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/DummyMatchAction.java
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
- * REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +19,9 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.xml.XmlPullParser;
+/**
+ * Dummy action attached to a match sequence. Action is not restored from XML
+ */
public class DummyMatchAction implements MatchAction {
@Override
@@ -30,6 +32,5 @@ public class DummyMatchAction implements MatchAction {
public void restoreXml(XmlPullParser parser) {
parser.discardSubTree();
}
-
-}
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/GenericByteSequencePattern.java b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/GenericByteSequencePattern.java
new file mode 100644
index 0000000000..be3dfa7533
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/GenericByteSequencePattern.java
@@ -0,0 +1,61 @@
+/* ###
+ * 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.data.DataType;
+
+/**
+ * Templated simple DittedBitSequence Pattern for a byte/mask pattern and associated action.
+ * The DittedBitSequence is provided by value and mask in byte arrays.
+ *
+ * This class is normally used to find some number of SequencePatterns within a seqence of bytes.
+ * When the byte/mask pattern is matched, the GenericMatchAction will be "applied".
+ *
+ * @param the class of match action, used to specify a specialized momento to be used by the action when it is "applied".
+ */
+
+public class GenericByteSequencePattern extends Pattern {
+
+ /**
+ * Construct a sequence of bytes with no mask, and associated action
+ * to be called if this pattern matches.
+ *
+ * @param bytesSequence sequence of bytes to match
+ * @param action action to apply if the match succeeds
+ */
+ public GenericByteSequencePattern(byte[] bytesSequence, GenericMatchAction action) {
+ super(new DittedBitSequence(bytesSequence), 0, new PostRule[0], new MatchAction[1]);
+
+ MatchAction[] matchActions = getMatchActions();
+ matchActions[0] = action;
+ }
+
+ /**
+ * Construct a sequence of bytes with a mask, and associated action
+ * to be called if this pattern matches.
+ *
+ * @param bytesSequence sequence of bytes to match
+ * @param mask mask, bits that are 1 must match the byteSequence bits
+ * @param action to apply if the match succeeds
+ */
+ public GenericByteSequencePattern(byte[] bytesSequence, byte[] mask,
+ GenericMatchAction action) {
+ super(new DittedBitSequence(bytesSequence, mask), 0, new PostRule[0], new MatchAction[1]);
+
+ MatchAction[] matchActions = getMatchActions();
+ matchActions[0] = action;
+ }
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/GenericMatchAction.java b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/GenericMatchAction.java
new file mode 100644
index 0000000000..3b337c6fed
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/GenericMatchAction.java
@@ -0,0 +1,43 @@
+/* ###
+ * 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;
+
+/**
+ * Template for generic match action attached to a match sequence.
+ * Used to store an associated value to the matching sequence.
+ * The associated value can be retrieved when the sequence is matched.
+ *
+ * @param - object to attach to match sequence, generally used to specify
+ * a specialized momento to be used by the action when it is "applied".
+ */
+public class GenericMatchAction extends DummyMatchAction {
+ T matchValue;
+
+ /**
+ * Construct a match action used when a match occurs for some GenericByteSequece
+ * @param matchValue specialized object used when match occurs
+ */
+ public GenericMatchAction(T matchValue) {
+ this.matchValue = matchValue;
+ }
+
+ /**
+ * @return the specialized object associated with this match action
+ */
+ public T getMatchValue() {
+ return this.matchValue;
+ }
+}
diff --git a/Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/Match.java b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/Match.java
similarity index 54%
rename from Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/Match.java
rename to Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/Match.java
index fb48f8bb07..59dac95a56 100644
--- a/Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/Match.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/Match.java
@@ -15,13 +15,27 @@
*/
package ghidra.util.bytesearch;
+/**
+ * Represents a match of a DittedBitSequence at a given offset in a byte sequence.
+ *
+ * There is a hidden assumption that the sequence is actually a Pattern
+ * that might have a ditted-bit-sequence, a set of match actions,
+ * and post match rules/checks
+ *
+ */
public class Match {
- private DittedBitSequence sequence; // Pattern that matches
- private long offset; // starting offset within bytestream of match
+ private DittedBitSequence sequence; // Pattern that matched
+ private long offset; // Offset within bytestream where the match occurred
- public Match(DittedBitSequence seq, long off) {
- sequence = seq;
- offset = off;
+ /**
+ * Construct a Match of a DittedBitSequence at an offset within a byte stream.
+ * Object normally used when a match occurs during a MemoryBytePatternSearch.
+ * @param sequence that matched
+ * @param offset from the start of byte stream where the matched occured
+ */
+ public Match(DittedBitSequence sequence, long offset) {
+ this.sequence = sequence;
+ this.offset = offset;
}
/**
@@ -39,42 +53,70 @@ public class Match {
return sequence.getNumFixedBits() - sequence.getNumInitialFixedBits(marked);
}
+ /**
+ * @return actions associated with this match
+ */
public MatchAction[] getMatchActions() {
return ((Pattern) sequence).getMatchActions();
}
+ /**
+ * @return size in bytes of sequence
+ */
public int getSequenceSize() {
return sequence.getSize();
}
+ /**
+ * @return index of sequence in a possibly longer set of sequences
+ */
public int getSequenceIndex() {
return sequence.getIndex();
}
+ /**
+ * @return the offset of the match within a longer byte sequence
+ */
public long getMarkOffset() {
return offset + ((Pattern) sequence).getMarkOffset();
}
+ /**
+ * @return offset of match in sequence of bytes
+ */
public long getMatchStart() {
return offset;
}
+ /**
+ * Check that the possible post rules are satisfied
+ *
+ * @param streamoffset offset within from match location to check postrules.
+ *
+ * @return true if post rules are satisfied
+ */
public boolean checkPostRules(long streamoffset) {
long curoffset = streamoffset + offset;
Pattern pattern = (Pattern) sequence;
PostRule[] postRules = pattern.getPostRules();
- for (int i = 0; i < postRules.length; ++i) {
- if (!postRules[i].apply(pattern, curoffset)) {
+ for (PostRule postRule : postRules) {
+ if (!postRule.apply(pattern, curoffset)) {
return false;
}
}
return true;
}
+ /**
+ * @return ditted bit sequence as a string
+ */
public String getHexString() {
return sequence.getHexString();
}
+ /**
+ * @return the sequence that was matched
+ */
public DittedBitSequence getSequence() {
return sequence;
}
diff --git a/Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/MatchAction.java b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/MatchAction.java
similarity index 62%
rename from Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/MatchAction.java
rename to Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/MatchAction.java
index 1ef3b59008..6c81bfe9a9 100644
--- a/Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/MatchAction.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/MatchAction.java
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
- * REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,11 +20,22 @@ import ghidra.program.model.listing.Program;
import ghidra.xml.XmlPullParser;
/**
- * Action that should be applied to a Program at the Address a pattern matches
- *
+ * Interface for a match action to be taken for the Program@Address for a ditted bit seqence pattern
*/
public interface MatchAction {
- public void apply(Program program,Address addr,Match match);
-
+ /**
+ * Apply the match action to the program at the address.
+ *
+ * @param program program in which the match occurred
+ * @param addr where the match occured
+ * @param match information about the match that occurred
+ */
+ public void apply(Program program, Address addr, Match match);
+
+ /**
+ * Action can be constructed from XML
+ *
+ * @param parser XML pull parser to restore action from XML
+ */
public void restoreXml(XmlPullParser parser);
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/MemoryBytePatternSearcher.java b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/MemoryBytePatternSearcher.java
new file mode 100644
index 0000000000..5249bc9c69
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/MemoryBytePatternSearcher.java
@@ -0,0 +1,268 @@
+/* ###
+ * 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.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+import ghidra.program.model.address.*;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.mem.MemoryBlock;
+import ghidra.util.Msg;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.task.TaskMonitor;
+
+/**
+ * Multi pattern/mask/action memory searcher
+ * Patterns must be supplied/added, or a pre-initialized searchState supplied
+ *
+ * Preload search patterns and actions, then call search method.
+ */
+
+public class MemoryBytePatternSearcher {
+ private static final long RESTRICTED_PATTERN_BYTE_RANGE = 32;
+
+ SequenceSearchState root = null;
+
+ ArrayList patternList;
+
+ private String searchName = "";
+
+ private boolean doExecutableBlocksOnly = false; // only search executable blocks
+
+ private long numToSearch = 0;
+ private long numSearched = 0;
+
+ /**
+ * Create with pre-created patternList
+ * @param searchName name of search
+ * @param patternList - list of patterns(bytes/mask/action)
+ */
+ public MemoryBytePatternSearcher(String searchName, ArrayList patternList) {
+ this.searchName = searchName;
+ this.patternList = new ArrayList(patternList);
+ }
+
+ /**
+ * Create with an initialized SequenceSearchState
+ * @param searchName name of search
+ * @param root search state pre-initialized
+ */
+ public MemoryBytePatternSearcher(String searchName, SequenceSearchState root) {
+ this.searchName = searchName;
+ this.root = root;
+ }
+
+ /**
+ * Create with no patternList, must add patterns before searching
+ * @param searchName name of search
+ *
+ */
+ public MemoryBytePatternSearcher(String searchName) {
+ this.searchName = searchName;
+ patternList = new ArrayList<>();
+ }
+
+ /**
+ * Add a search pattern
+ * @param pattern - pattern(bytes/mask/action)
+ */
+ public void addPattern(Pattern pattern) {
+ patternList.add(pattern);
+ }
+
+ public void setSearchExecutableOnly(boolean doExecutableBlocksOnly) {
+ this.doExecutableBlocksOnly = doExecutableBlocksOnly;
+ }
+
+ /**
+ * Search initialized memory blocks for all patterns(bytes/mask/action).
+ * Call associated action for each pattern matched.
+ *
+ * @param program to be searched
+ * @param searchSet set of bytes to restrict search, if null or empty then search all memory blocks
+ * @param monitor allow canceling and reporting of progress
+ *
+ * @throws CancelledException if canceled
+ */
+ public void search(Program program, AddressSetView searchSet, TaskMonitor monitor)
+ throws CancelledException {
+ if (root == null) {
+ root = SequenceSearchState.buildStateMachine(patternList);
+ }
+
+ numToSearch = getNumToSearch(program, searchSet);
+ monitor.setMessage(searchName + " Search");
+ monitor.initialize(numToSearch);
+
+ MemoryBlock[] blocks = program.getMemory().getBlocks();
+ for (MemoryBlock block : blocks) {
+ 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(root, program, block, searchSet, monitor);
+ }
+ catch (IOException e) {
+ Msg.error(this, "Unable to scan block " + block.getName() + " for " + searchName);
+ }
+ numSearched += block.getSize();
+ }
+ }
+
+ private long getNumToSearch(Program program, AddressSetView searchSet) {
+ long numAddresses = 0;
+ MemoryBlock[] blocks = program.getMemory().getBlocks();
+ for (MemoryBlock block : blocks) {
+ // 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;
+ }
+ numAddresses += block.getSize();
+ }
+ return numAddresses;
+ }
+
+ /**
+ * Search through bytes of a memory block using the finite state machine -root-
+ * Apply any additional rules for matching patterns.
+ *
+ * @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(SequenceSearchState rootState, Program program, 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.checkCanceled();
+ monitor.setMessage(searchName + " Search");
+ monitor.setProgress(progress + (long) (numInBlock * ((float) numDone / numInDoneSet)));
+ AddressRange addressRange = addressRanges.next();
+ long numAddressesInRange = addressRange.getLength();
+
+ ArrayList 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);
+
+ rootState.apply(data, maxBlockSearchLength, mymatches, monitor);
+ monitor.checkCanceled();
+
+ 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.checkCanceled();
+ monitor.setProgress(
+ matchProgress + (long) (numAddressesInRange * ((float) i / mymatches.size())));
+ Match match = mymatches.get(i);
+ Address addr = blockStartAddr.add(match.getMarkOffset() + blockOffset);
+ if (!match.checkPostRules(streamoffset + blockOffset)) {
+ continue;
+ }
+
+ MatchAction[] matchactions = match.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
+ }
+
+ /**
+ * Called after any match rules are applied
+ * Can use for cross post rule matching state application and cleanup.
+ * @param matchactions actions that matched
+ * @param addr adress of match
+ */
+ public void postMatchApply(MatchAction[] matchactions, Address addr) {
+ // override if any cleanup from rule match application is needed
+ }
+}
diff --git a/Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/Pattern.java b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/Pattern.java
similarity index 88%
rename from Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/Pattern.java
rename to Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/Pattern.java
index c158590b58..9ba00c0662 100644
--- a/Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/Pattern.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/Pattern.java
@@ -24,12 +24,22 @@ import generic.jar.ResourceFile;
import ghidra.util.xml.SpecXmlUtils;
import ghidra.xml.*;
+/**
+ * Pattern is an association of a DittedBitSequence to match,
+ * a set of post rules after a match is found that must be satisfied,
+ * and a set of actions to be taken if the pattern matches.
+ *
+ * These patterns can be restored from an XML file.
+ */
public class Pattern extends DittedBitSequence {
private int markOffset; // Within pattern what is the 'marked' byte
private PostRule[] postrule;
private MatchAction[] actions;
+ /**
+ * Construct an empty pattern. Use XML to initialize
+ */
public Pattern() {
markOffset = 0;
postrule = null;
@@ -37,7 +47,17 @@ public class Pattern extends DittedBitSequence {
}
- public Pattern(DittedBitSequence seq, int offset, PostRule[] postArray, MatchAction[] matchArray) {
+ /**
+ * Construct the pattern based on a DittedByteSequence a match offset, post matching rules,
+ * and a set of actions to take when the match occurs.
+ *
+ * @param seq DittedByteSequence
+ * @param offset offset from the actual match location to report a match
+ * @param postArray post set of rules to check for the match
+ * @param matchArray MatchActions to apply when a match occurs
+ */
+ public Pattern(DittedBitSequence seq, int offset, PostRule[] postArray,
+ MatchAction[] matchArray) {
super(seq);
markOffset = offset;
postrule = postArray;
@@ -51,8 +71,8 @@ public class Pattern extends DittedBitSequence {
public MatchAction[] getMatchActions() {
return actions;
}
-
- public void setMatchActions(MatchAction[] actions){
+
+ public void setMatchActions(MatchAction[] actions) {
this.actions = actions;
}
diff --git a/Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/PatternFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/PatternFactory.java
similarity index 68%
rename from Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/PatternFactory.java
rename to Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/PatternFactory.java
index f37b22beb6..cfbe60acd7 100644
--- a/Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/PatternFactory.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/PatternFactory.java
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
- * REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,8 +15,22 @@
*/
package ghidra.util.bytesearch;
+/**
+ * Interface for factories that create Match Pattern classes
+ */
public interface PatternFactory {
+ /**
+ * Get a named match action
+ *
+ * @param nm name of action to find
+ * @return match action with the given name, null otherwise
+ */
public MatchAction getMatchActionByName(String nm);
-
+
+ /**
+ * Get a named post match rule by name
+ * @param nm name of the post rule
+ * @return the post rule with the name, null otherwise
+ */
public PostRule getPostRuleByName(String nm);
}
diff --git a/Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/PatternPairSet.java b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/PatternPairSet.java
similarity index 58%
rename from Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/PatternPairSet.java
rename to Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/PatternPairSet.java
index 9741d87d7c..bd6fe348c5 100644
--- a/Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/PatternPairSet.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/PatternPairSet.java
@@ -23,66 +23,104 @@ import ghidra.xml.XmlElement;
import ghidra.xml.XmlPullParser;
/**
- * Two collections of patterns that are paired together to create larger patterns
- * The final large patterns all must first match a pattern from the "pre" pattern collection
- * followed immediately by a pattern from the "post" pattern collection
+ * A set of "pre" DittedBitSequences and a set of "post" Patterns are paired to form a larger pattern.
+ * To match, a sequence from the "pre" sequence set must first match, then one of the "post" patterns
+ * is matched relative to the matching "pre" pattern. This class is really a storage object for the
+ * patterns and provides a mechanism to read the pre/post patterns from an XML file.
*
+ * The larger pattern has the idea of bits of check, which means the number of bits that are fixed to
+ * a value when matching (not don't care). There is a pre pattern bits of check and post pattern bits
+ * of check. The bits of check are used to statistically gauge the accuracy of the pattern.
+ *
+ * An example of the XML format follows:
+ *
+ *
+ * 0xe12fff1.
+ * 0xe12fff1e 0x46c0
+ * 0xe12fff1e 0xe1a00000
+ *
+ *
+ *
+ * 0xe24dd... 11101001 00101101 .1...... ....0000
+ * 11101001 00101101 .1...... ....0000 0xe24dd...
+ * 11101001 00101101 .1...... ....0000 0x........ 0xe24dd...
+ *
+ *
+ *
+ *
+ *
+ *
+ * Note: The post Patterns can also have a set of rules that must be satisfied along with one of the
+ * Pattern DittedBitSequence matches.
*/
public class PatternPairSet {
private int totalBitsOfCheck; // Minimum number of bits of check in final patterns
private int postBitsOfCheck; // Minimum bits of check in "post" part of pattern
private ArrayList preSequences;
private ArrayList postPatterns;
-
+
+ /**
+ * Construct an empty PatternPairSet. Use XML to initialize the pattern sets.
+ */
public PatternPairSet() {
preSequences = new ArrayList();
postPatterns = new ArrayList();
}
-
+
public void createFinalPatterns(ArrayList finalpats) {
- for(int i=0;i postpats) {
- for(int i=0;i postdit = new ArrayList();
- while(el.isStart() && el.getName().equals("data")) {
+ while (el.isStart() && el.getName().equals("data")) {
DittedBitSequence postseq = new DittedBitSequence();
postseq.restoreXmlData(parser);
if (postseq.getNumFixedBits() >= postBitsOfCheck) {
@@ -99,8 +137,8 @@ public class PatternPairSet {
postRuleArray.toArray(postRules);
MatchAction[] matchActions = new MatchAction[matchActionArray.size()];
matchActionArray.toArray(matchActions);
- for(int i=0;i {
+
+ private static final int PATTERN_ENDED = Integer.MAX_VALUE;
+ private SequenceSearchState parent;
+ private ArrayList possible; // Patterns that could still match in this state
+ private ArrayList success; // Patterns that have matched successfully if we reached this state
+ private SequenceSearchState[] trans; // State transitions based on next byte
+
+ /**
+ * Construct a sub sequence state with a parent sequence
+ *
+ * @param parent parent SequenceSearchState
+ */
+ public SequenceSearchState(SequenceSearchState parent) {
+ this.parent = parent;
+ possible = new ArrayList();
+ success = null;
+ trans = null;
+ }
+
+ /**
+ * @return maximum number of bytes that could be matched by this sequence
+ */
+ public int getMaxSequenceSize() {
+ int max = 0;
+ for (DittedBitSequence element : possible) {
+ int val = element.getSize();
+ if (val > max) {
+ max = val;
+ }
+ }
+ return max;
+ }
+
+ /**
+ * Add a pattern to this search sequence. The last pattern added is the successful
+ * match pattern.
+ * @param pat pattern to add
+ * @param pos position within the current set of patterns to add this pattern
+ */
+ public void addSequence(DittedBitSequence pat, int pos) {
+ possible.add(pat);
+ if (pos == pat.getSize()) {
+ if (success == null) {
+ success = new ArrayList();
+ }
+ success.add(pat);
+ }
+ }
+
+ /**
+ * Sort the sequences that have been added
+ */
+ public void sortSequences() {
+ Comparator comp = new Comparator() {
+ @Override
+ public int compare(DittedBitSequence o1, DittedBitSequence o2) {
+ return o1.getIndex() - o2.getIndex();
+ }
+ };
+ Collections.sort(possible, comp);
+ if (success != null) {
+ Collections.sort(success, comp);
+ }
+ }
+
+ @Override
+ public int compareTo(SequenceSearchState o) {
+ int i = 0;
+ for (;;) {
+ if (possible.size() <= i) {
+ if (o.possible.size() <= i) {
+ return 0;
+ }
+ return -1;
+ }
+ if (o.possible.size() <= i) {
+ return 1;
+ }
+ int indus = possible.get(i).getIndex(); // Lexicographic compare an sequence of sequences
+ int indthem = o.possible.get(i).getIndex();
+ if (indus != indthem) {
+ return (indus < indthem) ? -1 : 1;
+ }
+ i += 1;
+ }
+ }
+
+ private void buildSingleTransition(ArrayList all, int pos, int val) {
+ SequenceSearchState newstate = null;
+ for (DittedBitSequence curpat : possible) {
+ if (curpat.isMatch(pos, val)) {
+ if (newstate == null) {
+ newstate = new SequenceSearchState(this);
+ }
+ newstate.addSequence(curpat, pos + 1);
+ }
+ }
+ trans[val] = newstate;
+ if (newstate != null) {
+ newstate.sortSequences();
+ all.add(newstate);
+ }
+ }
+
+ private void exportSuccess(ArrayList match, int offset) {
+ for (DittedBitSequence succes : success) { // If we found matches
+ Match newmatch = new Match(succes, offset);
+ match.add(newmatch);
+ }
+ }
+
+ /**
+ * Merge in -op- and this as a single state
+ * @param op
+ */
+ private void merge(SequenceSearchState op) {
+ SequenceSearchState parent = op.parent;
+ for (int i = 0; i < 256; ++i) {
+ if (parent.trans[i] == op) {
+ parent.trans[i] = this; // Should be replaced with this
+ }
+ }
+ if (op.success != null) { // Merge
+ if (success == null) {
+ success = op.success;
+ }
+ else {
+ ArrayList tmp = new ArrayList();
+ int i = 0;
+ int j = 0;
+ int curpat = -1;
+ int thispat = success.get(i).getIndex();
+ int oppat = op.success.get(j).getIndex();
+ while ((i < success.size()) || (j < op.success.size())) {
+ if (thispat == oppat) {
+ if (curpat != thispat) {
+ tmp.add(success.get(i));
+ curpat = thispat;
+ }
+ i += 1;
+ j += 1;
+ thispat = (i == success.size()) ? PATTERN_ENDED : success.get(i).getIndex();
+ oppat =
+ (j == op.success.size()) ? PATTERN_ENDED : op.success.get(j).getIndex();
+ }
+ else if (thispat < oppat) {
+ if (curpat != thispat) {
+ tmp.add(success.get(i));
+ curpat = thispat;
+ }
+ i += 1;
+ thispat = (i == success.size()) ? PATTERN_ENDED : success.get(i).getIndex();
+ }
+ else {
+ if (curpat != oppat) {
+ tmp.add(op.success.get(j));
+ curpat = oppat;
+ }
+ j += 1;
+ oppat =
+ (j == op.success.size()) ? PATTERN_ENDED : op.success.get(j).getIndex();
+ }
+ }
+ success = tmp;
+ }
+ }
+ }
+
+ /**
+ * Try to match this Sequence to the byteArray, and add any matches to the match list
+ * @param bytearray array of bytes to match
+ * @param numbytes retrict number of bytes to allow to match
+ * @param match list of matches, the result
+ */
+ public void sequenceMatch(byte[] bytearray, int numbytes, ArrayList match) {
+ int subindex = 0;
+ SequenceSearchState curstate = this;
+
+ do {
+ if (curstate.success != null) {
+ curstate.exportSuccess(match, 0);
+ }
+ if (subindex >= numbytes) {
+ return;
+ }
+ curstate = curstate.trans[0xff & bytearray[subindex]]; // Perform state transition based on next byte in buffer
+ subindex += 1;
+ }
+ while (curstate != null);
+
+ }
+
+ /**
+ * Search for patterns in a byte array. All matches are returned.
+ * @param buffer is the array of bytes to search
+ * @param match is populated with a Match object for each pattern and position that matches
+ */
+ public void apply(byte[] buffer, ArrayList match) {
+ SequenceSearchState curstate;
+ int subindex;
+ for (int offset = 0; offset < buffer.length; ++offset) {
+ curstate = this; // New starting offset -> Root state
+ subindex = offset;
+ do {
+ if (curstate.success != null) {
+ curstate.exportSuccess(match, offset);
+ }
+ if (subindex >= buffer.length) { // if we've run out of bytes, must restart at next offset
+ break;
+ }
+ curstate = curstate.trans[0xff & buffer[subindex]]; // Perform state transition based on next byte
+ subindex += 1;
+ }
+ while (curstate != null);
+ }
+ }
+
+ /**
+ * Search for pattern in the stream -in-.
+ * @param in - The stream to scan for matches
+ * @param match - Any matches are appended as Match records to this ArrayList
+ * @param monitor - if non-null, check for user cancel, and maintain progress info
+ * @throws IOException
+ */
+ public void apply(InputStream in, ArrayList match, TaskMonitor monitor)
+ throws IOException {
+ apply(in, -1L, match, monitor);
+ }
+
+ /**
+ * Search for pattern in the stream -in-.
+ * @param in - The stream to scan for matches
+ * @param maxBytes - The maximum number of bytes to scan forward in this stream
+ * @param match - Any matches are appended as Match records to this ArrayList
+ * @param monitor - if non-null, check for user cancel, and maintain progress info
+ * @throws IOException
+ */
+ public void apply(InputStream in, long maxBytes, ArrayList match, TaskMonitor monitor)
+ throws IOException {
+ long progress = monitor.getProgress();
+
+ int maxSize = getMaxSequenceSize() + 1;
+ if (maxSize < 4096) {
+ maxSize = 4096;
+ }
+ if (maxBytes > 0) {
+ maxBytes += getMaxSequenceSize() + 1;
+ }
+ byte[] firstBuf = new byte[maxSize];
+ byte[] secondBuf = new byte[maxSize];
+ byte[] curBuf;
+ SequenceSearchState curState;
+ int fullBuffers; // Number of buffers that are completely full
+ int ra = in.read(firstBuf);
+ if (ra == firstBuf.length) {
+ ra = in.read(secondBuf);
+ if (ra == secondBuf.length) {
+ fullBuffers = 2;
+ }
+ else {
+ if (ra < 0) {
+ ra = 0;
+ }
+ fullBuffers = 1;
+ byte[] tmp = new byte[ra];
+ for (int i = 0; i < ra; ++i) {
+ tmp[i] = secondBuf[i];
+ }
+ secondBuf = tmp;
+ }
+ }
+ else if (ra < 0) {
+ return; // No bytes at all were read
+ }
+ else {
+ byte[] tmp = new byte[ra];
+ for (int i = 0; i < ra; ++i) {
+ tmp[i] = firstBuf[i];
+ }
+ firstBuf = tmp;
+ fullBuffers = 0;
+ secondBuf = new byte[0];
+ }
+ int offset = 0;
+ int bufRelativeOffset = 0;
+ int subIndex;
+ while (fullBuffers == 2) {
+ curState = this; // New starting offset -> Root state
+ subIndex = bufRelativeOffset;
+ curBuf = firstBuf;
+ do {
+ if (curState.success != null) {
+ curState.exportSuccess(match, offset);
+ }
+ if (subIndex >= curBuf.length) { // check that we have enough bytes in current buffer
+ curBuf = secondBuf; // If not, switch to secondary buffer
+ subIndex = 0;
+ }
+ curState = curState.trans[0xff & curBuf[subIndex]]; // Perform state transition based on next byte in buffer
+ subIndex += 1;
+ }
+ while (curState != null);
+ offset += 1; // Advance to next starting offset
+ if (maxBytes > 0 && offset > maxBytes) {
+ break;
+ }
+ bufRelativeOffset += 1;
+ if (bufRelativeOffset == firstBuf.length) { // If starting offset no longer falls in firstbuf
+ byte[] tmp = firstBuf; // Switch firstbuf with secondbuf
+ firstBuf = secondBuf;
+ secondBuf = tmp;
+ ra = in.read(secondBuf); // refill secondbuf (old firstbuf) with new bytes
+ if (monitor != null) {
+ if (monitor.isCancelled()) {
+ return;
+ }
+ monitor.setProgress(progress + offset);
+ }
+ if (ra != secondBuf.length) {
+ fullBuffers = 1;
+ if (ra < 0) {
+ ra = 0;
+ }
+ tmp = new byte[ra];
+ for (int i = 0; i < ra; ++i) {
+ tmp[i] = secondBuf[i];
+ }
+ secondBuf = tmp;
+ }
+ bufRelativeOffset = 0;
+ }
+ }
+
+ while (fullBuffers >= 0 && (maxBytes <= 0 || offset < maxBytes)) {
+ if (secondBuf.length == 0) {
+ fullBuffers = 0;
+ }
+ curState = this;
+ subIndex = bufRelativeOffset;
+ curBuf = firstBuf;
+ do {
+ if (curState.success != null) {
+ curState.exportSuccess(match, offset);
+ }
+ if (subIndex >= curBuf.length) {
+ if (curBuf == secondBuf) {
+ break; // Out of data, all pending patterns fail
+ }
+ curBuf = secondBuf;
+ subIndex = 0;
+ if (curBuf.length == 0) {
+ break;
+ }
+ }
+ curState = curState.trans[0xff & curBuf[subIndex]];
+ subIndex += 1;
+ }
+ while (curState != null);
+ offset += 1;
+ bufRelativeOffset += 1;
+ if (bufRelativeOffset == firstBuf.length) {
+ if (fullBuffers == 0) {
+ break;
+ }
+ firstBuf = secondBuf;
+ fullBuffers = 0;
+ bufRelativeOffset = 0;
+ secondBuf = new byte[0];
+ }
+ }
+ }
+
+ /**
+ * Build a new transition level for the state machine
+ *
+ * @param prev previous search sequences
+ * @param pos position within the search sequence state for this level
+ * @return list of possible new search states to be added to the state machine
+ */
+ static ArrayList buildTransitionLevel(ArrayList prev,
+ int pos) {
+ ArrayList res = new ArrayList();
+ Iterator iterator = prev.iterator();
+ while (iterator.hasNext()) { // For each current state
+ SequenceSearchState next = iterator.next();
+ next.trans = new SequenceSearchState[256];
+ for (int i = 0; i < 256; ++i) { // Try every byte transition
+ next.buildSingleTransition(res, pos, i);
+ }
+ }
+ if (res.isEmpty()) {
+ return res;
+ }
+ // Prepare to dedup the states
+ Collections.sort(res);
+ ArrayList finalres = new ArrayList();
+ Iterator iter = res.iterator();
+ SequenceSearchState curpat = iter.next();
+ finalres.add(curpat);
+ while (iter.hasNext()) {
+ SequenceSearchState nextpat = iter.next();
+ int comp = curpat.compareTo(nextpat);
+ if (comp == 0) { // Identical states
+ curpat.merge(nextpat);
+ }
+ else {
+ curpat = nextpat;
+ finalres.add(curpat);
+ }
+ }
+ return finalres;
+ }
+
+ /**
+ * Build a search state machine from a list of DittedBitSequences
+ * @param patterns bit sequence patterns
+ * @return search state the will match the given sequences
+ */
+ static public SequenceSearchState buildStateMachine(
+ ArrayList extends DittedBitSequence> patterns) {
+ SequenceSearchState root = new SequenceSearchState(null);
+ for (int i = 0; i < patterns.size(); ++i) {
+ DittedBitSequence pat = patterns.get(i);
+ pat.setIndex(i);
+ root.addSequence(pat, 0);
+ }
+ root.sortSequences();
+ ArrayList statelevel = new ArrayList();
+ statelevel.add(root);
+ int level = 0;
+ do {
+ statelevel = buildTransitionLevel(statelevel, level);
+ level += 1;
+ }
+ while (!statelevel.isEmpty());
+ return root;
+ }
+}
diff --git a/Ghidra/Features/BytePatterns/src/main/java/ghidra/app/analyzers/FunctionStartAnalyzer.java b/Ghidra/Features/BytePatterns/src/main/java/ghidra/app/analyzers/FunctionStartAnalyzer.java
index 24e5061152..81e44397fe 100644
--- a/Ghidra/Features/BytePatterns/src/main/java/ghidra/app/analyzers/FunctionStartAnalyzer.java
+++ b/Ghidra/Features/BytePatterns/src/main/java/ghidra/app/analyzers/FunctionStartAnalyzer.java
@@ -15,8 +15,6 @@
*/
package ghidra.app.analyzers;
-import java.io.IOException;
-import java.io.InputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Iterator;
@@ -51,7 +49,6 @@ public class FunctionStartAnalyzer extends AbstractAnalyzer implements PatternFa
private static final String DESCRIPTION =
"Search for architecture specific byte patterns: typically starts of functions";
private static final String PRE_FUNCTION_MATCH_PROPERTY_NAME = "PreFunctionMatch";
- private static final int RESTRICTED_PATTERN_BYTE_RANGE = 32;
private final static String OPTION_NAME_DATABLOCKS = "Search Data Blocks";
private static final String OPTION_DESCRIPTION_DATABLOCKS =
"Search for byte patterns in blocks that are not executable";
@@ -214,7 +211,6 @@ public class FunctionStartAnalyzer extends AbstractAnalyzer implements PatternFa
applyActionToSet(program, addr, funcResult, match);
}
-
protected boolean checkPreRequisites(Program program, Address addr) {
/**
* If the match's mark point occurs in undefined data, schedule disassembly
@@ -254,10 +250,9 @@ public class FunctionStartAnalyzer extends AbstractAnalyzer implements PatternFa
return false;
}
}
-
+
return true;
}
-
protected void applyActionToSet(Program program, Address addr, AddressSet resultSet,
Match match) {
@@ -632,25 +627,34 @@ public class FunctionStartAnalyzer extends AbstractAnalyzer implements PatternFa
return false;
}
- AddressSet restrictedSet = removeNotSearchedAddresses(program, set);
+ boolean doExecutableBlocksOnly = checkForExecuteBlock(program) && executableBlocksOnly;
// clear out any previous potential matches, because we are re-looking at these places
// this will keep cruft from accumulating in the property map.
- getOrCreatePotentialMatchPropertyMap(program).remove(restrictedSet);
+ getOrCreatePotentialMatchPropertyMap(program).remove(set);
- MemoryBlock[] blocks = program.getMemory().getBlocks();
- for (MemoryBlock block2 : blocks) {
- MemoryBlock block = block2;
- if (!restrictedSet.intersects(block.getStart(), block.getEnd())) {
- continue;
+ MemoryBytePatternSearcher patternSearcher;
+ patternSearcher = new MemoryBytePatternSearcher("Function Starts", root) {
+
+ @Override
+ public void preMatchApply(MatchAction[] actions, Address addr) {
+ contextValueList = null; // make sure, only context from these actions used
}
- try {
- searchBlock(root, program, block, restrictedSet, monitor);
+
+ @Override
+ public void postMatchApply(MatchAction[] actions, Address addr) {
+ // Actions might have set context, check if postcondition failed first
+ if (!postreqFailedResult.contains(addr)) {
+ setCurrentContext(program, addr);
+ }
+ // get rid of the context list.
+ contextValueList = null;
}
- catch (IOException e) {
- log.appendMsg("Unable to scan block " + block.getName() + " for function starts");
- }
- }
+ };
+ patternSearcher.setSearchExecutableOnly(doExecutableBlocksOnly);
+
+ patternSearcher.search(program, set, monitor);
+
AutoAnalysisManager analysisManager = AutoAnalysisManager.getAnalysisManager(program);
if (!disassemResult.isEmpty()) {
analysisManager.disassemble(disassemResult);
@@ -708,34 +712,17 @@ public class FunctionStartAnalyzer extends AbstractAnalyzer implements PatternFa
}
/**
- * Get rid of any blocks from the address set that shouldn't be searched.
- *
- * @param program
- * @param bset
- * @return
+ * @return true - if there are any blocks marked executable
*/
- private AddressSet removeNotSearchedAddresses(Program program, AddressSetView bset) {
- AddressSet restrictedSet = new AddressSet(bset);
+ private boolean checkForExecuteBlock(Program program) {
MemoryBlock[] blocks = program.getMemory().getBlocks();
- boolean hasExecutable = false;
+
for (MemoryBlock block : blocks) {
if (block.isExecute()) {
- hasExecutable = true;
+ return true;
}
}
- for (MemoryBlock block : blocks) {
- if (!block.isInitialized()) {
- restrictedSet.deleteRange(block.getStart(), block.getEnd());
- continue;
- }
- if (executableBlocksOnly && hasExecutable) {
- if (!block.isExecute()) {
- restrictedSet.deleteRange(block.getStart(), block.getEnd());
- continue;
- }
- }
- }
- return restrictedSet;
+ return false;
}
@Override
@@ -816,94 +803,6 @@ public class FunctionStartAnalyzer extends AbstractAnalyzer implements PatternFa
return patlist;
}
- /**
- * Search through bytes of a memory block using the finite state machine -root-
- * Apply any additional rules for matching patterns.
- * @param program is the Program being searched
- * @param block is the specific block of bytes being searched
- * @throws IOException
- * @throws CancelledException
- */
- private void searchBlock(SequenceSearchState root, Program program, MemoryBlock block,
- AddressSetView restrictSet, TaskMonitor monitor)
- throws IOException, CancelledException {
-
- // if no restricted set, make restrict set the full block
- AddressSet doneSet = new AddressSet(restrictSet);
- if (doneSet.isEmpty()) {
- doneSet.addRange(block.getStart(), block.getEnd());
- }
- doneSet = doneSet.intersectRange(block.getStart(), block.getEnd());
-
- Address blockStartAddr = block.getStart();
-
- // pull each range off the restricted set
- AddressRangeIterator addressRanges = doneSet.getAddressRanges();
- while (addressRanges.hasNext()) {
- monitor.checkCanceled();
- AddressRange addressRange = addressRanges.next();
-
- monitor.setMessage("Function Search");
- monitor.initialize(doneSet.getNumAddresses());
- monitor.setProgress(0);
-
- ArrayList 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);
-
- root.apply(data, maxBlockSearchLength, mymatches, monitor);
- monitor.checkCanceled();
-
- monitor.setMessage("Function Search (Examine Matches)");
- monitor.initialize(mymatches.size());
- monitor.setProgress(0);
-
- // TODO: DANGER there is much offset<-->address calculation here
- // should be OK, since they are all relative to the block.
- for (int i = 0; i < mymatches.size(); ++i) {
- monitor.checkCanceled();
- monitor.setProgress(i);
- Match match = mymatches.get(i);
- Address addr = blockStartAddr.add(match.getMarkOffset() + blockOffset);
- if (!match.checkPostRules(streamoffset + blockOffset)) {
- continue;
- }
- MatchAction[] matchactions = match.getMatchActions();
- contextValueList = null; // make sure, only context from these actions used
- for (MatchAction matchaction : matchactions) {
- matchaction.apply(program, addr, match);
- }
- // Actions might have set context, check if postcondition failed first
- if (!postreqFailedResult.contains(addr)) {
- setCurrentContext(program, addr);
- }
- else {
- // didn't apply it, get rid of the context list.
- contextValueList = null;
- }
- }
- }
- }
-
@Override
public MatchAction getMatchActionByName(String nm) {
if (nm.equals("funcstart")) {
diff --git a/Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/AlignRule.java b/Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/AlignRule.java
deleted file mode 100644
index a1e9f21394..0000000000
--- a/Ghidra/Features/BytePatterns/src/main/java/ghidra/util/bytesearch/AlignRule.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/* ###
- * 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.util.xml.SpecXmlUtils;
-import ghidra.xml.XmlElement;
-import ghidra.xml.XmlPullParser;
-
-public class AlignRule implements PostRule {
-
- private int mark; // Position, relative to start of pattern, to check alignment at
- private int alignmask; // Mask of bits that must be zero
-
-
- public AlignRule(){
- }
-
- public AlignRule(int mark, int alignmask){
- this.mark = mark;
- this.alignmask = alignmask;
- }
-
- @Override
- public boolean apply(Pattern pat, long matchoffset) {
- int off = (int)matchoffset;
- return (((off + mark) & alignmask)==0);
- }
-
- @Override
- public void restoreXml(XmlPullParser parser) {
- XmlElement el = parser.start("align");
- mark = SpecXmlUtils.decodeInt(el.getAttribute("mark"));
- int bits = SpecXmlUtils.decodeInt(el.getAttribute("bits"));
- alignmask = (1< {
-
- private SequenceSearchState parent;
- private ArrayList possible; // Patterns that could still match in this state
- private ArrayList success; // Patterns that have matched successfully if we reached this state
- private SequenceSearchState[] trans; // State transitions based on next byte
-
- public SequenceSearchState(SequenceSearchState par) {
- parent = par;
- possible = new ArrayList();
- success = null;
- trans = null;
- }
-
- public int getMaxSequenceSize() {
- int max = 0;
- for(int i=0;i max)
- max = val;
- }
- return max;
- }
-
- public void addSequence(DittedBitSequence pat,int pos) {
- possible.add(pat);
- if (pos == pat.getSize()) {
- if (success == null)
- success = new ArrayList();
- success.add(pat);
- }
- }
-
- public void sortSequences() {
- Comparator comp = new Comparator() {
- @Override
- public int compare(DittedBitSequence o1, DittedBitSequence o2) {
- return o1.getIndex() - o2.getIndex();
- }
- };
- Collections.sort(possible,comp);
- if (success != null)
- Collections.sort(success,comp);
- }
-
- @Override
- public int compareTo(SequenceSearchState o) {
- int i=0;
- for(;;) {
- if (possible.size() <= i) {
- if (o.possible.size() <=i)
- return 0;
- return -1;
- }
- if (o.possible.size() <= i)
- return 1;
- int indus = possible.get(i).getIndex(); // Lexicographic compare an sequence of sequences
- int indthem = o.possible.get(i).getIndex();
- if (indus != indthem)
- return (indus < indthem) ? -1 : 1;
- i += 1;
- }
- }
-
- private void buildSingleTransition(ArrayList all,int pos,int val) {
- SequenceSearchState newstate = null;
- for(int i=0;i match,int offset) {
- for(int i=0;i tmp = new ArrayList();
- int i=0;
- int j=0;
- int curpat = -1;
- int thispat = success.get(i).index;
- int oppat = op.success.get(j).index;
- while((i match) {
- int subindex = 0;
- SequenceSearchState curstate = this;
-
- do {
- if (curstate.success != null)
- curstate.exportSuccess(match, 0);
- if (subindex >= numbytes) return;
- curstate = curstate.trans[ 0xff & bytearray[subindex] ]; // Perform state transition based on next byte in buffer
- subindex += 1;
- } while(curstate != null);
-
- }
-
- /**
- * Search for patterns in a byte array. All matches are returned.
- * @param buffer is the array of bytes to search
- * @param match is populated with a Match object for each pattern and position that matches
- */
- public void apply(byte[] buffer,ArrayList match) {
- SequenceSearchState curstate;
- int subindex;
- for(int offset=0;offset Root state
- subindex = offset;
- do {
- if (curstate.success != null) // Check for any successful pattern matches for bytes up to this point
- curstate.exportSuccess(match, offset);
- if (subindex >= buffer.length) { // if we've run out of bytes, must restart at next offset
- break;
- }
- curstate = curstate.trans[ 0xff & buffer[subindex] ]; // Perform state transition based on next byte
- subindex += 1;
- } while(curstate != null);
- }
- }
-
- /**
- * Search for pattern in the stream -in-.
- * @param in - The stream to scan for matches
- * @param match - Any matches are appended as Match records to this ArrayList
- * @param monitor - if non-null, check for user cancel, and maintain progress info
- * @throws IOException
- */
- public void apply(InputStream in,ArrayList match,TaskMonitor monitor) throws IOException {
- apply(in,-1L,match,monitor);
- }
-
- /**
- * Search for pattern in the stream -in-.
- * @param in - The stream to scan for matches
- * @param maxBytes - The maximum number of bytes to scan forward in this stream
- * @param match - Any matches are appended as Match records to this ArrayList
- * @param monitor - if non-null, check for user cancel, and maintain progress info
- * @throws IOException
- */
- public void apply(InputStream in, long maxBytes, ArrayList match,TaskMonitor monitor) throws IOException {
- int maxsize = getMaxSequenceSize()+1;
- if (maxsize <4096)
- maxsize = 4096;
- if (maxBytes > 0) {
- maxBytes += getMaxSequenceSize()+1;
- }
- byte[] firstbuf=new byte[maxsize];
- byte[] secondbuf=new byte[maxsize];
- byte[] curbuf;
- SequenceSearchState curstate;
- int fullbuffers; // Number of buffers that are completely full
- int ra = in.read(firstbuf);
- if (ra == firstbuf.length) {
- ra = in.read(secondbuf);
- if (ra == secondbuf.length) {
- fullbuffers = 2;
- }
- else {
- if (ra < 0)
- ra = 0;
- fullbuffers = 1;
- byte[] tmp = new byte[ra];
- for(int i=0;i Root state
- subindex = bufreloff;
- curbuf = firstbuf;
- do {
- if (curstate.success != null) // Check for any successful pattern matches for bytes up to this point
- curstate.exportSuccess(match, offset);
- if (subindex >= curbuf.length) { // check that we have enough bytes in current buffer
- curbuf = secondbuf; // If not, switch to secondary buffer
- subindex = 0;
- }
- curstate = curstate.trans[ 0xff & curbuf[subindex] ]; // Perform state transition based on next byte in buffer
- subindex += 1;
- } while(curstate != null);
- offset += 1; // Advance to next starting offset
- if (maxBytes > 0 && offset > maxBytes) {
- break;
- }
- bufreloff += 1;
- if (bufreloff == firstbuf.length) { // If starting offset no longer falls in firstbuf
- byte[] tmp = firstbuf; // Switch firstbuf with secondbuf
- firstbuf = secondbuf;
- secondbuf = tmp;
- ra = in.read(secondbuf); // refill secondbuf (old firstbuf) with new bytes
- if (monitor!=null) {
- if (monitor.isCancelled()) return;
- monitor.setProgress(offset);
- }
- if (ra != secondbuf.length) {
- fullbuffers = 1;
- if (ra < 0)
- ra = 0;
- tmp = new byte[ra];
- for(int i=0;i= 0 && (maxBytes <= 0 || offset < maxBytes)) {
- if (secondbuf.length == 0)
- fullbuffers = 0;
- curstate = this;
- subindex = bufreloff;
- curbuf = firstbuf;
- do {
- if (curstate.success != null)
- curstate.exportSuccess(match, offset);
- if (subindex >= curbuf.length) {
- if (curbuf == secondbuf) break; // Out of data, all pending patterns fail
- curbuf = secondbuf;
- subindex = 0;
- if (curbuf.length==0) break;
- }
- curstate = curstate.trans[ 0xff & curbuf[subindex] ];
- subindex += 1;
- } while(curstate != null);
- offset += 1;
- bufreloff += 1;
- if (bufreloff == firstbuf.length) {
- if (fullbuffers == 0) break;
- firstbuf = secondbuf;
- fullbuffers = 0;
- bufreloff = 0;
- secondbuf = new byte[0];
- }
- }
- }
-
- static public ArrayList buildTransitionLevel(ArrayList prev,int pos) {
- ArrayList res = new ArrayList();
- Iterator iterator = prev.iterator();
- while(iterator.hasNext()) { // For each current state
- SequenceSearchState next = iterator.next();
- next.trans = new SequenceSearchState[256];
- for(int i=0;i<256;++i) { // Try every byte transition
- next.buildSingleTransition(res, pos, i);
- }
- }
- if (res.isEmpty()) return res;
- // Prepare to dedup the states
- Collections.sort(res);
- ArrayList finalres = new ArrayList();
- Iterator iter = res.iterator();
- SequenceSearchState curpat = iter.next();
- finalres.add(curpat);
- while(iter.hasNext()) {
- SequenceSearchState nextpat = iter.next();
- int comp = curpat.compareTo(nextpat);
- if (comp == 0) { // Identical states
- curpat.merge(nextpat);
- }
- else {
- curpat = nextpat;
- finalres.add(curpat);
- }
- }
- return finalres;
- }
-
- static public SequenceSearchState buildStateMachine(ArrayList extends DittedBitSequence> patterns) {
- SequenceSearchState root = new SequenceSearchState(null);
- for(int i=0;i statelevel = new ArrayList();
- statelevel.add(root);
- int level = 0;
- do {
- statelevel = buildTransitionLevel(statelevel, level);
- level += 1;
- } while(!statelevel.isEmpty());
- return root;
- }
-}
diff --git a/Ghidra/Features/MicrosoftCodeAnalyzer/src/main/java/ghidra/app/cmd/data/AbstractCreateDataBackgroundCmd.java b/Ghidra/Features/MicrosoftCodeAnalyzer/src/main/java/ghidra/app/cmd/data/AbstractCreateDataBackgroundCmd.java
index 283dd1cc65..76c3c1a213 100644
--- a/Ghidra/Features/MicrosoftCodeAnalyzer/src/main/java/ghidra/app/cmd/data/AbstractCreateDataBackgroundCmd.java
+++ b/Ghidra/Features/MicrosoftCodeAnalyzer/src/main/java/ghidra/app/cmd/data/AbstractCreateDataBackgroundCmd.java
@@ -38,7 +38,7 @@ public abstract class AbstractCreateDataBackgroundCmd {
private static final String RTTI_4_NAME = "RTTI Complete Object Locator";
private List vfTableBlocks;
+ private List rtti4Locations;
/**
* Constructs a command for applying an RTTI4 dataType at an address.
@@ -54,12 +57,44 @@ public class CreateRtti4BackgroundCmd extends AbstractCreateDataBackgroundCmd