From 7c5523362cfb00e796902cf8d8a43a5bdeff76f8 Mon Sep 17 00:00:00 2001 From: ghidra1 Date: Fri, 17 May 2019 19:01:55 -0400 Subject: [PATCH] Emulator - added simplified program emulation API via EmulatorHelper --- .../EmuX86DeobfuscateExampleScript.java | 207 +++++ ...EmuX86GccDeobfuscateHookExampleScript.java | 288 +++++++ .../java/ghidra/app/emulator/Emulator.java | 453 +++++++++++ .../app/emulator/EmulatorConfiguration.java | 48 ++ .../ghidra/app/emulator/EmulatorHelper.java | 725 ++++++++++++++++++ .../app/emulator/FilteredMemoryState.java | 66 ++ .../app/emulator/MemoryAccessFilter.java | 85 ++ .../emulator/memory/CompositeLoadImage.java | 75 ++ .../app/emulator/memory/EmulatorLoadData.java | 31 + .../app/emulator/memory/MemoryImage.java | 68 ++ .../app/emulator/memory/MemoryLoadImage.java | 27 + .../app/emulator/memory/ProgramLoadImage.java | 263 +++++++ .../memory/ProgramMappedLoadImage.java | 53 ++ .../emulator/memory/ProgramMappedMemory.java | 273 +++++++ .../app/emulator/state/DumpMiscState.java | 83 ++ .../state/FilteredMemoryPageOverlay.java | 39 + .../emulator/state/FilteredRegisterBank.java | 34 + .../app/emulator/state/RegisterState.java | 35 + .../java/ghidra/pcode/emulate/Emulate.java | 55 +- .../ExerciseFiles/Emulation/Source/README.txt | 10 + .../Emulation/Source/deobExample.c | 100 +++ .../Emulation/Source/deobHookExample.c | 120 +++ GhidraDocs/certification.manifest | 1 + 23 files changed, 3112 insertions(+), 27 deletions(-) create mode 100644 Ghidra/Features/Base/ghidra_scripts/EmuX86DeobfuscateExampleScript.java create mode 100644 Ghidra/Features/Base/ghidra_scripts/EmuX86GccDeobfuscateHookExampleScript.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/Emulator.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/EmulatorConfiguration.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/EmulatorHelper.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/FilteredMemoryState.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/MemoryAccessFilter.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/CompositeLoadImage.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/EmulatorLoadData.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/MemoryImage.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/MemoryLoadImage.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/ProgramLoadImage.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/ProgramMappedLoadImage.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/ProgramMappedMemory.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/state/DumpMiscState.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/state/FilteredMemoryPageOverlay.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/state/FilteredRegisterBank.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/state/RegisterState.java create mode 100644 GhidraDocs/GhidraClass/ExerciseFiles/Emulation/Source/README.txt create mode 100644 GhidraDocs/GhidraClass/ExerciseFiles/Emulation/Source/deobExample.c create mode 100644 GhidraDocs/GhidraClass/ExerciseFiles/Emulation/Source/deobHookExample.c diff --git a/Ghidra/Features/Base/ghidra_scripts/EmuX86DeobfuscateExampleScript.java b/Ghidra/Features/Base/ghidra_scripts/EmuX86DeobfuscateExampleScript.java new file mode 100644 index 0000000000..099fd5be68 --- /dev/null +++ b/Ghidra/Features/Base/ghidra_scripts/EmuX86DeobfuscateExampleScript.java @@ -0,0 +1,207 @@ +/* ### + * 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. + */ +// An example script demonstrating the ability to emulate a specific portion of code within +// a disassembled program to extract return values of interest (deobfuscated data in this case) +// and generate program listing comments. +// This script emulates the "main" function within the deobExample program +// (see docs/GhidraClass/ExerciseFiles/Emulation/Source) built with gcc for x86-64. +// The program's "data" array contains simple obfuscated data and has a function "deobfuscate" +// which is called for each piece of obfuscated data. The "main" function loops through all +// the data and deobfuscates each one invoking the "use_string" function for each deobfuscated +// data. Breakpoints are placed on the call (and just after the call) +// to the function "deobfuscate" so that the various return values can be recorded with a comment +// placed just after the call. +//@category Examples.Emulation +import ghidra.app.emulator.EmulatorHelper; +import ghidra.app.script.GhidraScript; +import ghidra.app.util.opinion.ElfLoader; +import ghidra.pcode.emulate.EmulateExecutionState; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Instruction; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.*; +import ghidra.util.Msg; +import ghidra.util.exception.NotFoundException; + +public class EmuX86DeobfuscateExampleScript extends GhidraScript { + + private static String PROGRAM_NAME = "deobExample"; + + private EmulatorHelper emuHelper; + + // Important breakpoint locations + private Address deobfuscateCall; + private Address deobfuscateReturn; + + // Function locations + private Address mainFunctionEntry; // start of emulation address + + // Address used as final return location + // A breakpoint will be set here so we can determine when function execution + // has completed. + private static final long CONTROLLED_RETURN_OFFSET = 0; + private Address controlledReturnAddr; // end of emulation address + + // First argument passed to deobfuscate function on last call (used for comment generation) + private long lastDeobfuscateArg0; + + @Override + protected void run() throws Exception { + + String format = + currentProgram.getOptions(Program.PROGRAM_INFO).getString("Executable Format", null); + + if (currentProgram == null || !currentProgram.getName().startsWith(PROGRAM_NAME) || + !"x86:LE:64:default".equals(currentProgram.getLanguageID().toString()) || + !ElfLoader.ELF_NAME.equals(format)) { + + printerr( + "This emulation example script is specifically intended to be executed against the\n" + + PROGRAM_NAME + + " program whose source is contained within the GhidraClass exercise files\n" + + "(see docs/GhidraClass/ExerciseFiles/Emulation/" + PROGRAM_NAME + ".c).\n" + + "This program should be compiled using gcc for x86 64-bit, imported into your project, \n" + + "analyzed and open as the active program before running ths script."); + return; + } + + // Identify function to be emulated + mainFunctionEntry = getSymbolAddress("main"); + + // Obtain entry instruction in order to establish initial processor context + Instruction entryInstr = getInstructionAt(mainFunctionEntry); + if (entryInstr == null) { + printerr("Instruction not found at main entry point: " + mainFunctionEntry); + return; + } + + // Identify important symbol addresses + // NOTE: If the sample is recompiled the following addresses may need to be adjusted + Instruction callSite = getCalledFromInstruction("deobfuscate"); + if (callSite == null) { + printerr("Instruction not found at call site for: deobfuscate"); + return; + } + + deobfuscateCall = callSite.getAddress(); + deobfuscateReturn = callSite.getFallThrough(); // instruction address immediately after deobfuscate call + + // Remove prior pre-comment + setPreComment(deobfuscateReturn, null); + + // Establish emulation helper + emuHelper = new EmulatorHelper(currentProgram); + try { + + // Initialize stack pointer (not used by this example) + long stackOffset = + (entryInstr.getAddress().getAddressSpace().getMaxAddress().getOffset() >>> 1) - + 0x7fff; + emuHelper.writeRegister(emuHelper.getStackPointerRegister(), stackOffset); + + // Setup breakpoints + emuHelper.setBreakpoint(deobfuscateCall); + emuHelper.setBreakpoint(deobfuscateReturn); + + // Set controlled return location so we can identify return from emulated function + controlledReturnAddr = getAddress(CONTROLLED_RETURN_OFFSET); + emuHelper.writeStackValue(0, 8, CONTROLLED_RETURN_OFFSET); + emuHelper.setBreakpoint(controlledReturnAddr); + + Msg.debug(this, "EMU starting at " + mainFunctionEntry); + + // Execution loop until return from function or error occurs + while (!monitor.isCancelled()) { + boolean success = + (emuHelper.getEmulateExecutionState() == EmulateExecutionState.BREAKPOINT) + ? emuHelper.run(monitor) + : emuHelper.run(mainFunctionEntry, entryInstr, monitor); + Address executionAddress = emuHelper.getExecutionAddress(); + if (monitor.isCancelled()) { + println("Emulation cancelled"); + return; + } + if (executionAddress.equals(controlledReturnAddr)) { + println("Returned from function"); + return; + } + if (!success) { + String lastError = emuHelper.getLastError(); + printerr("Emulation Error: " + lastError); + return; + } + processBreakpoint(executionAddress); + } + } + finally { + // cleanup resources and release hold on currentProgram + emuHelper.dispose(); + } + } + + private Address getAddress(long offset) { + return currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(offset); + } + + /** + * Perform processing for the various breakpoints. + * @param addr current execute address where emulation has been suspended + * @throws Exception if an error occurs + */ + private void processBreakpoint(Address addr) throws Exception { + + if (addr.equals(deobfuscateCall)) { + lastDeobfuscateArg0 = emuHelper.readRegister("RDI").longValue(); + } + + else if (addr.equals(deobfuscateReturn)) { + long deobfuscateReturnValue = emuHelper.readRegister("RAX").longValue(); + String str = "deobfuscate(src=0x" + Long.toHexString(lastDeobfuscateArg0) + ") -> \"" + + emuHelper.readNullTerminatedString(getAddress(deobfuscateReturnValue), 32) + "\""; + String comment = getPreComment(deobfuscateReturn); + if (comment == null) { + comment = ""; + } + else { + comment += "\n"; + } + comment += str; + println("Updated pre-comment at " + deobfuscateReturn); + setPreComment(deobfuscateReturn, comment); + } + } + + private Instruction getCalledFromInstruction(String functionName) { + Symbol s = SymbolUtilities.getExpectedLabelOrFunctionSymbol(currentProgram, functionName, + m -> printerr(m)); + for (Reference ref : s.getReferences(monitor)) { + if (ref.getReferenceType().isCall()) { + return currentProgram.getListing().getInstructionAt(ref.getFromAddress()); + } + } + return null; + } + + private Address getSymbolAddress(String symbolName) throws NotFoundException { + Symbol symbol = SymbolUtilities.getLabelOrFunctionSymbol(currentProgram, symbolName, + err -> Msg.error(this, err)); + if (symbol != null) { + return symbol.getAddress(); + } + throw new NotFoundException("Failed to locate label: " + symbolName); + } + +} diff --git a/Ghidra/Features/Base/ghidra_scripts/EmuX86GccDeobfuscateHookExampleScript.java b/Ghidra/Features/Base/ghidra_scripts/EmuX86GccDeobfuscateHookExampleScript.java new file mode 100644 index 0000000000..e11adaebbc --- /dev/null +++ b/Ghidra/Features/Base/ghidra_scripts/EmuX86GccDeobfuscateHookExampleScript.java @@ -0,0 +1,288 @@ +/* ### + * 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. + */ +// An example script demonstrating the ability to emulate a specific portion of code within +// a disassembled program to dump data of interest (deobfuscated data in this case). +// This script emulates the "main" function within the deobHookExampleX86 program +// (see docs/GhidraClass/ExerciseFiles/Emulation/Source) built with gcc for x86-64. +// The program's "data" array contains simple obfuscated data and has a function "deobfuscate" +// which is called for each piece of ofuscated data. The "main" function loops through all +// the data and deobfuscates each one invoking the "use_string" function for each deobfuscated +// data. This script hooks the functions "malloc", "free" and "use_string" where the later +// simply prints the deobfuscated string passed as an argument. +//@category Examples.Emulation +import java.util.HashMap; +import java.util.Map; + +import ghidra.app.emulator.EmulatorHelper; +import ghidra.app.script.GhidraScript; +import ghidra.app.util.opinion.ElfLoader; +import ghidra.pcode.emulate.EmulateExecutionState; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.InsufficientBytesException; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.*; +import ghidra.util.Msg; +import ghidra.util.exception.NotFoundException; + +public class EmuX86GccDeobfuscateHookExampleScript extends GhidraScript { + + private static String PROGRAM_NAME = "deobHookExample"; + + // Heap allocation area + private static final int MALLOC_REGION_SIZE = 0x1000; + + // Address used as final return location + private static final long CONTROLLED_RETURN_OFFSET = 0; + + private EmulatorHelper emuHelper; + private SimpleMallocMgr mallocMgr; + + // Important breakpoint locations for hooking behavior not contained with binary (e.g., dynamic library) + private Address mallocEntry; + private Address freeEntry; + private Address strlenEntry; + private Address useStringEntry; + + // Function locations + private Address mainFunctionEntry; // start of emulation + private Address controlledReturnAddr; // end of emulation + + @Override + protected void run() throws Exception { + + String format = + currentProgram.getOptions(Program.PROGRAM_INFO).getString("Executable Format", null); + + if (currentProgram == null || !currentProgram.getName().startsWith(PROGRAM_NAME) || + !"x86:LE:64:default".equals(currentProgram.getLanguageID().toString()) || + !ElfLoader.ELF_NAME.equals(format)) { + + printerr( + "This emulation example script is specifically intended to be executed against the\n" + + PROGRAM_NAME + + " program whose source is contained within the GhidraClass exercise files\n" + + "(see docs/GhidraClass/ExerciseFiles/Emulation/" + PROGRAM_NAME + ".c).\n" + + "This program should be compiled using gcc for x86 64-bit, imported into your project, \n" + + "analyzed and open as the active program before running ths script."); + return; + } + + // Identify function be emulated + mainFunctionEntry = getSymbolAddress("main"); + useStringEntry = getSymbolAddress("use_string"); + + // Identify important symbol addresses + mallocEntry = getExternalThunkAddress("malloc"); + freeEntry = getExternalThunkAddress("free"); + strlenEntry = getExternalThunkAddress("strlen"); + + // Establish emulation helper + emuHelper = new EmulatorHelper(currentProgram); + try { + // Initialize stack pointer (not used by this example) + long stackOffset = + (mainFunctionEntry.getAddressSpace().getMaxAddress().getOffset() >>> 1) - 0x7fff; + emuHelper.writeRegister(emuHelper.getStackPointerRegister(), stackOffset); + + // Establish simple malloc memory manager with memory region spaced relative to stack pointer + mallocMgr = new SimpleMallocMgr(getAddress(stackOffset - 0x10000), MALLOC_REGION_SIZE); + + // Setup hook breakpoints + emuHelper.setBreakpoint(mallocEntry); + emuHelper.setBreakpoint(freeEntry); + emuHelper.setBreakpoint(strlenEntry); + emuHelper.setBreakpoint(useStringEntry); + + // Set controlled return location so we can identify return from emulated function + controlledReturnAddr = getAddress(CONTROLLED_RETURN_OFFSET); + emuHelper.writeStackValue(0, 8, CONTROLLED_RETURN_OFFSET); + emuHelper.setBreakpoint(controlledReturnAddr); + + // This example directly manipulates the PC register to facilitate hooking + // which must alter the PC during a breakpoint, and optional stepping which does not + // permit an initial address to be specified. + emuHelper.writeRegister(emuHelper.getPCRegister(), mainFunctionEntry.getOffset()); + Msg.debug(this, "EMU starting at " + emuHelper.getExecutionAddress()); + + // Execution loop until return from function or error occurs + while (!monitor.isCancelled()) { + // Use stepping if needed for troubleshooting - although it runs much slower + //boolean success = emuHelper.step(); + boolean success = emuHelper.run(monitor); + Address executionAddress = emuHelper.getExecutionAddress(); + if (monitor.isCancelled()) { + println("Emulation cancelled"); + return; + } + if (executionAddress.equals(controlledReturnAddr)) { + println("Returned from function"); + return; + } + if (!success) { + String lastError = emuHelper.getLastError(); + printerr("Emulation Error: " + lastError); + return; + } + processBreakpoint(executionAddress); + } + } + finally { + // cleanup resources and release hold on currentProgram + emuHelper.dispose(); + } + } + + private Address getAddress(long offset) { + return currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(offset); + } + + /** + * Perform processing for the various hook points where breakpoints have been set. + * @param addr current execute address where emulation has been suspended + * @throws Exception if an error occurs + */ + private void processBreakpoint(Address addr) throws Exception { + + // malloc hook + if (addr.equals(mallocEntry)) { + int size = emuHelper.readRegister("RDI").intValue(); + Address memAddr = mallocMgr.malloc(size); + emuHelper.writeRegister("RAX", memAddr.getOffset()); + } + + // free hook + else if (addr.equals(freeEntry)) { + Address freeAddr = getAddress(emuHelper.readRegister("RDI").longValue()); + mallocMgr.free(freeAddr); + } + + // strlen hook + else if (addr.equals(strlenEntry)) { + Address ptr = getAddress(emuHelper.readRegister("RDI").longValue()); + int len = 0; + while (emuHelper.readMemoryByte(ptr) != 0) { + ++len; + ptr = ptr.next(); + } + emuHelper.writeRegister("RAX", len); + } + + // use_string hook - print string + else if (addr.equals(useStringEntry)) { + Address stringAddr = getAddress(emuHelper.readRegister("RDI").longValue()); + String str = emuHelper.readNullTerminatedString(stringAddr, 32); + println("use_string: " + str); // output string argument to consoles + } + + // unexpected + else { + if (emuHelper.getEmulateExecutionState() != EmulateExecutionState.BREAKPOINT) { + // assume we are stepping and simply return + return; + } + throw new NotFoundException("Unhandled breakpoint at " + addr); + } + + // force early return + long returnOffset = emuHelper.readStackValue(0, 8, false).longValue(); + + emuHelper.writeRegister(emuHelper.getPCRegister(), returnOffset); + } + + /** + * Get the thunk function corresponding to an external function. Such thunks + * should reside within the EXTERNAL block. (Note: this is specific to the ELF import) + * @param symbolName external function name + * @return address of thunk function which corresponds to an external function + * @throws NotFoundException if thunk not found + */ + private Address getExternalThunkAddress(String symbolName) throws NotFoundException { + Symbol externalSymbol = currentProgram.getSymbolTable().getExternalSymbol(symbolName); + if (externalSymbol != null && externalSymbol.getSymbolType() == SymbolType.FUNCTION) { + Function f = (Function) externalSymbol.getObject(); + Address[] thunkAddrs = f.getFunctionThunkAddresses(); + if (thunkAddrs.length == 1) { + return thunkAddrs[0]; + } + } + throw new NotFoundException("Failed to locate label: " + symbolName); + } + + /** + * Get the global namespace symbol address which corresponds to the specified name. + * @param symbolName global symbol name + * @return symbol address + * @throws NotFoundException if symbol not found + */ + private Address getSymbolAddress(String symbolName) throws NotFoundException { + Symbol symbol = SymbolUtilities.getLabelOrFunctionSymbol(currentProgram, symbolName, + err -> Msg.error(this, err)); + if (symbol != null) { + return symbol.getAddress(); + } + throw new NotFoundException("Failed to locate label: " + symbolName); + } + + /** + * SimpleMallocMgr provides a simple malloc memory manager to be used by the + * malloc/free hooked implementations. + */ + private class SimpleMallocMgr { + + private AddressSet allocSet; + private Map mallocMap = new HashMap<>(); + + /** + * SimpleMallocMgr constructor. + * @param rangeStart start of the free malloc region (i.e., Heap) which has been + * deemed a safe + * @param byteSize + * @throws AddressOverflowException + */ + SimpleMallocMgr(Address rangeStart, int byteSize) throws AddressOverflowException { + allocSet = new AddressSet( + new AddressRangeImpl(rangeStart, rangeStart.addNoWrap(byteSize - 1))); + } + + synchronized Address malloc(int byteLength) throws InsufficientBytesException { + if (byteLength <= 0) { + throw new IllegalArgumentException("malloc request for " + byteLength); + } + for (AddressRange range : allocSet.getAddressRanges()) { + if (range.getLength() >= byteLength) { + AddressRange mallocRange = new AddressRangeImpl(range.getMinAddress(), + range.getMinAddress().add(byteLength - 1)); + mallocMap.put(mallocRange.getMinAddress(), mallocRange); + allocSet.delete(mallocRange); + return mallocRange.getMinAddress(); + } + } + throw new InsufficientBytesException( + "SimpleMallocMgr failed to allocate " + byteLength + " bytes"); + } + + synchronized void free(Address mallocRangeAddr) { + AddressRange range = mallocMap.remove(mallocRangeAddr); + if (range == null) { + throw new IllegalArgumentException( + "free request for unallocated block at " + mallocRangeAddr); + } + allocSet.add(range); + } + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/Emulator.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/Emulator.java new file mode 100644 index 0000000000..89fb31b92b --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/Emulator.java @@ -0,0 +1,453 @@ +/* ### + * 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.emulator; + +import java.util.*; + +import ghidra.app.emulator.memory.*; +import ghidra.app.emulator.state.*; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.pcode.emulate.*; +import ghidra.pcode.error.LowlevelError; +import ghidra.pcode.memstate.*; +import ghidra.program.disassemble.Disassembler; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.Instruction; +import ghidra.util.*; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; +import ghidra.util.task.TaskMonitorAdapter; + +public class Emulator { + + private final MemoryFaultHandler faultHandler; + + private SleighLanguage language; + private AddressFactory addrFactory; + + private CompositeLoadImage loadImage = new CompositeLoadImage(); + + private RegisterState mstate; + private MemoryPageBank registerState; + private FilteredMemoryState memState; + private ghidra.pcode.emulate.BreakTableCallBack breakTable; + private Emulate emulator; + + private boolean emuHalt = true; + private boolean isExecuting = false; + + private boolean writeBack = false; + private int pageSize; + + private String pcName; + private long initialPC; + private int instExecuted = 0; + + public Emulator(EmulatorConfiguration cfg) { + + this.faultHandler = cfg.getMemoryFaultHandler(); + + pcName = cfg.getProgramCounterName(); + writeBack = cfg.isWriteBackEnabled(); + pageSize = cfg.getPreferredMemoryPageSize(); + + Language lang = cfg.getLanguage(); + if (!(lang instanceof SleighLanguage)) { + throw new IllegalArgumentException("Invalid configuartion language [" + + lang.getLanguageID() + "]: only Sleigh languages are supported by emulator"); + } + + // TODO: The way this is currently done, we are unable to emulate within overlay spaces + // The addrFactory should be obtained memState which is a reversal + // When a program load image is used the addrFactory should come from the program and + // not the language. Things may also get complex in terms of handling loads/stores and + // flow associated with overlays. + + language = (SleighLanguage) lang; + addrFactory = lang.getAddressFactory(); + + EmulatorLoadData load = cfg.getLoadData(); + loadImage.addProvider(load.getMemoryLoadImage(), load.getView()); + mstate = load.getInitialRegisterState(); + + initMemState(mstate); + + breakTable = new BreakTableCallBack(language); + emulator = new Emulate(language, memState, breakTable); + + try { + setExecuteAddress(initialPC); + } + catch (LowlevelError lle) { + Msg.warn(this, "pc is unmappable -- no execution possible"); + } + } + + private int getValidPageSize(AddressSpace space) { + int ps = pageSize; + long maxOffset = space.getMaxAddress().getOffset(); + if (ps > maxOffset && maxOffset > 0) { + ps = (int) maxOffset; + } + else { + ps -= (ps % space.getAddressableUnitSize()); + } + if (pageSize != ps) { + Msg.warn(this, "Emulator using adjusted page size of " + ps + " bytes for " + + space.getName() + " address space"); + } + return ps; + } + + private void initMemState(RegisterState rstate) { + + memState = new FilteredMemoryState(language); + + for (AddressSpace space : addrFactory.getPhysicalSpaces()) { + if (!space.isLoadedMemorySpace()) { + continue; + } + FilteredMemoryPageOverlay ramBank = getMemoryBank(space, getValidPageSize(space)); + memState.setMemoryBank(ramBank); + } + + AddressSpace registerSpace = addrFactory.getRegisterSpace(); + registerState = new FilteredRegisterBank(registerSpace, pageSize, rstate, language, + writeBack, faultHandler); + + memState.setMemoryBank(registerState); + + initRegisters(false); + } + + public MemoryState cloneMemory() { + MemoryState newMemState = new FilteredMemoryState(language); + + for (AddressSpace space : addrFactory.getPhysicalSpaces()) { + if (!space.isLoadedMemorySpace()) { + continue; + } + FilteredMemoryPageOverlay ramBank = getMemoryBank(space, getValidPageSize(space)); + newMemState.setMemoryBank(ramBank); + } + return newMemState; + } + + public FilteredMemoryPageOverlay getMemoryBank(AddressSpace space, int ps) { + MemoryImage image = + new MemoryImage(space, language.isBigEndian(), ps, loadImage, faultHandler); + return new FilteredMemoryPageOverlay(space, image, writeBack); + } + + /** + * Initialize memory state using the initial register state. If restore is true, + * only those registers within the register space which have been modified will + * be reported and restored to their initial state. + * @param restore if true restore modified registers within the register space only + */ + private void initRegisters(boolean restore) { + DataConverter conv = + language.isBigEndian() ? new BigEndianDataConverter() : new LittleEndianDataConverter(); + Set keys = mstate.getKeys(); + for (String key : keys) { + List vals = mstate.getVals(key); + List initiailizedVals = mstate.isInitialized(key); + for (int i = 0; i < vals.size(); i++) { + String useKey = ""; + if (key.equals("GDTR") || key.equals("IDTR") || key.equals("LDTR")) { + if (i == 0) + useKey = key + "_Limit"; + if (i == 1) + useKey = key + "_Address"; + } + else if (key.equals("S.base")) { + Integer lval = conv.getInt(vals.get(i)); + if (lval != 0 && i < vals.size() - 1) { + useKey = "FS_OFFSET"; // Colossal hack + memState.setValue("FS", (i + 2) * 0x8); + } + } + else { + useKey = (vals.size() > 1) ? key + i : key; + } + Register register = language.getRegister(useKey); + if (register == null) { + useKey = useKey.toUpperCase(); + register = language.getRegister(useKey); + } + if (register != null) { + if (restore && !register.getAddress().isRegisterAddress()) { + continue; // only restore registers within register space + } + byte[] valBytes = vals.get(i); + boolean initializedValue = initiailizedVals.get(i); + + Address regAddr = register.getAddress(); + + if (restore) { + byte[] curVal = new byte[valBytes.length]; + memState.getChunk(curVal, regAddr.getAddressSpace(), regAddr.getOffset(), + register.getMinimumByteSize(), false); + if (Arrays.equals(curVal, valBytes)) { + continue; + } + System.out.println( + "resetRegisters : " + useKey + "=" + dumpBytesAsSingleValue(valBytes) + + "->" + dumpBytesAsSingleValue(curVal)); + } + + memState.setChunk(valBytes, regAddr.getAddressSpace(), regAddr.getOffset(), + register.getMinimumByteSize()); + + if (!initializedValue) { + memState.setInitialized(false, regAddr.getAddressSpace(), + regAddr.getOffset(), register.getMinimumByteSize()); + } + + if (register.isProgramCounter() || + register.getName().equalsIgnoreCase(pcName)) { + initialPC = conv.getValue(valBytes, valBytes.length); + } + } + } + } + } + + private String dumpBytesAsSingleValue(byte[] bytes) { + StringBuffer buf = new StringBuffer("0x"); + if (language.isBigEndian()) { + for (int i = 0; i < bytes.length; i++) { + String byteStr = Integer.toHexString(bytes[i] & 0xff); + if (byteStr.length() == 1) { + buf.append('0'); + } + buf.append(byteStr); + } + } + else { + for (int i = bytes.length - 1; i >= 0; i--) { + String byteStr = Integer.toHexString(bytes[i] & 0xff); + if (byteStr.length() == 1) { + buf.append('0'); + } + buf.append(byteStr); + } + } + return buf.toString(); + } + + public void dispose() { + emuHalt = true; + emulator.dispose(); + if (writeBack) { + initRegisters(true); + mstate.dispose(); + } + loadImage.dispose(); + } + + public Address genAddress(String addr) { + return addrFactory.getDefaultAddressSpace().getAddress(NumericUtilities.parseHexLong(addr)); + } + + public long getPC() { + return memState.getValue(pcName); + } + + public String getPCRegisterName() { + return pcName; + } + + public MemoryState getMemState() { + return memState; + } + + public FilteredMemoryState getFilteredMemState() { + return memState; + } + + public void addMemoryAccessFilter(MemoryAccessFilter filter) { + filter.addFilter(this); + } + + public BreakTableCallBack getBreakTable() { + return breakTable; + } + + public void setExecuteAddress(long addressableWordOffset) { + AddressSpace space = addrFactory.getDefaultAddressSpace(); + Address address = space.getTruncatedAddress(addressableWordOffset, true); + emulator.setExecuteAddress(address); + } + + public Address getExecuteAddress() { + return emulator.getExecuteAddress(); + } + + public Address getLastExecuteAddress() { + return emulator.getLastExecuteAddress(); + } + + public Set getDefaultContext() { + return mstate.getKeys(); + } + + public void setHalt(boolean halt) { + emuHalt = halt; + } + + public boolean getHalt() { + return emuHalt; + } + + public void executeInstruction(boolean stopAtBreakpoint, TaskMonitor monitor) + throws CancelledException, LowlevelError, InstructionDecodeException { + isExecuting = true; + try { + emulator.executeInstruction(stopAtBreakpoint, monitor); + instExecuted++; + } + finally { + isExecuting = false; + } + } + + /** + * @return true if halted at a breakpoint + */ + public boolean isAtBreakpoint() { + return getHalt() && emulator.getExecutionState() == EmulateExecutionState.BREAKPOINT; + } + + /** + * @return emulator execution state. This can be useful within a memory fault handler to + * determine if a memory read was associated with instruction parsing (i.e., PCODE_EMIT) or + * normal an actual emulated read (i.e., EXECUTE). + */ + public EmulateExecutionState getEmulateExecutionState() { + return emulator.getExecutionState(); + } + + /** + * @return true if emulator is busy executing an instruction + */ + public boolean isExecuting() { + return isExecuting; + } + + public SleighLanguage getLanguage() { + return language; + } + + /** + * Disassemble from the current execute address + * @param count number of contiguous instructions to disassemble + * @return list of instructions + */ + public List disassemble(Integer count) { + if (!emuHalt || isExecuting) { + throw new IllegalStateException("disassembly not allowed while emulator is executing"); + } + + // TODO: This can provide bad disassembly if reliant on future context state (e.g., end of loop) + + List disassembly = new ArrayList<>(); + + EmulateDisassemblerContext disassemblerContext = emulator.getNewDisassemblerContext(); + Address addr = getExecuteAddress(); + EmulateMemoryStateBuffer memBuffer = new EmulateMemoryStateBuffer(memState, addr); + + Disassembler disassembler = Disassembler.getDisassembler(language, addrFactory, + TaskMonitorAdapter.DUMMY_MONITOR, null); + + boolean stopOnError = false; + + while (count > 0 && !stopOnError) { + memBuffer.setAddress(addr); + disassemblerContext.setCurrentAddress(addr); + + InstructionBlock block = disassembler.pseudoDisassembleBlock(memBuffer, + disassemblerContext.getCurrentContextRegisterValue(), count); + + if (block.hasInstructionError() && count > block.getInstructionCount()) { + InstructionError instructionError = block.getInstructionConflict(); + Msg.error(this, + "Target disassembler error at " + instructionError.getConflictAddress() + ": " + + instructionError.getConflictMessage()); + stopOnError = true; + } + + Instruction lastInstr = null; + Iterator iterator = block.iterator(); + while (iterator.hasNext() && count != 0) { + Instruction instr = iterator.next(); + disassembly.add(instr.getAddressString(false, true) + " " + instr.toString()); + lastInstr = instr; + --count; + } + + try { + addr = lastInstr.getAddress().addNoWrap(lastInstr.getLength()); + } + catch (Exception e) { + count = 0; + } + } + + return disassembly; + } + + public int getTickCount() { + return instExecuted; + } + + /** + * Returns the current context register value. The context value returned reflects + * its state when the previously executed instruction was + * parsed/executed. The context value returned will feed into the next + * instruction to be parsed with its non-flowing bits cleared and + * any future context state merged in. + */ + public RegisterValue getContextRegisterValue() { + return emulator.getContextRegisterValue(); + } + + /** + * Sets the context register value at the current execute address. + * The Emulator should not be running when this method is invoked. + * Only flowing context bits should be set, as non-flowing bits + * will be cleared prior to parsing on instruction. In addition, + * any future context state set by the pcode emitter will + * take precedence over context set using this method. This method + * is primarily intended to be used to establish the initial + * context state. + * @param regValue + */ + public void setContextRegisterValue(RegisterValue regValue) { + emulator.setContextRegisterValue(regValue); + } + + /** + * Add memory load image provider + * @param provider memory load image provider + * @param view memory region which corresponds to provider + */ + public void addProvider(MemoryLoadImage provider, AddressSetView view) { + loadImage.addProvider(provider, view); + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/EmulatorConfiguration.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/EmulatorConfiguration.java new file mode 100644 index 0000000000..5e4f53dad6 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/EmulatorConfiguration.java @@ -0,0 +1,48 @@ +/* ### + * 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.emulator; + +import ghidra.app.emulator.memory.EmulatorLoadData; +import ghidra.pcode.memstate.MemoryFaultHandler; +import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.Register; + +public interface EmulatorConfiguration { + + Language getLanguage(); + + EmulatorLoadData getLoadData(); + + MemoryFaultHandler getMemoryFaultHandler(); + + default boolean isWriteBackEnabled() { + return false; + } + + default int getPreferredMemoryPageSize() { + return 0x1000; + } + + default String getProgramCounterName() { + Language lang = getLanguage(); + Register pcReg = lang.getProgramCounter(); + if (pcReg == null) { + throw new IllegalStateException( + "Language has not defined Program Counter Register: " + lang.getLanguageID()); + } + return pcReg.getName(); + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/EmulatorHelper.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/EmulatorHelper.java new file mode 100644 index 0000000000..d95396e973 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/EmulatorHelper.java @@ -0,0 +1,725 @@ +/* ### + * 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.emulator; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; + +import ghidra.app.emulator.memory.*; +import ghidra.app.emulator.state.DumpMiscState; +import ghidra.app.emulator.state.RegisterState; +import ghidra.framework.store.LockException; +import ghidra.pcode.emulate.BreakCallBack; +import ghidra.pcode.emulate.EmulateExecutionState; +import ghidra.pcode.memstate.MemoryFaultHandler; +import ghidra.pcode.memstate.MemoryState; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.program.model.mem.MemoryConflictException; +import ghidra.util.*; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.DuplicateNameException; +import ghidra.util.task.TaskMonitor; + +public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration { + + private final Program program; + private final Emulator emulator; + + private Register stackPtrReg; + private AddressSpace stackMemorySpace; + + private String lastError; + private MemoryWriteTracker memoryWriteTracker; + + private MemoryFaultHandler faultHandler; + + private BreakCallBack addressBreak = new BreakCallBack() { + @Override + public boolean addressCallback(Address addr) { + emulator.setHalt(true); + return true; + } + }; + + public EmulatorHelper(Program program) { + + this.program = program; + + stackPtrReg = program.getCompilerSpec().getStackPointer(); + stackMemorySpace = program.getCompilerSpec().getStackBaseSpace(); + + emulator = new Emulator(this); + } + + public void dispose() { + emulator.dispose(); + if (memoryWriteTracker != null) { + memoryWriteTracker.dispose(); + memoryWriteTracker = null; + } + } + + @Override + public MemoryFaultHandler getMemoryFaultHandler() { + return this; + } + + @Override + public EmulatorLoadData getLoadData() { + + return new EmulatorLoadData() { + + @Override + public MemoryLoadImage getMemoryLoadImage() { + return new ProgramMappedLoadImage( + new ProgramMappedMemory(program, EmulatorHelper.this)); + } + + @Override + public RegisterState getInitialRegisterState() { + return new DumpMiscState(getLanguage()); + } + }; + } + + @Override + public Language getLanguage() { + return program.getLanguage(); + } + + public Program getProgram() { + return program; + } + + /** + * Get Program Counter (PC) register defined by applicable processor specification + * @return Program Counter register + */ + public Register getPCRegister() { + return program.getLanguage().getProgramCounter(); + } + + /** + * Get Stack Pointer register defined by applicable compiler specification + * @return Stack Pointer register + */ + public Register getStackPointerRegister() { + return stackPtrReg; + } + + /** + * Provides ability to install a low-level memory fault handler. + * The handler methods should generally return 'false' to allow + * the default handler to generate the appropriate target error. + * Within the fault handler, the EmulateExecutionState can be used + * to distinguish the pcode-emit state and the actual execution state + * since an attempt to execute an instruction at an uninitialized + * memory location will cause an uninitializedRead during the PCODE_EMIT + * state. + * @param handler memory fault handler. + */ + public void setMemoryFaultHandler(MemoryFaultHandler handler) { + faultHandler = handler; + } + + /** + * @return the low-level emulator execution state + */ + public EmulateExecutionState getEmulateExecutionState() { + return emulator.getEmulateExecutionState(); + } + + private Register getRegister(String regName) throws IllegalArgumentException { + Register reg = program.getRegister(regName); + if (reg == null) { + throw new IllegalArgumentException("Undefined register: " + regName); + } + return reg; + } + + public BigInteger readRegister(Register reg) { + if (reg.isProcessorContext()) { + RegisterValue contextRegisterValue = emulator.getContextRegisterValue(); + if (!reg.equals(contextRegisterValue.getRegister())) { + contextRegisterValue = contextRegisterValue.getRegisterValue(reg); + } + return contextRegisterValue.getSignedValueIgnoreMask(); + } + if (reg.getName().equals(emulator.getPCRegisterName())) { + return BigInteger.valueOf(emulator.getPC()); + } + return emulator.getMemState().getBigInteger(reg); + } + + public BigInteger readRegister(String regName) { + Register reg = getRegister(regName); + if (reg == null) { + throw new IllegalArgumentException("Undefined register: " + regName); + } + return readRegister(reg); + } + + public void writeRegister(Register reg, long value) { + writeRegister(reg, BigInteger.valueOf(value)); + } + + public void writeRegister(String regName, long value) { + writeRegister(regName, BigInteger.valueOf(value)); + } + + public void writeRegister(Register reg, BigInteger value) { + if (reg.isProcessorContext()) { + RegisterValue contextRegisterValue = new RegisterValue(reg, value); + RegisterValue existingRegisterValue = emulator.getContextRegisterValue(); + if (!reg.equals(existingRegisterValue.getRegister())) { + contextRegisterValue = existingRegisterValue.combineValues(contextRegisterValue); + } + emulator.setContextRegisterValue(contextRegisterValue); + return; + } + emulator.getMemState().setValue(reg, value); + if (reg.getName().equals(emulator.getPCRegisterName())) { + emulator.setExecuteAddress(value.longValue()); + } + } + + public void writeRegister(String regName, BigInteger value) { + Register reg = getRegister(regName); + if (reg == null) { + throw new IllegalArgumentException("Undefined register: " + regName); + } + writeRegister(reg, value); + } + + /** + * Read string from memory state. + * @param addr memory address + * @param maxLength limit string read to this length. If return string is + * truncated, "..." will be appended. + * @return string read from memory state + */ + public String readNullTerminatedString(Address addr, int maxLength) { + int len = 0; + byte[] bytes = new byte[maxLength]; + byte b = 0; + while (len < maxLength && (b = readMemoryByte(addr)) != 0) { + bytes[len++] = b; + addr = addr.next(); + } + String str = new String(bytes, 0, len); + if (b != 0) { + str += "..."; // indicate string truncation + } + return str; + } + + public byte readMemoryByte(Address addr) { + byte[] value = readMemory(addr, 1); + return value[0]; + } + + public byte[] readMemory(Address addr, int length) { + byte[] res = new byte[length]; + int len = emulator.getMemState().getChunk(res, addr.getAddressSpace(), addr.getOffset(), + length, false); + if (len == 0) { + Msg.error(this, "Failed to read memory from Emulator at: " + addr); + return null; + } + else if (len < length) { + Msg.error(this, + "Only " + len + " of " + length + " bytes read memory from Emulator at: " + addr); + } + return res; + } + + public void writeMemory(Address addr, byte[] bytes) { + emulator.getMemState().setChunk(bytes, addr.getAddressSpace(), addr.getOffset(), + bytes.length); + } + + public void writeMemoryValue(Address addr, int size, long value) { + emulator.getMemState().setValue(addr.getAddressSpace(), addr.getOffset(), size, value); + } + + /** + * Read a stack value from the memory state. + * @param relativeOffset offset relative to current stack pointer + * @param size data size in bytes + * @param signed true if value read is signed, false if unsigned + * @return value + * @throws Exception error occurs reading stack pointer + */ + public BigInteger readStackValue(int relativeOffset, int size, boolean signed) + throws Exception { + long offset = readRegister(stackPtrReg).longValue() + relativeOffset; + byte[] bytes = readMemory(stackMemorySpace.getAddress(offset), size); + if (program.getMemory().isBigEndian()) { + return BigEndianDataConverter.INSTANCE.getBigInteger(bytes, size, signed); + } + return LittleEndianDataConverter.INSTANCE.getBigInteger(bytes, size, signed); + } + + /** + * Write a value onto the stack + * @param relativeOffset offset relative to current stack pointer + * @param size data size in bytes + * @param value + * @throws Exception error occurs reading stack pointer + */ + public void writeStackValue(int relativeOffset, int size, long value) throws Exception { + long offset = readRegister(stackPtrReg).longValue() + relativeOffset; + byte[] bytes = new byte[size]; + if (program.getMemory().isBigEndian()) { + BigEndianDataConverter.INSTANCE.getBytes(value, bytes); + } + else { + LittleEndianDataConverter.INSTANCE.getBytes(value, bytes); + } + writeMemory(stackMemorySpace.getAddress(offset), bytes); + } + + /** + * Write a value onto the stack + * @param relativeOffset offset relative to current stack pointer + * @param size data size in bytes + * @param value + * @throws Exception error occurs reading stack pointer + */ + public void writeStackValue(int relativeOffset, int size, BigInteger value) throws Exception { + // TODO: verify that sign byte is not added to size of bytes + long offset = readRegister(stackPtrReg).longValue() + relativeOffset; + byte[] bytes; + if (program.getMemory().isBigEndian()) { + bytes = BigEndianDataConverter.INSTANCE.getBytes(value, size); + } + else { + bytes = LittleEndianDataConverter.INSTANCE.getBytes(value, size); + } + writeMemory(stackMemorySpace.getAddress(offset), bytes); + } + + /** + * Establish breakpoint + * @param address memory address for new breakpoint + */ + public void setBreakpoint(Address addr) { + emulator.getBreakTable().registerAddressCallback(addr, addressBreak); + } + + /** + * Clear breakpoint + * @param address memory address for breakpoint to be cleared + */ + public void clearBreakpoint(Address addr) { + emulator.getBreakTable().unregisterAddressCallback(addr); + } + + /** + * Set current context register value. + * Keep in mind that any non-flowing context values will be stripped. + * @param ctxRegValue + */ + public void setContextRegister(RegisterValue ctxRegValue) { + emulator.setContextRegisterValue(ctxRegValue); + } + + /** + * Set current context register value. + * Keep in mind that any non-flowing context values will be stripped. + * @param ctxReg context register + * @param value context value + */ + public void setContextRegister(Register ctxReg, BigInteger value) { + emulator.setContextRegisterValue(new RegisterValue(ctxReg, value)); + } + + /** + * Get the current context register value + * @return context register value or null if not set or unknown + */ + public RegisterValue getContextRegister() { + return emulator.getContextRegisterValue(); + } + + /** + * Register callback for language defined pcodeop (call other). + * WARNING! Using this method may circumvent the default CALLOTHER emulation support + * when supplied by the Processor module. + * @param pcodeOpName + * @param callback + */ + public void registerCallOtherCallback(String pcodeOpName, BreakCallBack callback) { + emulator.getBreakTable().registerPcodeCallback(pcodeOpName, callback); + } + + /** + * Register default callback for language defined pcodeops (call other). + * WARNING! Using this method may circumvent the default CALLOTHER emulation support + * when supplied by the Processor module. + * @param pcodeOpName + * @param callback + */ + public void registerDefaultCallOtherCallback(BreakCallBack callback) { + emulator.getBreakTable().registerPcodeCallback("*", callback); + } + + /** + * Unregister callback for language defined pcodeop (call other). + * @param pcodeOpName + */ + public void unregisterCallOtherCallback(String pcodeOpName) { + emulator.getBreakTable().unregisterPcodeCallback(pcodeOpName); + } + + /** + * Unregister default callback for language defined pcodeops (call other). + * WARNING! Using this method may circumvent the default CALLOTHER emulation support + * when supplied by the Processor module. + */ + public void unregisterDefaultCallOtherCallback() { + emulator.getBreakTable().unregisterPcodeCallback("*"); + } + + /** + * Get current execution address + * @return current execution address + */ + public Address getExecutionAddress() { + return emulator.getExecuteAddress(); + } + + /** + * Start execution at the specified address using the initial context specified. + * Method will block until execution stops. This method will initialize context + * register based upon the program stored context if not already done. In addition, + * both general register value and the context register may be further modified + * via the context parameter if specified. + * @param addr initial program address + * @param context optional context settings which override current program context + * @param monitor + * @return true if execution completes without error (i.e., is at breakpoint) + * @throws CancelledException if execution cancelled via monitor + */ + public boolean run(Address addr, ProcessorContext context, TaskMonitor monitor) + throws CancelledException { + + if (emulator.isExecuting()) { + throw new IllegalStateException("Emulator is already running"); + } + + // Initialize context + ProgramContext programContext = program.getProgramContext(); + Register baseContextRegister = programContext.getBaseContextRegister(); + RegisterValue contextRegValue = null; + boolean mustSetContextReg = false; + + if (baseContextRegister != null) { + contextRegValue = getContextRegister(); + if (contextRegValue == null) { + contextRegValue = programContext.getRegisterValue(baseContextRegister, addr); + mustSetContextReg = (contextRegValue != null); + } + } + + if (context != null) { + for (Register reg : context.getRegisters()) { + // skip non-base registers + if (reg.isBaseRegister() && context.hasValue(reg)) { + RegisterValue registerValue = context.getRegisterValue(reg); + if (reg.isProcessorContext()) { + if (contextRegValue != null) { + contextRegValue = contextRegValue.combineValues(registerValue); + } + else { + contextRegValue = registerValue; + } + mustSetContextReg = true; + } + else { + BigInteger value = registerValue.getUnsignedValueIgnoreMask(); + writeRegister(reg, value); + } + } + } + } + + long pcValue = addr.getAddressableWordOffset(); + emulator.setExecuteAddress(pcValue); + + if (mustSetContextReg) { + setContextRegister(contextRegValue); + } + + continueExecution(monitor); + return emulator.isAtBreakpoint(); + } + + /** + * Continue execution from the current execution address. + * No adjustment will be made to the context beyond the normal + * context flow behavior defined by the language. + * Method will block until execution stops. + * @param monitor + * @return true if execution completes without error (i.e., is at breakpoint) + * @throws CancelledException if execution cancelled via monitor + */ + public synchronized boolean run(TaskMonitor monitor) throws CancelledException { + + if (emulator.isExecuting()) { + throw new IllegalStateException("Emulator is already running"); + } + continueExecution(monitor); + return emulator.isAtBreakpoint(); + } + + /** + * Continue execution and block until either a breakpoint hits or error occurs. + * @throws CancelledException if execution was cancelled + */ + private void continueExecution(TaskMonitor monitor) throws CancelledException { + emulator.setHalt(false); + do { + executeInstruction(true, monitor); + } + while (!emulator.getHalt()); + } + + /** + * Execute instruction at current address + * @param stopAtBreakpoint if true and breakpoint hits at current execution address + * execution will halt without executing instruction. + * @throws CancelledException if execution was cancelled + */ + private void executeInstruction(boolean stopAtBreakpoint, TaskMonitor monitor) + throws CancelledException { + + lastError = null; + try { + if (emulator.getLastExecuteAddress() == null) { + setProcessorContext(); + } + emulator.executeInstruction(stopAtBreakpoint, monitor); + } + catch (Throwable t) { +// TODO: need to enumerate errors better !! + lastError = t.getMessage(); + emulator.setHalt(true); // force execution to stop + if (t instanceof CancelledException) { + throw (CancelledException) t; + } + } + } + + /** + * Used when the emulator has had the execution address changed to + * make sure it has a context consistent with the program context + * if there is one. + */ + private void setProcessorContext() { + // this assumes you have set the emulation address + // the emu will have cleared the context for the new address + RegisterValue contextRegisterValue = emulator.getContextRegisterValue(); + if (contextRegisterValue != null) { + return; + } + + Address executeAddress = emulator.getExecuteAddress(); + Instruction instructionAt = program.getListing().getInstructionAt(executeAddress); + if (instructionAt != null) { + RegisterValue disassemblyContext = + instructionAt.getRegisterValue(instructionAt.getBaseContextRegister()); + emulator.setContextRegisterValue(disassemblyContext); + } + } + + /** + * @return last error message associated with execution failure + */ + public String getLastError() { + return lastError; + } + + /** + * Step execution one instruction which may consist of multiple + * pcode operations. No adjustment will be made to the context beyond the normal + * context flow behavior defined by the language. + * Method will block until execution stops. + * @return true if execution completes without error + * @throws CancelledException if execution cancelled via monitor + */ + public synchronized boolean step(TaskMonitor monitor) throws CancelledException { + executeInstruction(true, monitor); + return lastError == null; + } + + /** + * Create a new initialized memory block using the current emulator memory state + * @param name block name + * @param start start address of the block + * @param length the size of the block + * @param overlay if true, the block will be created as an OVERLAY which means that a new + * overlay address space will be created and the block will have a starting address at the same + * offset as the given start address parameter, but in the new address space. + * @param monitor + * @return new memory block + * @throws LockException if exclusive lock not in place (see haveLock()) + * @throws MemoryConflictException if the new block overlaps with a + * previous block + * @throws AddressOverflowException if the start is beyond the + * address space + * @throws CancelledException user cancelled operation + * @throws DuplicateNameException + */ + public MemoryBlock createMemoryBlockFromMemoryState(String name, final Address start, + final int length, boolean overlay, TaskMonitor monitor) throws MemoryConflictException, + AddressOverflowException, CancelledException, LockException, DuplicateNameException { + + if (emulator.isExecuting()) { + throw new IllegalStateException("Emulator must be paused to access memory state"); + } + + InputStream memStateStream = new InputStream() { + + private MemoryState memState = emulator.getMemState(); + + private byte[] buffer = new byte[1024]; + private long nextBufferOffset = start.getOffset(); + private int bytesRemaining = length; + private int bufferPos = buffer.length; + + @Override + public int read() throws IOException { + + if (bytesRemaining <= 0) { + return -1; + } + + if (bufferPos == buffer.length) { + int size = Math.min(buffer.length, bytesRemaining); + memState.getChunk(buffer, start.getAddressSpace(), nextBufferOffset, size, + false); + nextBufferOffset += buffer.length; + bufferPos = 0; + } + + byte b = buffer[bufferPos++]; + --bytesRemaining; + return b; + } + }; + + MemoryBlock block; + boolean success = false; + int txId = program.startTransaction("Create Memory Block"); + try { + block = program.getMemory().createInitializedBlock(name, start, memStateStream, length, + monitor, overlay); + success = true; + } + finally { + program.endTransaction(txId, success); + } + return block; + } + + /** + * Enable/Disable tracking of memory writes in the form of an + * address set. + * @param enable + */ + public void enableMemoryWriteTracking(boolean enable) { + + if (!enable) { + if (memoryWriteTracker != null) { + memoryWriteTracker.dispose(); + memoryWriteTracker = null; + } + return; + } + + memoryWriteTracker = new MemoryWriteTracker(); + emulator.addMemoryAccessFilter(memoryWriteTracker); + } + + /** + * @return address set of memory locations written by the emulator + * if memory write tracking is enabled, otherwise null is returned. + * The address set returned will continue to be updated unless + * memory write tracking becomes disabled. + */ + public AddressSetView getTrackedMemoryWriteSet() { + if (memoryWriteTracker != null) { + return memoryWriteTracker.writeSet; + } + return null; + } + + private class MemoryWriteTracker extends MemoryAccessFilter { + + AddressSet writeSet = new AddressSet(); + + @Override + protected void processRead(AddressSpace spc, long off, int size, byte[] values) { + // do nothing + } + + @Override + protected void processWrite(AddressSpace spc, long off, int size, byte[] values) { + AddressRange range = + new AddressRangeImpl(spc.getAddress(off), spc.getAddress(off + size - 1)); + writeSet.add(range); + } + } + + @Override + public boolean unknownAddress(Address address, boolean write) { + if (faultHandler != null) { + return faultHandler.unknownAddress(address, write); + } + Address pc = emulator.getExecuteAddress(); + String access = write ? "written" : "read"; + Msg.warn(this, "Unknown address " + access + " at " + pc + ": " + address); + return false; + } + + @Override + public boolean uninitializedRead(Address address, int size, byte[] buf, int bufOffset) { + if (faultHandler != null) { + return faultHandler.uninitializedRead(address, size, buf, bufOffset); + } + if (emulator.getEmulateExecutionState() == EmulateExecutionState.INSTRUCTION_DECODE) { + return false; + } + Address pc = emulator.getExecuteAddress(); + Register reg = program.getRegister(address, size); + if (reg != null) { + Msg.warn(this, "Uninitialized register read at " + pc + ": " + reg); + return true; + } + Msg.warn(this, + "Uninitialized memory read at " + pc + ": " + address.toString(true) + ":" + size); + return true; + } + + public Emulator getEmulator() { + return emulator; + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/FilteredMemoryState.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/FilteredMemoryState.java new file mode 100644 index 0000000000..8629ec11fb --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/FilteredMemoryState.java @@ -0,0 +1,66 @@ +/* ### + * 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.emulator; + +import ghidra.pcode.memstate.MemoryState; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Language; + +class FilteredMemoryState extends MemoryState { + + private MemoryAccessFilter filter; + private boolean filterEnabled = true; // used to prevent filtering filter queries + + FilteredMemoryState(Language lang) { + super(lang); + } + + @Override + public int getChunk(byte[] res, AddressSpace spc, long off, int size, + boolean stopOnUnintialized) { + int readLen = super.getChunk(res, spc, off, size, stopOnUnintialized); + if (filterEnabled && filter != null) { + filterEnabled = false; + try { + filter.filterRead(spc, off, readLen, res); + } + finally { + filterEnabled = true; + } + } + return readLen; + } + + @Override + public void setChunk(byte[] res, AddressSpace spc, long off, int size) { + super.setChunk(res, spc, off, size); + if (filterEnabled && filter != null) { + filterEnabled = false; + try { + filter.filterWrite(spc, off, size, res); + } + finally { + filterEnabled = true; + } + } + } + + MemoryAccessFilter setFilter(MemoryAccessFilter filter) { + MemoryAccessFilter oldFilter = this.filter; + this.filter = filter; + return oldFilter; + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/MemoryAccessFilter.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/MemoryAccessFilter.java new file mode 100644 index 0000000000..5bf2dbb179 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/MemoryAccessFilter.java @@ -0,0 +1,85 @@ +/* ### + * 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.emulator; + +import ghidra.program.model.address.AddressSpace; + +public abstract class MemoryAccessFilter { + + private MemoryAccessFilter prevFilter; + private MemoryAccessFilter nextFilter; + + protected Emulator emu; + + private boolean filterOnExecutionOnly = true; + + final void filterRead(AddressSpace spc, long off, int size, byte [] values) { + if (filterOnExecutionOnly() && !emu.isExecuting()) return; // do not filter idle queries + processRead(spc, off, size, values); + if (nextFilter != null) { + nextFilter.filterRead(spc, off, size, values); + } + } + + protected abstract void processRead(AddressSpace spc, long off, int size, byte[] values); + + final void filterWrite(AddressSpace spc, long off, int size, byte [] values) { + if (filterOnExecutionOnly() && !emu.isExecuting()) return; // do not filter idle queries + processWrite(spc, off, size, values); + if (nextFilter != null) { + nextFilter.filterWrite(spc, off, size, values); + } + } + + protected abstract void processWrite(AddressSpace spc, long off, int size, byte[] values); + + final void addFilter(Emulator emu) { + this.emu = emu; + nextFilter = emu.getFilteredMemState().setFilter(this); + if (nextFilter != null) { + nextFilter.prevFilter = this; + } + } + + /** + * Dispose this filter which will cause it to be removed from the memory state. + * If overriden, be sure to invoke super.dispose(). + */ + public void dispose() { + if (nextFilter != null) { + nextFilter.prevFilter = prevFilter; + } + if (prevFilter != null) { + prevFilter.nextFilter = nextFilter; + } + else { + emu.getFilteredMemState().setFilter(nextFilter); + } + } + + public boolean filterOnExecutionOnly() { + return filterOnExecutionOnly; + } + + public void setFilterOnExecutionOnly(boolean filterOnExecutionOnly) { + this.filterOnExecutionOnly = filterOnExecutionOnly; + } + +// public void compare(String id); +// public void clear(); +// public void updateFlags(String id); + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/CompositeLoadImage.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/CompositeLoadImage.java new file mode 100644 index 0000000000..1972346ee6 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/CompositeLoadImage.java @@ -0,0 +1,75 @@ +/* ### + * 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.emulator.memory; + +import java.util.*; + +import ghidra.pcode.memstate.MemoryPage; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSetView; + +public class CompositeLoadImage implements MemoryLoadImage { + + private List providers = new ArrayList(); + private HashMap addrSets = + new HashMap(); + + public void addProvider(MemoryLoadImage provider, AddressSetView view) { + if (view == null) { + providers.add(providers.size(), provider); + } + else { + providers.add(0, provider); + } + addrSets.put(provider, view); + } + + @Override + public byte[] loadFill(byte[] buf, int size, Address addr, int bufOffset, + boolean generateInitializedMask) { + // Warning: this implementation assumes that the memory page (specified by addr and size) + // will only correspond to a single program image. + Address endAddr = addr.add(size - 1); + for (MemoryLoadImage provider : providers) { + AddressSetView view = addrSets.get(provider); + if (view == null || view.intersects(addr, endAddr)) { + return provider.loadFill(buf, size, addr, bufOffset, generateInitializedMask); + } + } + return generateInitializedMask ? MemoryPage.getInitializedMask(size, false) : null; + } + + @Override + public void writeBack(byte[] bytes, int size, Address addr, int offset) { + // Warning: this implementation assumes that the memory page (specified by addr and size) + // will only correspond to a single program image. + Address endAddr = addr.add(size - 1); + for (MemoryLoadImage provider : providers) { + AddressSetView view = addrSets.get(provider); + if (view == null || view.intersects(addr, endAddr)) { + provider.writeBack(bytes, size, addr, offset); + } + } + } + + @Override + public void dispose() { + for (MemoryLoadImage provider : providers) { + provider.dispose(); + } + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/EmulatorLoadData.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/EmulatorLoadData.java new file mode 100644 index 0000000000..9b4ab98624 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/EmulatorLoadData.java @@ -0,0 +1,31 @@ +/* ### + * 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.emulator.memory; + +import ghidra.app.emulator.state.RegisterState; +import ghidra.program.model.address.AddressSetView; + +public interface EmulatorLoadData { + + public MemoryLoadImage getMemoryLoadImage(); + + public RegisterState getInitialRegisterState(); + + public default AddressSetView getView() { + return null; + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/MemoryImage.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/MemoryImage.java new file mode 100644 index 0000000000..a66bec12e6 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/MemoryImage.java @@ -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.emulator.memory; + +import ghidra.pcode.memstate.*; +import ghidra.program.model.address.AddressSpace; + +/// A kind of MemoryBank which retrieves its data from an underlying LoadImage +/// +/// Any bytes requested on the bank which lie in the LoadImage are retrieved from +/// the LoadImage. Other addresses in the space are filled in with zero. +/// This bank cannot be written to. +public class MemoryImage extends MemoryBank { + + private MemoryLoadImage loader; // The underlying LoadImage + + /// A MemoryImage needs everything a basic memory bank needs and is needs to know + /// the underlying LoadImage object to forward read requests to. + /// \param spc is the address space associated with the memory bank + /// \param ws is the number of bytes in the preferred wordsize (must be power of 2) + /// \param ps is the number of bytes in a page (must be power of 2) + /// \param ld is the underlying LoadImage + public MemoryImage(AddressSpace spc, boolean isBigEndian, int ps, MemoryLoadImage ld, + MemoryFaultHandler faultHandler) { + super(spc, isBigEndian, ps, faultHandler); + loader = ld; + } + + /// Retrieve an aligned page from the bank. First an attempt is made to retrieve the + /// page from the LoadImage, which may do its own zero filling. If the attempt fails, the + /// page is entirely filled in with zeros. + @Override + public MemoryPage getPage(long addr) { + MemoryPage page = new MemoryPage(getPageSize()); + // Assume that -addr- is page aligned + AddressSpace spc = getSpace(); + byte[] maskUpdate = + loader.loadFill(page.data, getPageSize(), spc.getAddress(addr), 0, true); + page.setInitialized(0, getPageSize(), maskUpdate); + return page; + } + + @Override + protected void setPage(long addr, byte[] val, int skip, int size, int bufOffset) { + AddressSpace spc = getSpace(); + loader.writeBack(val, size, spc.getAddress(addr), bufOffset); + } + + @Override + protected void setPageInitialized(long addr, boolean initialized, int skip, int size, + int bufOffset) { + // unsupported + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/MemoryLoadImage.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/MemoryLoadImage.java new file mode 100644 index 0000000000..74fb7e2fa2 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/MemoryLoadImage.java @@ -0,0 +1,27 @@ +/* ### + * 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.emulator.memory; + +import ghidra.pcode.loadimage.LoadImage; +import ghidra.program.model.address.Address; + +public interface MemoryLoadImage extends LoadImage { + + public void writeBack(byte[] bytes, int size, Address addr, int offset); + + public void dispose(); + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/ProgramLoadImage.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/ProgramLoadImage.java new file mode 100644 index 0000000000..6424f63cc8 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/ProgramLoadImage.java @@ -0,0 +1,263 @@ +/* ### + * 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.emulator.memory; + +import java.util.Arrays; + +import ghidra.pcode.error.LowlevelError; +import ghidra.pcode.memstate.MemoryFaultHandler; +import ghidra.pcode.memstate.MemoryPage; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.*; +import ghidra.util.Msg; + +// Derived from ProgramMappedMemory +public class ProgramLoadImage { + + private Program program; + private AddressSetView initializedAddressSet; + private MemoryFaultHandler faultHandler; + + public ProgramLoadImage(Program program, MemoryFaultHandler faultHandler) { + this.program = program; + Memory memory = program.getMemory(); + initializedAddressSet = memory.getLoadedAndInitializedAddressSet(); + for (MemoryBlock block : memory.getBlocks()) { + if (!block.isInitialized() && (block instanceof MappedMemoryBlock)) { + initializedAddressSet = addMappedInitializedMemory((MappedMemoryBlock) block); + } + } + this.faultHandler = faultHandler; + // TODO: consider adding program consumer (would require proper dispose) + } + + private AddressSetView addMappedInitializedMemory(MappedMemoryBlock mappedBlock) { + long size = mappedBlock.getSize(); + if (size <= 0) { + // TODO: can't handle massive mapped blocks + return initializedAddressSet; + } + AddressSet modifiedSet = new AddressSet(initializedAddressSet); + Address mapStart = mappedBlock.getOverlayedMinAddress(); + Address mapEnd = mapStart.add(size - 1); + AddressSet mappedAreas = initializedAddressSet.intersectRange(mapStart, mapEnd); + for (AddressRange range : mappedAreas) { + Address start = mappedBlock.getStart().add(range.getMinAddress().subtract(mapStart)); + Address end = mappedBlock.getStart().add(range.getMaxAddress().subtract(mapStart)); + modifiedSet.add(start, end); + } + return modifiedSet; + } + + public void dispose() { + // do nothing + } + + // TODO: Need to investigate program write-back transaction issues - + // it could also be very expensive writing memory without some form of write-back cache + public void write(byte[] bytes, int size, Address addr, int offset) { + Memory memory = program.getMemory(); + int currentOffset = offset; + int remaining = size; + Address nextAddr = addr; + Address endAddr; + try { + endAddr = addr.addNoWrap(size - 1); + } + catch (AddressOverflowException e) { + throw new LowlevelError( + "Illegal memory write request: " + addr + ", length=" + size + " bytes"); + } + while (true) { + int chunkSize = remaining; + AddressRangeIterator it = initializedAddressSet.getAddressRanges(nextAddr, true); + AddressRange range = it.hasNext() ? it.next() : null; + + /// + /// Begin change for addressSet changes - wcb + /// + + if (range == null) { + // nextAddr not in memory and is bigger that any initialized memory + handleWriteFault(bytes, currentOffset, remaining, nextAddr); + break; + } + else if (range.contains(nextAddr)) { + // nextAddr is in memory + if (endAddr.compareTo(range.getMaxAddress()) > 0) { + chunkSize = (int) (range.getMaxAddress().subtract(nextAddr) + 1); + } + try { + memory.setBytes(nextAddr, bytes, currentOffset, chunkSize); + } + catch (MemoryAccessException e) { + throw new LowlevelError("Unexpected memory write error: " + e.getMessage()); + } + } + else { + // nextAddr not in initialized memory, but is less than some initialized range + Address rangeAddr = range.getMinAddress(); + if (!rangeAddr.getAddressSpace().equals(addr.getAddressSpace())) { + handleWriteFault(bytes, currentOffset, remaining, nextAddr); + break; + } + long gapSize = rangeAddr.subtract(nextAddr); + chunkSize = (int) Math.min(gapSize, remaining); + handleWriteFault(bytes, currentOffset, chunkSize, nextAddr); + } + + /// + /// End change for addressSet changes - wcb + /// + + if (chunkSize == remaining) { + break; // done + } + + // prepare for next chunk + try { + nextAddr = nextAddr.addNoWrap(chunkSize); + } + catch (AddressOverflowException e) { + throw new LowlevelError("Unexpected error: " + e.getMessage()); + } + currentOffset += chunkSize; + remaining -= chunkSize; + } + + } + + private void handleWriteFault(byte[] bytes, int currentOffset, int remaining, + Address nextAddr) { + // TODO: Should we create blocks or convert to initialized as needed ? + } + + public byte[] read(byte[] bytes, int size, Address addr, int offset, + boolean generateInitializedMask) { + + Memory memory = program.getMemory(); + int currentOffset = offset; + int remaining = size; + Address nextAddr = addr; + Address endAddr; + byte[] initializedMask = null; + try { + endAddr = addr.addNoWrap(size - 1); + } + catch (AddressOverflowException e) { + throw new LowlevelError( + "Illegal memory read request: " + addr + ", length=" + size + " bytes"); + } + while (true) { + int chunkSize = remaining; + + /// + /// Begin change for addressSet changes - wcb + /// + + AddressRangeIterator it = initializedAddressSet.getAddressRanges(nextAddr, true); + AddressRange range = it.hasNext() ? it.next() : null; + + if (range == null) { + if (generateInitializedMask) { + initializedMask = getInitializedMask(bytes.length, offset, currentOffset, + remaining, initializedMask); + } + else { + handleReadFault(bytes, currentOffset, remaining, nextAddr); + } + break; + } + else if (range.contains(nextAddr)) { + // nextAddr found in initialized memory + if (endAddr.compareTo(range.getMaxAddress()) > 0) { + chunkSize = (int) (range.getMaxAddress().subtract(nextAddr) + 1); + } + try { + memory.getBytes(nextAddr, bytes, currentOffset, chunkSize); + } + catch (MemoryAccessException e) { + //throw new LowlevelError("Unexpected memory read error: " + e.getMessage()); + Msg.warn(this, "Unexpected memory read error: " + e.getMessage()); + } + } + else { + Address rangeAddr = range.getMinAddress(); + if (!rangeAddr.getAddressSpace().equals(addr.getAddressSpace())) { + if (generateInitializedMask) { + initializedMask = getInitializedMask(bytes.length, offset, currentOffset, + remaining, initializedMask); + } + else { + handleReadFault(bytes, currentOffset, remaining, nextAddr); + } + break; + } + + long gapSize = rangeAddr.subtract(nextAddr); + chunkSize = (gapSize > 0) ? (int) Math.min(gapSize, remaining) : remaining; + if (generateInitializedMask) { + initializedMask = getInitializedMask(bytes.length, offset, currentOffset, + chunkSize, initializedMask); + } + else { + handleReadFault(bytes, currentOffset, chunkSize, nextAddr); + } + } + /// + /// End change for addressSet changes - wcb + /// + + if (chunkSize == remaining) { + break; // done + } + + // prepare for next chunk + try { + nextAddr = nextAddr.addNoWrap(chunkSize); + } + catch (AddressOverflowException e) { + throw new LowlevelError("Unexpected error: " + e.getMessage()); + } + currentOffset += chunkSize; + remaining -= chunkSize; + } + return initializedMask; + } + + private static byte[] getInitializedMask(int bufsize, int initialOffset, + int uninitializedOffset, int uninitializedSize, byte[] initializedMask) { + if (initializedMask == null) { + initializedMask = MemoryPage.getInitializedMask(bufsize, 0, initialOffset, false); + } + MemoryPage.setUninitialized(initializedMask, uninitializedOffset, uninitializedSize); + return initializedMask; + } + + private void handleReadFault(byte[] bytes, int offset, int size, Address addr) { +// NOTE: This can trigger a load from a different external library depending upon the specific fault handler installed + Arrays.fill(bytes, offset, offset + size, (byte) 0); + if (faultHandler != null) { + faultHandler.uninitializedRead(addr, size, bytes, size); + } + } + + public AddressSetView getInitializedAddressSet() { + return initializedAddressSet; + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/ProgramMappedLoadImage.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/ProgramMappedLoadImage.java new file mode 100644 index 0000000000..1d92680147 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/ProgramMappedLoadImage.java @@ -0,0 +1,53 @@ +/* ### + * 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.emulator.memory; + +import ghidra.program.model.address.Address; + +public class ProgramMappedLoadImage implements MemoryLoadImage { + + private ProgramMappedMemory pmm; + //private Language lang; + + public ProgramMappedLoadImage(ProgramMappedMemory memory) { + this.pmm = memory; + //this.lang = memory.getProgram().getLanguage(); + } + + @Override + public byte[] loadFill(byte[] bytes, int size, Address addr, int offset, boolean generateInitializedMask) { + return pmm.read(bytes, size, addr, offset, generateInitializedMask); +// boolean initialized = false; +// for (byte b : bytes) { +// if (b != 0) { +// initialized = true; +// break; +// } +// } +// return generateInitializedMask ? MemoryPage.getInitializedMask(size, initialized) : null; + } + + @Override + public void writeBack(byte[] bytes, int size, Address addr, int offset) { + pmm.write(bytes, size, addr, offset); + } + + @Override + public void dispose() { + pmm.dispose(); + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/ProgramMappedMemory.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/ProgramMappedMemory.java new file mode 100644 index 0000000000..050aebf844 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/memory/ProgramMappedMemory.java @@ -0,0 +1,273 @@ +/* ### + * 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.emulator.memory; + +import java.util.Arrays; + +import ghidra.pcode.error.LowlevelError; +import ghidra.pcode.memstate.MemoryFaultHandler; +import ghidra.pcode.memstate.MemoryPage; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.*; +import ghidra.util.Msg; + +public class ProgramMappedMemory { + + private Program program; + + private AddressSetView initializedAddressSet; + + private MemoryFaultHandler faultHandler; + + public ProgramMappedMemory(Program program, MemoryFaultHandler faultHandler) { + this.program = program; + Memory memory = program.getMemory(); + + initializedAddressSet = memory.getLoadedAndInitializedAddressSet(); + for (MemoryBlock block : memory.getBlocks()) { + if (!block.isInitialized() && (block instanceof MappedMemoryBlock)) { + initializedAddressSet = addMappedInitializedMemory((MappedMemoryBlock) block); + } + } + + program.addConsumer(this); + this.faultHandler = faultHandler; + } + + private AddressSetView addMappedInitializedMemory(MappedMemoryBlock mappedBlock) { + long size = mappedBlock.getSize(); + if (size <= 0) { + // TODO: can't handle massive mapped blocks + return initializedAddressSet; + } + AddressSet modifiedSet = new AddressSet(initializedAddressSet); + Address mapStart = mappedBlock.getOverlayedMinAddress(); + Address mapEnd = mapStart.add(size - 1); + AddressSet mappedAreas = initializedAddressSet.intersectRange(mapStart, mapEnd); + for (AddressRange range : mappedAreas) { + Address start = mappedBlock.getStart().add(range.getMinAddress().subtract(mapStart)); + Address end = mappedBlock.getStart().add(range.getMaxAddress().subtract(mapStart)); + modifiedSet.add(start, end); + } + return modifiedSet; + } + + public Program getProgram() { + return program; + } + + public void dispose() { + if (program != null) { + program.release(this); + program = null; + } + } + + // TODO: Need to investigate program write-back transaction issues - + // it could also be very expensive writing memory without some form of write-back cache + public void write(byte[] bytes, int size, Address addr, int offset) { + Memory memory = program.getMemory(); + int currentOffset = offset; + int remaining = size; + Address nextAddr = addr; + Address endAddr; + try { + endAddr = addr.addNoWrap(size - 1); + } + catch (AddressOverflowException e) { + throw new LowlevelError( + "Illegal memory write request: " + addr + ", length=" + size + " bytes"); + } + while (true) { + int chunkSize = remaining; + AddressRangeIterator it = initializedAddressSet.getAddressRanges(nextAddr, true); + AddressRange range = it.hasNext() ? it.next() : null; + + /// + /// Begin change for addressSet changes - wcb + /// + + if (range == null) { + // nextAddr not in memory and is bigger that any initialized memory + handleWriteFault(bytes, currentOffset, remaining, nextAddr); + break; + } + else if (range.contains(nextAddr)) { + // nextAddr is in memory + if (endAddr.compareTo(range.getMaxAddress()) > 0) { + chunkSize = (int) (range.getMaxAddress().subtract(nextAddr) + 1); + } + try { + memory.setBytes(nextAddr, bytes, currentOffset, chunkSize); + } + catch (MemoryAccessException e) { + throw new LowlevelError("Unexpected memory write error: " + e.getMessage()); + } + } + else { + // nextAddr not in initialized memory, but is less than some initialized range + Address rangeAddr = range.getMinAddress(); + if (!rangeAddr.getAddressSpace().equals(addr.getAddressSpace())) { + handleWriteFault(bytes, currentOffset, remaining, nextAddr); + break; + } + long gapSize = rangeAddr.subtract(nextAddr); + chunkSize = (int) Math.min(gapSize, remaining); + handleWriteFault(bytes, currentOffset, chunkSize, nextAddr); + } + + /// + /// End change for addressSet changes - wcb + /// + + if (chunkSize == remaining) { + break; // done + } + + // prepare for next chunk + try { + nextAddr = nextAddr.addNoWrap(chunkSize); + } + catch (AddressOverflowException e) { + throw new LowlevelError("Unexpected error: " + e.getMessage()); + } + currentOffset += chunkSize; + remaining -= chunkSize; + } + + } + + private void handleWriteFault(byte[] bytes, int currentOffset, int remaining, + Address nextAddr) { + // TODO: Should we create blocks or convert to initialized as needed ? + } + + public byte[] read(byte[] bytes, int size, Address addr, int offset, + boolean generateInitializedMask) { + + Memory memory = program.getMemory(); + int currentOffset = offset; + int remaining = size; + Address nextAddr = addr; + Address endAddr; + byte[] initializedMask = null; + try { + endAddr = addr.addNoWrap(size - 1); + } + catch (AddressOverflowException e) { + throw new LowlevelError( + "Illegal memory read request: " + addr + ", length=" + size + " bytes"); + } + while (true) { + int chunkSize = remaining; + + /// + /// Begin change for addressSet changes - wcb + /// + + AddressRangeIterator it = initializedAddressSet.getAddressRanges(nextAddr, true); + AddressRange range = it.hasNext() ? it.next() : null; + + if (range == null) { + if (generateInitializedMask) { + initializedMask = getInitializedMask(bytes.length, offset, currentOffset, + remaining, initializedMask); + } + else { + handleReadFault(bytes, currentOffset, remaining, nextAddr); + } + break; + } + else if (range.contains(nextAddr)) { + // nextAddr found in initialized memory + if (endAddr.compareTo(range.getMaxAddress()) > 0) { + chunkSize = (int) (range.getMaxAddress().subtract(nextAddr) + 1); + } + try { + memory.getBytes(nextAddr, bytes, currentOffset, chunkSize); + } + catch (MemoryAccessException e) { + //throw new LowlevelError("Unexpected memory read error: " + e.getMessage()); + Msg.warn(this, "Unexpected memory read error: " + e.getMessage()); + } + } + else { + Address rangeAddr = range.getMinAddress(); + if (!rangeAddr.getAddressSpace().equals(addr.getAddressSpace())) { + if (generateInitializedMask) { + initializedMask = getInitializedMask(bytes.length, offset, currentOffset, + remaining, initializedMask); + } + else { + handleReadFault(bytes, currentOffset, remaining, nextAddr); + } + break; + } + + long gapSize = rangeAddr.subtract(nextAddr); + chunkSize = (gapSize > 0) ? (int) Math.min(gapSize, remaining) : remaining; + if (generateInitializedMask) { + initializedMask = getInitializedMask(bytes.length, offset, currentOffset, + chunkSize, initializedMask); + } + else { + handleReadFault(bytes, currentOffset, chunkSize, nextAddr); + } + } + /// + /// End change for addressSet changes - wcb + /// + + if (chunkSize == remaining) { + break; // done + } + + // prepare for next chunk + try { + nextAddr = nextAddr.addNoWrap(chunkSize); + } + catch (AddressOverflowException e) { + throw new LowlevelError("Unexpected error: " + e.getMessage()); + } + currentOffset += chunkSize; + remaining -= chunkSize; + } + return initializedMask; + } + + private static byte[] getInitializedMask(int bufsize, int initialOffset, + int uninitializedOffset, int uninitializedSize, byte[] initializedMask) { + if (initializedMask == null) { + initializedMask = MemoryPage.getInitializedMask(bufsize, 0, initialOffset, false); + } + MemoryPage.setUninitialized(initializedMask, uninitializedOffset, uninitializedSize); + return initializedMask; + } + + private void handleReadFault(byte[] bytes, int offset, int size, Address addr) { +// NOTE: This can trigger a load from a different external library depending upon the specific fault handler installed + Arrays.fill(bytes, offset, offset + size, (byte) 0); + if (faultHandler != null) { + faultHandler.uninitializedRead(addr, size, bytes, size); + } + } + + public AddressSetView getInitializedAddressSet() { + return initializedAddressSet; + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/state/DumpMiscState.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/state/DumpMiscState.java new file mode 100644 index 0000000000..f579296869 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/state/DumpMiscState.java @@ -0,0 +1,83 @@ +/* ### + * 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.emulator.state; + +import generic.stl.Pair; +import ghidra.program.model.lang.Language; +import ghidra.util.*; + +import java.util.*; + +public class DumpMiscState implements RegisterState { + + private Map> context = + new HashMap>(); + + private DataConverter dc; + + public DumpMiscState(Language lang) { + dc = + lang.isBigEndian() ? BigEndianDataConverter.INSTANCE + : LittleEndianDataConverter.INSTANCE; + } + + @Override + public void dispose() { + context.clear(); + } + + @Override + public Set getKeys() { + return context.keySet(); + } + + @Override + public List getVals(String key) { + List list = new ArrayList(); + Pair pair = context.get(key); + if (pair != null && pair.second != null) { + list.add(pair.second); + } + return list; + } + + @Override + public List isInitialized(String key) { + List list = new ArrayList(); + Pair pair = context.get(key); + if (pair != null && pair.first != null) { + list.add(pair.first); + } + else { + list.add(Boolean.FALSE); + } + return list; + } + + @Override + public void setVals(String key, byte[] vals, boolean setInitiailized) { + Pair pair = new Pair(setInitiailized, vals); + context.put(key, pair); + } + + @Override + public void setVals(String key, long val, int size, boolean setInitiailized) { + byte[] bytes = new byte[size]; + dc.getBytes(val, size, bytes, 0); + setVals(key, bytes, setInitiailized); + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/state/FilteredMemoryPageOverlay.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/state/FilteredMemoryPageOverlay.java new file mode 100644 index 0000000000..c053fa9ed5 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/state/FilteredMemoryPageOverlay.java @@ -0,0 +1,39 @@ +/* ### + * 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.emulator.state; + +import ghidra.pcode.memstate.MemoryBank; +import ghidra.pcode.memstate.MemoryPageOverlay; +import ghidra.program.model.address.AddressSpace; + +public class FilteredMemoryPageOverlay extends MemoryPageOverlay { + + private boolean writeBack; + + public FilteredMemoryPageOverlay(AddressSpace spc, MemoryBank ul, boolean writeBack) { + super(spc, ul, ul.getMemoryFaultHandler()); + this.writeBack = writeBack; + } + + @Override + public void setChunk(long offset, int size, byte[] val) { + super.setChunk(offset, size, val); + if (writeBack) { + underlie.setChunk(offset, size, val); + } + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/state/FilteredRegisterBank.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/state/FilteredRegisterBank.java new file mode 100644 index 0000000000..b554a9b6e0 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/state/FilteredRegisterBank.java @@ -0,0 +1,34 @@ +/* ### + * 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.emulator.state; + +import ghidra.pcode.memstate.MemoryFaultHandler; +import ghidra.pcode.memstate.MemoryPageBank; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Language; + +public class FilteredRegisterBank extends MemoryPageBank { + + //private final RegisterState regState; + //private final boolean writeBack; + + public FilteredRegisterBank(AddressSpace spc, int ps, RegisterState initState, Language lang, boolean writeBack, MemoryFaultHandler faultHandler) { + super(spc, lang.isBigEndian(), ps, faultHandler); + //regState = initState; + //this.writeBack = writeBack; + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/state/RegisterState.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/state/RegisterState.java new file mode 100644 index 0000000000..c3c832b6cf --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/emulator/state/RegisterState.java @@ -0,0 +1,35 @@ +/* ### + * 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.emulator.state; + +import java.util.List; +import java.util.Set; + +public interface RegisterState { + + public Set getKeys(); + + public List getVals(String key); + + public List isInitialized(String key); + + public void setVals(String key, byte[] vals, boolean setInitiailized); + + public void setVals(String key, long val, int size, boolean setInitiailized); + + public void dispose(); + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/emulate/Emulate.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/emulate/Emulate.java index 1d08a7514c..30a3414e87 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/emulate/Emulate.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/emulate/Emulate.java @@ -31,6 +31,8 @@ import ghidra.program.model.listing.Instruction; import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.Varnode; import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitorAdapter; /// \brief A SLEIGH based implementation of the Emulate interface /// @@ -78,7 +80,8 @@ public class Emulate { pcReg = lang.getProgramCounter(); breaktable = b; breaktable.setEmulate(this); - memBuffer = new EmulateMemoryStateBuffer(s, addrFactory.getDefaultAddressSpace().getMinAddress()); + memBuffer = + new EmulateMemoryStateBuffer(s, addrFactory.getDefaultAddressSpace().getMinAddress()); uniqueBank = new UniqueMemoryBank(lang.getAddressFactory().getUniqueSpace(), lang.isBigEndian()); @@ -91,24 +94,24 @@ public class Emulate { initInstuctionStateModifier(); } - + public void dispose() { executionState = EmulateExecutionState.STOPPED; } @SuppressWarnings("unchecked") private void initInstuctionStateModifier() { - String classname = - language.getProperty(GhidraLanguagePropertyKeys.EMULATE_INSTRUCTION_STATE_MODIFIER_CLASS); + String classname = language.getProperty( + GhidraLanguagePropertyKeys.EMULATE_INSTRUCTION_STATE_MODIFIER_CLASS); if (classname == null) { return; } try { Class c = Class.forName(classname); if (!EmulateInstructionStateModifier.class.isAssignableFrom(c)) { - Msg.error(this, "Language " + language.getLanguageID() + - " does not specify a valid " + - GhidraLanguagePropertyKeys.EMULATE_INSTRUCTION_STATE_MODIFIER_CLASS); + Msg.error(this, + "Language " + language.getLanguageID() + " does not specify a valid " + + GhidraLanguagePropertyKeys.EMULATE_INSTRUCTION_STATE_MODIFIER_CLASS); throw new RuntimeException(classname + " does not implement interface " + EmulateInstructionStateModifier.class.getName()); } @@ -121,8 +124,9 @@ public class Emulate { catch (Exception e) { Msg.error(this, "Language " + language.getLanguageID() + " does not specify a valid " + GhidraLanguagePropertyKeys.EMULATE_INSTRUCTION_STATE_MODIFIER_CLASS); - throw new RuntimeException("Failed to instantiate " + classname + " for language " + - language.getLanguageID(), e); + throw new RuntimeException( + "Failed to instantiate " + classname + " for language " + language.getLanguageID(), + e); } } @@ -367,11 +371,12 @@ public class Emulate { /// and invoked as needed for the current address. If this routine is invoked while execution is /// in the middle of a machine instruction, execution is continued until the current instruction /// completes. - public void executeInstruction(boolean stopAtBreakpoint) throws LowlevelError, - InstructionDecodeException { + public void executeInstruction(boolean stopAtBreakpoint, TaskMonitor monitor) + throws CancelledException, LowlevelError, InstructionDecodeException { if (executionState == EmulateExecutionState.STOPPED) { if (last_execute_address == null && instructionStateModifier != null) { - instructionStateModifier.initialExecuteCallback(this, current_address, nextContextRegisterValue); + instructionStateModifier.initialExecuteCallback(this, current_address, + nextContextRegisterValue); } if (breaktable.doAddressBreak(current_address) && stopAtBreakpoint) { executionState = EmulateExecutionState.BREAKPOINT; @@ -400,6 +405,7 @@ public class Emulate { } executionState = EmulateExecutionState.EXECUTE; do { + monitor.checkCanceled(); executeCurrentOp(); } while (executionState == EmulateExecutionState.EXECUTE); @@ -441,8 +447,8 @@ public class Emulate { OpBehavior behave = raw.getBehavior(); if (behave == null) { // unsupported opcode - throw new LowlevelError("Unsupported pcode op (opcode=" + op.getOpcode() + ", seq=" + - op.getSeqnum() + ")"); + throw new LowlevelError( + "Unsupported pcode op (opcode=" + op.getOpcode() + ", seq=" + op.getSeqnum() + ")"); } if (behave instanceof UnaryOpBehavior) { UnaryOpBehavior uniaryBehave = (UnaryOpBehavior) behave; @@ -450,16 +456,14 @@ public class Emulate { Varnode outvar = op.getOutput(); if (in1var.getSize() > 8 || outvar.getSize() > 8) { BigInteger in1 = memstate.getBigInteger(op.getInput(0), false); - BigInteger out = - uniaryBehave.evaluateUnary(op.getOutput().getSize(), op.getInput(0).getSize(), - in1); + BigInteger out = uniaryBehave.evaluateUnary(op.getOutput().getSize(), + op.getInput(0).getSize(), in1); memstate.setValue(op.getOutput(), out); } else { long in1 = memstate.getValue(op.getInput(0)); - long out = - uniaryBehave.evaluateUnary(op.getOutput().getSize(), op.getInput(0).getSize(), - in1); + long out = uniaryBehave.evaluateUnary(op.getOutput().getSize(), + op.getInput(0).getSize(), in1); memstate.setValue(op.getOutput(), out); } fallthruOp(); @@ -471,17 +475,15 @@ public class Emulate { if (in1var.getSize() > 8 || outvar.getSize() > 8) { BigInteger in1 = memstate.getBigInteger(op.getInput(0), false); BigInteger in2 = memstate.getBigInteger(op.getInput(1), false); - BigInteger out = - binaryBehave.evaluateBinary(outvar.getSize(), op.getInput(0).getSize(), in1, - in2); + BigInteger out = binaryBehave.evaluateBinary(outvar.getSize(), + op.getInput(0).getSize(), in1, in2); memstate.setValue(outvar, out); } else { long in1 = memstate.getValue(op.getInput(0)); long in2 = memstate.getValue(op.getInput(1)); - long out = - binaryBehave.evaluateBinary(outvar.getSize(), op.getInput(0).getSize(), in1, - in2); + long out = binaryBehave.evaluateBinary(outvar.getSize(), op.getInput(0).getSize(), + in1, in2); memstate.setValue(outvar, out); } fallthruOp(); // All binary ops are fallthrus @@ -762,4 +764,3 @@ public class Emulate { - PcodeOpRaw and - VarnodeData */ - diff --git a/GhidraDocs/GhidraClass/ExerciseFiles/Emulation/Source/README.txt b/GhidraDocs/GhidraClass/ExerciseFiles/Emulation/Source/README.txt new file mode 100644 index 0000000000..0881996c56 --- /dev/null +++ b/GhidraDocs/GhidraClass/ExerciseFiles/Emulation/Source/README.txt @@ -0,0 +1,10 @@ +See the example emulation scripts contained within Ghidra/Features/Base/ghidra_scripts. + +Sample scripts deobExampleX86 and deobHookExampleX86 may be built under Linux. + + cc -std=c99 -Wimplicit-function-declaration -o deobExampleX86 deobExample.c + cc -std=c99 -Wimplicit-function-declaration -o deobHookExampleX86 deobHookExample.c + +Once these examples have been compiled they may be imported into a Ghidra project and the +corresponding Ghidra Scripts (EmuX86DeobfuscateExampleScript and EmuX86GccDeobfuscateHookExampleScript) +used to demonstrate the use of the EmulatorHelper class. diff --git a/GhidraDocs/GhidraClass/ExerciseFiles/Emulation/Source/deobExample.c b/GhidraDocs/GhidraClass/ExerciseFiles/Emulation/Source/deobExample.c new file mode 100644 index 0000000000..eeddf82861 --- /dev/null +++ b/GhidraDocs/GhidraClass/ExerciseFiles/Emulation/Source/deobExample.c @@ -0,0 +1,100 @@ +/* ### + * 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. + */ +int length(char *s) { + int len = 0; + while (*s++ != 0) { + ++len; + } + return len; +} + +const char data[] = { +0xec, +0xc3, +0xd8, +0xd9, +0xde, +0x8a, +0xcf, +0xc4, +0xde, +0xd8, +0xd3, +0x00, +0xf9, +0xcf, +0xc9, +0xc5, +0xc4, +0xce, +0x8a, +0xcf, +0xc4, +0xde, +0xd8, +0xd3, +0x00, +0xfe, +0xc2, +0xc3, +0xd8, +0xce, +0x8a, +0xcf, +0xc4, +0xde, +0xd8, +0xd3, +0x00, +0x00 +}; + +char buffer[64]; + +char * deobfuscate(char *src, char *dst, int len) { + char *ptr = dst; + for (int i = 0; i < len; i++) { + *ptr++ = *src++ ^ 0xAA; + } + *ptr = 0; + return dst; +} + +void use_string(char * str, int index) { +// fprintf(stderr, "String[%d]: %s\n", index, str); +} + +int main (int argc, char **argv) { + char *ptr = (char *)data; + int index = 0; + while (*ptr != 0) { + int len = length(ptr); + char *str = deobfuscate(ptr, buffer, len); + use_string(str, index++); + ptr += len + 1; + } + return 0; +} + +#ifndef __x86_64 + + +int _start() { + char *argv[] = { "deobExample" }; + return main(1, argv); +} + +#endif diff --git a/GhidraDocs/GhidraClass/ExerciseFiles/Emulation/Source/deobHookExample.c b/GhidraDocs/GhidraClass/ExerciseFiles/Emulation/Source/deobHookExample.c new file mode 100644 index 0000000000..85be5c1284 --- /dev/null +++ b/GhidraDocs/GhidraClass/ExerciseFiles/Emulation/Source/deobHookExample.c @@ -0,0 +1,120 @@ +/* ### + * 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. + */ +#ifdef __x86_64 + +#include +#include + +#else + +// Library routine not linked in for cross-build + +void * malloc(int size) { + // missing implementation + return (void *)0; +} + +void free(void *ptr) { + // missing implementation +} + +int strlen(char *s) { + int len = 0; + while (*s++ != 0) { + ++len; + } + return len; +} + +#endif + +const char data[] = { +0xec, +0xc3, +0xd8, +0xd9, +0xde, +0x8a, +0xcf, +0xc4, +0xde, +0xd8, +0xd3, +0x00, +0xf9, +0xcf, +0xc9, +0xc5, +0xc4, +0xce, +0x8a, +0xcf, +0xc4, +0xde, +0xd8, +0xd3, +0x00, +0xfe, +0xc2, +0xc3, +0xd8, +0xce, +0x8a, +0xcf, +0xc4, +0xde, +0xd8, +0xd3, +0x00, +0x00 +}; + +char * deobfuscate(char *src, int len) { + char *buf = (char *)malloc(len + 1); + char *ptr = buf; + for (int i = 0; i < len; i++) { + *ptr++ = *src++ ^ 0xAA; + } + *ptr = 0; + return buf; +} + +void use_string(char * str, int index) { +// fprintf(stderr, "String[%d]: %s\n", index, str); +} + +int main (int argc, char **argv) { + char *ptr = (char *)data; + int index = 0; + while (*ptr != 0) { + int len = strlen(ptr); + char *str = deobfuscate(ptr, len); + use_string(str, index++); + free(str); + ptr += len + 1; + } + return 0; +} + +#ifndef __x86_64 + + +int _start() { + char *argv[] = { "deobExample" }; + return main(1, argv); +} + +#endif diff --git a/GhidraDocs/certification.manifest b/GhidraDocs/certification.manifest index ba32ce1d2e..c4e36f50a2 100644 --- a/GhidraDocs/certification.manifest +++ b/GhidraDocs/certification.manifest @@ -39,6 +39,7 @@ GhidraClass/ExerciseFiles/Advanced/override.so||GHIDRA||||END| GhidraClass/ExerciseFiles/Advanced/setRegister||GHIDRA||||END| GhidraClass/ExerciseFiles/Advanced/sharedReturn||GHIDRA||||END| GhidraClass/ExerciseFiles/Advanced/switch||GHIDRA||||END| +GhidraClass/ExerciseFiles/Emulation/Source/README.txt||GHIDRA||||END| GhidraClass/ExerciseFiles/VersionTracking/WallaceSrc.exe||GHIDRA||||END| GhidraClass/ExerciseFiles/VersionTracking/WallaceVersion2.exe||GHIDRA||||END| GhidraClass/ExerciseFiles/WinhelloCPP/WinHelloCPP.exe||GHIDRA||||END|