Merge remote-tracking branch 'origin/GT-3341_emteere_RTTIPerformance'

This commit is contained in:
ghidorahrex 2020-02-19 12:06:25 -05:00
commit 024a6190e0
35 changed files with 1692 additions and 907 deletions

View file

@ -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<Match> 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")) {

View file

@ -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<<bits) - 1;
parser.end();
}
public int getAlignMask() {
return alignmask;
}
}

View file

@ -1,389 +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 java.io.IOException;
import java.util.ArrayList;
import java.util.zip.CRC32;
import ghidra.xml.XmlPullParser;
public class DittedBitSequence {
//Given a byte 0-255 (NOT a signed byte), retrieves its popcount.
public static int[] popcount = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, //0-15
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, //16-31
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, //32-47
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, //48-63
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, //64-79
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, //80-95
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, //96-111
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, //112-127
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, //128-143
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, //144-159
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, //160-175
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, //176-191
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, //192-207
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, //208-223
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, //224-239
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 byte[] bits; // value bits contained in the sequence
private byte[] dits; // a 1 indicates the bit is not ditted
public DittedBitSequence() {
bits = null;
dits = null;
}
/**
* Constructor from a ditted-bit-sequence string where white space is ignored (e.g., "10..11.0");
* @param dittedBitData
* @throws IllegalArgumentException if invalid dittedBitData specified
*/
public DittedBitSequence(String dittedBitData) {
initFromDittedStringData(dittedBitData);
}
/**
* 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
*/
public DittedBitSequence(String dittedBitData, boolean hex) {
if (hex && !dittedBitData.contains(".")) {
if (!dittedBitData.startsWith("0x")) {
dittedBitData = "0x" + dittedBitData;
}
}
initFromDittedStringData(dittedBitData);
}
/**
* Copy contructor
* @param op2 is bit sequence being copied
*/
public DittedBitSequence(DittedBitSequence op2) {
bits = op2.bits;
dits = op2.dits;
}
/**
* @return value bytes
*/
public byte[] getValueBytes() {
return bits.clone();
}
/**
* @return mask bytes which correspond to value bytes
*/
public byte[] getMaskBytes() {
return dits.clone();
}
/**
* Construct a sequence of bytes to search for. No bits are masked off.
* @param bytes
*/
public DittedBitSequence(byte[] bytes) {
bits = bytes;
dits = new byte[bytes.length];
for (int i = 0; i < bytes.length; ++i) {
dits[i] = (byte) 0xff;
}
}
/**
* Construct a bit pattern to search for consisting of
* 0 bits, 1 bits, and don't care bits
* @param bytes is an array of bytes indicating the 0 and 1 bits that are cared about
* @param mask is an array of bytes masking off the bits that should be cared about, a 0 indicates a "don't care"
*/
public DittedBitSequence(byte[] bytes, byte[] mask) {
bits = bytes;
dits = mask;
}
//Smallest ditted sequence commensurate with two other ditted sequences.
public DittedBitSequence(DittedBitSequence s1, DittedBitSequence s2) {
this.bits = new byte[s1.bits.length];
this.dits = new byte[s1.bits.length];
for (int i = 0; i < this.bits.length; i++) {
int tempInt = (s1.dits[i] & s2.dits[i] & (0xff ^ s1.bits[i] ^ s2.bits[i]));
this.dits[i] = (byte) tempInt;
this.bits[i] = (byte) (s1.bits[i] & s2.bits[i]);
}
}
@Override
public int hashCode() {
CRC32 crc = new CRC32();
crc.update(bits);
crc.update(dits);
return (int) crc.getValue();
}
//TODO: this notion of equality requires to sequences to have the same value
//on a particular bit even if that bit is ditted. Is this correct?
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
DittedBitSequence op2 = (DittedBitSequence) obj;
if (bits.length != op2.bits.length) {
return false;
}
for (int i = 0; i < bits.length; ++i) {
if (bits[i] != op2.bits[i]) {
return false;
}
if (dits[i] != op2.dits[i]) {
return false;
}
}
return true;
}
public DittedBitSequence concatenate(DittedBitSequence op2) {
DittedBitSequence res = new DittedBitSequence();
res.bits = new byte[bits.length + op2.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];
}
return res;
}
public boolean isMatch(int pos, int val) {
if (pos >= bits.length) {
return false;
}
return ((byte) (val & dits[pos])) == bits[pos];
}
public int getIndex() {
return index;
}
public int getSize() {
return bits.length;
}
public int getNumFixedBits() {
int popcnt = 0;
for (byte dit : dits) {
popcnt += popcount[0xff & dit];
}
return popcnt;
}
//Return the number of dits.
public int getNumUncertainBits() {
int popcnt = 0;
for (byte dit : dits) {
popcnt += popcount[0xff & dit];
}
return 8 * dits.length - popcnt;
}
public void writeBits(StringBuffer buf) {
for (int chunk = 0; chunk < this.bits.length; chunk++) {
buf.append(' ');
byte dchomp = this.dits[chunk];
int bchomp = this.bits[chunk];
for (int pos = 128; pos > 0; pos >>>= 1) {
if ((dchomp & pos) == 0) {
buf.append('.');
}
else {
buf.append((bchomp & pos) != 0 ? '1' : '0');
}
}
}
}
@Override
public String toString() {
StringBuffer buf = new StringBuffer();
writeBits(buf);
return buf.toString();
}
public String getHexString() {
String uncompressed = this.toString();
String[] parts = uncompressed.trim().split(" ");
StringBuilder sb = new StringBuilder();
for (int i = 0, max = parts.length; i < max; ++i) {
if (parts[i].contains(".")) {
sb.append(parts[i]);
if (i != (max - 1)) {
sb.append(" ");
}
continue;
}
String hexByte = Integer.toHexString(Integer.parseUnsignedInt(parts[i].trim(), 2));
if (hexByte.length() < 2) {
hexByte = "0" + hexByte;
}
sb.append("0x");
sb.append(hexByte);
if (i != (max - 1)) {
sb.append(" ");
}
}
return sb.toString();
}
protected int restoreXmlData(XmlPullParser parser) throws IOException {
parser.start("data");
String text = parser.end().getText();
try {
return initFromDittedStringData(text);
}
catch (IllegalArgumentException e) {
throw new IOException(
"Bad <data> tag in at line " + parser.getLineNumber() + " : " + text);
}
}
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
ArrayList<Byte> ditarray = new ArrayList<Byte>();
ArrayList<Byte> bitarray = new ArrayList<Byte>();
int i = 0;
while (i < text.length()) {
char c1, c2;
c1 = text.charAt(i);
if (mode == -2 && c1 != '\n') {
i += 1;
continue;
}
if (Character.isWhitespace(c1)) {
mode = -1;
i += 1;
continue;
}
if (c1 == '#') { // start comment - skip remainder of line
mode = -2;
i += 1;
continue;
}
if (mode == -1) {
if (c1 == '0') {
c2 = text.charAt(i + 1);
if (c2 == 'x') {
mode = 0; // Normal hexdecimal mode
i += 2;
continue;
}
}
else if (c1 == '*') {
markOffset = ditarray.size(); // Set mark at current number of bytes specified
i += 1;
continue;
}
else if ((c1 == '0') || (c1 == '1') || (c1 == '.')) {
mode = 1;
}
else {
throw new IllegalArgumentException("Bad ditted bit sequence");
}
}
if (mode == 0) {
c2 = text.charAt(i + 1);
i += 2;
int val = 0;
int mask = 0xff;
if (c1 == '.') {
mask ^= 0xf0;
}
else {
val = Character.getNumericValue(c1) << 4;
}
if (c2 == '.') {
mask ^= 0xf;
}
else {
val |= Character.getNumericValue(c2);
}
bitarray.add(Byte.valueOf((byte) val));
ditarray.add(Byte.valueOf((byte) mask));
}
else {
int val = 0;
int mask = 0;
for (int j = 0; j < 8; ++j) {
c1 = text.charAt(i + j);
if (c1 == '0') {
val <<= 1;
mask <<= 1;
mask |= 1;
}
else if (c1 == '.') {
val <<= 1;
mask <<= 1;
}
else {
val <<= 1;
val |= 1;
mask <<= 1;
mask |= 1;
}
}
i += 8;
bitarray.add(Byte.valueOf((byte) val));
ditarray.add(Byte.valueOf((byte) mask));
}
}
bits = new byte[bitarray.size()];
dits = new byte[ditarray.size()];
for (int k = 0; k < bits.length; ++k) {
bits[k] = bitarray.get(k).byteValue();
dits[k] = ditarray.get(k).byteValue();
}
return markOffset;
}
public int getNumInitialFixedBits(int marked) {
if (dits == null) {
return 0;
}
if ((marked <= 0) || (marked > dits.length)) {
return 0;//perhaps return -1 instead?
}
int popcnt = 0;
for (int i = 0; i < marked; ++i) {
popcnt += popcount[0xff & dits[i]];
}
return popcnt;
}
}

