GP-516 Added the "Variadic Function Signature Override" analyzer

This commit is contained in:
ghidra3
2020-11-19 08:36:08 -05:00
committed by ghidra1
parent fb5ab7569d
commit 28e0480f9d
7 changed files with 2012 additions and 0 deletions

View File

@@ -450,6 +450,18 @@
<P><U>Started By:</U> Importing or adding to a program, Auto Analyze command</P> <P><U>Started By:</U> Importing or adding to a program, Auto Analyze command</P>
</BLOCKQUOTE> </BLOCKQUOTE>
<H3><A name="Format_String_Analyzer"></A>Format String Analyzer</H3>
<BLOCKQUOTE>
<P>This analyzer detects variadic function calls in the bodies of each function that intersect
the current selection. It then parses their format string arguments to infer the correct function
call signatures. Currently, this analyzer only supports printf, scanf, and their variants (e.g., snprintf, fscanf).
If the current selection is emtpy, it searches through every function within the binary. Once
the signatures are inferred, they are overridden.</P>
<P><U>Started By:</U> Importing or adding to a program, Auto Analyze command</P>
</BLOCKQUOTE>
<H3><A name="Image"></A>Image Analyzer</H3> <H3><A name="Image"></A>Image Analyzer</H3>

View File

@@ -0,0 +1,63 @@
/* ###
* 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.plugin.core.string.variadic;
/**
* This class represents a single argument of a variadic function
*/
public class FormatArgument {
private String lengthModifier;
private String conversionSpecifier;
/**
* Constructor for a FormatArg
*
* @param lengthModifier length modifier of a format argument
* @param conversionSpec conversion specifier of a format argument
*/
public FormatArgument(String lengthModifier, String conversionSpec) {
this.lengthModifier = lengthModifier;
this.conversionSpecifier = conversionSpec;
}
/**
* lenghtModifier getter
*
* @return lengthModifier
*/
public String getLengthModifier() {
return this.lengthModifier;
}
/**
* convertionSpec getter
*
* @return conversionSpecifier
*/
public String getConversionSpecifier() {
return this.conversionSpecifier;
}
/**
* Converts FormatArg to String
*
* @return FormatArgument as String
*/
public String toString() {
return String.format("[%s, %s]", this.lengthModifier, this.conversionSpecifier);
}
}

View File

