mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 17:59:46 +02:00
460 lines
13 KiB
Java
460 lines
13 KiB
Java
/* ###
|
|
* 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.math.BigInteger;
|
|
import java.util.*;
|
|
|
|
import org.apache.commons.lang3.tuple.Pair;
|
|
|
|
import ghidra.app.plugin.core.debug.gui.watch.WatchRow;
|
|
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.services.DebuggerWatchesService;
|
|
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.pcode.utils.Utils;
|
|
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.NumericUtilities;
|
|
import ghidra.util.exception.CancelledException;
|
|
|
|
public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI {
|
|
|
|
public class Injects extends StructuredSleigh {
|
|
protected Injects() {
|
|
super(currentProgram);
|
|
}
|
|
}
|
|
|
|
protected List<Watch> collectWatches() {
|
|
DebuggerWatchesService watchService =
|
|
state.getTool().getService(DebuggerWatchesService.class);
|
|
List<Watch> watches = new ArrayList<>();
|
|
for (WatchRow row : watchService.getWatches()) {
|
|
DataType type = row.getDataType();
|
|
watches.add(new Watch(row.getExpression(),
|
|
type == null ? null : type(type.getName()), row.getSettings()));
|
|
}
|
|
return watches;
|
|
}
|
|
|
|
@Override
|
|
protected void run() throws Exception {
|
|
List<Watch> watches = collectWatches();
|
|
|
|
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);
|
|
if (w.type != null) {
|
|
tableDialog.addCustomColumn(new CheckRowWatchDisplay(loader, w, compiled.size()));
|
|
}
|
|
else {
|
|
tableDialog.addCustomColumn(new CheckRowRawDisplay(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());
|
|
}
|
|
|
|
@Override
|
|
public void stepPatch(String sleigh) {
|
|
super.stepPatch(sleigh);
|
|
position = position.patched(thread, language, sleigh);
|
|
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 CheckRowRawDisplay implements TypedDisplay<CheckRow, String> {
|
|
private final boolean isBigEndian = currentProgram.getLanguage().isBigEndian();
|
|
private final Watch watch;
|
|
private final int index;
|
|
|
|
public CheckRowRawDisplay(Watch watch, int index) {
|
|
this.watch = watch;
|
|
this.index = index;
|
|
}
|
|
|
|
@Override
|
|
public String getColumnName() {
|
|
return watch.expression;
|
|
}
|
|
|
|
@Override
|
|
public Class<String> getColumnClass() {
|
|
return String.class;
|
|
}
|
|
|
|
private String getStringValue(CheckRow r) {
|
|
Pair<byte[], ValueLocation> p = r.values.get(index);
|
|
Address addr = p.getRight() == null ? null : p.getRight().getAddress();
|
|
byte[] bytes = p.getLeft();
|
|
if (addr == null || !addr.getAddressSpace().isMemorySpace()) {
|
|
BigInteger asBigInt =
|
|
Utils.bytesToBigInteger(bytes, bytes.length, isBigEndian, false);
|
|
return "0x" + asBigInt.toString(16);
|
|
}
|
|
return "{ " + NumericUtilities.convertBytesToString(bytes, " ") + " }";
|
|
}
|
|
|
|
@Override
|
|
public int compareTyped(CheckRow r1, CheckRow r2) {
|
|
String s1 = getStringValue(r1);
|
|
String s2 = getStringValue(r2);
|
|
return s1.compareTo(s2);
|
|
}
|
|
|
|
@Override
|
|
public String getTypedValue(CheckRow r) {
|
|
return getStringValue(r);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
if (type == null) {
|
|
return null;
|
|
}
|
|
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 = (CheckRow) rowObject;
|
|
try {
|
|
emulate(row.schedule, monitor);
|
|
}
|
|
catch (CancelledException e) {
|
|
// Just be done
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|