Add x86 real mode interrupt analyzer plugin.

Right now this only recognises the two interrupts that can trigger a
program end (20h and 21h/4Ch) and end the function they are found in,
but this can be extended to cover the full range of interrupts available
in a common x86 real mode system.
This commit is contained in:
Alessandro Gatti 2020-02-17 19:12:41 +01:00
parent 1c415bc053
commit a36023eb25
2 changed files with 290 additions and 0 deletions

View File

@ -0,0 +1,265 @@
package ghidra.app.plugin.core.analysis;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import ghidra.app.services.AbstractAnalyzer;
import ghidra.app.services.AnalysisPriority;
import ghidra.app.services.AnalyzerType;
import ghidra.app.util.importer.MessageLog;
import ghidra.program.database.function.OverlappingFunctionException;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressOutOfBoundsException;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.listing.InstructionIterator;
import ghidra.program.model.listing.Program;
import ghidra.program.model.scalar.Scalar;
import ghidra.program.util.ContextEvaluatorAdapter;
import ghidra.program.util.SymbolicPropogator;
import ghidra.program.util.VarnodeContext;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class DOSInterruptAnalyzer extends AbstractAnalyzer {
private static final String NAME = "Resolve DOS interrupts";
private static final String DESCRIPTION = "Resolves selected DOS interrupt calls.";
private static final String LANGUAGE = "x86:LE:16:Real Mode";
/** Registered interrupt analyzers container. */
private Map<Integer, InterruptAnalyzer> interruptAnalyzers = new HashMap<>();
public DOSInterruptAnalyzer() {
this(NAME, DESCRIPTION, AnalyzerType.FUNCTION_ANALYZER);
}
public DOSInterruptAnalyzer(String name, String description, AnalyzerType type) {
super(name, description, type);
setDefaultEnablement(true);
setPriority(AnalysisPriority.FUNCTION_ANALYSIS.after());
addInterruptAnalyzer(0x20, new Int20hAnalyzer());
addInterruptAnalyzer(0x21, new Int21hAnalyzer());
}
/**
* Attaches the given interrupt analyzer to a specific interrupt number.
*
* At the moment there is no provision for interrupt analyzers chaining.
*
* @param number the number of the interrupt to attach to.
* @param analyzer the analyzer to attach.
* @return true if the analyzer was successfully attached, false otherwise.
*/
public boolean addInterruptAnalyzer(int number, InterruptAnalyzer analyzer) {
if (number < 0 || number > 255 || analyzer == null || interruptAnalyzers.containsKey(number)) {
return false;
}
interruptAnalyzers.put(number, analyzer);
return true;
}
/**
* Detaches analyzers from a specific interrupt number.
*
* At the moment there is no provision for interrupt analyzer chaining.
*
* @param number the number of the interrupt to detach analyzers from.
* @return true if the analyzer was successfully detached, false otherwise.
*/
public void removeInterruptAnalyzer(int number) {
if (number < 0 || number > 255 || !interruptAnalyzers.containsKey(number)) {
return;
}
interruptAnalyzers.remove(number);
}
@Override
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
throws CancelledException {
for (Function function : program.getFunctionManager().getFunctions(true)) {
AddressSetView addressSetView = function.getBody();
InstructionIterator instructionIterator = program.getListing().getInstructions(addressSetView, true);
boolean shouldContinue = true;
while (instructionIterator.hasNext() && shouldContinue) {
shouldContinue = analyzeInstruction(function, instructionIterator.next(), monitor);
}
}
return true;
}
@Override
public boolean canAnalyze(Program program) {
return program.getLanguage().getLanguageID().getIdAsString().equals(LANGUAGE);
}
/**
* Analyzes a single instruction and if it is an interrupt call invokes a
* secondary analyzer for more in-depth processing.
*
* @param function the function the given instruction belongs to.
* @param instruction the instruction to analyze.
* @param monitor the task monitor to detect cancellation requests.
* @return true whether analysis should continue to the following
* instruction, false otherwise.
* @throws CancelledException if analysis has been cancelled by the user
* in the meantime.
*/
private boolean analyzeInstruction(Function function, Instruction instruction, TaskMonitor monitor)
throws CancelledException {
if (!instruction.getMnemonicString().equals("INT") || instruction.getNumOperands() <= 0) {
return true;
}
int interruptNumber = (int) ((Scalar) instruction.getScalar(0)).getValue();
InterruptAnalyzer analyzer = interruptAnalyzers.getOrDefault(interruptNumber, null);
if (analyzer != null) {
return analyzer.processInstruction(function, instruction, monitor);
}
return true;
}
/**
* Utility class to track assignments to a particular register from a propagator.
*/
static class AssignmentContextEvaluator extends ContextEvaluatorAdapter {
/** The register to track assignments to. */
private Register targetRegister;
/** The last address to track assignments in. */
private Address endAddress;
/** The last seen register value. */
private RegisterValue registerValue;
/**
* Creates the evaluator instance.
*
* @param target the register to track assignments to.
* @param end the last address to track assignments in.
*/
public AssignmentContextEvaluator(Register target, Address end) {
targetRegister = target;
endAddress = end;
}
@Override
public boolean evaluateContext(VarnodeContext context, Instruction instr) {
RegisterValue value = context.getRegisterValue(targetRegister);
if (value != null) {
registerValue = value;
}
return instr.getAddress().equals(endAddress);
}
/**
* Returns the last seen register value.
*
* @return the last seen register value.
*/
public RegisterValue getRegisterValue() {
return registerValue;
}
}
/**
* DOS INT 20h analyzer.
*
* All calls to INT 20h, regardless of the registers state should imply
* the immediate program termination.
*
* If an INT 20h instruction is found, the current function is resized
* to have its end address as the address where the opcode was seen.
*/
static private class Int20hAnalyzer implements InterruptAnalyzer {
@Override
public boolean processInstruction(Function function, Instruction instruction, TaskMonitor monitor)
throws CancelledException {
// Resize the current function.
Address instructionAddress = instruction.getAddress();
if (!function.getBody().getMaxAddress().equals(instructionAddress)) {
try {
function.setBody(new AddressSet(function.getEntryPoint(), instructionAddress));
} catch (AddressOutOfBoundsException | OverlappingFunctionException e) {
e.printStackTrace();
}
}
// No further function analysis is needed.
return false;
}
}
/**
* DOS INT 21h analyzer.
*
* INT 21h is the entry point for most if not all DOS functions, therefore
* analysis is a bit more complex. At this time only function 0x4C is
* handled, which signals the program termination.
*
* If an INT 21h instruction with AH being 0x4C is found, the current
* function is resized to have its end address as the address where the
* opcode was seen.
*/
static private class Int21hAnalyzer implements InterruptAnalyzer {
@Override
public boolean processInstruction(Function function, Instruction instruction, TaskMonitor monitor)
throws CancelledException {
Address instructionAddress = instruction.getAddress();
if (function.getBody().getMaxAddress().equals(instructionAddress)) {
// End of function reached.
return false;
}
// Get the value of AH at this point in the function.
AssignmentContextEvaluator contextEvaluator = new AssignmentContextEvaluator(
instruction.getRegister("AH"), function.getBody().getMaxAddress());
SymbolicPropogator propagator = new SymbolicPropogator(function.getProgram());
propagator.flowConstants(function.getEntryPoint(), null, contextEvaluator, true, monitor);
// If AH was detected being 0x4C, resize the current function.
RegisterValue callCode = contextEvaluator.getRegisterValue();
if (callCode != null) {
BigInteger value = callCode.getUnsignedValue();
if (value != null && value.intValue() == 0x4C) {
try {
function.setBody(new AddressSet(function.getEntryPoint(), instructionAddress));
} catch (AddressOutOfBoundsException | OverlappingFunctionException e) {
e.printStackTrace();
}
// No further function analysis is needed.
return false;
}
}
return true;
}
}
}

View File

@ -0,0 +1,25 @@
package ghidra.app.plugin.core.analysis;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Instruction;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* x86 Interrupt analyzer interface.
*/
public interface InterruptAnalyzer {
/**
* Interrupt handler callback.
*
* @param function the function the given instruction belongs to.
* @param instruction the instruction to analyze.
* @param monitor the task monitor to detect cancellation requests.
* @return true whether analysis should continue to the following
* instruction, false otherwise.
* @throws CancelledException if analysis has been cancelled by the user
* in the meantime.
*/
boolean processInstruction(Function function, Instruction instruction, TaskMonitor monitor) throws CancelledException;
}