@@ -0,0 +1,392 @@
/* ###
* 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.plugin.core.string.variadic;
import java.util.*;
import org.apache.commons.collections4.IteratorUtils;
import ghidra.app.decompiler.*;
import ghidra.app.decompiler.parallel.*;
import ghidra.app.services.*;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.options.Options;
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.pcode.HighFunctionDBUtil;
import ghidra.program.model.pcode.PcodeOpAST;
import ghidra.program.util.DefinedDataIterator;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
public class FormatStringAnalyzer extends AbstractAnalyzer {
// Array of substrings of variadic function names that are searched for
private static final String[] VARIADIC_SUBSTRINGS = { "printf", "scanf" };
private static final String NAME = "Variadic Function Signature Override";
private static final String DESCRIPTION =
"Detects variadic function calls in the bodies of each function that intersect the" +
"current selection and parses their format string arguments to infer the correct " +
"signatures. Currently, this analyzer only supports printf, scanf, and thier variants " +
"(e.g., snprintf, fscanf). If the current selection is empty, it searches through " +
"every function. Once the correct signatures are inferred, they are overridden.";
private final static boolean OPTION_DEFAULT_CREATE_BOOKMARKS_ENABLED = false;
private final static String OPTION_NAME_CREATE_BOOKMARKS = "Create Analysis Bookmarks";
private static final String OPTION_DESCRIPTION_CREATE_BOOKMARKS =
"Select this check box if you want this analyzer to create analysis bookmarks " +
"when items of interest are created/identified by the analyzer.";
private boolean createBookmarksEnabled = OPTION_DEFAULT_CREATE_BOOKMARKS_ENABLED;
// Any function name containing this substring is determined to be an input type function
private static final String INPUT_FUNCTION_SUBSTRING = "scanf";
private Program currentProgram = null;
private FormatStringParser parser;
public FormatStringAnalyzer() {
super(NAME, DESCRIPTION, AnalyzerType.FUNCTION_SIGNATURES_ANALYZER);
setSupportsOneTimeAnalysis();
setPriority(AnalysisPriority.LOW_PRIORITY);
setDefaultEnablement(false);
setPrototype();
}
@Override
public boolean canAnalyze(Program program) {
return true;
}
private synchronized FormatStringParser getParser() {
if (parser == null) {
parser = new FormatStringParser(currentProgram);
}
return parser;
}
private synchronized void disposeParser() {
parser = null;
}
@Override
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) {
this.currentProgram = program;
try {
run(set, monitor);
}
catch (CancelledException e) {
// User cancelled analysis
}
finally {
disposeParser();
}
return true;
}
private void run(AddressSetView selection, TaskMonitor monitor)
throws CancelledException {
DefinedDataIterator dataIterator = DefinedDataIterator.definedStrings(currentProgram);
Map<Address, Data> stringsByAddress = new HashMap<>();
for (Data data : dataIterator) {
String s = data.getDefaultValueRepresentation();
if (s.contains("%")) {
stringsByAddress.put(data.getAddress(), data);
}
monitor.checkCanceled();
}
FunctionIterator functionIterator = currentProgram.getListing().getFunctions(true);
FunctionIterator externalIterator = currentProgram.getListing().getExternalFunctions();
Iterator<Function> programFunctionIterator = IteratorUtils.chainedIterator(functionIterator,externalIterator);
Map<String, List<DataType>> namesToParameters = new HashMap<>();
Map<String, DataType> namesToReturn = new HashMap<>();
Set<Function> toDecompile = new HashSet<>();
Set<String> variadicFunctionNames = new HashSet<>();
// Find variadic function names and their parameter data types
for (Function function : IteratorUtils.asIterable(programFunctionIterator)) {
String name = function.getName().strip();
if (usesVariadicFormatString(function)) {
for (String variadicSubstring : VARIADIC_SUBSTRINGS) {
if (name.contains(variadicSubstring)) {
variadicFunctionNames.add(name);
namesToParameters.put(name, getParameters(function));
namesToReturn.put(name, function.getReturnType());
break;
}
}
}
monitor.checkCanceled();
}
Iterator<Function> functionsToSearchIterator = selection != null
? currentProgram.getFunctionManager()
.getFunctionsOverlapping(selection)
: currentProgram.getFunctionManager().getFunctionsNoStubs(true);
// Find functions that call variadic functions
while (functionsToSearchIterator.hasNext()) {
Function function = functionsToSearchIterator.next();
Set<Function> calledFunctions = function.getCalledFunctions(monitor);
for (Function calledFunction : calledFunctions) {
// If this function calls a variadic function, add it to functions to decompile
if (namesToParameters.containsKey(calledFunction.getName())) {
toDecompile.add(function);
break;
}
}
monitor.checkCanceled();
}
decompile(currentProgram, monitor, stringsByAddress, variadicFunctionNames,
namesToParameters,
namesToReturn,
toDecompile);
}
private void decompile(Program program, TaskMonitor monitor,
Map<Address, Data> stringsByAddress,
Set<String> variadicFunctionNames,
Map<String, List<DataType>> namesToParameters, Map<String, DataType> namesToReturn,
Set<Function> toDecompile) {
DecompilerCallback<Void> callback = initDecompilerCallback(program, stringsByAddress,
variadicFunctionNames, namesToParameters, namesToReturn);
if (toDecompile.isEmpty()) {
Msg.info(this, "No functions detected that make variadic function calls with " +
"format strings containing format specifiers");
return;
}
try {
ParallelDecompiler.decompileFunctions(callback, toDecompile, monitor);
}
catch (Exception e) {
Msg.error(this, "Error: could not decompile functions with ParallelDecompiler", e);
}
finally {
callback.dispose();
}
}
private DecompilerCallback<Void> initDecompilerCallback(Program program,
Map<Address, Data> stringsByAddress,
Set<String> variadicFuncNames, Map<String, List<DataType>> namesToParameters,
Map<String, DataType> namesToReturn) {
return new DecompilerCallback<>(program,
new VariadicSignatureDecompileConfigurer()) {
@Override
public Void process(DecompileResults results, TaskMonitor tMonitor) throws Exception {
if (results == null) {
return null;
}
Function function = results.getFunction();
PcodeFunctionParser pcodeParser = new PcodeFunctionParser(program);
if (results.getHighFunction() == null ||
results.getHighFunction().getPcodeOps() == null) {
return null;
}
Iterator<PcodeOpAST> pcodeOpASTIterator = results.getHighFunction().getPcodeOps();
List<PcodeOpAST> pcodeOpASTs = new ArrayList<>();
if ((results.getHighFunction() != null) && pcodeOpASTIterator != null) {
while (pcodeOpASTIterator.hasNext()) {
PcodeOpAST pcodeAST = pcodeOpASTIterator.next();
pcodeOpASTs.add(pcodeAST);
}
}
List<FunctionCallData> functionCallDataList = pcodeParser.parseFunctionForCallData(
pcodeOpASTs, stringsByAddress, variadicFuncNames);
if (functionCallDataList != null && functionCallDataList.size() > 0) {
overrideCallList(program, function, functionCallDataList, namesToParameters,
namesToReturn);
}
tMonitor.checkCanceled();
return null;
}
};
}
private List<DataType> getParameters(Function function) {
// NOTE: Currently only considers variadic functions with format string
// arguments.
List<DataType> dataTypes = new ArrayList<>();
for (ParameterDefinition pd : function.getSignature().getArguments()) {
dataTypes.add(pd.getDataType());
}
return dataTypes;
}
private boolean usesVariadicFormatString(Function function) {
int paramCount = function.getParameterCount();
return function.hasVarArgs() && paramCount > 0 &&
isCharPointer(function.getParameters()[paramCount - 1].getDataType());
}
private boolean isCharPointer(DataType dataType) {
if (dataType instanceof TypeDef) {
dataType = ((TypeDef) dataType).getBaseDataType();
}
if (!(dataType instanceof Pointer)) {
return false;
}
DataType dt = ((Pointer) dataType).getDataType();
return dt instanceof CharDataType || dt instanceof WideCharDataType ||
dt instanceof WideChar16DataType || dt instanceof WideChar32DataType;
}
private class VariadicSignatureDecompileConfigurer implements DecompileConfigurer {
// DecompInterface allows for control of decompilation processes
@Override
public void configure(DecompInterface decompiler) {
decompiler.toggleCCode(true); // Produce C code
decompiler.toggleSyntaxTree(true); // Produce syntax tree
decompiler.openProgram(currentProgram);
decompiler.setSimplificationStyle("normalize");
DecompileOptions options = new DecompileOptions();
options.grabFromProgram(currentProgram);
decompiler.setOptions(options);
}
}
private ParameterDefinition[] parseParameters(Function function,
Address address,
String callFunctionName, String formatString,
Map<String, List<DataType>> namesToParameters) {
Program functionProgram = function.getProgram();
FormatStringParser parser = getParser();
// DataTypes of arguments are treated differently when the variadic function
// looks like scanf since it takes in inputs. We need this information
// so that the correct DataType arguments are generated
boolean isOutputType = !callFunctionName.contains(INPUT_FUNCTION_SUBSTRING);
List<FormatArgument> formatArguments =
parser.convertToFormatArgumentList(formatString, isOutputType);
DataType[] dataTypes = isOutputType ? parser.convertToOutputDataTypes(formatArguments)
: parser.convertToInputDataTypes(formatArguments);
if (dataTypes == null) {
currentProgram.getBookmarkManager()
.setBookmark(address, BookmarkType.ANALYSIS, "Unrecognized format string",
"Format string could not be parsed: " + formatString);
return null;
}
ParameterDefinition[] paramDefs =
createParameters(callFunctionName, dataTypes, functionProgram, namesToParameters);
return paramDefs;
}
private ParameterDefinition[] createParameters(String callFunctionName, DataType[] dataTypes,
Program program, Map<String, List<DataType>> namesToParameters) {
List<DataType> initialFunctionParameters = namesToParameters.get(callFunctionName);
int numberOfParameters = initialFunctionParameters.size() + dataTypes.length;
if (numberOfParameters == 0) {
return null; // Invalid function
}
ParameterDefinition[] parameterDefinitions = new ParameterDefinition[numberOfParameters];
for (int i = 0; i < numberOfParameters; i++) {
if (i < initialFunctionParameters.size()) {
parameterDefinitions[i] =
new ParameterDefinitionImpl("param" + i, initialFunctionParameters.get(i), "");
}
else {
parameterDefinitions[i] = new ParameterDefinitionImpl("param" + i,
dataTypes[i - initialFunctionParameters.size()], "");
}
}
return parameterDefinitions;
}
private FunctionSignature initSignature(Function function, Address address,
String callFunctionName, String formatString,
Map<String, List<DataType>> namesToParameters, Map<String, DataType> namesToReturn) {
ParameterDefinition[] parameterDefinitions =
parseParameters(function, address, callFunctionName, formatString, namesToParameters);
if (parameterDefinitions == null || parameterDefinitions.length == 0) {
return null;
}
FunctionDefinitionDataType signature = new FunctionDefinitionDataType(callFunctionName);
signature.setArguments(parameterDefinitions);
signature.setReturnType(namesToReturn.get(callFunctionName));
return signature;
}
private void overrideCallList(Program program, Function function,
List<FunctionCallData> functionCallDataList,
Map<String, List<DataType>> namesToParameters, Map<String, DataType> namesToReturn) {
if (function == null || functionCallDataList == null) {
return;
}
for (FunctionCallData data : functionCallDataList) {
overrideFunctionCall(program, function, data.getAddressOfCall(), data.getCallFuncName(),
data.getFormatString(), namesToParameters, namesToReturn);
}
}
private void overrideFunctionCall(Program program, Function function, Address address,
String callFunctionName, String formatString,
Map<String, List<DataType>> namesToParameters,
Map<String, DataType> namesToReturn) {
if (formatString == null) {
return;
}
FunctionSignature functionSignature = initSignature(function, address, callFunctionName,
formatString, namesToParameters, namesToReturn);
if (functionSignature == null || function == null || address == null) {
return;
}
try {
if (createBookmarksEnabled) {
BookmarkManager bookmark = program.getBookmarkManager();
bookmark.setBookmark(address, BookmarkType.ANALYSIS,
"Function Signature Override",
"Override for call to function " + callFunctionName);
}
HighFunctionDBUtil.writeOverride(function, address, functionSignature);
}
catch (InvalidInputException e) {
Msg.error(this, "Error: invalid input given to writeOverride()", e);
}
}
@Override
public boolean removed(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
throws CancelledException {
return false;
}
@Override
public void registerOptions(Options options, Program program) {
options.registerOption(OPTION_NAME_CREATE_BOOKMARKS, createBookmarksEnabled, null,
OPTION_DESCRIPTION_CREATE_BOOKMARKS);
}
@Override
public void optionsChanged(Options options, Program program) {
createBookmarksEnabled =
options.getBoolean(OPTION_NAME_CREATE_BOOKMARKS, createBookmarksEnabled);
}
}

View File

@@ -0,0 +1,961 @@
/* ###
* 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.plugin.core.string.variadic;
import java.util.*;
import java.util.stream.Collectors;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
/**
* Class for parsing a variadic function's format String to determine the proper
* number of arguments and their DataTypes. It analyzes format strings from variadic functions.
* Parses format strings adhering to docs https://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html
* and https://en.cppreference.com/w/c/io/fscanf. If a format string doesn't adhere properly
* to what is specified in the docs, the string will not continue to be parsed since this is
* undefined behavior.
* <br>
* The standard C formats may make optional use of the following extended precision types
* which may be defined as a {@link TypeDef} the appropriate Datatype implementation.
* If a format string is encountered which refers to one of these types which has not
* previously been defined, a TypeDef will be fabricated although it may not be correct.
* <ul>
* <li>intmax_t - maximum sized signed integer (default: long long)</li>
* <li>uintmax_t - maximum size unsigned integer (default: unsigned long long)</li>
* <li>size_t - unsigned integer type corresponding to sizeof (default: varies) </li>
* <li>ptrdiff_t - signed integer type (default: varies) </li>
* </ul>
*/
public class FormatStringParser {
public static final String INTMAX_T_NAME = "intmax_t";
public static final String UINTMAX_T_NAME = "uintmax_t";
public static final String SIZE_T_NAME = "size_t";
public static final String PTRDIFF_T_NAME = "ptrdiff_t";
private DataTypeManager dataTypeManager;
private TypeDef intmax_t;
private TypeDef uintmax_t;
private TypeDef size_t;
private TypeDef ptrdiff_t;
/**
* Constructor for FormatStringParser.
* <br>
* NOTE: Warning messages will be logged once per instantiation when
* appropriate required TypeDef (intmax_t, uintmax_t, size_t, ptrdif_t)
* has not been predefined.
*
* @param program currentProgram
*/
public FormatStringParser(Program program) {
this.dataTypeManager = program.getDataTypeManager();
}
/**
* This function takes in a format string and returns List of Strings each holding
* format data. Each String is a substring of the given format string that corresponds to one
* or more DataTypes. These DataTypes determine which arguments need to be given to the variadic
* function. For instance, given the format String "%d %4.2s", this function will return
* the List ["d", "4.2s"]
*
* @param formatString format String
* @return List of substrings of formatStr
*/
private List<String> parseFormatString(String formatString) {
List<String> formatArgumentList = new ArrayList<>();
String current = "";
for (int i = 0; i < formatString.length(); i++) {
char c = formatString.charAt(i);
if (c == '%') {
if (emitPercent(formatString, i)) {
++i;
}
else {
++i;
c = formatString.charAt(i);
while (!isConversionSpecifier(c)) {
current += c;
++i;
if (i >= formatString.length()) {
return null;
}
c = formatString.charAt(i);
}
formatArgumentList.add(current + c);
current = "";
}
}
}
return formatArgumentList;
}
/**
* Takes in a single String from parseFormatString's output List and converts it to
* the corresponding FormatArgument(s) and populates the formatArgumentList List.
* isOutputType is true when using a format string for a function that "outputs"
* Strings (e.g., printf, fprintf, etc.). When it's false, it evaluates the
* String's data types as if the function "inputs" Strings (e.g., scanf)
*
* @param formatString Format String
* @param formatArgumentList List of FormatArgument that will be written to
* @param isOutputType Type of variadic function
* @return True if format string successfully parsed
*/
private boolean convertToFormatArguments(String formatString,
List<FormatArgument> formatArgumentList, boolean isOutputType) {
FormatParsingData data = new FormatParsingData();
for (int i = 0; i < formatString.length(); i++) {
char c = formatString.charAt(i);
i = preprocessChar(formatString, i, isOutputType);
if (i == -1) {
return false;
}
if (isFlag(c)) {
continue;
}
if (data.getLengthModifier() != null) {
return addArgumentWithModifier(c, data, formatArgumentList);
}
data.setLengthModifier(detectLengthModifier(c));
if (data.getLengthModifier() == null) {
data.setConversionSpecifier(detectConversionSpecifier(c));
if (data.getConversionSpecifier() != null) {
if (!verifyConversionPair(data.getLengthModifier(),
data.getConversionSpecifier())) {
return false;
}
formatArgumentList.add(new FormatArgument(data.getLengthModifier(),
data.getConversionSpecifier()));
return true;
}
// If length modifier and conversion specs aren't present
// and we get an unknown char, format string is invalid
if (data.isPrecisionComplete()) {
return false;
}
if (!Character.isDigit(c) && c != '.' && c != '*') {
return false;
}
if (isOutputType) {
// At this point c is either a number, '*', or '.'
i = handleOutputConversionArgument(formatString, i, data, formatArgumentList);
if (i == -1) {
return false;
}
}
else {
i = handleInputConversionArgument(formatString, i, data, formatArgumentList);
if (i == -1) {
return false;
}
}
}
else if (i + 1 < formatString.length()) {
i = initiateLengthModifierExtension(formatString, i, data);
}
}
return true;
}
private int preprocessChar(String formatString, int i, boolean isOutputType) {
char c = formatString.charAt(i);
if (c == '$') {
return -1;
}
if (isFlag(c)) {
if (!isOutputType) {
return -1;
}
i = skipFlags(formatString, i);
}
return i;
}
private int initiateLengthModifierExtension(String formatString, int i,
FormatParsingData data) {
String tmpLengthModifier =
extendLengthModifier(data.getLengthModifier(), formatString.charAt(i + 1));
if (tmpLengthModifier != null) {
++i;
data.setLengthModifier(tmpLengthModifier);
}
return i;
}
private boolean addArgumentWithModifier(char c, FormatParsingData data,
List<FormatArgument> formatArgumentList) {
data.setConversionSpecifier(detectConversionSpecifier(c));
if ((data.getConversionSpecifier() == null) ||
!(verifyConversionPair(data.getLengthModifier(), data.getConversionSpecifier()))) {
return false; // Problem with format string
}
formatArgumentList
.add(new FormatArgument(data.getLengthModifier(), data.getConversionSpecifier()));
return true;
}
private int handleOutputConversionArgument(String formatString, int i, FormatParsingData data,
List<FormatArgument> formatArgumentList) {
char c = formatString.charAt(i);
if (!data.isPrecisionComplete() && !data.isFieldWidthComplete() && c != '.') {
if (c == '*') {
formatArgumentList.add(new FormatArgument(null, "*"));
}
else {
i = skipIntegers(formatString, i);
}
if (i == -1) {
return i;
}
data.setFieldWidthComplete(true);
}
else if (data.isFieldWidthComplete() && c != '.') {
return -1;
}
else if (!data.isPrecisionComplete() && c == '.') {
if (i + 1 < formatString.length() && formatString.charAt(i + 1) == '*') {
++i;
formatArgumentList.add(new FormatArgument(null, "*"));
}
else {
i = skipIntegers(formatString, i + 1);
}
if (i == -1) {
return i;
}
data.setPrecisionComplete(true);
}
else {
return -1;
}
return i;
}
private int handleInputConversionArgument(String formatString, int i, FormatParsingData data,
List<FormatArgument> formatArgumentList) {
char c = formatString.charAt(i);
if (c == '*') {
formatArgumentList.add(new FormatArgument(null, "*"));
}
else if (Character.isDigit(c)) {
i = skipIntegers(formatString, i + 1);
if (i == -1) {
return i;
}
data.setPrecisionComplete(true);
}
else {
return -1;
}
return i;
}
/**
* Takes in a String and converts it to a List of FormatArgument with each FormatArgument
* corresponding to an additional argument. isOutputType is true when using a
* format string for output data types (e.g. printf, fprintf, etc.). When it's
* false, it evaluates the String's data types as if they were input types (e.g.
* scanf)
*
* @param formatString format String
* @param isOutputType Type of variadic function
* @return List of FormatArgument
*/
public List<FormatArgument> convertToFormatArgumentList(String formatString,
boolean isOutputType) {
if (formatString == null) {
return null;
}
List<String> formatStrArgumentList = parseFormatString(formatString);
if (formatStrArgumentList == null) {
return null;
}
List<FormatArgument> formatArgumentList = new ArrayList<>();
for (String formatStrArgument : formatStrArgumentList) {
boolean status =
convertToFormatArguments(formatStrArgument, formatArgumentList, isOutputType);
if (!status) {
if (formatStrArgumentList.stream()
.filter(str -> str.contains("$"))
.findAny()
.isPresent()) {
return analyzeFormatStringWithParameters(formatString);
}
return null;
}
}
return formatArgumentList.contains(null) ? null : formatArgumentList;
}
/**
*
* Handles format Strings with parameters. In this parser, we define a format
* String parameter to be an integer n provided in the form: "%n$" or "*n$", where n is
* the index of the referred argument. If a placeholder uses a format
* argument parameter, all other placeholders must also have a parameter. Also,
* all gaps between format argument indices are not supported. For instance, if
* the first and third arguments are used, there must also be a parameter for a
* second argument. Any parameter pattern beginning with % or * and ending with
* $ must have integer in between. Failing to adhere by the format string
* parameter requirements returns null.
*
* @param formatString format String
* @return List of FormatArgument
*
*
* TODO: What if multiple conversion specs refer to the same placeholder
* with different types? Ex: "%1$*1$x" (uses unsigned int and int)
* Currently just overwrites previous type
*
*/
public List<FormatArgument> analyzeFormatStringWithParameters(String formatString) {
FormatParsingData data = new FormatParsingData();
Map<Integer, FormatArgument> formatArgumentMap = new HashMap<>();
for (int i = 0; i < formatString.length(); i++) {
char c = formatString.charAt(i);
if (c == ' ') {
continue;
}
if (c == '%') {
if (emitPercent(formatString, i)) {
++i;
}
else {
data.setInConversion(true);
data.clearData();
data.setParameterIndex(locateParameterIndex(formatString, i));
if (data.getParameterIndex() == 0) {
return null; // $ operand number is required
}
i += Integer.toString(data.getParameterIndex()).length() + 1; // i should be at $
if (isFlag(formatString.charAt(i + 1))) {
i = skipFlags(formatString, i + 1);
}
continue;
}
}
if (data.isInConversion()) {
if (data.getLengthModifier() != null) {
data.setConversionSpecifier(detectConversionSpecifier(c));
if (data.getConversionSpecifier() == null) {
return null; // Problem with format string
}
formatArgumentMap.put(data.getParameterIndex(), new FormatArgument(
data.getLengthModifier(), data.getConversionSpecifier()));
data.setInConversion(false);
continue;
}
data.setLengthModifier(detectLengthModifier(c));
if (data.getLengthModifier() == null) {
i = searchWithNullModifier(formatString, i, data, formatArgumentMap);
if (i == -1) {
return null;
}
}
}
}
return convertMapToList(formatArgumentMap);
}
// Continue format String conversion parsing for when the length modifier is null
private int searchWithNullModifier(String formatString, int i, FormatParsingData data,
Map<Integer, FormatArgument> formatArgumentMap) {
char c = formatString.charAt(i);
data.setConversionSpecifier(detectConversionSpecifier(c));
if (data.getConversionSpecifier() != null) {
formatArgumentMap.put(data.getParameterIndex(),
new FormatArgument(data.getLengthModifier(), data.getConversionSpecifier()));
data.setInConversion(false);
}
else {
if (data.isPrecisionComplete()) {
return -1;
}
if (!Character.isDigit(c) && c != '.' && c != '*') {
return -1;
}
// At this point c is either a number, '*', or '.'
if (!data.isPrecisionComplete() && !data.isFieldWidthComplete() && c != '.') {
i = handleOutputConversionForParameters(formatString, i, data, formatArgumentMap);
if (i == -1) {
return -1;
}
}
else if (data.isFieldWidthComplete() && c != '.') {
return -1;
}
else if (!data.isPrecisionComplete() && c == '.') {
i = handlePrecisionForParameters(formatString, i, data, formatArgumentMap);
if (i == -1) {
return -1;
}
}
else {
return -1;
}
}
return i;
}
// Takes care of optional precision indicated by a period ('.') and followed by an
// asterick or series of integers
private int handlePrecisionForParameters(String formatString, int i, FormatParsingData data,
Map<Integer, FormatArgument> formatArgumentMap) {
if (i + 1 < formatString.length() && formatString.charAt(i + 1) == '*') {
++i;
int precisionIdx = locateParameterIndex(formatString, i);
if (precisionIdx == 0) {
return -1;
}
i += Integer.toString(precisionIdx).length() + 1;
// i should be at $
formatArgumentMap.put(precisionIdx, new FormatArgument(null, "d"));
}
else {
i = skipIntegers(formatString, i + 1); // i should be at last number
if (i == -1) {
return -1;
}
}
data.setPrecisionComplete(true);
return i;
}
private int handleOutputConversionForParameters(String formatString, int i,
FormatParsingData data, Map<Integer, FormatArgument> formatArgumentMap) {
char c = formatString.charAt(i);
if (c == '*') {
int fieldWidthIdx = locateParameterIndex(formatString, i);
if (fieldWidthIdx == 0) {
return i;
}
i += Integer.toString(fieldWidthIdx).length() + 1;
// i should be at $
formatArgumentMap.put(fieldWidthIdx, new FormatArgument(null, "d"));
}
else {
i = skipIntegers(formatString, i);
if (i == -1) {
return i;
}
}
data.setFieldWidthComplete(true);
return i;
}
private List<FormatArgument> convertMapToList(Map<Integer, FormatArgument> formatArgumentMap) {
List<FormatArgument> formatArgumentList = new ArrayList<>();
for (int i = 1; i <= formatArgumentMap.size(); i++) {
FormatArgument formatArgument = formatArgumentMap.get(i);
if (formatArgument == null) {
return null;
}
formatArgumentList.add(formatArgument);
}
return formatArgumentList;
}
/**
* In a format string with format argument parameters, retrieve that parameter.
* In other words, in the following cases: "%n$" and "*n$", return n where n is
* the index of the referred argument. n cannot be less than 1; return 0 if
* there's a problem.
*
* @param formatString format String
* @param i index within formatStr
* @return formar argument parameter
*/
private int locateParameterIndex(String formatString, int i) {
char c = formatString.charAt(i);
if (c == '%' || c == '*') {
++i;
c = formatString.charAt(i);
}
else {
return 0;
}
String paramIndexString = "";
while (Character.isDigit(c)) {
paramIndexString += Character.toString(c);
++i;
c = formatString.charAt(i);
}
return c != '$' || paramIndexString.length() == 0 || Integer.parseInt(paramIndexString) == 0
? 0
: Integer.parseInt(paramIndexString);
}
/**
* Skips a series of flags within a format String. returns the index of the
* format string at the last digit before another non-digit character
*
* @param formatString format String
* @param i index into formatStr
* @return new index into formatStr
*/
private int skipFlags(String formatString, int i) {
for (; isFlag(formatString.charAt(i)); i++) {
// Iterate through chars until all flags are skipped
}
return i - 1;
}
/**
* Skips a series of numbers (field width or precision) within a format String.
* returns the index of the format String at the last digit before another
* non-digit character
*
* @param formatString format String
* @param i index into formatStr
* @return new index into formatString
*/
private int skipIntegers(String formatString, int i) {
char c = formatString.charAt(i);
if (!Character.isDigit(c)) {
if (isLengthModifier(c) || isConversionSpecifier(c)) {
return i - 1;
}
return -1;
}
for (; Character.isDigit(formatString.charAt(i)); i++) {
// Skip chars until a non-integer is found
}
return i - 1;
}
// If there are two consecutive '%' signs, do not evaluate the data types
private boolean emitPercent(String formatString, int i) {
if (formatString.charAt(i) == '%' && i + 1 < formatString.length() &&
formatString.charAt(i + 1) == '%') {
return true;
}
return false;
}
public DataType[] convertToOutputDataTypes(List<FormatArgument> formatArguments) {
if (formatArguments == null) {
return null;
}
List<DataType> dataTypeList = formatArguments.stream().map(argument -> {
String conversionSpecifier = argument.getConversionSpecifier();
DataType dt = convertPairToDataType(argument.getLengthModifier(),
conversionSpecifier.equals("*") ? "d" : conversionSpecifier);
return dt;
}).collect(Collectors.toList());
return dataTypeList.contains(null) ? null
: dataTypeList.toArray(DataType[]::new);
}
public DataType[] convertToInputDataTypes(List<FormatArgument> formatArguments) {
if (formatArguments == null) {
return null;
}
List<DataType> dataTypesList = new ArrayList<>();
for (int i = 0; i < formatArguments.size(); i++) {
FormatArgument argument = formatArguments.get(i);
// * means to skip
if (argument.getConversionSpecifier().equals("*")) {
if (formatArguments.get(i + 1).getConversionSpecifier().equals("*")) {
return null;
}
++i;
continue;
}
DataType dt = convertPairToDataType(argument.getLengthModifier(),
argument.getConversionSpecifier());
if (dt == null) {
return null;
}
if (!(dt instanceof PointerDataType) ||
isVoidPointer(argument.getConversionSpecifier())) {
dataTypesList.add(dataTypeManager.getPointer(dt));
}
else {
dataTypesList.add(dt);
}
}
return dataTypesList.stream().toArray(size -> new DataType[size]);
}
private boolean verifyConversionPair(String lengthModifier, String conversionSpecifier) {
if (lengthModifier == null || lengthModifier.equals("l")) {
return true;
}
if ((lengthModifier.equals("L") && isDouble(conversionSpecifier)) ||
(!lengthModifier.equals("L") &&
(isInteger(conversionSpecifier) || isIntegerPointer(conversionSpecifier)))) {
return true;
}
return false;
}
private DataType convertPairToDataType(String lengthModifier, String conversionSpecifier) {
if (lengthModifier == null || conversionSpecifier.equals("c") ||
conversionSpecifier.equals("s") ||
conversionSpecifier.equals("C") ||
conversionSpecifier.equals("S")) {
return conversionSpecifierToDataType(conversionSpecifier);
}
switch (lengthModifier) {
case "h":
return shortLengthModification(conversionSpecifier);
case "hh":
return charLengthModification(conversionSpecifier);
case "l":
return longLengthModification(conversionSpecifier);
case "ll":
case "q":
return longLongLengthModification(conversionSpecifier);
case "j":
return intmax_t_LengthModification(conversionSpecifier);
case "z":
return size_t_LengthModification(conversionSpecifier);
case "t":
return ptrdiff_t_LengthModification(conversionSpecifier);
case "L":
return longDoubleLengthModification(conversionSpecifier);
default:
return null;
}
}
private DataType conversionSpecifierToDataType(String conversionSpecifier) {
switch (conversionSpecifier.charAt(0)) {
case 'd':
case 'i':
return new IntegerDataType(dataTypeManager);
case 'o':
case 'u':
case 'x':
case 'X':
return new UnsignedIntegerDataType(dataTypeManager);
case 'p':
return dataTypeManager.getPointer(DataType.VOID);
case 's':
return dataTypeManager.getPointer(new CharDataType(dataTypeManager));
case 'n':
return dataTypeManager.getPointer(new IntegerDataType(dataTypeManager));
case 'c':
return new UnsignedCharDataType(dataTypeManager);
case 'a':
case 'A':
case 'g':
case 'G':
case 'e':
case 'E':
case 'f':
return new DoubleDataType(dataTypeManager);
case 'S':
case 'C':
return dataTypeManager.getPointer(new WideCharDataType(dataTypeManager));
default:
return null;
}
}
private DataType longLengthModification(String conversionSpecifier) {
if (isIntegerPointer(conversionSpecifier)) {
return dataTypeManager.getPointer(new LongDataType(dataTypeManager));
}
if (conversionSpecifier.contentEquals("s") || conversionSpecifier.contentEquals("c")) {
return dataTypeManager.getPointer(new WideCharDataType(dataTypeManager));
}
return isSignedInteger(conversionSpecifier) ? new LongDataType(dataTypeManager)
: new UnsignedLongDataType(dataTypeManager);
}
private DataType longLongLengthModification(String conversionSpecifier) {
if (isIntegerPointer(conversionSpecifier)) {
return dataTypeManager.getPointer(new LongLongDataType(dataTypeManager));
}
return isSignedInteger(conversionSpecifier)
? new LongLongDataType(dataTypeManager)
: new UnsignedLongLongDataType(dataTypeManager);
}
private DataType shortLengthModification(String conversionSpecifier) {
if (isIntegerPointer(conversionSpecifier)) {
return dataTypeManager.getPointer(new ShortDataType(dataTypeManager));
}
return isSignedInteger(conversionSpecifier)
? new ShortDataType(dataTypeManager)
: new UnsignedShortDataType(dataTypeManager);
}
private DataType charLengthModification(String conversionSpecifier) {
if (isIntegerPointer(conversionSpecifier)) {
return dataTypeManager.getPointer(new CharDataType(dataTypeManager));
}
return isSignedInteger(conversionSpecifier) ? new CharDataType(dataTypeManager)
: new UnsignedCharDataType(dataTypeManager);
}
private TypeDef lookupTypeDef(String name) {
List<DataType> typeList = new ArrayList<>();
dataTypeManager.findDataTypes(name, typeList);
for (DataType dt : typeList) {
if (!(dt instanceof TypeDef)) {
continue;
}
TypeDef td = (TypeDef) dt;
if (td.getBaseDataType() instanceof AbstractIntegerDataType) {
return td;
}
}
return null;
}
private TypeDef getIntMaxT() {
if (intmax_t != null) {
return intmax_t;
}
intmax_t = lookupTypeDef(INTMAX_T_NAME);
if (intmax_t == null) {
intmax_t = new TypedefDataType(INTMAX_T_NAME, new LongLongDataType(dataTypeManager));
Msg.warn(this, INTMAX_T_NAME + " not defined. Generated as `" + intmax_t + "'");
}
return intmax_t;
}
private TypeDef getUIntMaxT() {
if (uintmax_t != null) {
return uintmax_t;
}
uintmax_t = lookupTypeDef(UINTMAX_T_NAME);
if (uintmax_t == null) {
uintmax_t =
new TypedefDataType(UINTMAX_T_NAME, new UnsignedLongLongDataType(dataTypeManager));
Msg.warn(this, UINTMAX_T_NAME + " not defined. Generated as `" + uintmax_t + "'");
}
return uintmax_t;
}
private AbstractIntegerDataType getIntegralPointerType(boolean signed) {
DataOrganization dataOrganization = dataTypeManager.getDataOrganization();
int size = dataOrganization.getPointerSize();
if (size < dataOrganization.getLongSize() && size >= dataOrganization.getIntegerSize()) {
return signed ? new IntegerDataType(dataTypeManager)
: new UnsignedIntegerDataType(dataTypeManager);
}
return signed ? new LongDataType(dataTypeManager)
: new UnsignedLongDataType(dataTypeManager);
}
private TypeDef getSizeT() {
if (size_t != null) {
return size_t;
}
size_t = lookupTypeDef(SIZE_T_NAME);
if (size_t == null) {
size_t = new TypedefDataType(SIZE_T_NAME, getIntegralPointerType(false));
Msg.warn(this, SIZE_T_NAME + " not defined. Generated as `" + size_t + "'");
}
return size_t;
}
private TypeDef getPtrDiffT() {
if (ptrdiff_t != null) {
return ptrdiff_t;
}
ptrdiff_t = lookupTypeDef(PTRDIFF_T_NAME);
if (ptrdiff_t == null) {
ptrdiff_t = new TypedefDataType(PTRDIFF_T_NAME, getIntegralPointerType(true));
Msg.warn(this, PTRDIFF_T_NAME + " not defined. Generated as `" + ptrdiff_t + "'");
}
return ptrdiff_t;
}
private DataType intmax_t_LengthModification(String conversionSpecifier) {
TypeDef intType = isUnsignedInteger(conversionSpecifier) ? getUIntMaxT() : getIntMaxT();
return isIntegerPointer(conversionSpecifier)
? dataTypeManager.getPointer(intType)
: intType;
}
private DataType size_t_LengthModification(String conversionSpecifier) {
TypeDef sizeType = getSizeT();
return isIntegerPointer(conversionSpecifier)
? dataTypeManager.getPointer(sizeType)
: sizeType;
}
private DataType ptrdiff_t_LengthModification(String conversionSpecifier) {
TypeDef type = isUnsignedInteger(conversionSpecifier) ? getSizeT() : getPtrDiffT();
return isIntegerPointer(conversionSpecifier)
? dataTypeManager.getPointer(type)
: type;
}
private DataType longDoubleLengthModification(String conversionSpecifier) {
return new LongDoubleDataType(dataTypeManager);
}
private boolean isInteger(String conversionSpecifier) {
return isUnsignedInteger(conversionSpecifier) || isSignedInteger(conversionSpecifier);
}
private boolean isDouble(String conversionSpecifier) {
char c = conversionSpecifier.charAt(0);
String doubleConversionSpecifierSet = "aAeEfFgG";
return doubleConversionSpecifierSet.indexOf(c) != -1;
}
private boolean isUnsignedInteger(String conversionSpecifier) {
char c = conversionSpecifier.charAt(0);
String unsignedIntSpecifierSet = "ouxX";
return unsignedIntSpecifierSet.indexOf(c) != -1;
}
private boolean isSignedInteger(String conversionSpecifier) {
char c = conversionSpecifier.charAt(0);
String signedIntSpecifierSet = "di";
return signedIntSpecifierSet.indexOf(c) != -1;
}
private boolean isIntegerPointer(String conversionSpecifier) {
char c = conversionSpecifier.charAt(0);
String pointerSpecifierSet = "n";
return pointerSpecifierSet.indexOf(c) != -1;
}
private boolean isVoidPointer(String conversionSpecifier) {
char c = conversionSpecifier.charAt(0);
String voidPointerSpecifierSet = "p";
return voidPointerSpecifierSet.indexOf(c) != -1;
}
private boolean isFlag(char c) {
String flagSpecifierSet = "0+ -#'";
return flagSpecifierSet.indexOf(c) != -1;
}
private String extendLengthModifier(String lengthModifier, char nextChar) {
if ((lengthModifier.equals("h") && nextChar == 'h') ||
(lengthModifier.equals("l") && nextChar == 'l')) {
return lengthModifier + Character.toString(nextChar);
}
return null;
}
private boolean isConversionSpecifier(char c) {
return detectConversionSpecifier(c) != null;
}
private boolean isLengthModifier(char c) {
return detectLengthModifier(c) != null;
}
private String detectLengthModifier(char c) {
String lengthModifierSet = "hljztLq";
return lengthModifierSet.indexOf(c) != -1 ? Character.toString(c) : null;
}
private String detectConversionSpecifier(char c) {
String conversionSpecifierSet = "diuofeaFEApcsxXgGnCS";
return conversionSpecifierSet.indexOf(c) != -1 ? Character.toString(c) : null;
}
public int skipToNextWhitespace(String formatStr, int i) {
char c = formatStr.charAt(i);
while (c != ' ') {
++i;
c = formatStr.charAt(i);
}
return i;
}
private class FormatParsingData {
private String conversionSpecifier = null;
private String lengthModifier = null;
private boolean fieldWidthComplete = false;
private boolean precisionComplete = false;
private boolean inConversion = false;
private int parameterIndex = 0;
private void setParameterIndex(int parameterIndex) {
this.parameterIndex = parameterIndex;
}
private int getParameterIndex() {
return this.parameterIndex;
}
private void setConversionSpecifier(String conversionSpecifier) {
this.conversionSpecifier = conversionSpecifier;
}
private String getConversionSpecifier() {
return this.conversionSpecifier;
}
private void setLengthModifier(String lengthModifier) {
this.lengthModifier = lengthModifier;
}
private String getLengthModifier() {
return this.lengthModifier;
}
private boolean isFieldWidthComplete() {
return this.fieldWidthComplete;
}
private void setFieldWidthComplete(boolean fieldWidthComplete) {
this.fieldWidthComplete = fieldWidthComplete;
}
private boolean isPrecisionComplete() {
return this.precisionComplete;
}
private void setPrecisionComplete(boolean precisionComplete) {
this.precisionComplete = precisionComplete;
}
private void setInConversion(boolean inConversion) {
this.inConversion = inConversion;
}
private boolean isInConversion() {
return this.inConversion;
}
private void clearData() {
this.precisionComplete = false;
this.fieldWidthComplete = false;
this.lengthModifier = null;
this.conversionSpecifier = null;
}
}
}

