Files
2025-02-21 20:27:02 +00:00

535 lines
18 KiB
Java
Executable File

/* ###
* 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 emits trace information.
//It provides the set-up code and then outputs a trace binary.
//@author
//@category Emulation
//@keybinding
//@menupath
//@toolbar
import java.nio.charset.Charset;
import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils;
import ghidra.app.emulator.EmulatorHelper;
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.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
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.CompilerSpecID;
import ghidra.program.model.lang.LanguageID;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.lang.ConstantPool.Record;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.pcode.emu.BytesPcodeThread;
import ghidra.pcode.emu.DefaultPcodeThread;
import ghidra.pcode.emu.SleighInstructionDecoder;
import ghidra.pcode.emu.ThreadPcodeExecutorState;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.pcode.opbehavior.*;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.util.Map;
import db.Transaction;
import java.util.HashMap;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.math.BigInteger;
public class TraceScript extends GhidraScript {
private final static Charset UTF8 = Charset.forName("utf8");
private class Tracer {
/**
* Record type used to identify the file as a trace.
*/
private final static byte[] MagicBytes = new byte[]{0x65, 0x78, 0x00, 0x3c, 0x7f};
private ArchKind currArch;
public PcodeEmulator emulator;
private OutputStream outputStream;
public Tracer(String filename, String archKindString) throws Exception{
outputStream = new FileOutputStream(filename); //FIXME how do i close this? the finalize() is not right neither is try-with-resources.
currArch = this.getArchKind(archKindString);
emitRecord(RecordKind.MAGIC, MagicBytes);
emitArchRecord(currArch);
}
public static enum ArchKind {
X86(new byte[]{(byte) 0x20}, 4, true),
X86_64(new byte[]{(byte)0x40 }, 8, true),
X86_64Compat32(new byte[]{(byte)0x21 }, 8, true),
PowerPc(new byte[]{(byte) 0x10}, 4, false),
PowerPc64(new byte[]{(byte) 0x11}, 8, false),
Arm(new byte[]{(byte)0x12, (byte) 0x00 }, 4, true),
Arm64(new byte[]{(byte) 0x12, (byte) 0x01}, 8, true),
M68k(new byte[]{(byte) 0x01}, 4, false),
Mips(new byte[]{(byte) 0x60}, 4, false),
Mips64(new byte[]{(byte) 0x61}, 8, false),
Mipsel(new byte[]{(byte) 0x62}, 4, true),
Mipsel64(new byte[]{(byte) 0x63}, 8, true),
Sparc(new byte[]{(byte) 0x80}, 4, false),
Sparc64(new byte[]{(byte) 0x81}, 8, false),
RiscV(new byte[]{(byte) 0x70}, 4, true),
RiscV64(new byte[]{(byte) 0x71}, 8, true);
private final byte[] value;
private final boolean LEBool;
private final int byteSize;
ArchKind(byte[] value, int byteSize, boolean LEBool){
this.value = value;
this.byteSize = byteSize;
this.LEBool = LEBool;
}
byte[] getValue() {
return value;
}
}
/**
* Retrieve the RecordKind based on a String version of the LanguageId
* WARNING: This seem likes a poor method to retrieve this informaiton but it was the suggested method from others and i am not sure of a better way
* @param LanguageIdStr
* @return
* @throws Exception
*/
public static ArchKind getArchKind(String LanguageIdStr) throws Exception{
switch (LanguageIdStr) {
case "x86:LE:64:default":
return Tracer.ArchKind.X86_64;
//TODO add all language types that map to ArchKind Enum.
default:
throw new Exception("Undefined mapping from LanguageId=`" + LanguageIdStr + "'");
}
}
/**
* an structure to hold record kinds
*/
public static enum RecordKind {
/**
* Record type used to identify the file as a trace.
*/
MAGIC ((byte) 0xf0),
/**
* Record type used to specify the architecture of the program being traced.
*/
ARCH ((byte)0x00),
/**
* Record type used to provide meta data that applies to the entire trace.
*/
FILE_META ((byte)0x04),
/**
* Record type that indicates that a new region of memory has been mapped in.
*/
MAP ((byte)0x10),
/**
* Record type that indicates that a region of memory has been unmapped.
*/
UNMAP ((byte)0x1c),
/**
* Record type that indicates that an instruction has been executed.
*/
INSTRUCTION ((byte) 0x20),
/**
* Record type that indicates that a new instruction has been fetched.
*/
PC ((byte) 0x24),
/**
* Record type that provides metadata to the following record(s).
*/
META ((byte) 0x30),
/**
* Record type for an interupt
*/
INTERRUPT ((byte) 0x38),
/**
* Record type that indicates that a register has been read from.
*/
REG_READ ((byte) 0x40),
/**
* Record type that indicates that a register has been written to.
*/
REG_WRITE ((byte) 0x44),
// /**
// * Record type that indicates that a register has been written to using the register number provided by the RegisterNameMap record.
// */
// REG_WRITE_NATIVE ((byte) 0x54),
/**
* Record type that indicates that a memory address has been read from.
*/
MEM_READ ((byte)0x80),
/**
* Record type that indicates that a memory address has been written to.
*/
MEM_WRITE ((byte)0x84);
private final byte value;
RecordKind(byte value){
this.value = value;
}
byte getValue() {
return value;
}
}
private static class VlenRlenObj {
public final byte[] vlen;
public final byte[] rlen;
public VlenRlenObj(byte[] vlen, byte[] rlen) {
this.vlen = vlen;
this.rlen = rlen;
}
}
private static byte VlenLenToLenLen (byte[] vlen) throws Exception {
/*
* 00 = 0 bytes
* 01 = 1 byte
* 10 = 2 bytes
* 11 = 4 bytes
*/
switch (vlen.length) {
case 0 :
return 0b0000_0000;
case 1 :
return 0b0000_0001;
case 2 :
return 0b0000_0010;
case 4 :
return 0b0000_0011;
default:
throw new Exception("Invaild Vlen Length");
}
}
private static VlenRlenObj calc_vlen_rlen(long size) throws Exception{
if (size < (0xff -1)) {
return new VlenRlenObj(u8(size+1), u8(1+1+size));
} else if (((0xff -1) <= size) | (size < (0xffff-4))) {
return new VlenRlenObj(u16(size+4+1), u32(1+2+size));
} else if (((0xffff - 4) <= size) | (size < 0xffff_ffffL- 4)) {
return new VlenRlenObj(u32(size+4+1), u32(1+4+size));
} else {
throw new Exception("Data to large for record");
}
//throw new Exception("I should never get here there was an if else logic error");
}
public static byte[] u8 (long val) {
return new byte[]{(byte)val};
};
public static byte[] u16 ( long val) {
byte[] bb = ByteBuffer.allocate(8).putLong(val).array();
return new byte[] {bb[6], bb[7]};//TODO FIXME IT DONT THINK THIS IS RIGHT!!!!!
}
public static byte[] u32 ( long val) {
byte[] bb = ByteBuffer.allocate(8).putLong(val).array();
return new byte[] {bb[3], bb[2], bb[1], bb[0]}; //TODO FIXME I DONT THINK THIS IS RIGHT!!!!!
}
public static byte[] u32_BE ( long val) {
return ByteBuffer.allocate(4).putLong(val).array();//TODO FIXME I DONT THINK THIS IS RIGHT!!!!!
}
public static byte[] u64 ( long val) {
byte[] bb = ByteBuffer.allocate(8).putLong(val).array();
return new byte[] {bb[7], bb[6], bb[5], bb[4], bb[3], bb[2], bb[1], bb[0]};//TODO FIXME I DONT THINK THIS IS RIGHT!!!!!
}
public static byte[] u64_BE ( long val) {
return ByteBuffer.allocate(8).putLong(val).array(); //TODO FIXME I DONT THINK THIS IS RIGHT!!!!!
}
protected void emitRecord(Tracer.RecordKind kind, byte[] data ) throws Exception {
//vlen, rlen = calculate_vlen_rlen(len(data));
VlenRlenObj vro = calc_vlen_rlen(data.length);
byte combinedKindLen = (byte)(kind.value | VlenLenToLenLen(vro.vlen));
this.outputStream.write(combinedKindLen);
this.outputStream.write(vro.vlen);
this.outputStream.write(data);
this.outputStream.write(vro.rlen);
}
protected void emitArchRecord(ArchKind archKind ) throws Exception {
byte[] a = new byte[]{archKind.value[0],(byte)0,(byte)0,(byte)0};
emitRecord(RecordKind.ARCH, a);
}
protected void emitMemReadRecord(byte[] offset, byte[] data ) throws Exception {
byte[] rec = new byte[offset.length + data.length];
System.arraycopy(offset, 0, rec, 0, offset.length);
System.arraycopy(data, 0, rec, offset.length, data.length);
emitRecord(RecordKind.MEM_READ, rec);
}
protected void emitMemWriteRecord(byte[] offset, byte[] data ) throws Exception {
byte[] rec = new byte[offset.length + data.length];
System.arraycopy(offset, 0, rec, 0, offset.length);
System.arraycopy(data, 0, rec, offset.length, data.length);
emitRecord(RecordKind.MEM_WRITE, rec);
}
protected void emitRegWriteRecord(int regnum, byte[] data ) throws Exception {
byte[] rec = new byte[4 + data.length];
byte[] offset = ByteBuffer.allocate(4).order(currArch.LEBool ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN).putInt(regnum).array();
System.arraycopy(offset, 0, rec, 0, offset.length);
System.arraycopy(data, 0, rec, offset.length, data.length);
emitRecord(RecordKind.REG_WRITE, rec);
}
protected void emitPcRecord(long pc) throws Exception {
byte[] offset = ByteBuffer.allocate(8).order(currArch.LEBool ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN).putLong(pc).array();
emitRecord(RecordKind.PC, offset);
}
protected void emitInsRecord(long pc, byte[] insbytes) throws Exception {
byte[] rec = new byte[8 + insbytes.length];
byte[] offset = ByteBuffer.allocate(8).order(currArch.LEBool ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN).putLong(pc).array();
System.arraycopy(offset, 0, rec, 0, offset.length);
System.arraycopy(insbytes, 0, rec, offset.length, insbytes.length);
emitRecord(RecordKind.INSTRUCTION, rec);
}
public PcodeEmulator createEmulator(SleighLanguage language) {
Tracer self = this;
emulator = new PcodeEmulator(language) {
protected BytesPcodeThread createThread(String name) {
return new BytesPcodeThread(name, this) {
@Override protected PcodeThreadExecutor<byte[]> createExecutor() {
return new PcodeThreadExecutor<>(this) {
@Override public void stepOp(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary<byte[]> library) {
println(op.toString());
super.stepOp(op, frame, library);
Varnode outVar = op.getOutput();
if(op.getOpcode() == PcodeOp.LOAD) {
int spaceID = getIntConst(op.getInput(0));
AddressSpace space = language.getAddressFactory().getAddressSpace(spaceID);
Varnode inOffset = op.getInput(1);
byte[] offset = state.getVar(inOffset, reason);
byte[] val = state.getVar(space, offset, outVar.getSize(), true, reason);
print("MEM READ " + String.format("0x%x", arithmetic.toLong(offset, Purpose.LOAD)) + " " + outVar.getSize() + "\n VAL: ");
for(int i = 0; i < val.length; i++) {
print(String.format("%02x ", val[i]));
}
print("\n");
try {
self.emitMemReadRecord(offset, val);
} catch(Exception e) {
e.printStackTrace();
}
} else if(op.getOpcode() == PcodeOp.STORE) {
int spaceID = getIntConst(op.getInput(0));
AddressSpace space = language.getAddressFactory().getAddressSpace(spaceID);
Varnode inOffset = op.getInput(1);
byte[] offset = state.getVar(inOffset, reason);
Varnode valVar = op.getInput(2);
byte[] val = state.getVar(valVar, reason);
print("MEM WRITE " + String.format("0x%x", arithmetic.toLong(offset, Purpose.LOAD)) + " " + valVar.getSize() + "\n VAL: ");
for(int i = 0; i < val.length; i++) {
print(String.format("%02x ", val[i]));
}
print("\n");
try {
self.emitMemWriteRecord(offset, val);
} catch(Exception e) {
e.printStackTrace();
}
}
OpBehavior b = OpBehaviorFactory.getOpBehavior(op.getOpcode());
if (b != null && (b instanceof UnaryOpBehavior unOp || b instanceof BinaryOpBehavior binOp)) {
for(Varnode var : op.getInputs()) {
AddressSpace space = var.getAddress().getAddressSpace();
if(space.getType() == AddressSpace.TYPE_RAM ) {
byte[] offset = Tracer.u64(var.getOffset());
byte[] val = state.getVar(var, reason);
print("OTHER MEM READ " + String.format("0x%x", arithmetic.toLong(offset, Purpose.LOAD)) + " " + var.getSize() + "\n VAL: ");
for(int i = 0; i < val.length; i++) {
print(String.format("%02x ", val[i]));
}
print("\n");
try {
self.emitMemReadRecord(offset, val);
} catch(Exception e) {
e.printStackTrace();
}
}
}
}
if(outVar != null && outVar.getAddress().getAddressSpace().getType() == AddressSpace.TYPE_REGISTER) {
byte[] val = state.getVar(outVar.getAddress().getAddressSpace(), outVar.getAddress().getOffset(), outVar.getSize(), true, reason);
print("REG WRITE " + outVar.getAddress().toString() + " \n VAL: ");
for(int i = 0; i < val.length; i++) {
print(String.format("%02x ", val[i]));
}
print("\n");
try {
self.emitRegWriteRecord((int)outVar.getAddress().getOffset(), val);
} catch(Exception e) {
e.printStackTrace();
}
}
if(outVar != null && outVar.getAddress().getAddressSpace().getType() == AddressSpace.TYPE_RAM) {
byte[] offset = Tracer.u64(outVar.getOffset());
byte[] val = state.getVar(outVar, reason);
print("OTHER MEM WRITE " + String.format("0x%x", arithmetic.toLong(offset, Purpose.LOAD)) + " " + outVar.getSize() + "\n VAL: ");
for(int i = 0; i < val.length; i++) {
print(String.format("%02x ", val[i]));
}
print("\n");
try {
self.emitMemReadRecord(offset, val);
} catch(Exception e) {
e.printStackTrace();
}
}
}
};
}
@Override
public void executeInstruction() {
Instruction instruction = decoder.decodeInstruction(getCounter(), getContext());
Address pc = getCounter();
print("PC " + pc.toString()+":"+instruction.toString()+"\n");
try {
self.emitPcRecord(pc.getOffset());
} catch(Exception e) {
e.printStackTrace();
}
super.executeInstruction();
print("INS " + instruction.toString()+"\n");
try {
self.emitInsRecord(pc.getOffset(), instruction.getParsedBytes());
} catch(Exception e) {
e.printStackTrace();
}
}
@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);
return instruction;
}
};
}
};
}
};
this.emulator = emulator;
return emulator;
}
public BytesPcodeThread createThread(String name, Address entrypoint, Program program) {
AddressSpace space = program.getLanguage().getDefaultSpace();
BytesPcodeThread thread = (BytesPcodeThread)this.emulator.newThread(name);
PcodeUseropLibrary<byte[]> library = thread.getUseropLibrary();
for(MemoryBlock block : currentProgram.getMemory().getBlocks()) {
byte[] data = new byte[(int)block.getSize()];
try {
block.getBytes(block.getStart(), data);
thread.getState().getSharedState().setVar(space, block.getStart().getOffset(), (int)block.getSize(), true, data);
print(block.getStart().toString() + "\n");
for(int i = 0; i < (int)block.getSize(); i++) {
print(String.format("%02x ", data[i]));
}
print("\n");
} catch(Exception exc) {
continue;
}
}
thread.overrideContextWithDefault();
thread.reInitialize();
thread.setCounter(entrypoint);
return thread;
}
}
@Override
protected void run() throws Exception {
SleighLanguage language = (SleighLanguage) getLanguage(currentProgram.getLanguageID());
AddressSpace dyn = language.getDefaultSpace();
try {
Tracer t = new Tracer("/tmp/ghidra.trace", currentProgram.getLanguageID().getIdAsString());
Address entryAddr = dyn.getAddress(0x00401000L);
t.createEmulator(language);
BytesPcodeThread thread = t.createThread("trace", entryAddr, currentProgram);
try {
while(true) {
thread.stepInstruction();
}
}
catch (InterruptPcodeExecutionException e) {
println("Exception: " + e);
return;
}
}
catch (Exception e) {
println("Exception: " + e);
return;
}
}
}