mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-25 17:00:36 -09:00
GP-6397: Fix JIT bugs, esp., found by converting deob scripts.
This commit is contained in:
@@ -23,11 +23,15 @@
|
||||
// data. This script hooks the functions "malloc", "free" and "use_string" where the later
|
||||
// simply prints the deobfuscated string passed as an argument.
|
||||
//@category Examples.Emulation
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.app.util.opinion.ElfLoader;
|
||||
import ghidra.pcode.emu.*;
|
||||
import ghidra.pcode.emu.jit.JitConfiguration;
|
||||
import ghidra.pcode.emu.jit.JitConfiguration.Opt;
|
||||
import ghidra.pcode.emu.jit.JitPcodeEmulator;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.InsufficientBytesException;
|
||||
@@ -94,13 +98,23 @@ public class EmuX86GccDeobfuscateHookExampleScript extends GhidraScript {
|
||||
strlenEntry = getExternalThunkAddress("strlen");
|
||||
|
||||
// Establish emulator
|
||||
emu = new PcodeEmulator(currentProgram.getLanguage()) {
|
||||
emu = new JitPcodeEmulator(currentProgram.getLanguage(), new JitConfiguration(
|
||||
Opt.REMOVE_UNUSED_OPERATIONS, Opt.EMIT_COUNTERS/*, Opt.LOG_STACK_TRACES*/),
|
||||
MethodHandles.lookup()) {
|
||||
|
||||
@Override
|
||||
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
|
||||
return super.createUseropLibrary().compose(new DeobfUseropLibrary<byte[]>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuspended() {
|
||||
// Because the monitor-cancelled listener isn't reliable
|
||||
return super.isSuspended() || monitor.isCancelled();
|
||||
}
|
||||
};
|
||||
monitor.addCancelledListener(() -> {
|
||||
// Why isn't this reliable?
|
||||
emu.setSuspended(true);
|
||||
});
|
||||
emuThread = emu.newThread();
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package ghidra.pcode.emu.jit;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The configuration for a JIT-accelerated emulator.
|
||||
*
|
||||
@@ -30,23 +32,64 @@ package ghidra.pcode.emu.jit;
|
||||
* limit is exceeded, the ASM library throws an exception. When this happens, the
|
||||
* compiler will retry the whole process, but with this configuration parameter halved.
|
||||
* @param maxPassageStrides The maximum number of strides to include.
|
||||
* @param removeUnusedOperations Some p-code ops produce outputs that are never used later. One
|
||||
* common case is flags computed from arithmetic operations. If this option is enabled,
|
||||
* the JIT compiler will remove those p-code ops.
|
||||
* @param emitCounters Causes the translator to emit a call to
|
||||
* {@link JitPcodeThread#count(int, int)} at the start of each basic block.
|
||||
* @param removeUnusedOperations See {@link Opt#REMOVE_UNUSED_OPERATIONS}
|
||||
* @param emitCounters See {@link Opt#EMIT_COUNTERS}
|
||||
* @param logStackTraces See {@link Opt#LOG_STACK_TRACES}
|
||||
*/
|
||||
public record JitConfiguration(
|
||||
int maxPassageInstructions,
|
||||
int maxPassageOps,
|
||||
int maxPassageStrides,
|
||||
boolean removeUnusedOperations,
|
||||
boolean emitCounters) {
|
||||
boolean emitCounters,
|
||||
boolean logStackTraces) {
|
||||
|
||||
/**
|
||||
* Fluent specifiers for the boolean options of {@link JitConfiguration}
|
||||
*/
|
||||
public enum Opt {
|
||||
/**
|
||||
* Some p-code ops produce outputs that are never used later. One common case is flags
|
||||
* computed from arithmetic operations. If this option is enabled, the JIT compiler will
|
||||
* remove those p-code ops.
|
||||
*/
|
||||
REMOVE_UNUSED_OPERATIONS,
|
||||
/**
|
||||
* Causes the translator to emit a call to {@link JitPcodeThread#count(int, int)} at the
|
||||
* start of each basic block.
|
||||
*/
|
||||
EMIT_COUNTERS,
|
||||
/**
|
||||
* Causes the translator to emit code to print a stack trace in its exception handlers.
|
||||
*/
|
||||
LOG_STACK_TRACES,
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a default configuration
|
||||
*/
|
||||
public JitConfiguration() {
|
||||
this(1000, 5000, 10, true, true);
|
||||
this(1000, 5000, 10, true, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a configuration with default maxes and the given boolean options
|
||||
*
|
||||
* @param opts the options
|
||||
*/
|
||||
public JitConfiguration(Set<Opt> opts) {
|
||||
this(1000, 5000, 10,
|
||||
opts.contains(Opt.REMOVE_UNUSED_OPERATIONS),
|
||||
opts.contains(Opt.EMIT_COUNTERS),
|
||||
opts.contains(Opt.LOG_STACK_TRACES));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a configuration with default maxes and the given boolean options
|
||||
*
|
||||
* @param opts the options
|
||||
*/
|
||||
public JitConfiguration(Opt... opts) {
|
||||
this(Set.of(opts));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.app.plugin.processors.sleigh.template.OpTpl;
|
||||
import ghidra.app.util.PseudoInstruction;
|
||||
import ghidra.pcode.emu.PcodeMachine;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
@@ -1063,6 +1064,27 @@ public class JitPassage extends PcodeProgram {
|
||||
}).collect(Collectors.joining("\n ")) + "\n>\n" + format(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(boolean numberOps) {
|
||||
return new MyFormatter(this, numberOps) {
|
||||
@Override
|
||||
protected FormatResult formatOpTemplate(MyAppender appender, OpTpl tpl) {
|
||||
if (tpl.getOpcode() != PcodeOp.UNIMPLEMENTED) {
|
||||
return super.formatOpTemplate(appender, tpl);
|
||||
}
|
||||
return switch (code.get(appender.getOpIdx())) {
|
||||
case NopPcodeOp nop -> {
|
||||
appender.appendIndent();
|
||||
appender.appendString("NOP(%d)".formatted(System.identityHashCode(nop)));
|
||||
appender.endLine();
|
||||
yield FormatResult.CONTINUE;
|
||||
}
|
||||
default -> super.formatOpTemplate(appender, tpl);
|
||||
};
|
||||
}
|
||||
}.formatOps(language, code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given p-code op is the first of an instruction.
|
||||
*
|
||||
|
||||
@@ -243,7 +243,7 @@ public class JitPcodeThread extends BytesPcodeThread {
|
||||
finishInstruction();
|
||||
}
|
||||
EntryPoint next = null;
|
||||
while (!isSuspended()) {
|
||||
while (!isSuspended() && !getMachine().isSuspended()) {
|
||||
if (next == null) {
|
||||
next = getEntry(new AddrCtx(getContext(), getCounter()));
|
||||
}
|
||||
@@ -272,7 +272,7 @@ public class JitPcodeThread extends BytesPcodeThread {
|
||||
* in the current (partial) instruction.
|
||||
*/
|
||||
public void count(int instructions, int trailingOps) {
|
||||
if (isSuspended()) {
|
||||
if (isSuspended() || getMachine().isSuspended()) {
|
||||
throw new SuspendedPcodeExecutionException(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ class DecoderExecutor extends PcodeExecutor<Object>
|
||||
final AddrCtx at;
|
||||
|
||||
private PseudoInstruction instruction;
|
||||
private NopPcodeOp termNop;
|
||||
private final Map<PcodeFrame, NopPcodeOp> termNopsPerFrame = new HashMap<>();
|
||||
|
||||
private RegisterValue flow;
|
||||
private final Map<Address, RegisterValue> futCtx = new HashMap<>();
|
||||
@@ -225,6 +225,7 @@ class DecoderExecutor extends PcodeExecutor<Object>
|
||||
@Override
|
||||
public void finish(PcodeFrame frame, PcodeUseropLibrary<Object> library) {
|
||||
super.finish(frame, library);
|
||||
NopPcodeOp termNop = termNopsPerFrame.remove(frame);
|
||||
if (termNop != null) {
|
||||
opsForThisStep.add(termNop);
|
||||
}
|
||||
@@ -319,9 +320,9 @@ class DecoderExecutor extends PcodeExecutor<Object>
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>
|
||||
* This create an {@link IntBranch} record and collects it for this instruction step. The record
|
||||
* will first be used to check for fall through. Then, the passage decoder is notified, which
|
||||
* collects the records to later passage-wide control flow analysis.
|
||||
* This creates an {@link IntBranch} record and collects it for this instruction step. The
|
||||
* record will first be used to check for fall through. Then, the passage decoder is notified,
|
||||
* which collects the records for later passage-wide control flow analysis.
|
||||
*
|
||||
* @see #checkFallthroughAndAccumulate(PcodeProgram)
|
||||
*/
|
||||
@@ -329,9 +330,8 @@ class DecoderExecutor extends PcodeExecutor<Object>
|
||||
protected void branchInternal(PcodeOp op, PcodeFrame frame, int relative) {
|
||||
int tgtSeq = op.getSeqnum().getTime() + relative;
|
||||
if (tgtSeq == frame.getCode().size()) {
|
||||
if (termNop == null) {
|
||||
termNop = new NopPcodeOp(at, tgtSeq);
|
||||
}
|
||||
NopPcodeOp termNop =
|
||||
termNopsPerFrame.computeIfAbsent(frame, f -> new NopPcodeOp(at, tgtSeq));
|
||||
branchesForThisStep.add(new SIntBranch(op, termNop, false));
|
||||
}
|
||||
else {
|
||||
@@ -454,16 +454,6 @@ class DecoderExecutor extends PcodeExecutor<Object>
|
||||
* @return the reachability of the fall-through flow
|
||||
*/
|
||||
public Reachability checkFallthroughAndAccumulate(PcodeProgram from) {
|
||||
if (instruction instanceof DecodeErrorInstruction) {
|
||||
stride.opsForStride.addAll(opsForThisStep);
|
||||
for (Branch branch : branchesForThisStep) {
|
||||
switch (branch) {
|
||||
case ErrBranch eb -> stride.passage.otherBranches.put(eb.from(), eb);
|
||||
default -> throw new AssertionError();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (opsForThisStep.isEmpty()) {
|
||||
return Reachability.WITHOUT_CTXMOD;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import ghidra.pcode.emu.jit.gen.JitCodeGenerator.PcGen;
|
||||
import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage;
|
||||
import ghidra.pcode.emu.jit.gen.util.*;
|
||||
import ghidra.pcode.emu.jit.gen.util.Emitter.*;
|
||||
import ghidra.pcode.emu.jit.gen.util.Methods.Inv;
|
||||
import ghidra.pcode.emu.jit.gen.util.Types.TRef;
|
||||
import ghidra.program.model.pcode.PcodeOp;
|
||||
|
||||
@@ -65,8 +66,16 @@ public record ExceptionHandler(PcodeOp op, JitBlock block, Lbl<Ent<Bot, TRef<Thr
|
||||
*/
|
||||
public <THIS extends JitCompiledPassage> Emitter<Dead> genRun(Emitter<Dead> em,
|
||||
Local<TRef<THIS>> localThis, JitCodeGenerator<THIS> gen) {
|
||||
return em
|
||||
.emit(Lbl::placeDead, lbl)
|
||||
var emLive = em.emit(Lbl::placeDead, lbl);
|
||||
if (gen.context.getConfiguration().logStackTraces()) {
|
||||
emLive = emLive
|
||||
.emit(Op::dup)
|
||||
.emit(Op::invokevirtual, GenConsts.T_THROWABLE, "printStackTrace",
|
||||
GenConsts.MDESC_THROWABLE__PRINT_STACK_TRACE, false)
|
||||
.step(Inv::takeObjRef)
|
||||
.step(Inv::retVoid);
|
||||
}
|
||||
return emLive
|
||||
.emit(gen::genExit, localThis, block, PcGen.loadOffset(gen.getAddressForOp(op)),
|
||||
gen.getExitContext(op))
|
||||
.emit(Op::athrow);
|
||||
|
||||
@@ -272,6 +272,8 @@ public interface GenConsts {
|
||||
.param(Types.T_INT_ARR)
|
||||
.param(Types.T_INT)
|
||||
.build();
|
||||
public static final MthDesc<TVoid, Bot> MDESC_THROWABLE__PRINT_STACK_TRACE =
|
||||
MthDesc.returns(Types.T_VOID).build();
|
||||
|
||||
/**
|
||||
* This is just to assure all the methods referred to below have the same signature. The fields
|
||||
|
||||
@@ -171,8 +171,8 @@ public interface IntPredBinOpGen<T extends JitBinOp> extends BinOpGen<T> {
|
||||
JitCodeGenerator<THIS> gen, T op, JitBlock block, Scope scope) {
|
||||
JitType lType = gen.resolveType(op.l(), op.lType());
|
||||
JitType rType = gen.resolveType(op.r(), op.rType());
|
||||
assert rType == lType;
|
||||
return new LiveOpResult(switch (lType) {
|
||||
JitType uType = JitType.unify(lType, rType);
|
||||
return new LiveOpResult(switch (uType) {
|
||||
case IntJitType t -> em
|
||||
.emit(gen::genReadToStack, localThis, op.l(), t, ext())
|
||||
.emit(gen::genReadToStack, localThis, op.r(), t, rExt())
|
||||
|
||||
@@ -54,11 +54,11 @@ public class PcodeProgram {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void appendString(String string) {
|
||||
public void appendString(String string) {
|
||||
buf.append(string);
|
||||
}
|
||||
|
||||
protected void endLine() {
|
||||
public void endLine() {
|
||||
buf.append("\n");
|
||||
}
|
||||
|
||||
@@ -90,6 +90,10 @@ public class PcodeProgram {
|
||||
buf.append(": ");
|
||||
}
|
||||
}
|
||||
|
||||
public int getOpIdx() {
|
||||
return opIdx;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class MyFormatter extends AbstractPcodeFormatter<String, MyAppender> {
|
||||
|
||||
@@ -144,7 +144,7 @@ public interface PcodeFormatter<T> {
|
||||
Collections.sort(offsetList);
|
||||
for (int i = offsetList.size() - 1; i >= 0; i--) {
|
||||
int labelOffset = offsetList.get(i);
|
||||
if (labelOffset > pcodeOps.size()) {
|
||||
if (labelOffset > pcodeOps.size() || labelOffset < 0) {
|
||||
// Skip jumps out of this block/program
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user