fix(ghidra): normalize Mach-O imported symbol names
This commit is contained in:
parent
49e6f00ed0
commit
f88c99021b
|
|
@ -33,7 +33,7 @@ public final class ReaGhidraNoReturnFix extends GhidraScript {
|
|||
for (Function f : fm.getExternalFunctions()) {
|
||||
monitor.checkCancelled();
|
||||
// Do not apply import-name assumptions to local functions or to arbitrary externals.
|
||||
if (!f.hasNoReturn() || !RETURNING_IMPORTS.contains(f.getName())) continue;
|
||||
if (!f.hasNoReturn() || !RETURNING_IMPORTS.contains(externalAbiName(f))) continue;
|
||||
f.setNoReturn(false);
|
||||
enqueueReturning(f, cleared, pending);
|
||||
}
|
||||
|
|
@ -98,6 +98,14 @@ public final class ReaGhidraNoReturnFix extends GhidraScript {
|
|||
}
|
||||
}
|
||||
|
||||
private String externalAbiName(Function function) {
|
||||
String name = function.getName();
|
||||
// Mach-O stores C-linkage symbols with one object-format underscore prefix.
|
||||
if (currentProgram.getExecutableFormat().contains("Mach-O") && name.startsWith("_"))
|
||||
return name.substring(1);
|
||||
return name;
|
||||
}
|
||||
|
||||
private boolean clearDecodedReturn(Function function, Listing listing) throws Exception {
|
||||
if (!function.hasNoReturn() || function.isThunk()) return false;
|
||||
for (Instruction instruction : listing.getInstructions(function.getBody(), true)) {
|
||||
|
|
|
|||
|
|
@ -23,8 +23,16 @@ import { completeVerifierRun, createVerifierRun } from "./lib/verifier-run.mjs";
|
|||
|
||||
const exec = promisify(execFile);
|
||||
const run = createVerifierRun();
|
||||
assert.equal(process.platform, "linux", "This regression lane requires Linux");
|
||||
assert.equal(process.arch, "x64", "This regression lane requires x86-64");
|
||||
const procedureName = (name) =>
|
||||
process.platform === "darwin" ? `_${name}` : name;
|
||||
assert.ok(
|
||||
process.platform === "linux" || process.platform === "darwin",
|
||||
"This regression lane requires Linux or macOS",
|
||||
);
|
||||
assert.ok(
|
||||
process.arch === "x64" || process.arch === "arm64",
|
||||
"This regression lane requires x86-64 or arm64",
|
||||
);
|
||||
const config = parseConfig(process.env);
|
||||
if (!config.ok) throw config.error;
|
||||
const installation = inspectGhidraInstallation({
|
||||
|
|
@ -43,9 +51,22 @@ let cleaned = true;
|
|||
let report;
|
||||
const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
||||
try {
|
||||
const targetPath = join(workspace, "noreturn.so");
|
||||
const targetPath = join(
|
||||
workspace,
|
||||
process.platform === "darwin" ? "noreturn.dylib" : "noreturn.so",
|
||||
);
|
||||
const targetOptions =
|
||||
process.platform === "darwin"
|
||||
? [
|
||||
"-dynamiclib",
|
||||
"-undefined",
|
||||
"dynamic_lookup",
|
||||
"-U_FORTIFY_SOURCE",
|
||||
"-D_FORTIFY_SOURCE=0",
|
||||
]
|
||||
: ["-shared"];
|
||||
await exec(process.env.REA_CC ?? "cc", [
|
||||
"-shared",
|
||||
...targetOptions,
|
||||
"-fPIC",
|
||||
"-O0",
|
||||
"-fno-builtin",
|
||||
|
|
@ -154,7 +175,7 @@ try {
|
|||
"analyze_function",
|
||||
]) {
|
||||
const result = await client.execute(operation, {
|
||||
procedure: "rea_abort",
|
||||
procedure: procedureName("rea_abort"),
|
||||
});
|
||||
if (!result.ok) throw result.error;
|
||||
const warnings = result.value.limitations.filter((value) =>
|
||||
|
|
@ -169,7 +190,7 @@ try {
|
|||
observations.push({ operation, warnings });
|
||||
}
|
||||
const healthy = await client.execute("procedure_pseudo_code", {
|
||||
procedure: "rea_tls",
|
||||
procedure: procedureName("rea_tls"),
|
||||
});
|
||||
if (!healthy.ok) throw healthy.error;
|
||||
assert.ok(
|
||||
|
|
@ -192,7 +213,7 @@ try {
|
|||
probe,
|
||||
observations,
|
||||
scope:
|
||||
"Real Ghidra ELF analysis with injected flags; real read-only TCP Evidence projection. No target execution; exact Amethyst artifact and Windows are unverified.",
|
||||
"Real Ghidra ELF or Mach-O analysis with injected flags; real read-only TCP Evidence projection. No target execution; exact Amethyst artifact and Windows are unverified.",
|
||||
};
|
||||
} finally {
|
||||
if (cleaned) await rm(workspace, { recursive: true, force: true });
|
||||
|
|
|
|||
|
|
@ -15,12 +15,13 @@ binaries and Ghidra projects are never committed.
|
|||
exported entry, direct and indirect calls, a volatile string reference, and a
|
||||
multi-block branch across loaders.
|
||||
- `no-return.c` and `ReaNoReturnProbe.java` inject incorrect import flags into a
|
||||
real ELF analysis database, verify caller recovery, propagated flag repair and signature preservation,
|
||||
and retain genuine/unknown no-return calls and independently identified entries.
|
||||
Run `npm run build:cached`, then `node scripts/verify-real-ghidra-noreturn.mjs`
|
||||
with the Ghidra/JDK environment below on Linux x64 and a procps-compatible process
|
||||
table that exposes REA's current process for owned cleanup. It also checks named
|
||||
limitations through the real read-only TCP bridge and provider Evidence
|
||||
real ELF or Mach-O analysis database, verify caller recovery, propagated flag
|
||||
repair and signature preservation, and retain genuine/unknown no-return calls
|
||||
and independently identified entries. Run `npm run build:cached`, then
|
||||
`node scripts/verify-real-ghidra-noreturn.mjs` with the Ghidra/JDK environment
|
||||
below on Linux or macOS x64/arm64. On Linux, use a procps-compatible process
|
||||
table that exposes REA's current process for owned cleanup. It also checks
|
||||
named limitations through the real read-only TCP bridge and provider Evidence
|
||||
projection; default Unix-socket transport, Windows, and the reported Amethyst
|
||||
artifact need separate verification. Fixture target code is never executed.
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import java.util.LinkedHashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** Injects the reported analysis flags into real imported ELF functions; never executes ELF code. */
|
||||
/** Injects reported analysis flags into imported ELF/Mach-O functions; never executes target code. */
|
||||
public class ReaNoReturnProbe extends GhidraScript {
|
||||
private final JsonArray checks = new JsonArray();
|
||||
private final Map<Function, Address> originalEnds = new LinkedHashMap<>();
|
||||
|
|
@ -32,15 +32,21 @@ public class ReaNoReturnProbe extends GhidraScript {
|
|||
checks.add(name);
|
||||
}
|
||||
|
||||
private boolean matchesSymbol(Function function, String name) {
|
||||
String observed = function.getName();
|
||||
// Mach-O symbol names use a leading underscore for C-linkage names.
|
||||
return observed.equals(name) || observed.equals("_" + name);
|
||||
}
|
||||
|
||||
private Function function(String name) {
|
||||
for (Function f : currentProgram.getFunctionManager().getFunctions(true))
|
||||
if (!f.isThunk() && f.getName().equals(name)) return f;
|
||||
if (!f.isThunk() && matchesSymbol(f, name)) return f;
|
||||
throw new IllegalStateException("Missing fixture function " + name);
|
||||
}
|
||||
|
||||
private Function external(String name) {
|
||||
for (Function f : currentProgram.getFunctionManager().getExternalFunctions())
|
||||
if (f.getName().equals(name)) return f;
|
||||
if (matchesSymbol(f, name)) return f;
|
||||
throw new IllegalStateException("Missing imported function " + name);
|
||||
}
|
||||
|
||||
|
|
@ -151,7 +157,7 @@ public class ReaNoReturnProbe extends GhidraScript {
|
|||
pseudocode.setAccessible(true);
|
||||
JsonObject query = new JsonObject();
|
||||
query.addProperty("document", currentProgram.getName());
|
||||
query.addProperty("procedure", "rea_tls");
|
||||
query.addProperty("procedure", function("rea_tls").getName());
|
||||
var field = bridge.getClass().getDeclaredField("decompiler");
|
||||
field.setAccessible(true);
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <errno.h>
|
||||
|
||||
extern void *__tls_get_addr(void *);
|
||||
extern int *__errno_location(void);
|
||||
extern void rea_unknown_stop(void);
|
||||
extern void _Unwind_Resume(void *) __attribute__((noreturn));
|
||||
volatile unsigned long rea_after_call;
|
||||
|
|
|
|||
Loading…
Reference in New Issue