View file

@ -1,35 +0,0 @@
/* ###
* 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.
* 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;
import ghidra.program.model.listing.Program;
import ghidra.xml.XmlPullParser;
public class DummyMatchAction implements MatchAction {
@Override
public void apply(Program program, Address addr, Match match) {
}
@Override
public void restoreXml(XmlPullParser parser) {
parser.discardSubTree();
}
}

View file

@ -1,81 +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;
public class Match {
private DittedBitSequence sequence; // Pattern that matches
private long offset; // starting offset within bytestream of match
public Match(DittedBitSequence seq, long off) {
sequence = seq;
offset = off;
}
/**
* If the sequence corresponds to a PatternPair, return the number of postbits
* @return the number of post bits
*/
public int getNumPostBits() {
if (!(sequence instanceof Pattern)) {
return 0;
}
int marked = ((Pattern) sequence).getMarkOffset();
if (marked == 0) {
return sequence.getNumFixedBits();
}
return sequence.getNumFixedBits() - sequence.getNumInitialFixedBits(marked);
}
public MatchAction[] getMatchActions() {
return ((Pattern) sequence).getMatchActions();
}
public int getSequenceSize() {
return sequence.getSize();
}
public int getSequenceIndex() {
return sequence.getIndex();
}
public long getMarkOffset() {
return offset + ((Pattern) sequence).getMarkOffset();
}
public long getMatchStart() {
return offset;
}
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)) {
return false;
}
}
return true;
}
public String getHexString() {
return sequence.getHexString();
}
public DittedBitSequence getSequence() {
return sequence;
}
}

