mirror of
https://github.com/NationalSecurityAgency/ghidra
synced 2024-10-02 16:33:49 +00:00
Merge remote-tracking branch 'origin/GP-2289_Dan_emuDeskCheck--SQUASHED'
This commit is contained in:
commit
f7bb9e9e43
427
Ghidra/Debug/Debugger/ghidra_scripts/EmuDeskCheckScript.java
Normal file
427
Ghidra/Debug/Debugger/ghidra_scripts/EmuDeskCheckScript.java
Normal file
|
@ -0,0 +1,427 @@
|
|||
/* ###
|
||||
* 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[], Address>> inspector =
|
||||
new PcodeExecutor<>(language,
|
||||
new PairedPcodeArithmetic<>(arithmetic,
|
||||
AddressOfPcodeArithmetic.INSTANCE),
|
||||
state.paired(new AddressOfPcodeExecutorStatePiece(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[], Address>> 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[], Address>> values;
|
||||
|
||||
public CheckRow(TraceSchedule schedule, Address pc,
|
||||
List<Pair<byte[], Address>> values) {
|
||||
this.schedule = schedule;
|
||||
this.pc = pc;
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getAddress() {
|
||||
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[], Address> p = r.values.get(index);
|
||||
Address addr = p.getRight();
|
||||
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[], Address> p = r.values.get(index);
|
||||
Address addr = p.getRight();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.pcode.struct;
|
||||
|
||||
import ghidra.pcode.struct.StructuredSleigh.Label;
|
||||
import ghidra.pcode.struct.StructuredSleigh.RVal;
|
||||
|
||||
public class GotoStmt extends AbstractStmt {
|
||||
|
||||
private final RValInternal target;
|
||||
|
||||
protected GotoStmt(StructuredSleigh ctx, RVal target) {
|
||||
super(ctx);
|
||||
this.target = (RValInternal) target;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StringTree generate(Label next, Label fall) {
|
||||
StringTree st = new StringTree();
|
||||
st.append("goto [");
|
||||
st.append(target.generate());
|
||||
st.append("];\n");
|
||||
return st;
|
||||
}
|
||||
|
||||
}
|
|
@ -29,9 +29,9 @@ class ReturnStmt extends AbstractStmt {
|
|||
@Override
|
||||
protected StringTree generate(Label next, Label fall) {
|
||||
StringTree st = new StringTree();
|
||||
st.append("return ");
|
||||
st.append("return [");
|
||||
st.append(target.generate());
|
||||
st.append(";\n");
|
||||
st.append("];\n");
|
||||
return st;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1636,17 +1636,28 @@ public class StructuredSleigh {
|
|||
* Generate a "return" statement
|
||||
*
|
||||
* <p>
|
||||
* This models (in part) a C-style return from the current target function to its callee. It
|
||||
* This models (in part) a C-style return from the current target function to its caller. It
|
||||
* simply generates the "return" Sleigh statement, which is an indirect branch to the given
|
||||
* target. The target must be at an address in the processor's code space.
|
||||
* target. Target is typically popped from the stack or read from a link register.
|
||||
*
|
||||
* <p>
|
||||
* Contrast with {@link #_result(RVal)}
|
||||
*
|
||||
* @param target the offset of the target
|
||||
*/
|
||||
protected void _return(RVal target) {
|
||||
new ReturnStmt(this, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a "goto" statement to another address in the processor's code space
|
||||
*
|
||||
* @param target the offset of the target address
|
||||
*/
|
||||
protected void _goto(RVal target) {
|
||||
new GotoStmt(this, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the method lookup for this context
|
||||
*
|
||||
|
|
|
@ -230,11 +230,11 @@ public class StructuredSleighTest extends AbstractGhidraHeadlessIntegrationTest
|
|||
StructuredSleigh ss = new TestStructuredSleigh() {
|
||||
@StructuredUserop
|
||||
public void my_userop() {
|
||||
_return(lit(0xdeadbeefL, 8).deref());
|
||||
_return(lit(0xdeadbeefL, 8));
|
||||
}
|
||||
};
|
||||
SleighPcodeUseropDefinition<Object> myUserop = ss.generate().get("my_userop");
|
||||
assertEquals("return (* 0xdeadbeef:8);\n", myUserop.getBody());
|
||||
assertEquals("return [0xdeadbeef:8];\n", myUserop.getBody());
|
||||
// TODO: Test that the generated code compiles in a slaspec file.
|
||||
// It's rejected for injects because "return" is not valid there.
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue