Ghidraツール用のghidranodejsモジュールを開発していたとき、SLEIGHアセンブリ言語でV8オペコード(Node.jsで使用されるJavaScriptエンジン)を正しく実装できるとは限らないことに気付きました。V8、JVMなどのランタイム環境では、単一のオペコードで非常に複雑なアクションを実行できます。この問題を解決するために、Ghidraは、構成概念Pコード(Ghidraの中間表現言語)を動的に注入するためのメカニズムを提供します。このメカニズムを使用して、逆コンパイラーの出力を次のように変換することができました。
これで:
CallRuntime. . . Runtime- V8 (kRuntimeId). (range — -, rangedst — ). SLEIGH, Ghidra , :
, , .
Runtime- kRuntimeId.
, .
.
.
.
, SLEIGH, , . , ( -) ( ) , p-code, Ghidra. ?
(slaspec) , CallRuntimeCallOther. , ( — ), , Ghidra Java , Java , p-code , Java.
, .
SLEIGH
CallRuntime . SLEIGH Ghidra v8.
:
define pcodeop CallRuntimeCallOther;
:
:CallRuntime [kRuntimeId], range^rangedst is op = 0x53; kRuntimeId; range; rangedst {
CallRuntimeCallOther(2, 0);
}
, , 0x53, CallRuntime
CallRuntimeCallOther
2 0. (CallRuntime
) (CallWithSpread
, CallUndefinedReceiver
. .).
, : V8_PcodeInjectLibrary. ghidra.program.model.lang.PcodeInjectLibrary
p-code .
V8_PcodeInjectLibrary
:
package v8_bytecode;
import …
public class V8_PcodeInjectLibrary extends PcodeInjectLibrary {
public V8_PcodeInjectLibrary(SleighLanguage l) {
}
}
V8_PcodeInjectLibrary
, Ghidra, pcodeInjectLibraryClass
pspec, Ghidra , p-code.
<?xml version="1.0" encoding="UTF-8"?>
<processor_spec>
<programcounter register="pc"/>
<properties>
<property key="pcodeInjectLibraryClass" value="v8_bytecode.V8_PcodeInjectLibrary"/>
</properties>
</processor_spec>
CallRuntimeCallOther
cspec. Ghidra V8_PcodeInjectLibrary
, cspec-.
<callotherfixup targetop="CallRuntimeCallOther">
<pcode dynamic="true">
<input name="outsize"/>
</pcode>
</callotherfixup>
(, , ) .
HashSet, . — language. CallRuntimeCallOther
, , .
public class V8_PcodeInjectLibrary extends PcodeInjectLibrary {
private Set<String> implementedOps;
private SleighLanguage language;
public V8_PcodeInjectLibrary(SleighLanguage l) {
super(l);
language = l;
String translateSpec = language.buildTranslatorTag(language.getAddressFactory(),
getUniqueBase(), language.getSymbolTable());
PcodeParser parser = null;
try {
parser = new PcodeParser(translateSpec);
}
catch (JDOMException e1) {
e1.printStackTrace();
}
implementedOps = new HashSet<>();
implementedOps.add("CallRuntimeCallOther");
}
}
Ghidra getPayload
V8_PcodeInjectLibrary
CallRuntimeCallOther
, V8_InjectCallVariadic
( ) .
@Override
/**
* This method is called by DecompileCallback.getPcodeInject.
*/
public InjectPayload getPayload(int type, String name, Program program, String context) {
if (type == InjectPayload.CALLMECHANISM_TYPE) {
return null;
}
if (!implementedOps.contains(name)) {
return super.getPayload(type, name, program, context);
}
V8_InjectPayload payload = null;
switch (name) {
case ("CallRuntimeCallOther"):
payload = new V8_InjectCallVariadic("", language, 0);
break;
default:
return super.getPayload(type, name, program, context);
}
return payload;
}
p-code
p-code V8_InjectCallVariadic. .
package v8_bytecode;
import …
public class V8_InjectCallVariadic extends V8_InjectPayload {
public V8_InjectCallVariadic(String sourceName, SleighLanguage language, long uniqBase) {
super(sourceName, language, uniqBase);
}
// . RUNTIMETYPE
int INTRINSICTYPE = 1;
int RUNTIMETYPE = 2;
int PROPERTYTYPE = 3;
@Override
public PcodeOp[] getPcode(Program program, InjectContext context) {
}
@Override
public String getName() {
return "InjectCallVariadic";
}
}
, getPcode
pCode V8_PcodeOpEmitter
pCode ( ).
V8_PcodeOpEmitter pCode = new V8_PcodeOpEmitter(language, context.baseAddr, uniqueBase);
context ( ) , .
Address opAddr = context.baseAddr;
:
Instruction instruction = program.getListing().getInstructionAt(opAddr);
context
, SLEIGH.
Integer funcType = (int) context.inputlist.get(0).getOffset();
Integer receiver = (int) context.inputlist.get(1).getOffset();
Pcode.
//
if (funcType != PROPERTYTYPE) {
// kRuntimeId —
Integer index = (int) instruction.getScalar(0).getValue();
// Pcode cpool pCode V8_PcodeOpEmitter. .
pCode.emitAssignVarnodeFromPcodeOpCall("call_target", 4, "cpool", "0", "0x" + opAddr.toString(), index.toString(),
funcType.toString());
}
…
// « »
Object[] tOpObjects = instruction.getOpObjects(2);
// get caller args count to save only necessary ones
Object[] opObjects;
Register recvOp = null;
if (receiver == 1) {
…
}
else {
opObjects = new Object[tOpObjects.length];
System.arraycopy(tOpObjects, 0, opObjects, 0, tOpObjects.length);
}
//
try {
callerParamsCount = program.getListing().getFunctionContaining(opAddr).getParameterCount();
}
catch(Exception e) {
callerParamsCount = 0;
}
// aN . , Ghidra
Integer callerArgIndex = 0;
for (; callerArgIndex < callerParamsCount; callerArgIndex++) {
pCode.emitPushCat1Value("a" + callerArgIndex);
}
// aN
Integer argIndex = opObjects.length;
for (Object o: opObjects) {
argIndex--;
Register currentOp = (Register)o;
pCode.emitAssignVarnodeFromVarnode("a" + argIndex, currentOp.toString(), 4);
}
//
pCode.emitVarnodeCall("call_target", 4);
//
while (callerArgIndex > 0) {
callerArgIndex--;
pCode.emitPopCat1Value("a" + callerArgIndex);
}
// P-Code
return pCode.getPcodeOps();
V8_PcodeOpEmitter (https://github.com/PositiveTechnologies/ghidra_nodejs/blob/main/src/main/java/v8_bytecode/V8_PcodeOpEmitter.java), JVM. p-code . .
emitAssignVarnodeFromPcodeOpCall(String varnodeName, int size, String pcodeop, String... args)
Varnode — p-code, , p-code. , — Varnode.
. p-code pcodeop
args
varnodeName
:
varnodeName = pcodeop(args[0], args[1], …);
emitPushCat1Value(String valueName) emitPopCat1Value (String valueName)
p-code push pop Varnode valueName
.
emitAssignVarnodeFromVarnode (String varnodeOutName, String varnodeInName, int size)
p-code varnodeOutName = varnodeInName
emitVarnodeCall (String target, int size)
P-Code target.
Ghidra. p-code — bytenode Node.JS. github.com. , -!
- , - .