View file

@ -1,31 +0,0 @@
/* ###
* 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.
* 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;
import ghidra.program.model.listing.Program;
import ghidra.xml.XmlPullParser;
/**
* Action that should be applied to a Program at the Address a pattern matches
*
*/
public interface MatchAction {
public void apply(Program program,Address addr,Match match);
public void restoreXml(XmlPullParser parser);
}

View file

@ -1,210 +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 java.io.*;
import java.util.ArrayList;
import org.xml.sax.*;
import generic.jar.ResourceFile;
import ghidra.util.xml.SpecXmlUtils;
import ghidra.xml.*;
public class Pattern extends DittedBitSequence {
private int markOffset; // Within pattern what is the 'marked' byte
private PostRule[] postrule;
private MatchAction[] actions;
public Pattern() {
markOffset = 0;
postrule = null;
actions = null;
}
public Pattern(DittedBitSequence seq, int offset, PostRule[] postArray, MatchAction[] matchArray) {
super(seq);
markOffset = offset;
postrule = postArray;
actions = matchArray;
}
public PostRule[] getPostRules() {
return postrule;
}
public MatchAction[] getMatchActions() {
return actions;
}
public void setMatchActions(MatchAction[] actions){
this.actions = actions;
}
public int getMarkOffset() {
return markOffset;
}
/**
* Restore the PostRule and the MatchAction tags
* @param parser is the parser at the start of tags
* @param pfactory is the factory for the PostRule and MatchAction objects
* @throws IOException
*/
public static void restoreXmlAttributes(ArrayList<PostRule> postrulelist,
ArrayList<MatchAction> actionlist, XmlPullParser parser, PatternFactory pfactory)
throws IOException {
XmlElement el = parser.peek();
while (el.isStart()) {
PostRule newrule = pfactory.getPostRuleByName(el.getName());
if (newrule != null) {
newrule.restoreXml(parser);
postrulelist.add(newrule);
}
else {
MatchAction matchaction = pfactory.getMatchActionByName(el.getName());
if (matchaction != null) {
matchaction.restoreXml(parser);
actionlist.add(matchaction);
}
else {
throw new IOException("Bad <pattern> subtag");
}
}
el = parser.peek();
}
}
public void restoreXml(XmlPullParser parser, PatternFactory pfactory) throws IOException {
markOffset = 0;
ArrayList<PostRule> postrulelist = new ArrayList<PostRule>();
ArrayList<MatchAction> actionlist = new ArrayList<MatchAction>();
XmlElement el = parser.start("pattern");
String markstring = el.getAttribute("mark");
if (markstring != null) {
markOffset = SpecXmlUtils.decodeInt(markstring);
}
int moff = restoreXmlData(parser); // Restore data portion of the tag
if (moff >= 0) {
markOffset = moff;
}
if (pfactory != null) {
restoreXmlAttributes(postrulelist, actionlist, parser, pfactory);
}
parser.end();
actions = new MatchAction[actionlist.size()];
actionlist.toArray(actions);
postrule = new PostRule[postrulelist.size()];
postrulelist.toArray(postrule);
}
/**
* Read patterns from specified file
* @param file pattern file
* @param patlist list for patterns to be added to
* @param pfactory optional factory for use in parsing PostRule and MatchAction elements.
* If null such elements may not be present.
* @throws SAXException
* @throws IOException
*/
public static void readPatterns(ResourceFile file, ArrayList<Pattern> patlist,
PatternFactory pfactory) throws SAXException, IOException {
ErrorHandler handler = new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw new SAXException("Error: " + exception);
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw new SAXException("Fatal error: " + exception);
}
@Override
public void warning(SAXParseException exception) throws SAXException {
throw new SAXException("Warning: " + exception);
}
};
XmlPullParser parser;
try (InputStream inputStream = file.getInputStream()) {
parser = new NonThreadedXmlPullParserImpl(inputStream, file.getName(), handler, false);
}
parser.start("patternlist");
XmlElement el = parser.peek();
while (el.isStart()) {
if (el.getName().equals("patternpairs")) {
PatternPairSet pairset = new PatternPairSet();
pairset.restoreXml(parser, pfactory);
pairset.createFinalPatterns(patlist);
}
else {
Pattern pat = new Pattern();
pat.restoreXml(parser, pfactory);
patlist.add(pat);
}
el = parser.peek();
}
parser.end();
}
/**
* Read just the post patterns from the <patternpair> tags
* @param file is the file to read from
* @param patternlist collects the resulting Pattern objects
* @param pfactory is the factory for constructing postrules and matchactions
* @throws IOException
* @throws SAXException
*/
public static void readPostPatterns(File file, ArrayList<Pattern> patlist,
PatternFactory pfactory) throws SAXException, IOException {
ErrorHandler handler = new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw new SAXException("Error: " + exception);
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw new SAXException("Fatal error: " + exception);
}
@Override
public void warning(SAXParseException exception) throws SAXException {
throw new SAXException("Warning: " + exception);
}
};
XmlPullParser parser = new NonThreadedXmlPullParserImpl(file, handler, false);
parser.start("patternlist");
XmlElement el = parser.peek();
while (el.isStart()) {
if (el.getName().equals("patternpairs")) {
PatternPairSet pairset = new PatternPairSet();
pairset.restoreXml(parser, pfactory);
pairset.extractPostPatterns(patlist);
}
else {
parser.next();
}
el = parser.peek();
}
parser.end();
}
}