View File

@@ -0,0 +1,68 @@
/* ###
* 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.plugin.core.string.variadic;
import ghidra.program.model.address.*;
/**
* Class for encapsulating a variadic function call
*/
public class FunctionCallData {
private Address addressOfCall;
private String callFunctionName;
private String formatString;
/**
* Constructore for FuncCallData
*
* @param addressOfCall Address of function call
* @param callFunctionName variadic function name
* @param formatString format String
*/
public FunctionCallData(Address addressOfCall, String callFunctionName, String formatString) {
this.addressOfCall = addressOfCall;
this.callFunctionName = callFunctionName;
this.formatString = formatString;
}
/**
* addressOfCall getter
*
* @return addressOfCall
*/
public Address getAddressOfCall() {
return this.addressOfCall;
}
/**
* callFunctionName getter
*
* @return callFunctionName
*/
public String getCallFuncName() {
return this.callFunctionName;
}
/**
* formatString getter
*
* @return formatString
*/
public String getFormatString() {
return this.formatString;
}
}

View File

@@ -0,0 +1,184 @@
/* ###
* 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.plugin.core.string.variadic;
import java.util.*;
import ghidra.docking.settings.SettingsImpl;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.StringDataInstance;
import ghidra.program.model.data.StringDataType;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryBufferImpl;
import ghidra.program.model.pcode.PcodeOpAST;
import ghidra.program.model.pcode.Varnode;
/**
* Class for parsing functions' Pcode representations and finding variadic
* functions being called
*
*/
public class PcodeFunctionParser {
// All values within the range [32, 126] are ascii readable
private static final int READABLE_ASCII_LOWER_BOUND = 32;
private static final int READABLE_ASCII_UPPER_BOUND = 126;
// How many bytes to read from a memory address when initial format
// String cannot be found. This normally only happens for short format
// Strings with lengths less than 5
private static final int BUFFER_LENGTH = 20;
private static final String CALL_INSTRUCTION = "CALL";
private Program program;
public PcodeFunctionParser(Program program) {
this.program = program;
}
/**
* Takes pcode ops of a function and parses them to determine whether there are
* any calls to variadic functions that use format Strings.
*
* @param pcodeOps List of PcodeOpAST for a function
* @param addressToCandidateData map of Addresses to format String data
* @param variadicFunctionNames Set of variadic functions to look for
* @return List of variadic functions that the current function calls
*/
public List<FunctionCallData> parseFunctionForCallData(List<PcodeOpAST> pcodeOps,
Map<Address, Data> addressToCandidateData, Set<String> variadicFunctionNames) {
if (pcodeOps == null || addressToCandidateData == null || variadicFunctionNames == null ||
this.program == null) {
return null;
}
List<FunctionCallData> functionCallDataList = new ArrayList<>();
for (PcodeOpAST ast : pcodeOps) {
Varnode firstNode = ast.getInput(0);
if (firstNode == null) {
continue;
}
if (ast.getMnemonic().contentEquals(CALL_INSTRUCTION)) {
FunctionManager functionManager = this.program.getFunctionManager();
Function function = functionManager.getFunctionAt(firstNode.getAddress());
if (function == null) {
return null;
}
String functionName = function.getName();
if (variadicFunctionNames.contains(functionName)) {
Varnode[] inputs = ast.getInputs();
if (inputs.length > 0) {
boolean hasDefinedFormatString = searchForVariadicCallData(ast,
addressToCandidateData, functionCallDataList, functionName);
if (!hasDefinedFormatString) {
searchForHiddenFormatStrings(ast, functionCallDataList, functionName);
}
}
}
}
}
return functionCallDataList;
}
private boolean searchForVariadicCallData(PcodeOpAST ast,
Map<Address, Data> addressToCandidateData, List<FunctionCallData> functionCallDataList,
String functionName) {
boolean hasDefinedFormatString = false;
Varnode[] inputs = ast.getInputs();
for (int i = 1; i < inputs.length; i++) {
Varnode v = inputs[i];
Data data = null;
Address ramSpaceAddress = convertAddressToRamSpace(v.getAddress());
if (addressToCandidateData.containsKey(ramSpaceAddress)) {
data = addressToCandidateData.get(ramSpaceAddress);
functionCallDataList.add(new FunctionCallData(ast.getSeqnum().getTarget(),
functionName, data.getDefaultValueRepresentation()));
hasDefinedFormatString = true;
}
}
return hasDefinedFormatString;
}
// If addrToCandidateData doesn't have format String data for this call
// and we are calling a variadic function, parse the String to determine
// whether it's a format String.
private void searchForHiddenFormatStrings(PcodeOpAST ast,
List<FunctionCallData> functionCallDataList, String functionName) {
Varnode[] inputs = ast.getInputs();
// Initialize i = 1 to skip first input
for (int i = 1; i < inputs.length; ++i) {
Varnode v = inputs[i];
String formatStringCandidate = findFormatString(v.getAddress());
if (formatStringCandidate == null) {
continue;
}
if (formatStringCandidate.contains("%")) {
functionCallDataList.add(new FunctionCallData(ast.getSeqnum().getTarget(),
functionName, formatStringCandidate));
}
break;
}
}
private Address convertAddressToRamSpace(Address address) {
String addressString = address.toString(false);
return this.program.getAddressFactory().getAddress(addressString);
}
/**
* Looks at bytes at given address and converts to format String
*
* @param address Address of format String
* @return format String
*/
private String findFormatString(Address address) {
if (!address.getAddressSpace().isConstantSpace()) {
return null;
}
// Old address associated with constant space which doesn't work
Address ramSpaceAddress = convertAddressToRamSpace(address);
MemoryBufferImpl memoryBuffer =
new MemoryBufferImpl(this.program.getMemory(), ramSpaceAddress);
SettingsImpl settings = new SettingsImpl();
StringDataInstance stringDataInstance = StringDataInstance
.getStringDataInstance(new StringDataType(), memoryBuffer, settings, BUFFER_LENGTH);
String stringValue = stringDataInstance.getStringValue();
if (stringValue == null) {
return null;
}
String formatStringCandidate = "";
for (int i = 0; i < stringValue.length(); i++) {
if (!isAsciiReadable(stringValue.charAt(i))) {
break;
}
formatStringCandidate += stringValue.charAt(i);
}
return formatStringCandidate;
}
private boolean isAsciiReadable(char c) {
return c >= READABLE_ASCII_LOWER_BOUND && c <= READABLE_ASCII_UPPER_BOUND;
}
}

