mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 17:11:11 -09:00
GP-3071: Remove stale 'commitByDefault' documentation
GP-3071: Fix test compilation GP-3071: Certify GP-3071: Put lifecycle stuff in Emulation, not Utility GP-3071: Fix tests GP-3071: Mock language for framework tests GP-3071: WIP: Move tests and sort out dependencies GP-3071: Actually, not Generic, but Emulation GP-3071: Move both emulators into new Emulation module GP-3071: WIP: Move some tests GP-3071: NICK: Remove import/ref from PcodeEmulator javadoc GP-3071: WIP: Move stuff GP-3071: WIP: Move AnnotationUtilities GP-3071: NICK: Remove an import and ref in javadoc GP-3071: Create SysteEmulation feature. Move stuff. GP-3071: WIP: Move stuff GP-3071: Create emulation module
This commit is contained in:
@@ -1,216 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
//An example emulation script that integrates well with the Debgger UI.
|
||||
//It provides the set-up code and then demonstrates some use cases.
|
||||
//It should work with any x64 program, but some snippets may require specific conditions.
|
||||
//It should be easily ported to other platforms just by adjusting register names.
|
||||
//@author
|
||||
//@category Emulation
|
||||
//@keybinding
|
||||
//@menupath
|
||||
//@toolbar
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import ghidra.app.plugin.assembler.Assembler;
|
||||
import ghidra.app.plugin.assembler.Assemblers;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.BytesDebuggerPcodeEmulator;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess;
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
||||
import ghidra.pcode.exec.trace.TraceSleighUtils;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.database.ProgramDB;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.lang.LanguageID;
|
||||
import ghidra.program.model.listing.InstructionIterator;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.Memory;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.guest.TracePlatform;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.trace.model.time.TraceTimeManager;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
|
||||
public class DebuggerEmuExampleScript extends GhidraScript {
|
||||
private final static Charset UTF8 = Charset.forName("utf8");
|
||||
|
||||
@Override
|
||||
protected void run() throws Exception {
|
||||
/*
|
||||
* First, get all the services and stuff:
|
||||
*/
|
||||
PluginTool tool = state.getTool();
|
||||
ProgramManager programManager = tool.getService(ProgramManager.class);
|
||||
DebuggerTraceManagerService traceManager =
|
||||
tool.getService(DebuggerTraceManagerService.class);
|
||||
SleighLanguage language = (SleighLanguage) getLanguage(new LanguageID("x86:LE:64:default"));
|
||||
|
||||
/*
|
||||
* I'll generate a new program, because I don't want to require the user to pick something
|
||||
* specific.
|
||||
*/
|
||||
Address entry;
|
||||
Address injectHere;
|
||||
Program program = null;
|
||||
try {
|
||||
program =
|
||||
new ProgramDB("emu_example", language, language.getDefaultCompilerSpec(), this);
|
||||
// Save the program into the project so it has a URL for the trace's static mapping
|
||||
tool.getProject()
|
||||
.getProjectData()
|
||||
.getRootFolder()
|
||||
.createFile("emu_example", program, monitor);
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Init")) {
|
||||
AddressSpace space = program.getAddressFactory().getDefaultAddressSpace();
|
||||
entry = space.getAddress(0x00400000);
|
||||
Address dataEntry = space.getAddress(0x00600000);
|
||||
Memory memory = program.getMemory();
|
||||
memory.createInitializedBlock(".text", entry, 0x1000, (byte) 0, monitor, false);
|
||||
Assembler asm = Assemblers.getAssembler(program);
|
||||
InstructionIterator ii = asm.assemble(entry,
|
||||
"MOV RCX, 0x" + dataEntry,
|
||||
"MOV RAX, 1",
|
||||
"SYSCALL",
|
||||
"MOV RAX, 2",
|
||||
"SYSCALL");
|
||||
ii.next(); // drop MOV RCX
|
||||
injectHere = ii.next().getAddress();
|
||||
memory.createInitializedBlock(".data", dataEntry, 0x1000, (byte) 0, monitor, false);
|
||||
memory.setBytes(dataEntry, "Hello, World!\n".getBytes(UTF8));
|
||||
}
|
||||
program.save("Init", monitor);
|
||||
// Display the program in the UI
|
||||
programManager.openProgram(program);
|
||||
}
|
||||
finally {
|
||||
if (program != null) {
|
||||
program.release(this);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Now, load the program into a trace. This doesn't copy any bytes, it just sets up a static
|
||||
* mapping. The emulator will know how to read through to the mapped program. We use a
|
||||
* utility, which is the same used by the "Emulate Program" action in the UI. It will load
|
||||
* the program, allocate a stack, and initialize the first thread to the given entry.
|
||||
*/
|
||||
Trace trace = null;
|
||||
try {
|
||||
trace = ProgramEmulationUtils.launchEmulationTrace(program, entry, this);
|
||||
// Display the trace in the UI
|
||||
traceManager.openTrace(trace);
|
||||
traceManager.activateTrace(trace);
|
||||
}
|
||||
finally {
|
||||
if (trace != null) {
|
||||
trace.release(this);
|
||||
}
|
||||
}
|
||||
// Get the initial thread
|
||||
TraceThread traceThread = trace.getThreadManager().getAllThreads().iterator().next();
|
||||
traceManager.activateThread(traceThread);
|
||||
|
||||
/*
|
||||
* Instead of using the UI's emulator, this script will create its own with a custom
|
||||
* library. This emulator will still know how to integrate with the UI, reading through to
|
||||
* open programs and writing state back into the trace.
|
||||
*/
|
||||
TracePlatform host = trace.getPlatformManager().getHostPlatform();
|
||||
DefaultPcodeDebuggerAccess access = new DefaultPcodeDebuggerAccess(tool, null, host, 0);
|
||||
BytesDebuggerPcodeEmulator emulator = new BytesDebuggerPcodeEmulator(access) {
|
||||
@Override
|
||||
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
|
||||
return new DemoPcodeUseropLibrary(language, DebuggerEmuExampleScript.this);
|
||||
}
|
||||
};
|
||||
// Conventionally, emulator threads are named after their trace thread's path.
|
||||
PcodeThread<byte[]> thread = emulator.getThread(traceThread.getPath(), true);
|
||||
|
||||
/*
|
||||
* Inject a call to our custom print userop. Otherwise, the language itself will never
|
||||
* invoke it.
|
||||
*/
|
||||
emulator.inject(injectHere, """
|
||||
print_utf8(RCX);
|
||||
emu_exec_decoded();
|
||||
""");
|
||||
|
||||
/*
|
||||
* Run the experiment: This should interrupt on the second SYSCALL, because any value other
|
||||
* than 1 calls emu_swi.
|
||||
*
|
||||
* For demonstration, we'll record a trace snapshot for every step of emulation. This is not
|
||||
* ordinarily recommended except for very small experiments. A more reasonable approach in
|
||||
* practice may be to snapshot on specific breakpoints.
|
||||
*/
|
||||
TraceTimeManager time = trace.getTimeManager();
|
||||
TraceSnapshot snapshot = time.getSnapshot(0, true);
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Emulate")) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
println("Executing: " + thread.getCounter());
|
||||
thread.stepInstruction();
|
||||
snapshot =
|
||||
time.createSnapshot("Stepped to " + thread.getCounter());
|
||||
emulator.writeDown(host, snapshot.getKey(), 0);
|
||||
}
|
||||
printerr("We should not have completed 10 steps!");
|
||||
}
|
||||
catch (InterruptPcodeExecutionException e) {
|
||||
println("Terminated via interrupt. Good.");
|
||||
}
|
||||
// Display the final snapshot in the UI
|
||||
traceManager.activateSnap(snapshot.getKey());
|
||||
|
||||
/*
|
||||
* Inspect the machine. You can always do this by accessing the state directly, but for
|
||||
* anything other than simple variables, you may find compiling an expression more
|
||||
* convenient.
|
||||
*
|
||||
* This works the same as in the stand-alone case.
|
||||
*/
|
||||
println("RCX = " +
|
||||
Utils.bytesToLong(thread.getState().getVar(language.getRegister("RCX"), Reason.INSPECT),
|
||||
8, language.isBigEndian()));
|
||||
|
||||
println("RCX = " + Utils.bytesToLong(
|
||||
SleighProgramCompiler.compileExpression(language, "RCX").evaluate(thread.getExecutor()),
|
||||
8, language.isBigEndian()));
|
||||
|
||||
println("RCX+4 = " +
|
||||
Utils.bytesToLong(SleighProgramCompiler.compileExpression(language, "RCX+4")
|
||||
.evaluate(thread.getExecutor()),
|
||||
8, language.isBigEndian()));
|
||||
|
||||
/*
|
||||
* To evaluate a Sleigh expression against the trace: The result is the same as evaluating
|
||||
* directly against the emulator, but these work with any trace, no matter the original data
|
||||
* source (live target, emulated, imported, etc.) It's also built into utilities, making it
|
||||
* easier to use.
|
||||
*/
|
||||
println("RCX+4 (trace) = " +
|
||||
TraceSleighUtils.evaluate("RCX+4", trace, snapshot.getKey(), traceThread, 0));
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
||||
import ghidra.pcode.struct.StructuredSleigh;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.lang.CompilerSpec;
|
||||
import ghidra.program.model.pcode.Varnode;
|
||||
|
||||
/**
|
||||
* A userop library for the emulator
|
||||
*
|
||||
* <p>
|
||||
* If you do not need a custom userop library, use {@link PcodeUseropLibrary#NIL}. These libraries
|
||||
* allow you to implement userops, including those declared by the language. Without these, the
|
||||
* emulator must interrupt whenever a userop ({@code CALLOTHER}) is encountered. You can also define
|
||||
* new userops, which can be invoked from Sleigh code injected into the emulator.
|
||||
*
|
||||
* <p>
|
||||
* These libraries can have both Java-callback and p-code implementations of userops. If only using
|
||||
* p-code implementations, the library can be parameterized with type {@code <T>} and just pass that
|
||||
* over to {@link AnnotatedPcodeUseropLibrary}. Because this will demo a Java callback that assumes
|
||||
* concrete bytes, we will fix the library's type to {@code byte[]}. With careful use of the
|
||||
* {@link PcodeArithmetic}, you can keep the type an abstract {@code <T>} with Java callbacks.
|
||||
*
|
||||
* <p>
|
||||
* Methods in this class (not including those in its nested classes) are implemented as Java
|
||||
* callbacks.
|
||||
*/
|
||||
public class DemoPcodeUseropLibrary extends AnnotatedPcodeUseropLibrary<byte[]> {
|
||||
private final static Charset UTF8 = Charset.forName("utf8");
|
||||
|
||||
private final SleighLanguage language;
|
||||
private final GhidraScript script;
|
||||
private final AddressSpace space;
|
||||
|
||||
public DemoPcodeUseropLibrary(SleighLanguage language, GhidraScript script) {
|
||||
this.language = language;
|
||||
this.script = script;
|
||||
this.space = language.getDefaultSpace();
|
||||
|
||||
new DemoStructuredPart(language.getDefaultCompilerSpec()).generate(ops);
|
||||
}
|
||||
|
||||
/**
|
||||
* Treats the input as an offset to a C-style string and prints it to the console
|
||||
*
|
||||
* <p>
|
||||
* Because we want to dereference start, we will need access to the emulator's state, so we
|
||||
* employ the {@link OpState} annotation. {@code start} takes the one input we expect. Because
|
||||
* its type is the value type rather than {@link Varnode}, we will get the input's value.
|
||||
* Similarly, we can just return the resulting value, and the emulator will place that into the
|
||||
* output variable for us.
|
||||
*
|
||||
* @param state the calling thread's state
|
||||
* @param start the offset of the first character
|
||||
* @return the length of the string in bytes
|
||||
*/
|
||||
@PcodeUserop
|
||||
public byte[] print_utf8(@OpExecutor PcodeExecutor<byte[]> executor, byte[] start) {
|
||||
PcodeExecutorState<byte[]> state = executor.getState();
|
||||
long offset = Utils.bytesToLong(start, start.length, language.isBigEndian());
|
||||
long end = offset;
|
||||
Reason reason = executor.getReason();
|
||||
while (state.getVar(space, end, 1, true, reason)[0] != 0) {
|
||||
end++;
|
||||
}
|
||||
if (end == offset) {
|
||||
script.println("");
|
||||
return Utils.longToBytes(0, Long.BYTES, language.isBigEndian());
|
||||
}
|
||||
byte[] bytes = state.getVar(space, offset, (int) (end - offset), true, reason);
|
||||
String str = new String(bytes, UTF8);
|
||||
script.println(str);
|
||||
return Utils.longToBytes(end - offset, Long.BYTES, language.isBigEndian());
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods in this class are implemented using p-code compiled from Structured Sleigh
|
||||
*/
|
||||
public class DemoStructuredPart extends StructuredSleigh {
|
||||
final Var RAX = lang("RAX", type("long"));
|
||||
final Var RCX = lang("RAX", type("byte *"));
|
||||
final UseropDecl emu_swi = userop(type("void"), "emu_swi", List.of());
|
||||
|
||||
protected DemoStructuredPart(CompilerSpec cs) {
|
||||
super(cs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Not really a syscall dispatcher
|
||||
*
|
||||
* <p>
|
||||
* In cases where the userop expects parameters, you would annotate them with {@link Param}
|
||||
* and use them just like other {@link Var}s. See the javadocs.
|
||||
*
|
||||
* <p>
|
||||
* This is just a cheesy demo: If RAX is 1, then this method computes the number of bytes in
|
||||
* the C-style string pointed to by RCX and stores the result in RAX. Otherwise, interrupt
|
||||
* the emulator. See {@link DemoSyscallLibrary} for actual system call simulation.
|
||||
*/
|
||||
@StructuredUserop
|
||||
public void syscall() {
|
||||
_if(RAX.eq(1), () -> {
|
||||
Var i = local("i", RCX);
|
||||
_while(i.deref().neq(0), () -> {
|
||||
i.inc();
|
||||
});
|
||||
RAX.set(i.subi(RAX));
|
||||
})._else(() -> {
|
||||
emu_swi.call();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collection;
|
||||
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.pcode.emu.PcodeMachine;
|
||||
import ghidra.pcode.emu.linux.EmuLinuxAmd64SyscallUseropLibrary;
|
||||
import ghidra.pcode.emu.linux.EmuLinuxX86SyscallUseropLibrary;
|
||||
import ghidra.pcode.emu.sys.AnnotatedEmuSyscallUseropLibrary;
|
||||
import ghidra.pcode.emu.sys.EmuSyscallLibrary;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
|
||||
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
||||
import ghidra.pcode.struct.StructuredSleigh;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.data.DataTypeManager;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
||||
/**
|
||||
* A userop library that includes system call simulation
|
||||
*
|
||||
* <p>
|
||||
* Such a library needs to implement {@link EmuSyscallLibrary}. Here we extend
|
||||
* {@link AnnotatedEmuSyscallUseropLibrary}, which allows us to implement it using annotated
|
||||
* methods. {@link EmuSyscallLibrary#syscall(PcodeExecutor, PcodeUseropLibrary)} is the system call
|
||||
* dispatcher, and it requires that each system call implement {@link EmuSyscallDefinition}. System
|
||||
* call libraries typically implement that interface by annotating p-code userops with
|
||||
* {@link EmuSyscall}. This allows system calls to be implemented via Java callback or Structured
|
||||
* Sleigh. Conventionally, the Java method names of system calls should be
|
||||
* <em>platform</em>_<em>name</em>. This is to prevent name conflicts among userops when several
|
||||
* libraries are composed.
|
||||
*
|
||||
* <p>
|
||||
* Stock implementations for a limited set of Linux system calls are provided for x86 and amd64 in
|
||||
* {@link EmuLinuxX86SyscallUseropLibrary} and {@link EmuLinuxAmd64SyscallUseropLibrary},
|
||||
* respectively. The type hierarchy is designed to facilitate the implementation of related systems
|
||||
* without (too much) code duplication. Because they derive from the annotation-based
|
||||
* implementations, you can add missing system calls by extending one and adding annotated methods
|
||||
* as needed.
|
||||
*
|
||||
* <p>
|
||||
* For demonstration, this will implement one from scratch for no particular operating system, but
|
||||
* it will borrow many conventions from Linux-amd64.
|
||||
*/
|
||||
public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]> {
|
||||
private final static Charset UTF8 = Charset.forName("utf8");
|
||||
|
||||
// Implement all the required plumbing first:
|
||||
|
||||
/**
|
||||
* An exception type for "user errors." These errors should be communicated back to the target
|
||||
* program rather than causing the emulator to interrupt. This is a bare minimum implementation.
|
||||
* In practice more information should be communicated internally, in case things go further
|
||||
* wrong. Also, a hierarchy of exceptions may be appropriate.
|
||||
*/
|
||||
static class UserError extends PcodeExecutionException {
|
||||
private final int errno;
|
||||
|
||||
public UserError(int errno) {
|
||||
super("errno: " + errno);
|
||||
this.errno = errno;
|
||||
}
|
||||
}
|
||||
|
||||
private final Register regRAX;
|
||||
private final GhidraScript script;
|
||||
|
||||
/**
|
||||
* Because the system call numbering is derived from the "syscall" overlay on OTHER space, a
|
||||
* program is required. Use the system call analyzer on your program to populate this space. The
|
||||
* program and its compiler spec are also used to derive (what it can of) the system call ABI.
|
||||
* Notably, it applies the calling convention of the functions placed in syscall overlay. Those
|
||||
* parts which cannot (yet) be derived from the program are instead implemented as abstract
|
||||
* methods of this class, e.g., {@link #readSyscallNumber(PcodeExecutorStatePiece)} and
|
||||
* {@link #handleError(PcodeExecutor, PcodeExecutionException)}.
|
||||
*
|
||||
* @param machine the emulator
|
||||
* @param program the program being emulated
|
||||
*/
|
||||
public DemoSyscallLibrary(PcodeMachine<byte[]> machine, Program program, GhidraScript script) {
|
||||
super(machine, program);
|
||||
this.script = script;
|
||||
this.regRAX = machine.getLanguage().getRegister("RAX");
|
||||
if (regRAX == null) {
|
||||
throw new AssertionError("This library only works on x64 targets");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* The dispatcher doesn't know where the system call number is stored. It relies on this method
|
||||
* to read that number from the state. Here we'll assume the target is x64 and RAX contains the
|
||||
* syscall number.
|
||||
*/
|
||||
@Override
|
||||
public long readSyscallNumber(PcodeExecutorState<byte[]> state, Reason reason) {
|
||||
return Utils.bytesToLong(state.getVar(regRAX, reason), regRAX.getNumBytes(),
|
||||
machine.getLanguage().isBigEndian());
|
||||
}
|
||||
|
||||
/**
|
||||
* If the error is a user error, put the errno into the machine as expected by the target
|
||||
* program. Here we negate the errno and put it into RAX. If it's not a user error, we return
|
||||
* false letting the dispatcher know it should interrupt the emulator.
|
||||
*/
|
||||
@Override
|
||||
public boolean handleError(PcodeExecutor<byte[]> executor, PcodeExecutionException err) {
|
||||
if (err instanceof UserError) {
|
||||
executor.getState()
|
||||
.setVar(regRAX, executor.getArithmetic()
|
||||
.fromConst(-((UserError) err).errno, regRAX.getNumBytes()));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for Structured Sleigh is built-in. To enable it, override this method and instantiate
|
||||
* the appropriate (usually nested) class.
|
||||
*/
|
||||
@Override
|
||||
protected StructuredPart newStructuredPart() {
|
||||
return new DemoStructuredPart();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<DataTypeManager> getAdditionalArchives() {
|
||||
// Add platform-specific data type archives, if needed
|
||||
return super.getAdditionalArchives();
|
||||
}
|
||||
|
||||
// Now, implement some system calls!
|
||||
|
||||
// First, a Java callback example
|
||||
|
||||
/**
|
||||
* Write a buffer of utf-8 characters to the console
|
||||
*
|
||||
* <p>
|
||||
* The {@link EmuSyscall} annotation allows us to specify the system call name, because the
|
||||
* userop name should be prefixed with the platform name, to avoid naming collisions among
|
||||
* composed libraries.
|
||||
*
|
||||
* <p>
|
||||
* For demonstration, we will export this as a system call, though that is not required for
|
||||
* {@link DemoStructuredPart#demo_console(StructuredSleigh.Var)} to invoke it. It does need to
|
||||
* be a userop, but it doesn't need to be a syscall.
|
||||
*
|
||||
* @param str a pointer to the start of the buffer
|
||||
* @param end a pointer to the end (exclusive) of the buffer
|
||||
*/
|
||||
@PcodeUserop
|
||||
@EmuSyscall("write")
|
||||
public void demo_write(@OpExecutor PcodeExecutor<byte[]> executor, byte[] str, byte[] end) {
|
||||
AddressSpace space = machine.getLanguage().getDefaultSpace();
|
||||
/**
|
||||
* Because we have concrete {@code byte[]}, we could use Utils.bytesToLong, but for
|
||||
* demonstration, here's how it can be done if we extended
|
||||
* {@link AnnotatedEmuSyscallUseropLibrary}{@code <T>} instead. If the value cannot be made
|
||||
* concrete, an exception will be thrown. For abstract types, it's a good idea to save a
|
||||
* copy of the arithmetic as a field at library construction time.
|
||||
*/
|
||||
PcodeArithmetic<byte[]> arithmetic = machine.getArithmetic();
|
||||
long strLong = arithmetic.toLong(str, Purpose.LOAD);
|
||||
long endLong = arithmetic.toLong(end, Purpose.OTHER);
|
||||
|
||||
byte[] stringBytes = machine.getSharedState()
|
||||
.getVar(space, strLong, (int) (endLong - strLong), true, executor.getReason());
|
||||
String string = new String(stringBytes, UTF8);
|
||||
script.println(string);
|
||||
}
|
||||
|
||||
// Second, a Structured Sleigh example
|
||||
|
||||
/**
|
||||
* The nested class for syscalls implemented using Structured Sleigh. Note that no matter the
|
||||
* implementation type, the Java method is annotated with {@link EmuSyscall}. We declare the
|
||||
* class public so that the annotation processor can access the methods. Alternatively, we could
|
||||
* override {@link #getMethodLookup()} to provide the processor private access.
|
||||
*/
|
||||
public class DemoStructuredPart extends StructuredPart {
|
||||
/**
|
||||
* This creates a handle to the "demo_write" p-code userop for use in Structured Sleigh.
|
||||
* Otherwise, there's no way to refer to the userop. Think of it like a "forward" or
|
||||
* "external" declaration.
|
||||
*/
|
||||
UseropDecl write = userop(type("void"), "demo_write", types("char *", "char *"));
|
||||
|
||||
/**
|
||||
* Write a C-style string to the console
|
||||
*
|
||||
* @param str the null-terminated utf-8 string
|
||||
*/
|
||||
@StructuredUserop
|
||||
@EmuSyscall("console")
|
||||
public void demo_console(@Param(type = "char *") Var str) {
|
||||
// Measure the string's length and then invoke write
|
||||
Var end = local("end", type("char *"));
|
||||
_for(end.set(str), end.deref().neq(0), end.inc(), () -> {
|
||||
});
|
||||
write.call(str, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,427 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.emulation.BytesDebuggerPcodeEmulator;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess;
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.app.tablechooser.*;
|
||||
import ghidra.debug.flatapi.FlatDebuggerAPI;
|
||||
import ghidra.docking.settings.*;
|
||||
import ghidra.pcode.emu.BytesPcodeThread;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
||||
import ghidra.pcode.struct.StructuredSleigh;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.ByteMemBufferImpl;
|
||||
import ghidra.program.model.symbol.Symbol;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.guest.TracePlatform;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
/**
|
||||
* NOTE: Testing with bash: set_shellopts
|
||||
*/
|
||||
public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI {
|
||||
|
||||
public class Injects extends StructuredSleigh {
|
||||
Var RAX = lang("RAX", type("void *"));
|
||||
Var RSP = lang("RSP", type("void *"));
|
||||
|
||||
protected Injects() {
|
||||
super(currentProgram);
|
||||
}
|
||||
|
||||
public Var POP() {
|
||||
Var tgt = local("tgt", RSP.cast(type("void **")).deref());
|
||||
RSP.set(RSP.addi(8));
|
||||
return tgt;
|
||||
}
|
||||
|
||||
public void RET() {
|
||||
_goto(POP());
|
||||
}
|
||||
|
||||
public void RET(RVal val) {
|
||||
RAX.set(val);
|
||||
RET();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: A framework for stubbing the functions. This is close, and the system calls stuff
|
||||
* can get us closer in its handling of calling conventions. We need either to generate
|
||||
* Sleigh that gets the parameters in place, or if we're going to use the aliasing idea that
|
||||
* the syscall stuff does, then we need to allow injection of the already-compiled Sleigh
|
||||
* program. For now, we'll have to declare the parameter-holding register as a language
|
||||
* variable.
|
||||
*
|
||||
* @param s
|
||||
*/
|
||||
@StructuredUserop
|
||||
public void strlen(/*@Param(name = "RDI", type = "char *") Var s*/) {
|
||||
Var s = lang("RDI", type("char *"));
|
||||
Var t = temp(type("char *"));
|
||||
_for(t.set(s), t.deref().neq(0), t.inc(), () -> {
|
||||
});
|
||||
RET(t.subi(s));
|
||||
}
|
||||
}
|
||||
|
||||
public final List<Watch> watches = List.of(
|
||||
watch("RAX", type("int")),
|
||||
watch("RCX", type("int"),
|
||||
set(FormatSettingsDefinition.DEF, FormatSettingsDefinition.DECIMAL)),
|
||||
watch("RSP", type("void *")));
|
||||
// TODO: Snarf from Watches window?
|
||||
|
||||
@Override
|
||||
protected void run() throws Exception {
|
||||
Trace trace = emulateLaunch(currentProgram, currentAddress);
|
||||
TracePlatform platform = trace.getPlatformManager().getHostPlatform();
|
||||
long snap = 0;
|
||||
|
||||
TableChooserDialog tableDialog =
|
||||
createTableChooserDialog("Desk Check", new CheckRowChooser());
|
||||
|
||||
tableDialog.show();
|
||||
|
||||
tableDialog.addCustomColumn(new CheckRowScheduleDisplay());
|
||||
tableDialog.addCustomColumn(new CheckRowCounterDisplay());
|
||||
|
||||
List<PcodeExpression> compiled = new ArrayList<>();
|
||||
TypeLoader loader = new TypeLoader(currentProgram);
|
||||
for (Watch w : watches) {
|
||||
PcodeExpression ce = SleighProgramCompiler
|
||||
.compileExpression((SleighLanguage) platform.getLanguage(), w.expression);
|
||||
tableDialog.addCustomColumn(new CheckRowWatchDisplay(loader, w, compiled.size()));
|
||||
compiled.add(ce);
|
||||
}
|
||||
|
||||
TraceSchedule schedule;
|
||||
while (true) {
|
||||
try {
|
||||
schedule = TraceSchedule
|
||||
.parse(askString("Schedule", "Enter the steping schedule", "0:t0-1000"));
|
||||
break;
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.showError(this, null, "Schedule", "Error: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
BytesDebuggerPcodeEmulator emu = new BytesDebuggerPcodeEmulator(
|
||||
new DefaultPcodeDebuggerAccess(state.getTool(), null, platform, snap)) {
|
||||
TraceSchedule position = TraceSchedule.snap(snap);
|
||||
|
||||
@Override
|
||||
protected BytesPcodeThread createThread(String name) {
|
||||
return new BytesPcodeThread(name, this) {
|
||||
TraceThread thread = trace.getThreadManager().getLiveThreadByPath(snap, name);
|
||||
PcodeExecutor<Pair<byte[], ValueLocation>> inspector =
|
||||
new PcodeExecutor<>(language,
|
||||
new PairedPcodeArithmetic<>(arithmetic,
|
||||
LocationPcodeArithmetic.forEndian(language.isBigEndian())),
|
||||
state.paired(new LocationPcodeExecutorStatePiece(language)),
|
||||
Reason.INSPECT);
|
||||
|
||||
{
|
||||
tableDialog.add(createRow());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stepInstruction() {
|
||||
super.stepInstruction();
|
||||
position = position.steppedForward(thread, 1);
|
||||
tableDialog.add(createRow());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stepPcodeOp() {
|
||||
super.stepPcodeOp();
|
||||
position = position.steppedPcodeForward(thread, 1);
|
||||
tableDialog.add(createRow());
|
||||
}
|
||||
|
||||
public CheckRow createRow() {
|
||||
List<Pair<byte[], ValueLocation>> values = new ArrayList<>();
|
||||
for (PcodeExpression exp : compiled) {
|
||||
values.add(exp.evaluate(inspector));
|
||||
}
|
||||
return new CheckRow(position, getCounter(), values);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
for (SleighPcodeUseropDefinition<?> inject : new Injects().generate().values()) {
|
||||
String source = inject.getBody();
|
||||
println("Injecting " + inject.getName() + ":\n" + source);
|
||||
for (Symbol sym : currentProgram.getSymbolTable()
|
||||
.getExternalSymbols(inject.getName())) {
|
||||
if (sym.getObject() instanceof Function fun) {
|
||||
Set<Address> addresses =
|
||||
new HashSet<>(List.of(fun.getFunctionThunkAddresses(true)));
|
||||
addresses.add(fun.getEntryPoint());
|
||||
for (Address sEntry : addresses) {
|
||||
Address dEntry = translateStaticToDynamic(sEntry);
|
||||
println(" " + sEntry + " ( -> " + dEntry + ")");
|
||||
emu.inject(dEntry, source);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
schedule.execute(trace, emu, monitor);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////
|
||||
// Configuration and support kruft below //
|
||||
///////////////////////////////////////////
|
||||
|
||||
public record Watch(String expression, TypeRec type, Settings settings) {
|
||||
}
|
||||
|
||||
interface Setting {
|
||||
void set(Settings settings);
|
||||
}
|
||||
|
||||
public record EnumSetting(EnumSettingsDefinition def, int value) implements Setting {
|
||||
@Override
|
||||
public void set(Settings settings) {
|
||||
def.setChoice(settings, value);
|
||||
}
|
||||
}
|
||||
|
||||
class TypeLoader extends StructuredSleigh {
|
||||
protected TypeLoader(Program program) {
|
||||
super(program);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DataType type(String path) {
|
||||
return super.type(path);
|
||||
}
|
||||
}
|
||||
|
||||
public record TypeRec(String path) {
|
||||
DataType get(TypeLoader loader) {
|
||||
return loader.type(path);
|
||||
}
|
||||
}
|
||||
|
||||
TypeRec type(String path) {
|
||||
return new TypeRec(path);
|
||||
}
|
||||
|
||||
static Setting set(EnumSettingsDefinition def, int value) {
|
||||
return new EnumSetting(def, value);
|
||||
}
|
||||
|
||||
static Watch watch(String expression, TypeRec type, Setting... settings) {
|
||||
Settings settingsImpl = new SettingsImpl();
|
||||
for (Setting set : settings) {
|
||||
set.set(settingsImpl);
|
||||
}
|
||||
return new Watch(expression, type, settingsImpl);
|
||||
}
|
||||
|
||||
class CheckRow implements AddressableRowObject {
|
||||
private final TraceSchedule schedule;
|
||||
private final Address pc;
|
||||
private final List<Pair<byte[], ValueLocation>> values;
|
||||
|
||||
public CheckRow(TraceSchedule schedule, Address pc,
|
||||
List<Pair<byte[], ValueLocation>> values) {
|
||||
this.schedule = schedule;
|
||||
this.pc = pc;
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getAddress() { // Instruction address
|
||||
TraceProgramView view = getCurrentView();
|
||||
if (view == null) {
|
||||
return Address.NO_ADDRESS;
|
||||
}
|
||||
Address st = translateDynamicToStatic(pc);
|
||||
return st == null ? Address.NO_ADDRESS : st;
|
||||
}
|
||||
}
|
||||
|
||||
public interface TypedDisplay<R, T> extends ColumnDisplay<T> {
|
||||
int compareTyped(R r1, R r2);
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
default int compare(AddressableRowObject o1, AddressableRowObject o2) {
|
||||
return compareTyped((R) o1, (R) o2);
|
||||
}
|
||||
|
||||
T getTypedValue(R r);
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
default T getColumnValue(AddressableRowObject rowObject) {
|
||||
return getTypedValue((R) rowObject);
|
||||
}
|
||||
}
|
||||
|
||||
public class CheckRowScheduleDisplay implements TypedDisplay<CheckRow, TraceSchedule> {
|
||||
@Override
|
||||
public int compareTyped(CheckRow r1, CheckRow r2) {
|
||||
return r1.schedule.compareTo(r2.schedule);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceSchedule getTypedValue(CheckRow row) {
|
||||
return row.schedule;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Schedule";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<TraceSchedule> getColumnClass() {
|
||||
return TraceSchedule.class;
|
||||
}
|
||||
}
|
||||
|
||||
public class CheckRowCounterDisplay implements TypedDisplay<CheckRow, Address> {
|
||||
@Override
|
||||
public int compareTyped(CheckRow r1, CheckRow r2) {
|
||||
return r1.pc.compareTo(r2.pc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getTypedValue(CheckRow row) {
|
||||
return row.pc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Counter";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Address> getColumnClass() {
|
||||
return Address.class;
|
||||
}
|
||||
}
|
||||
|
||||
public class CheckRowWatchDisplay implements TypedDisplay<CheckRow, Object> {
|
||||
private final boolean isBigEndian = currentProgram.getLanguage().isBigEndian();
|
||||
private final Watch watch;
|
||||
private final DataType type;
|
||||
private final int index;
|
||||
private final Class<?> valueClass;
|
||||
|
||||
public CheckRowWatchDisplay(TypeLoader loader, Watch watch, int index) {
|
||||
this.watch = watch;
|
||||
this.type = watch.type.get(loader);
|
||||
this.index = index;
|
||||
this.valueClass = type.getValueClass(watch.settings);
|
||||
}
|
||||
|
||||
private Object getObjectValue(CheckRow r) {
|
||||
try {
|
||||
Pair<byte[], ValueLocation> p = r.values.get(index);
|
||||
Address addr = p.getRight() == null ? null : p.getRight().getAddress();
|
||||
byte[] bytes = p.getLeft();
|
||||
return type.getValue(new ByteMemBufferImpl(addr, bytes, isBigEndian),
|
||||
watch.settings, bytes.length);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return "Err: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private String getStringValue(CheckRow r) {
|
||||
try {
|
||||
Pair<byte[], ValueLocation> p = r.values.get(index);
|
||||
Address addr = p.getRight() == null ? null : p.getRight().getAddress();
|
||||
byte[] bytes = p.getLeft();
|
||||
return type.getRepresentation(new ByteMemBufferImpl(addr, bytes, isBigEndian),
|
||||
watch.settings, bytes.length);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return "Err: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public int compareTyped(CheckRow r1, CheckRow r2) {
|
||||
if (Comparable.class.isAssignableFrom(valueClass)) {
|
||||
Object v1 = getObjectValue(r1);
|
||||
Object v2 = getObjectValue(r2);
|
||||
return ((Comparable) v1).compareTo(v2);
|
||||
}
|
||||
String s1 = getStringValue(r1);
|
||||
String s2 = getStringValue(r2);
|
||||
return s1.compareTo(s2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypedValue(CheckRow row) {
|
||||
return getStringValue(row);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return watch.expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Class<Object> getColumnClass() {
|
||||
return (Class<Object>) valueClass;
|
||||
}
|
||||
}
|
||||
|
||||
private final class CheckRowChooser implements TableChooserExecutor {
|
||||
@Override
|
||||
public String getButtonName() {
|
||||
return "Go To";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(AddressableRowObject rowObject) {
|
||||
CheckRow row = (EmuDeskCheckScript.CheckRow) rowObject;
|
||||
try {
|
||||
emulate(row.schedule, monitor);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// Just be done
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
//An example emulation script that uses a stand-alone emulator.
|
||||
//It provides the set-up code and then demonstrates some use cases.
|
||||
//@author
|
||||
//@category Emulation
|
||||
//@keybinding
|
||||
//@menupath
|
||||
//@toolbar
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import ghidra.app.plugin.assembler.*;
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.pcode.emu.PcodeEmulator;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.lang.LanguageID;
|
||||
|
||||
public class StandAloneEmuExampleScript extends GhidraScript {
|
||||
private final static Charset UTF8 = Charset.forName("utf8");
|
||||
private SleighLanguage language;
|
||||
private PcodeEmulator emulator;
|
||||
|
||||
@Override
|
||||
protected void run() throws Exception {
|
||||
/*
|
||||
* Create an emulator and start a thread
|
||||
*/
|
||||
language = (SleighLanguage) getLanguage(new LanguageID("x86:LE:64:default"));
|
||||
emulator = new PcodeEmulator(language) {
|
||||
@Override
|
||||
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
|
||||
return new DemoPcodeUseropLibrary(language, StandAloneEmuExampleScript.this);
|
||||
}
|
||||
|
||||
// Uncomment this to see instructions printed as they are decoded
|
||||
/*
|
||||
protected BytesPcodeThread createThread(String name) {
|
||||
return new BytesPcodeThread(name, this) {
|
||||
@Override
|
||||
protected SleighInstructionDecoder createInstructionDecoder(
|
||||
PcodeExecutorState<byte[]> sharedState) {
|
||||
return new SleighInstructionDecoder(language, sharedState) {
|
||||
@Override
|
||||
public Instruction decodeInstruction(Address address,
|
||||
RegisterValue context) {
|
||||
Instruction instruction = super.decodeInstruction(address, context);
|
||||
println("Decoded " + address + ": " + instruction);
|
||||
return instruction;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
*/
|
||||
};
|
||||
PcodeThread<byte[]> thread = emulator.newThread();
|
||||
// The emulator composes the full library for each thread
|
||||
PcodeUseropLibrary<byte[]> library = thread.getUseropLibrary();
|
||||
AddressSpace dyn = language.getDefaultSpace();
|
||||
|
||||
/*
|
||||
* Assemble a little test program and write it into the emulator
|
||||
*
|
||||
* We're not really going to implement system calls here. We're just using it to demonstrate
|
||||
* the implementation of a language-defined userop.
|
||||
*/
|
||||
Address entry = dyn.getAddress(0x00400000);
|
||||
Assembler asm = Assemblers.getAssembler(language);
|
||||
AssemblyBuffer buffer = new AssemblyBuffer(asm, entry);
|
||||
buffer.assemble("MOV RCX, 0xdeadbeef");
|
||||
Address injectHere = buffer.getNext();
|
||||
buffer.assemble("MOV RAX, 1");
|
||||
buffer.assemble("SYSCALL");
|
||||
buffer.assemble("MOV RAX, 2"); // Induce the interrupt we need to terminate
|
||||
buffer.assemble("SYSCALL");
|
||||
byte[] code = buffer.getBytes();
|
||||
emulator.getSharedState().setVar(dyn, entry.getOffset(), code.length, true, code);
|
||||
|
||||
/*
|
||||
* Initialize other parts of the emulator and thread state. Note the use of the L suffix on
|
||||
* 0xdeadbeefL, because Java with sign extend the (negative) int to a long otherwise.
|
||||
*/
|
||||
byte[] hw = "Hello, World!\n".getBytes(UTF8);
|
||||
emulator.getSharedState().setVar(dyn, 0xdeadbeefL, hw.length, true, hw);
|
||||
PcodeProgram init = SleighProgramCompiler.compileProgram(language, "init", String.format("""
|
||||
RIP = 0x%s;
|
||||
RSP = 0x00001000;
|
||||
""", entry), library);
|
||||
thread.getExecutor().execute(init, library);
|
||||
thread.overrideContextWithDefault();
|
||||
thread.reInitialize();
|
||||
|
||||
/*
|
||||
* Inject a call to our custom print userop. Otherwise, the language itself will never
|
||||
* invoke it.
|
||||
*/
|
||||
emulator.inject(injectHere, """
|
||||
print_utf8(RCX);
|
||||
emu_exec_decoded();
|
||||
""");
|
||||
|
||||
/*
|
||||
* Run the experiment: This should interrupt on the second SYSCALL, because any value other
|
||||
* than 1 calls emu_swi.
|
||||
*/
|
||||
try {
|
||||
thread.stepInstruction(10);
|
||||
printerr("We should not have completed 10 steps!");
|
||||
}
|
||||
catch (InterruptPcodeExecutionException e) {
|
||||
println("Terminated via interrupt. Good.");
|
||||
}
|
||||
|
||||
/*
|
||||
* Inspect the machine. You can always do this by accessing the state directly, but for
|
||||
* anything other than simple variables, you may find compiling an expression more
|
||||
* convenient.
|
||||
*/
|
||||
println("RCX = " +
|
||||
Utils.bytesToLong(thread.getState().getVar(language.getRegister("RCX"), Reason.INSPECT),
|
||||
8, language.isBigEndian()));
|
||||
|
||||
println("RCX = " + Utils.bytesToLong(
|
||||
SleighProgramCompiler.compileExpression(language, "RCX").evaluate(thread.getExecutor()),
|
||||
8, language.isBigEndian()));
|
||||
|
||||
println("RCX+4 = " +
|
||||
Utils.bytesToLong(SleighProgramCompiler.compileExpression(language, "RCX+4")
|
||||
.evaluate(thread.getExecutor()),
|
||||
8, language.isBigEndian()));
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
//An example script for using Structured Sleigh stand alone
|
||||
//@author
|
||||
//@category Sleigh
|
||||
//@keybinding
|
||||
//@menupath
|
||||
//@toolbar
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodHandles.Lookup;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.pcode.exec.SleighPcodeUseropDefinition;
|
||||
import ghidra.pcode.struct.StructuredSleigh;
|
||||
import ghidra.program.model.lang.LanguageID;
|
||||
|
||||
public class StandAloneStructuredSleighScript extends GhidraScript {
|
||||
private SleighLanguage language;
|
||||
|
||||
/**
|
||||
* This exists mostly so we can access the methods of anonymous nested classes deriving from
|
||||
* this one. The "compiler" will need to be able to access the methods, and that's not
|
||||
* ordinarily allowed since anonymous classes are implicitly "private." Conveniently, it also
|
||||
* allows us to implement a default constructor, so that can be elided where used, too.
|
||||
*/
|
||||
class LookupStructuredSleigh extends StructuredSleigh {
|
||||
protected LookupStructuredSleigh() {
|
||||
super(language.getDefaultCompilerSpec());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Lookup getMethodLookup() {
|
||||
return MethodHandles.lookup();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() throws Exception {
|
||||
/*
|
||||
* If you have a target language in mind, perhaps use it, but DATA provides a minimal
|
||||
* context
|
||||
*/
|
||||
language = (SleighLanguage) getLanguage(new LanguageID("DATA:BE:64:default"));
|
||||
|
||||
Map<String, SleighPcodeUseropDefinition<Object>> ops = new LookupStructuredSleigh() {
|
||||
/**
|
||||
* Add two in-memory vectors of 16 longs and store the result in memory
|
||||
*
|
||||
* @param d pointer to the destination vector
|
||||
* @param s1 pointer to the first operand vector
|
||||
* @param s2 pointer to the second operand vector
|
||||
*/
|
||||
@StructuredUserop
|
||||
public void vector_add(
|
||||
@Param(name = "d", type = "int *") Var d,
|
||||
@Param(name = "s1", type = "int *") Var s1,
|
||||
@Param(name = "s2", type = "int *") Var s2) {
|
||||
// Use Java's "for" to generate an unrolled loop
|
||||
// We could choose a Sleigh loop, instead. Consider both emu and analysis tradeoffs
|
||||
for (int i = 0; i < 16; i++) {
|
||||
// This will generate +0 on the first elements, but whatever
|
||||
d.index(i).deref().set(s1.index(i).deref().addi(s2.index(i).deref()));
|
||||
}
|
||||
}
|
||||
|
||||
@StructuredUserop
|
||||
public void memcpy(
|
||||
@Param(name = "d", type = "void *") Var d,
|
||||
@Param(name = "s", type = "void *") Var s,
|
||||
@Param(name = "n", type = "long") Var n) { // size_t is not built-in
|
||||
Var i = local("i", type("long"));
|
||||
// Note that these 2 casts don't generate Sleigh statements
|
||||
Var db = d.cast(type("byte *"));
|
||||
Var sb = s.cast(type("byte *"));
|
||||
// Must use a Sleigh loop here
|
||||
_for(i.set(0), i.ltiu(n), i.inc(), () -> {
|
||||
db.index(i).deref().set(sb.index(i).deref());
|
||||
});
|
||||
}
|
||||
}.generate();
|
||||
|
||||
/*
|
||||
* Now, dump the generated Sleigh source
|
||||
*/
|
||||
for (SleighPcodeUseropDefinition<?> userop : ops.values()) {
|
||||
print(userop.getName() + "(");
|
||||
print(userop.getInputs().stream().collect(Collectors.joining(",")));
|
||||
print(") {\n");
|
||||
print(userop.getBody());
|
||||
print("}\n\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,235 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
//An example emulation script that uses a stand-alone emulator with syscalls.
|
||||
//It provides the set-up code and then demonstrates some use cases.
|
||||
//@author
|
||||
//@category Emulation
|
||||
//@keybinding
|
||||
//@menupath
|
||||
//@toolbar
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import ghidra.app.plugin.assembler.Assembler;
|
||||
import ghidra.app.plugin.assembler.Assemblers;
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.pcode.emu.PcodeEmulator;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.emu.sys.EmuInvalidSystemCallException;
|
||||
import ghidra.pcode.emu.sys.EmuSyscallLibrary;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.database.ProgramDB;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.data.DataTypeConflictHandler;
|
||||
import ghidra.program.model.data.PointerDataType;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.Memory;
|
||||
import ghidra.program.model.mem.MemoryBlock;
|
||||
import ghidra.program.model.symbol.SourceType;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
|
||||
public class StandAloneSyscallEmuExampleScript extends GhidraScript {
|
||||
private final static Charset UTF8 = Charset.forName("utf8");
|
||||
|
||||
Program program = null;
|
||||
|
||||
@Override
|
||||
protected void run() throws Exception {
|
||||
/*
|
||||
* First, get all the services and stuff:
|
||||
*/
|
||||
SleighLanguage language = (SleighLanguage) getLanguage(new LanguageID("x86:LE:64:default"));
|
||||
|
||||
/*
|
||||
* I'll generate a new program, because I don't want to require the user to pick something
|
||||
* specific. It won't be displayed, though, so we'll just release it when we're done.
|
||||
*/
|
||||
Address entry;
|
||||
try {
|
||||
/*
|
||||
* "gcc" is the name of the compiler spec, but we're really interested in the Linux
|
||||
* syscall calling conventions.
|
||||
*/
|
||||
program =
|
||||
new ProgramDB("syscall_example", language,
|
||||
language.getCompilerSpecByID(new CompilerSpecID("gcc")), this);
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Init")) {
|
||||
AddressSpace space = program.getAddressFactory().getDefaultAddressSpace();
|
||||
entry = space.getAddress(0x00400000);
|
||||
Address dataEntry = space.getAddress(0x00600000);
|
||||
Memory memory = program.getMemory();
|
||||
memory.createInitializedBlock(".text", entry, 0x1000, (byte) 0, monitor, false);
|
||||
Assembler asm = Assemblers.getAssembler(program);
|
||||
asm.assemble(entry,
|
||||
"MOV RDI, 0x" + dataEntry,
|
||||
"MOV RAX, 1",
|
||||
"SYSCALL",
|
||||
"MOV RAX, 20",
|
||||
"SYSCALL");
|
||||
memory.createInitializedBlock(".data", dataEntry, 0x1000, (byte) 0, monitor, false);
|
||||
memory.setBytes(dataEntry, "Hello, World!\n".getBytes(UTF8));
|
||||
|
||||
/*
|
||||
* Because "pointer" is a built-in type, and the emulator does not modify the
|
||||
* program, we must ensure it has been resolved on the program's data type manager.
|
||||
*/
|
||||
program.getDataTypeManager()
|
||||
.resolve(PointerDataType.dataType, DataTypeConflictHandler.DEFAULT_HANDLER);
|
||||
|
||||
/*
|
||||
* We must also populate the system call numbering map. Ordinarily, this would be done
|
||||
* using the system call analyzer or another script. Here, we'll just fake it out.
|
||||
*/
|
||||
AddressSpace other =
|
||||
program.getAddressFactory().getAddressSpace(SpaceNames.OTHER_SPACE_NAME);
|
||||
MemoryBlock blockSyscall = program.getMemory()
|
||||
.createUninitializedBlock(EmuSyscallLibrary.SYSCALL_SPACE_NAME,
|
||||
other.getAddress(0), 0x1000, true);
|
||||
blockSyscall.setPermissions(true, false, true);
|
||||
|
||||
AddressSpace syscall = program.getAddressFactory()
|
||||
.getAddressSpace(EmuSyscallLibrary.SYSCALL_SPACE_NAME);
|
||||
/*
|
||||
* The system call names must match those from the EmuSyscall annotations in the
|
||||
* system call library, in our case from DemoSyscallLibrary. Because the x64
|
||||
* compiler specs define a "syscall" convention, we'll apply it. The syscall
|
||||
* dispatcher will use that convention to fetch the parameters out of the machine
|
||||
* state, pass them into the system call defintion, and store the result back into
|
||||
* the machine.
|
||||
*/
|
||||
// Map system call 0 to "write"
|
||||
program.getFunctionManager()
|
||||
.createFunction("write", syscall.getAddress(0),
|
||||
new AddressSet(syscall.getAddress(0)), SourceType.USER_DEFINED)
|
||||
.setCallingConvention(EmuSyscallLibrary.SYSCALL_CONVENTION_NAME);
|
||||
// Map system call 1 to "console"
|
||||
program.getFunctionManager()
|
||||
.createFunction("console", syscall.getAddress(1),
|
||||
new AddressSet(syscall.getAddress(1)), SourceType.USER_DEFINED)
|
||||
.setCallingConvention(EmuSyscallLibrary.SYSCALL_CONVENTION_NAME);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create an emulator and start a thread
|
||||
*/
|
||||
PcodeEmulator emulator = new PcodeEmulator(language) {
|
||||
@Override
|
||||
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
|
||||
return new DemoSyscallLibrary(this, program,
|
||||
StandAloneSyscallEmuExampleScript.this);
|
||||
}
|
||||
|
||||
// Uncomment this to see instructions printed as they are decoded
|
||||
/*
|
||||
@Override
|
||||
protected BytesPcodeThread createThread(String name) {
|
||||
return new BytesPcodeThread(name, this) {
|
||||
@Override
|
||||
protected SleighInstructionDecoder createInstructionDecoder(
|
||||
PcodeExecutorState<byte[]> sharedState) {
|
||||
return new SleighInstructionDecoder(language, sharedState) {
|
||||
@Override
|
||||
public Instruction decodeInstruction(Address address,
|
||||
RegisterValue context) {
|
||||
Instruction instruction = super.decodeInstruction(address, context);
|
||||
println("Decoded " + address + ": " + instruction);
|
||||
return instruction;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
*/
|
||||
};
|
||||
PcodeThread<byte[]> thread = emulator.newThread();
|
||||
// The emulator composes the full library for each thread
|
||||
PcodeUseropLibrary<byte[]> library = thread.getUseropLibrary();
|
||||
|
||||
/*
|
||||
* The library has a reference to the program and uses it to derive types and the system
|
||||
* call numbering. However, the emulator itself does not have access to the program. If we
|
||||
* followed the pattern in DebuggerEmuExampleScript, the emulator would have its state bound
|
||||
* (indirectly) to the program. We'll need to copy the bytes in. Because we created blocks
|
||||
* that were 0x1000 bytes in size, we can be fast and loose with our buffer. Ordinarily, you
|
||||
* may want to copy in chunks rather than taking entire memory blocks at a time.
|
||||
*/
|
||||
byte[] data = new byte[0x1000];
|
||||
for (MemoryBlock block : program.getMemory().getBlocks()) {
|
||||
if (!block.isInitialized()) {
|
||||
continue; // Skip the syscall/OTHER block
|
||||
}
|
||||
Address addr = block.getStart();
|
||||
block.getBytes(addr, data);
|
||||
emulator.getSharedState()
|
||||
.setVar(addr.getAddressSpace(), addr.getOffset(), data.length, true, data);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the thread
|
||||
*/
|
||||
PcodeProgram init =
|
||||
SleighProgramCompiler.compileProgram(language, "init", String.format("""
|
||||
RIP = 0x%s;
|
||||
RSP = 0x00001000;
|
||||
""", entry), library);
|
||||
thread.getExecutor().execute(init, library);
|
||||
thread.overrideContextWithDefault();
|
||||
thread.reInitialize();
|
||||
|
||||
/*
|
||||
* Run the experiment: This should interrupt on the second SYSCALL, because we didn't
|
||||
* provide a system call name in OTHER space for 20.
|
||||
*/
|
||||
try {
|
||||
thread.stepInstruction(10);
|
||||
printerr("We should not have completed 10 steps!");
|
||||
}
|
||||
catch (EmuInvalidSystemCallException e) {
|
||||
println("Terminated via invalid syscall. Good.");
|
||||
}
|
||||
|
||||
/*
|
||||
* Inspect the machine. You can always do this by accessing the state directly, but for
|
||||
* anything other than simple variables, you may find compiling an expression more
|
||||
* convenient.
|
||||
*/
|
||||
println("RDI = " +
|
||||
Utils.bytesToLong(
|
||||
thread.getState().getVar(language.getRegister("RDI"), Reason.INSPECT), 8,
|
||||
language.isBigEndian()));
|
||||
|
||||
println("RDI = " + Utils.bytesToLong(
|
||||
SleighProgramCompiler.compileExpression(language, "RDI")
|
||||
.evaluate(thread.getExecutor()),
|
||||
8, language.isBigEndian()));
|
||||
|
||||
println("RDI+4 = " +
|
||||
Utils.bytesToLong(SleighProgramCompiler.compileExpression(language, "RDI+4")
|
||||
.evaluate(thread.getExecutor()),
|
||||
8, language.isBigEndian()));
|
||||
|
||||
}
|
||||
finally {
|
||||
if (program != null) {
|
||||
program.release(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user