View file

@ -1,23 +0,0 @@
/* ###
* 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.
* 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;
public interface PatternFactory {
public MatchAction getMatchActionByName(String nm);
public PostRule getPostRuleByName(String nm);
}

View file

@ -1,143 +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 java.io.IOException;
import java.util.ArrayList;
import ghidra.util.xml.SpecXmlUtils;
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
*
*/
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<DittedBitSequence> preSequences;
private ArrayList<Pattern> postPatterns;
public PatternPairSet() {
preSequences = new ArrayList<DittedBitSequence>();
postPatterns = new ArrayList<Pattern>();
}
public void createFinalPatterns(ArrayList<Pattern> finalpats) {
for(int i=0;i<postPatterns.size();++i) {
Pattern postpattern = postPatterns.get(i);
int postcheck = postpattern.getNumFixedBits();
if (postcheck < postBitsOfCheck) {
continue;
}
for(int j=0;j<preSequences.size();++j) {
DittedBitSequence prepattern = preSequences.get(j);
int precheck = prepattern.getNumFixedBits();
if (precheck + postcheck < totalBitsOfCheck) {
continue;
}
DittedBitSequence concat = prepattern.concatenate(postpattern);
Pattern finalpattern = new Pattern(concat,prepattern.getSize(),postpattern.getPostRules(),postpattern.getMatchActions());
finalpats.add(finalpattern);
}
}
}
public void extractPostPatterns(ArrayList<Pattern> postpats) {
for(int i=0;i<postPatterns.size();++i) {
postpats.add(postPatterns.get(i));
}
}
public void restoreXml(XmlPullParser parser,PatternFactory pfactory) throws IOException {
XmlElement el = parser.start("patternpairs");
totalBitsOfCheck = SpecXmlUtils.decodeInt(el.getAttribute("totalbits"));
postBitsOfCheck = SpecXmlUtils.decodeInt(el.getAttribute("postbits"));
parser.start("prepatterns");
el= parser.peek();
while(el.isStart()) {
DittedBitSequence preseq = new DittedBitSequence();
preseq.restoreXmlData(parser);
preSequences.add(preseq);
el = parser.peek();
}
parser.end();
while(parser.peek().isStart()) {
parser.start("postpatterns");
el = parser.peek();
ArrayList<DittedBitSequence> postdit = new ArrayList<DittedBitSequence>();
while(el.isStart() && el.getName().equals("data")) {
DittedBitSequence postseq = new DittedBitSequence();
postseq.restoreXmlData(parser);
if (postseq.getNumFixedBits() >= postBitsOfCheck) {
postdit.add(postseq);
}
el = parser.peek();
}
ArrayList<PostRule> postRuleArray = new ArrayList<PostRule>();
ArrayList<MatchAction> matchActionArray = new ArrayList<MatchAction>();
if (pfactory != null) {
Pattern.restoreXmlAttributes(postRuleArray, matchActionArray, parser, pfactory);
}
PostRule[] postRules = new PostRule[postRuleArray.size()];
postRuleArray.toArray(postRules);
MatchAction[] matchActions = new MatchAction[matchActionArray.size()];
matchActionArray.toArray(matchActions);
for(int i=0;i<postdit.size();++i) {
Pattern postpat = new Pattern(postdit.get(i),0,postRules,matchActions);
postPatterns.add(postpat);
}
parser.end(); // End postpatterns
}
parser.end(); // End patternlist
}
/**
* Get the "pre" parts of the patterns
* @return pre sequences
*/
public ArrayList<DittedBitSequence> getPreSequences() {
return preSequences;
}
/**
* Get the "post" parts of the patterns
* @return post patterns
*/
public ArrayList<Pattern> getPostPatterns() {
return postPatterns;
}
/**
* Get the required number of fixed bits after the prepattern
* @return number of post bits
*/
public int getPostBitsOfCheck() {
return postBitsOfCheck;
}
/**
* Get the required number of fixed bits in the whole pattern
* @return number of total fixed bits
*/
public int getTotalBitsOfCheck() {
return totalBitsOfCheck;
}
}