View File

@@ -0,0 +1,332 @@
/* ###
* 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.plugin.core.string.variadic;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import generic.test.AbstractGenericTest;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB;
import ghidra.program.database.data.ProgramDataTypeManager;
import ghidra.program.model.data.*;
public class FormatStringParserTest extends AbstractGenericTest {
private ProgramBuilder builder;
private ProgramDB program;
@Before
public void setUp() throws Exception {
builder = new ProgramBuilder("FormatStringParserTest", ProgramBuilder._TOY, this);
assertNotNull(builder);
program = builder.getProgram();
assertNotNull(program);
}
// Determines whether null is properly returned for
// invalid format Strings. Each String is invalid due to
// either (1) invalid conversion specifier, (2) invalid
// length modifier, or (3) placeholder incorrectly used
@Test
public void testInvalidFormatString() {
runFormatTest("%r", null, true); // r is not a conversion specifier
runFormatTest("%%%lw", null, true); // w is not a conversion specifier
runFormatTest("%#0*.*ld", null, false); // scanf doesn't use flags or period
runFormatTest("%d::%%%ld%z", null, true); // z is not a conversion specifier
runFormatTest("thisisatest%%%#**u", null, true); // two consecutive astericks
runFormatTest("%#0'*rd", null, true); // r is not length modifier
runFormatTest("%%%#'*md", null, true); // m is not length modifier
runFormatTest("%*.**d", null, true); // two consecutive astericks
runFormatTest("%lD", null, true); // D is not a conversion specifier
runFormatTest("%-0+**d", null, false); // scanf doesn't use flags, two consecutive astericks
runFormatTest("%-0+*.*d", null, false); // scanf doesn't use flags or period
runFormatTest("%2.3d", null, false); // scanf doesn't use period
runFormatTest("%*1$d %d\n", null, true); // If one placeholder specifies parameter, the others must too
runFormatTest("%2$d %d\n", null, true); // If one placeholder specifies parameter, the others must too
}
// Tests format strings for scanf which have expected types of pointers instead
// of standard format strings
@Test
public void testScanfFormatString() {
DataType[] expectedTypes1 =
{ program.getDataTypeManager().getPointer(new IntegerDataType()) };
runFormatTest("%d", expectedTypes1, false);
DataType[] expectedTypes2 =
{ program.getDataTypeManager().getPointer(new IntegerDataType()),
program.getDataTypeManager().getPointer(new ShortDataType()) };
runFormatTest("%d%hi", expectedTypes2, false);
DataType[] expectedTypes3 =
{ program.getDataTypeManager().getPointer(new PointerDataType(DataType.VOID)),
program.getDataTypeManager().getPointer(new CharDataType()) };
runFormatTest("%p%*d%s", expectedTypes3, false);
DataType[] expectedTypes4 =
{ program.getDataTypeManager().getPointer(new LongDoubleDataType()),
program.getDataTypeManager().getPointer(new CharDataType()),
program.getDataTypeManager().getPointer(new PointerDataType(DataType.VOID)) };
runFormatTest("!:%12La%*d+=%2s%3p%*20d", expectedTypes4, false);
}
// Tests format strings that are more complex, containing less commonly
// used format patterns and more '%' characters
@Test
public void testComplexFormatString() {
DataType[] expectedTypes1 =
{ program.getDataTypeManager().getPointer(new IntegerDataType()), };
runFormatTest("#12%n\nd2", expectedTypes1, true);
DataType[] expectedTypes2 =
{ program.getDataTypeManager().getPointer(new CharDataType()), new LongDataType() };
runFormatTest("#thisisatest%+-4.12s%#.1lin\nd2", expectedTypes2, true);
DataType[] expectedTypes3 =
{ new PointerDataType(DataType.VOID), new LongDoubleDataType(),
new UnsignedCharDataType() };
runFormatTest("%01.3pp%%%#1.2Lg%%%%%hhXxn2", expectedTypes3, true);
DataType[] expectedTypes4 = { new IntegerDataType(), new IntegerDataType(),
new UnsignedCharDataType(), new IntegerDataType(), new LongDoubleDataType() };
runFormatTest("%0#+-*.*hhX%%%.*La", expectedTypes4, true);
DataType[] expectedTypes5 = { new IntegerDataType(),
program.getDataTypeManager().getPointer(new IntegerDataType()), new IntegerDataType(),
program.getDataTypeManager().getPointer(new WideCharDataType()), new IntegerDataType(),
new LongDoubleDataType() };
runFormatTest("%.*n%*C%%%%%.*LE", expectedTypes5, true);
}
// Tests format strings that use astericks to add another int
// argument to determine field width or precision
@Test
public void testAsterickFormatString() {
DataType[] expectedTypes1 = { new IntegerDataType(), new IntegerDataType() };
runFormatTest("%*d", expectedTypes1, true);
DataType[] expectedTypes2 = { new IntegerDataType(), new LongDataType() };
runFormatTest("%.*ld", expectedTypes2, true);
DataType[] expectedTypes3 =
{ new IntegerDataType(), new IntegerDataType(), new IntegerDataType() };
runFormatTest("%*.*d", expectedTypes3, true);
DataType[] expectedTypes4 =
{ new IntegerDataType(), new IntegerDataType(), new IntegerDataType() };
runFormatTest("*%%%+-*.*d", expectedTypes4, true);
}
// Test simple format strings with different length modifiers
@Test
public void testLengthModifierFormatString() {
DataType[] expectedTypes1 =
{ new LongDataType(), new PointerDataType(LongDataType.dataType) };
runFormatTest("%ld %ln", expectedTypes1, true);
DataType[] expectedTypes2 =
{ new ShortDataType(), new CharDataType(), new PointerDataType(ShortDataType.dataType),
new PointerDataType(CharDataType.dataType) };
runFormatTest("%hd %hhi %hn %hhn", expectedTypes2, true);
DataType[] expectedTypes3 = { new UnsignedShortDataType(), new UnsignedCharDataType() };
runFormatTest("%hx %hhu", expectedTypes3, true);
DataType[] expectedTypes4 =
{ new UnsignedLongDataType(), new LongLongDataType(), new UnsignedLongLongDataType(),
new PointerDataType(LongLongDataType.dataType) };
runFormatTest("%lX %lld %llx %lln", expectedTypes4, true);
DataType[] expectedTypes5 =
{ new LongDoubleDataType(), new LongLongDataType(), new UnsignedLongLongDataType(),
new UnsignedShortDataType(), new UnsignedCharDataType() };
runFormatTest("%LE %lli %llX %hu %hhX", expectedTypes5, true);
}
// Test simple format strings with different special length modifiers
// using generated default typedefs
@Test
public void testSpecialLengthModifierFormatStringDefault() {
DataType[] expectedTypes1 =
{ new TypedefDataType("size_t", UnsignedLongDataType.dataType) };
runFormatTest("%zd", expectedTypes1, true);
DataType[] expectedTypes2 =
{ new TypedefDataType("size_t", UnsignedLongDataType.dataType) };
runFormatTest("%zu", expectedTypes2, true);
DataType[] expectedTypes3 = { new TypedefDataType("ptrdiff_t", LongDataType.dataType) };
runFormatTest("%td", expectedTypes3, true);
DataType[] expectedTypes4 =
{ new TypedefDataType("size_t", UnsignedLongDataType.dataType) };
runFormatTest("%tu", expectedTypes4, true);
DataType[] expectedTypes5 = { new TypedefDataType("intmax_t", LongLongDataType.dataType) };
runFormatTest("%jd", expectedTypes5, true);
DataType[] expectedTypes6 =
{ new TypedefDataType("uintmax_t", UnsignedLongLongDataType.dataType) };
runFormatTest("%ju", expectedTypes6, true);
DataType[] expectedTypes7 =
{ new PointerDataType(new TypedefDataType("intmax_t", LongLongDataType.dataType)) };
runFormatTest("%jn", expectedTypes7, true);
}
// Test simple format strings with different special length modifiers
// using predefined typedefs
@Test
public void testSpecialLengthModifierFormatStringPredefined() {
int txId = program.startTransaction("Add TypeDefs");
try {
ProgramDataTypeManager dtm = program.getDataTypeManager();
DataType sizetDt =
dtm.resolve(new TypedefDataType("size_t", UnsignedLongLongDataType.dataType), null);
DataType ptrdiftDt =
dtm.resolve(new TypedefDataType("ptrdiff_t", LongLongDataType.dataType), null);
DataType intmaxtDt =
dtm.resolve(new TypedefDataType("intmax_t", LongDataType.dataType), null);
DataType uintmaxtDt =
dtm.resolve(new TypedefDataType("uintmax_t", UnsignedLongDataType.dataType), null);
DataType[] expectedTypes1 = { sizetDt };
runFormatTest("%zd", expectedTypes1, true);
DataType[] expectedTypes2 = { sizetDt };
runFormatTest("%zu", expectedTypes2, true);
DataType[] expectedTypes3 = { ptrdiftDt };
runFormatTest("%td", expectedTypes3, true);
DataType[] expectedTypes4 = { sizetDt };
runFormatTest("%tu", expectedTypes4, true);
DataType[] expectedTypes5 = { intmaxtDt };
runFormatTest("%jd", expectedTypes5, true);
DataType[] expectedTypes6 = { uintmaxtDt };
runFormatTest("%ju", expectedTypes6, true);
DataType[] expectedTypes7 = { new PointerDataType(intmaxtDt) };
runFormatTest("%jn", expectedTypes7, true);
}
finally {
program.endTransaction(txId, true);
}
}
// Test simple format Strings with different conversion specifiers
@Test
public void testConversionSpecFormatString() {
DataType[] expectedTypes1 = { new IntegerDataType() };
runFormatTest("%d", expectedTypes1, true);
DataType[] expectedTypes2 =
{ new IntegerDataType(), new IntegerDataType(), new UnsignedIntegerDataType(),
program.getDataTypeManager().getPointer(new CharDataType()) };
runFormatTest("%i %i %x %s", expectedTypes2, true);
DataType[] expectedTypes3 = { new IntegerDataType(), new IntegerDataType(),
program.getDataTypeManager().getPointer(new CharDataType()) };
runFormatTest("%d %d %s", expectedTypes3, true);
DataType[] expectedTypes4 = { new DoubleDataType(), new DoubleDataType(),
new DoubleDataType(), new DoubleDataType(), new UnsignedCharDataType() };
runFormatTest("%e %f %E %G %c", expectedTypes4, true);
DataType[] expectedTypes5 = { new UnsignedIntegerDataType(), new UnsignedIntegerDataType(),
new UnsignedIntegerDataType(), new DoubleDataType(), new DoubleDataType() };
runFormatTest("%u %x %X %e %g", expectedTypes5, true);
DataType[] expectedTypes6 = { new IntegerDataType() };
runFormatTest("%.d", expectedTypes6, true);
}
// Format Strings with field widths indicated by the sequence "*m$"
// where m is an integer that determines the position in the argument
// list of an integer argument
@Test
public void testFormatParameters() {
DataType[] expectedTypes1 = { new IntegerDataType() };
runFormatTest("%1$d", expectedTypes1, true);
DataType[] expectedTypes2 = { new IntegerDataType(), new IntegerDataType() };
runFormatTest("%1$*2$d", expectedTypes2, true);
DataType[] expectedTypes3 = { new IntegerDataType(), new IntegerDataType() };
runFormatTest("%1$.*2$d", expectedTypes3, true);
DataType[] expectedTypes4 = { new IntegerDataType(), new IntegerDataType(),
new IntegerDataType(), new IntegerDataType() };
runFormatTest("%1$d:%2$.*3$d:%4$.*3$d\n", expectedTypes4, true);
DataType[] expectedTypes5 =
{ new UnsignedIntegerDataType(), new UnsignedIntegerDataType() };
runFormatTest("%2$d %2$#x; %1$d %1$#x", expectedTypes5, true);
DataType[] expectedTypes6 =
{ new UnsignedIntegerDataType(), new IntegerDataType(), new IntegerDataType() };
runFormatTest("%2$+#*3$d:%2$#x;0-:'.~%1$0*2$d:!2%1$#x", expectedTypes6, true);
DataType[] expectedTypes7 =
{ new UnsignedLongLongDataType(), new DoubleDataType(), new IntegerDataType() };
runFormatTest("%2$+#*3$f:*;`2!%1$#qu", expectedTypes7, true);
}
private void runFormatTest(String testString, DataType[] expected, boolean runOutputAnalyzer) {
FormatStringParser parser = new FormatStringParser(program);
List<FormatArgument> formatArguments =
parser.convertToFormatArgumentList(testString, runOutputAnalyzer);
DataType[] dataTypes = runOutputAnalyzer ? parser.convertToOutputDataTypes(formatArguments)
: parser.convertToInputDataTypes(formatArguments);
assertEquivalent(dataTypes, expected);
}
private void assertEquivalent(DataType[] actual, DataType[] expected) {
if (expected == null) {
assertNull(actual);
return;
}
assertNotNull("Expected args were not produced", actual);
assertNotNull("Unexpected args were produced", expected);
assertEquals("Expected arg count differs from actual", actual.length, expected.length);
for (int i = 0; i < actual.length; i++) {
assertNotNull("Unexpected null arg returned", actual[i]);
if (!actual[i].isEquivalent(expected[i])) {
fail("Expected: " + expected[i] + ", Actual: " + actual[i]);
}
}
}
}