mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 17:11:11 -09:00
GP-2504,GP-2494 Arm switch patterns moved into pattern matching, adding additional pattern, fixed issues in function start patterns and validcode precondition. Added after='ptr'.
This commit is contained in:
@@ -16,8 +16,7 @@
|
||||
package ghidra.app.analyzers;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.*;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.cmd.function.CreateFunctionCmd;
|
||||
@@ -29,7 +28,8 @@ import ghidra.app.util.PseudoDisassemblerContext;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.lang.RegisterValue;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.mem.MemoryBlock;
|
||||
import ghidra.program.model.symbol.*;
|
||||
@@ -84,19 +84,29 @@ public class FunctionStartAnalyzer extends AbstractAnalyzer implements PatternFa
|
||||
protected AddressSet postreqFailedResult = null; // Discovered pattern, but a post req failed (not following a defined thing)
|
||||
protected ArrayList<RegisterValue> contextValueList = null;
|
||||
|
||||
private static ProgramDecisionTree getPatternDecisionTree() {
|
||||
private static ProgramDecisionTree initializePatternDecisionTree() {
|
||||
if (patternDecisitionTree == null) {
|
||||
patternDecisitionTree = Patterns.getPatternDecisionTree();
|
||||
}
|
||||
return patternDecisitionTree;
|
||||
}
|
||||
|
||||
public ProgramDecisionTree getPatternDecisionTree() {
|
||||
return initializePatternDecisionTree();
|
||||
}
|
||||
|
||||
public FunctionStartAnalyzer() {
|
||||
this(NAME, AnalyzerType.BYTE_ANALYZER);
|
||||
}
|
||||
|
||||
public FunctionStartAnalyzer(String name, AnalyzerType analyzerType) {
|
||||
super(name, DESCRIPTION, analyzerType);
|
||||
this(name, DESCRIPTION, analyzerType);
|
||||
|
||||
}
|
||||
|
||||
public FunctionStartAnalyzer(String name, String description, AnalyzerType analyzerType) {
|
||||
super(name, description, analyzerType);
|
||||
|
||||
setPriority(AnalysisPriority.CODE_ANALYSIS.after().after());
|
||||
setDefaultEnablement(true);
|
||||
setSupportsOneTimeAnalysis();
|
||||
@@ -148,20 +158,14 @@ public class FunctionStartAnalyzer extends AbstractAnalyzer implements PatternFa
|
||||
contextValueList = null;
|
||||
}
|
||||
|
||||
private void setDisassemblerContext(Program program, DisassemblerContext pcont) {
|
||||
private void setDisassemblerContext(Program program, PseudoDisassemblerContext pcont, Address addr) {
|
||||
if (contextValueList == null) {
|
||||
return;
|
||||
}
|
||||
Iterator<RegisterValue> iterator = contextValueList.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
RegisterValue contextValue = iterator.next();
|
||||
|
||||
try {
|
||||
pcont.setRegisterValue(contextValue);
|
||||
}
|
||||
catch (ContextChangeException e) {
|
||||
// context conflicts cause problems, let already layed down context win.
|
||||
}
|
||||
pcont.setValue(contextValue.getRegister(), addr, contextValue.getUnsignedValue());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,20 +199,29 @@ public class FunctionStartAnalyzer extends AbstractAnalyzer implements PatternFa
|
||||
|
||||
public class FunctionStartAction implements MatchAction {
|
||||
|
||||
private static final int MUST_HAVE_VALID_INSTRUCTIONS_NO_MIN = -1; // no minimum
|
||||
private static final int VALID_INSTRUCTIONS_NO_MAX = -1; // no maximum on instructions to check
|
||||
private static final int NO_VALID_INSTRUCTIONS_REQUIRED = 0;
|
||||
|
||||
private String afterName = null;
|
||||
private int validcode = 0; // -1 means in a valid subroutine
|
||||
private int validCodeMin = NO_VALID_INSTRUCTIONS_REQUIRED;
|
||||
private int validCodeMax = VALID_INSTRUCTIONS_NO_MAX;
|
||||
private String label = null;
|
||||
private boolean isThunk = false; // true if this function should be turned into a thunk
|
||||
private boolean noreturn = false; // true to set function non-returning
|
||||
boolean validFunction = false; // must be defined at a function
|
||||
boolean validFunction = false; // must be defined at a function
|
||||
private boolean contiguous = true; // require validcode instructions be contiguous
|
||||
|
||||
@Override
|
||||
public void apply(Program program, Address addr, Match match) {
|
||||
if (!checkPreRequisites(program, addr)) {
|
||||
// didn't match, get rid of contextValueList
|
||||
contextValueList = null;
|
||||
return;
|
||||
}
|
||||
|
||||
applyActionToSet(program, addr, funcResult, match);
|
||||
contextValueList = null;
|
||||
}
|
||||
|
||||
protected boolean checkPreRequisites(Program program, Address addr) {
|
||||
@@ -233,22 +246,31 @@ public class FunctionStartAnalyzer extends AbstractAnalyzer implements PatternFa
|
||||
}
|
||||
|
||||
// do we require some number of valid instructions
|
||||
if (validcode != 0) {
|
||||
if (validCodeMin != 0) {
|
||||
PseudoDisassembler pseudoDisassembler = new PseudoDisassembler(program);
|
||||
PseudoDisassemblerContext pcont =
|
||||
new PseudoDisassemblerContext(program.getProgramContext());
|
||||
setDisassemblerContext(program, pcont);
|
||||
|
||||
setDisassemblerContext(program, pcont, addr);
|
||||
boolean isvalid = false;
|
||||
if (validcode == -1) {
|
||||
isvalid = pseudoDisassembler.checkValidSubroutine(addr, pcont, true, true);
|
||||
if (validCodeMin == -1) {
|
||||
if (validCodeMax > 0) { // check at most N instructions
|
||||
pseudoDisassembler.setMaxInstructions(validCodeMax);
|
||||
}
|
||||
isvalid = pseudoDisassembler.checkValidSubroutine(addr, pcont, true, true, contiguous);
|
||||
}
|
||||
else {
|
||||
pseudoDisassembler.setMaxInstructions(validcode);
|
||||
isvalid = pseudoDisassembler.checkValidSubroutine(addr, pcont, true, false);
|
||||
}
|
||||
if (!isvalid) {
|
||||
return false;
|
||||
if (validCodeMax > 0) { // check at most N instructions
|
||||
pseudoDisassembler.setMaxInstructions(validCodeMax);
|
||||
}
|
||||
// disassemble only fallthru, must have validcode number of instructions
|
||||
isvalid = pseudoDisassembler.checkValidSubroutine(addr, pcont, true, false, contiguous);
|
||||
int instrCount = pseudoDisassembler.getLastCheckValidInstructionCount();
|
||||
if (instrCount < validCodeMin) {
|
||||
isvalid = false;
|
||||
}
|
||||
}
|
||||
return isvalid;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -387,6 +409,10 @@ public class FunctionStartAnalyzer extends AbstractAnalyzer implements PatternFa
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (name.startsWith("ptr")) {
|
||||
// if there are only pure data references to the location
|
||||
return pureDataReferencesOnly(program, addr);
|
||||
}
|
||||
else if (name.startsWith("def")) {
|
||||
// make sure there is something at location to check
|
||||
Instruction instr = program.getListing().getInstructionContaining(addrToCheck);
|
||||
@@ -400,8 +426,38 @@ public class FunctionStartAnalyzer extends AbstractAnalyzer implements PatternFa
|
||||
if (data != null) {
|
||||
return true;
|
||||
}
|
||||
// if there are only pure data references to the location
|
||||
return pureDataReferencesOnly(program, addr);
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are only pure data references to the location
|
||||
*
|
||||
* @param program program to check
|
||||
* @param addrToCheck location to check
|
||||
* @return true if there are only pure data references (no flow, or r/w)
|
||||
*/
|
||||
private boolean pureDataReferencesOnly(Program program, Address addrToCheck) {
|
||||
ReferenceIterator referencesTo = program.getReferenceManager().getReferencesTo(addrToCheck);
|
||||
if (!referencesTo.hasNext()) {
|
||||
return false;
|
||||
}
|
||||
for (Reference reference : referencesTo) {
|
||||
RefType refType = reference.getReferenceType();
|
||||
if (refType.isFlow()) {
|
||||
return false;
|
||||
}
|
||||
if (refType.isRead() || refType.isWrite()) {
|
||||
return false;
|
||||
}
|
||||
if (refType.isData()) {
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -494,52 +550,113 @@ public class FunctionStartAnalyzer extends AbstractAnalyzer implements PatternFa
|
||||
}
|
||||
|
||||
protected void restoreXmlAttributes(XmlElement el) {
|
||||
if (el.hasAttribute("after")) {
|
||||
afterName = el.getAttribute("after");
|
||||
if (afterName.startsWith("func")) {
|
||||
hasCodeConstraints = true;
|
||||
Map<String, String> attributes = el.getAttributes();
|
||||
Set<String> keySet = attributes.keySet();
|
||||
for (String attrName : keySet) {
|
||||
String attrValue = attributes.get(attrName);
|
||||
attrName = attrName.toLowerCase();
|
||||
switch (attrName) {
|
||||
case "after":
|
||||
afterName = attrValue;
|
||||
if (afterName.startsWith("func")) {
|
||||
hasCodeConstraints = true;
|
||||
}
|
||||
else if (afterName.startsWith("inst")) {
|
||||
hasCodeConstraints = true;
|
||||
}
|
||||
else if (afterName.startsWith("data")) {
|
||||
hasDataConstraints = true;
|
||||
}
|
||||
else if (afterName.startsWith("ptr")) {
|
||||
hasDataConstraints = true;
|
||||
}
|
||||
else if (afterName.startsWith("def")) {
|
||||
hasCodeConstraints = hasDataConstraints = true;
|
||||
}
|
||||
else {
|
||||
Msg.error(this,
|
||||
"funcstart pattern attribute 'after' must be one of 'function', 'instruction', 'data', 'defined'");
|
||||
}
|
||||
break;
|
||||
|
||||
// set check for valid code and the minimum number of instructions required
|
||||
// if no maximum is set, then the instructions MUST be fallthru instructions, don't check branch flows
|
||||
case "validcode":
|
||||
String validcodeStr = attrValue;
|
||||
if (validcodeStr.equals("0") || validcodeStr.equals("false")) {
|
||||
validCodeMin = NO_VALID_INSTRUCTIONS_REQUIRED;
|
||||
}
|
||||
else if (validcodeStr.equalsIgnoreCase("true") ||
|
||||
validcodeStr.equalsIgnoreCase("subroutine")) { // must be a valid subroutine
|
||||
validCodeMin = MUST_HAVE_VALID_INSTRUCTIONS_NO_MIN;
|
||||
}
|
||||
else if (validcodeStr.equalsIgnoreCase("function")) { // must be at a defined function
|
||||
validFunction = true;
|
||||
hasFunctionStartConstraints = true; // enable FunctionStartFuncAnalyzer to run
|
||||
validCodeMin = NO_VALID_INSTRUCTIONS_REQUIRED;
|
||||
}
|
||||
else { // must have <N> valid fallthru instruction to match
|
||||
validCodeMin = Integer.parseInt(validcodeStr);
|
||||
}
|
||||
if (validCodeMax == VALID_INSTRUCTIONS_NO_MAX) {
|
||||
// if no maximum instructions to check, only check the minimum number
|
||||
validCodeMax = validCodeMin;
|
||||
}
|
||||
break;
|
||||
|
||||
// set the maximum number of instructions to check
|
||||
// if maximum is set, then allow non fallthru instructions while flowing
|
||||
case "validcodemax":
|
||||
String validcodeMaxStr = attrValue;
|
||||
// check up <N> instructions for valid code
|
||||
validCodeMax = Integer.parseInt(validcodeMaxStr);
|
||||
if (validCodeMin == NO_VALID_INSTRUCTIONS_REQUIRED) {
|
||||
// if set a max and no minimum yet, must have some number of instructions
|
||||
// if a validcode minimum is set later, will override this default
|
||||
validCodeMin = MUST_HAVE_VALID_INSTRUCTIONS_NO_MIN;
|
||||
}
|
||||
break;
|
||||
|
||||
// minimum number of instructions for validcode must be contiguous instructions
|
||||
case "contiguous":
|
||||
String fallThruOnlyStr = attrValue;
|
||||
// check up <N> instructions for valid code
|
||||
contiguous = true;
|
||||
if (fallThruOnlyStr.equalsIgnoreCase("false")) {
|
||||
contiguous = false;
|
||||
}
|
||||
else if (fallThruOnlyStr.equalsIgnoreCase("true")) {
|
||||
contiguous = true;
|
||||
} else {
|
||||
Msg.error(this, "Bad contiguous option (true,false): " + attrName + " = " + attrValue);
|
||||
}
|
||||
break;
|
||||
|
||||
case "label":
|
||||
String name = attrValue;
|
||||
label = name;
|
||||
break;
|
||||
|
||||
case "thunk":
|
||||
isThunk = true;
|
||||
break;
|
||||
|
||||
case "noreturn":
|
||||
noreturn = true;
|
||||
break;
|
||||
|
||||
// TODO: add the ability to make data based on a pattern of bytes
|
||||
// useful after defined instructions/functions to take up filler byte patterns
|
||||
// will allow more finding of code that is after defined data
|
||||
// case "data":
|
||||
// String validcodeDataStr = attrValue;
|
||||
// // create undefined data of the given size
|
||||
// makeData = Integer.parseInt(validcodeDataStr);
|
||||
// break;
|
||||
|
||||
default:
|
||||
Msg.error(this, "Unknown Patten option: " + attrName + " = " + attrValue);
|
||||
}
|
||||
else if (afterName.startsWith("inst")) {
|
||||
hasCodeConstraints = true;
|
||||
}
|
||||
else if (afterName.startsWith("data")) {
|
||||
hasDataConstraints = true;
|
||||
}
|
||||
else if (afterName.startsWith("def")) {
|
||||
hasCodeConstraints = hasDataConstraints = true;
|
||||
}
|
||||
else {
|
||||
Msg.error(this,
|
||||
"funcstart pattern attribute 'after' must be one of 'function', 'instruction', 'data', 'defined'");
|
||||
}
|
||||
}
|
||||
if (el.hasAttribute("validcode")) {
|
||||
validcode = 8;
|
||||
String validcodeStr = el.getAttribute("validcode");
|
||||
if (validcodeStr.equals("0") || validcodeStr.equals("false")) {
|
||||
validcode = 0;
|
||||
}
|
||||
else if (validcodeStr.equalsIgnoreCase("true") ||
|
||||
validcodeStr.equalsIgnoreCase("subroutine")) { // must be a valid subroutine
|
||||
validcode = -1;
|
||||
}
|
||||
else if (validcodeStr.equalsIgnoreCase("function")) { // must be at a defined subroutine
|
||||
validFunction = true;
|
||||
hasFunctionStartConstraints = true;
|
||||
}
|
||||
else { // must have <N> valid instruction run
|
||||
validcode = Integer.parseInt(validcodeStr);
|
||||
}
|
||||
}
|
||||
if (el.hasAttribute("label")) {
|
||||
String name = el.getAttribute("label");
|
||||
label = name;
|
||||
}
|
||||
if (el.hasAttribute("thunk")) {
|
||||
isThunk = true;
|
||||
}
|
||||
if (el.hasAttribute("noreturn")) {
|
||||
noreturn = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/* ###
|
||||
* 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.app.analyzers;
|
||||
|
||||
import ghidra.app.services.AnalysisPriority;
|
||||
import ghidra.app.services.AnalyzerType;
|
||||
import ghidra.util.constraint.ProgramDecisionTree;
|
||||
|
||||
public class FunctionStartPreFuncAnalyzer extends FunctionStartAnalyzer {
|
||||
|
||||
protected static final String FUNCTION_START_PRE_SEARCH = "Function Start Pre Search";
|
||||
|
||||
private static final String DESCRIPTION =
|
||||
"Search for architecture/compiler specific patterns that are better found before any code is disassembled, " +
|
||||
"such as known patterns for ARM functions that handle switch tables and don't return.";
|
||||
|
||||
private static ProgramDecisionTree prePatternDecisitionTree;
|
||||
|
||||
private static ProgramDecisionTree initializePatternDecisionTree() {
|
||||
if (prePatternDecisitionTree == null) {
|
||||
prePatternDecisitionTree = Patterns.getPatternDecisionTree("prepatternconstraints.xml");
|
||||
}
|
||||
return prePatternDecisitionTree;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgramDecisionTree getPatternDecisionTree() {
|
||||
return initializePatternDecisionTree();
|
||||
}
|
||||
|
||||
public FunctionStartPreFuncAnalyzer() {
|
||||
super(FUNCTION_START_PRE_SEARCH, DESCRIPTION, AnalyzerType.BYTE_ANALYZER);
|
||||
|
||||
setPriority(AnalysisPriority.BLOCK_ANALYSIS.after());
|
||||
setDefaultEnablement(true);
|
||||
setSupportsOneTimeAnalysis();
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,6 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import generic.constraint.DecisionSet;
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.framework.Application;
|
||||
@@ -32,14 +30,20 @@ import ghidra.xml.XmlParseException;
|
||||
|
||||
public class Patterns {
|
||||
|
||||
private static final String PATTERN_FILE_NAME = "patternfile";
|
||||
private static final String DATA_PATTERNS = "data/patterns";
|
||||
public static final String DEFAULT_PATTERNCONSTRAINTS_XML = "patternconstraints.xml";
|
||||
|
||||
private static final String PATTERN_FILE_NAME_XMLTAG = "patternfile";
|
||||
private static final String DATA_PATTERNS_SUBDIR = "data/patterns";
|
||||
|
||||
public static ProgramDecisionTree getPatternDecisionTree() {
|
||||
List<ResourceFile> patternDirs = Application.findModuleSubDirectories(DATA_PATTERNS);
|
||||
List<ResourceFile> patternConstraintFiles = findPatternConstraintFiles(patternDirs);
|
||||
return getPatternDecisionTree(DEFAULT_PATTERNCONSTRAINTS_XML);
|
||||
}
|
||||
|
||||
public static ProgramDecisionTree getPatternDecisionTree(String patternConstraintsFileName) {
|
||||
List<ResourceFile> patternDirs = Application.findModuleSubDirectories(DATA_PATTERNS_SUBDIR);
|
||||
List<ResourceFile> patternConstraintFiles = findPatternConstraintFiles(patternDirs, patternConstraintsFileName);
|
||||
ProgramDecisionTree decisionTree = new ProgramDecisionTree();
|
||||
decisionTree.registerPropertyName(PATTERN_FILE_NAME);
|
||||
decisionTree.registerPropertyName(PATTERN_FILE_NAME_XMLTAG);
|
||||
for (ResourceFile resourceFile : patternConstraintFiles) {
|
||||
try {
|
||||
decisionTree.loadConstraints(resourceFile);
|
||||
@@ -53,27 +57,27 @@ public class Patterns {
|
||||
}
|
||||
|
||||
public static boolean hasPatternFiles(Program program, ProgramDecisionTree decisionTree) {
|
||||
DecisionSet decisionsSet = decisionTree.getDecisionsSet(program, PATTERN_FILE_NAME);
|
||||
DecisionSet decisionsSet = decisionTree.getDecisionsSet(program, PATTERN_FILE_NAME_XMLTAG);
|
||||
return !decisionsSet.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find any pattern files associated with this program
|
||||
* @param program find pattern files associated with this program
|
||||
* @param decisionTree decision tree parsed from getPatternDecisionTree
|
||||
* @return the array of File objects, one for each file
|
||||
* @throws IOException
|
||||
* @throws FileNotFoundException
|
||||
* @throws SAXException
|
||||
* @throws XmlParseException
|
||||
* @throws IOException pattern file could not be read
|
||||
* @throws FileNotFoundException pattern file not found
|
||||
* @throws XmlParseException pattern file had an XML parse error
|
||||
*/
|
||||
public static ResourceFile[] findPatternFiles(Program program, ProgramDecisionTree decisionTree)
|
||||
throws FileNotFoundException, IOException, XmlParseException {
|
||||
|
||||
DecisionSet decisionsSet = decisionTree.getDecisionsSet(program, PATTERN_FILE_NAME);
|
||||
DecisionSet decisionsSet = decisionTree.getDecisionsSet(program, PATTERN_FILE_NAME_XMLTAG);
|
||||
List<String> values = decisionsSet.getValues();
|
||||
|
||||
List<ResourceFile> patternFileList = new ArrayList<ResourceFile>();
|
||||
List<ResourceFile> patternDirs = Application.findModuleSubDirectories(DATA_PATTERNS);
|
||||
List<ResourceFile> patternDirs = Application.findModuleSubDirectories(DATA_PATTERNS_SUBDIR);
|
||||
|
||||
for (String patternFileName : values) {
|
||||
patternFileList.add(getPatternFile(patternDirs, patternFileName));
|
||||
@@ -93,12 +97,12 @@ public class Patterns {
|
||||
throw new FileNotFoundException("can't find pattern file: " + patternFileName);
|
||||
}
|
||||
|
||||
private static List<ResourceFile> findPatternConstraintFiles(List<ResourceFile> patternDirs) {
|
||||
private static List<ResourceFile> findPatternConstraintFiles(List<ResourceFile> patternDirs, String constraintsFileName) {
|
||||
|
||||
List<ResourceFile> patternConstraintFiles = new ArrayList<ResourceFile>();
|
||||
|
||||
for (ResourceFile dir : patternDirs) {
|
||||
ResourceFile file = new ResourceFile(dir, "patternconstraints.xml");
|
||||
ResourceFile file = new ResourceFile(dir, constraintsFileName);
|
||||
if (file.exists()) {
|
||||
patternConstraintFiles.add(file);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user