View file

@ -1,25 +0,0 @@
/* ###
* 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.
* 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.xml.XmlPullParser;
public interface PostRule {
public boolean apply(Pattern pat,long matchoffset);
public void restoreXml(XmlPullParser parser);
}

View file

@ -1,385 +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.task.TaskMonitor;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
public class SequenceSearchState implements Comparable<SequenceSearchState> {
private SequenceSearchState parent;
private ArrayList<DittedBitSequence> possible; // Patterns that could still match in this state
private ArrayList<DittedBitSequence> 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<DittedBitSequence>();
success = null;
trans = null;
}
public int getMaxSequenceSize() {
int max = 0;
for(int i=0;i<possible.size();++i) {
int val = possible.get(i).getSize();
if (val > 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<DittedBitSequence>();
success.add(pat);
}
}
public void sortSequences() {
Comparator<DittedBitSequence> comp = new Comparator<DittedBitSequence>() {
@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<SequenceSearchState> all,int pos,int val) {
SequenceSearchState newstate = null;
for(int i=0;i<possible.size();++i) {
DittedBitSequence curpat = possible.get(i);
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> match,int offset) {
for(int i=0;i<success.size();++i) { // If we found matches
Match newmatch = new Match(success.get(i),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) // Any references to -op- in parent
parent.trans[i] = this; // Should be replaced with this
}
if (op.success != null) { // Merge
if (success == null) {
success = op.success;
}
else {
ArrayList<DittedBitSequence> tmp = new ArrayList<DittedBitSequence>();
int i=0;
int j=0;
int curpat = -1;
int thispat = success.get(i).index;
int oppat = op.success.get(j).index;
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()) ? 10000000 : success.get(i).index;
oppat = (j==op.success.size()) ? 10000000 : op.success.get(j).index;
}
else if (thispat < oppat) {
if (curpat != thispat) {
tmp.add(success.get(i));
curpat = thispat;
}
i += 1;
thispat = (i==success.size()) ? 10000000 : success.get(i).index;
}
else {
if (curpat != oppat) {
tmp.add(op.success.get(j));
curpat = oppat;
}
j += 1;
oppat = (j==op.success.size()) ? 10000000 : op.success.get(j).index;
}
}
success = tmp;
}
}
}
public void sequenceMatch(byte[] bytearray,int numbytes,ArrayList<Match> 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> 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) // 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> 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> 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<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 bufreloff=0;
int subindex;
while(fullbuffers == 2) {
curstate = this; // New starting offset -> 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<ra;++i)
tmp[i] = secondbuf[i];
secondbuf = tmp;
}
bufreloff = 0;
}
}
while(fullbuffers >= 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<SequenceSearchState> buildTransitionLevel(ArrayList<SequenceSearchState> prev,int pos) {
ArrayList<SequenceSearchState> res = new ArrayList<SequenceSearchState>();
Iterator<SequenceSearchState> 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<SequenceSearchState> finalres = new ArrayList<SequenceSearchState>();
Iterator<SequenceSearchState> 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<patterns.size();++i) {
DittedBitSequence pat = patterns.get(i);
pat.index = i;
root.addSequence(pat, 0);
}
root.sortSequences();
ArrayList<SequenceSearchState> statelevel = new ArrayList<SequenceSearchState>();
statelevel.add(root);
int level = 0;
do {
statelevel = buildTransitionLevel(statelevel, level);
level += 1;
} while(!statelevel.isEmpty());
return root;
}
}