mirror of
https://github.com/morluto/rea.git
synced 2026-11-09 00:07:18 +00:00
feat(il2cpp): link native methods and export Ghidra seeds
Link IL2CPP metadata names to native addresses and types through shared CLI/MCP workflows, with bounded queries and atomic Ghidra seed export. Preserve the original implementation by 8rulerstar and follow-up correctness, resource, provenance, and CI dependency repairs. Verified against real Windows/Android artifacts, independent references, actual Ghidra, and focused CLI/MCP tests. Fixes #5112.
This commit is contained in:
1 parent
e50a271c72
commit
52d1a72af0
43 files changed
+5060
-368
No files matched your search
@@ -10,9 +10,14 @@ import {
|
||||
il2cppMetadataSectionSchema,
|
||||
il2cppMetadataParameterSchema,
|
||||
il2cppMetadataStringLiteralSchema,
|
||||
il2cppMethodAddressSchema,
|
||||
il2cppBinaryLinkSchema,
|
||||
il2cppCodeGenModuleSchema,
|
||||
il2cppTypeReferenceSchema,
|
||||
il2cppMetadataObjectSchema,
|
||||
il2cppMetadataParentSchema,
|
||||
il2cppMetadataMemberCollections,
|
||||
il2cppMetadataBinaryCollections,
|
||||
il2cppMetadataTypeSchema,
|
||||
} from "../il2cpp/il2cppMetadata.js";
|
||||
import { err, ok, type Result } from "../result.js";
|
||||
@@ -38,6 +43,19 @@ const countsSchema = z.strictObject({
|
||||
events: z.number().int().nonnegative().nullable(),
|
||||
parameters: z.number().int().nonnegative().nullable(),
|
||||
string_literals: z.number().int().nonnegative().nullable(),
|
||||
code_gen_modules: z.number().int().nonnegative().nullable(),
|
||||
method_addresses: z.number().int().nonnegative().nullable(),
|
||||
/** method_addresses rows per state; null when native observations were not collected. */
|
||||
method_address_states: z
|
||||
.strictObject({
|
||||
code: z.number().int().nonnegative(),
|
||||
no_body: z.number().int().nonnegative(),
|
||||
unknown: z.number().int().nonnegative(),
|
||||
})
|
||||
.nullable(),
|
||||
return_types: z.number().int().nonnegative().nullable(),
|
||||
field_types: z.number().int().nonnegative().nullable(),
|
||||
parameter_types: z.number().int().nonnegative().nullable(),
|
||||
});
|
||||
|
||||
export const il2cppMetadataSummarySchema = il2cppMetadataObjectSchema
|
||||
@@ -51,8 +69,19 @@ export const il2cppMetadataSummarySchema = il2cppMetadataObjectSchema
|
||||
events: true,
|
||||
parameters: true,
|
||||
string_literals: true,
|
||||
method_addresses: true,
|
||||
return_types: true,
|
||||
field_types: true,
|
||||
parameter_types: true,
|
||||
binary: true,
|
||||
})
|
||||
.extend({ counts: countsSchema });
|
||||
.extend({
|
||||
binary: il2cppBinaryLinkSchema
|
||||
.omit({ code_gen_modules: true })
|
||||
.nullable()
|
||||
.optional(),
|
||||
counts: countsSchema,
|
||||
});
|
||||
|
||||
const indexSchema = z.number().int().nonnegative();
|
||||
const sectionRowSchema = il2cppMetadataSectionSchema.extend({
|
||||
@@ -72,6 +101,15 @@ const parameterRowSchema = il2cppMetadataParameterSchema.extend({
|
||||
const stringLiteralRowSchema = il2cppMetadataStringLiteralSchema.extend({
|
||||
index: indexSchema,
|
||||
});
|
||||
const methodAddressRowSchema = il2cppMethodAddressSchema.extend({
|
||||
index: indexSchema,
|
||||
});
|
||||
const typeReferenceRowSchema = il2cppTypeReferenceSchema.extend({
|
||||
index: indexSchema,
|
||||
});
|
||||
const codeGenModuleRowSchema = il2cppCodeGenModuleSchema.extend({
|
||||
index: indexSchema,
|
||||
});
|
||||
const indexPage = {
|
||||
kind: z.literal("il2cpp-metadata"),
|
||||
indices: z.array(indexSchema).optional(),
|
||||
@@ -128,6 +166,33 @@ export const il2cppMetadataViewRequestSchema = z.discriminatedUnion(
|
||||
collection: z.literal("string_literals"),
|
||||
fields: z.array(stringLiteralRowSchema.keyof()).optional(),
|
||||
}),
|
||||
z.strictObject({
|
||||
...indexPage,
|
||||
collection: z.literal("method_addresses"),
|
||||
addresses: z.array(z.string().regex(/^0x[0-9a-fA-F]{1,16}$/u)).optional(),
|
||||
states: z.array(il2cppMethodAddressSchema.shape.state).optional(),
|
||||
fields: z.array(methodAddressRowSchema.keyof()).optional(),
|
||||
}),
|
||||
z.strictObject({
|
||||
...indexPage,
|
||||
collection: z.literal("code_gen_modules"),
|
||||
image_indices: z.array(indexSchema).optional(),
|
||||
fields: z.array(codeGenModuleRowSchema.keyof()).optional(),
|
||||
}),
|
||||
...(["return_types", "field_types"] as const).map((collection) =>
|
||||
z.strictObject({
|
||||
...indexPage,
|
||||
collection: z.literal(collection),
|
||||
fields: z.array(typeReferenceRowSchema.keyof()).optional(),
|
||||
}),
|
||||
),
|
||||
z.strictObject({
|
||||
...indexPage,
|
||||
collection: z.literal("parameter_types"),
|
||||
// Owners come from the retained parameters collection.
|
||||
method_indices: z.array(indexSchema).optional(),
|
||||
fields: z.array(typeReferenceRowSchema.keyof()).optional(),
|
||||
}),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -139,16 +204,30 @@ export const il2cppMetadataViewItemsSchema = z.union([
|
||||
z.array(memberRowSchema.partial().required({ index: true })),
|
||||
z.array(parameterRowSchema.partial().required({ index: true })),
|
||||
z.array(stringLiteralRowSchema.partial().required({ index: true })),
|
||||
z.array(methodAddressRowSchema.partial().required({ index: true })),
|
||||
z.array(typeReferenceRowSchema.partial().required({ index: true })),
|
||||
z.array(codeGenModuleRowSchema.partial().required({ index: true })),
|
||||
]);
|
||||
|
||||
export const il2cppMetadataViewContextSchema = z.strictObject({
|
||||
metadata_version: il2cppMetadataObjectSchema.shape.metadata_version,
|
||||
counts: countsSchema,
|
||||
binary_source: il2cppBinaryLinkSchema
|
||||
.pick({
|
||||
artifact: true,
|
||||
format: true,
|
||||
architecture: true,
|
||||
image_base: true,
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
type Report = z.infer<typeof il2cppMetadataParentSchema>;
|
||||
type Request = z.infer<typeof il2cppMetadataViewRequestSchema>;
|
||||
type Row = NonNullable<Report[Request["collection"]]>[number];
|
||||
type CollectionRows = Omit<Report, "binary"> & {
|
||||
code_gen_modules: NonNullable<Report["binary"]>["code_gen_modules"];
|
||||
};
|
||||
type Row = NonNullable<CollectionRows[Request["collection"]]>[number];
|
||||
interface CheckedReport {
|
||||
readonly report: Report;
|
||||
readonly counts: z.infer<typeof countsSchema>;
|
||||
@@ -190,6 +269,27 @@ const parseMetadataReport = (
|
||||
events: report.events?.length ?? null,
|
||||
parameters: report.parameters?.length ?? null,
|
||||
string_literals: report.string_literals?.length ?? null,
|
||||
code_gen_modules: report.binary?.code_gen_modules.length ?? null,
|
||||
method_addresses:
|
||||
report.binary == null
|
||||
? null
|
||||
: (report.method_addresses?.length ?? null),
|
||||
method_address_states:
|
||||
(report.binary == null ? undefined : report.method_addresses)?.reduce(
|
||||
(states, row) => {
|
||||
if (row.state === "code") states.code++;
|
||||
else if (row.state === "no-body") states.no_body++;
|
||||
else states.unknown++;
|
||||
return states;
|
||||
},
|
||||
{ code: 0, no_body: 0, unknown: 0 },
|
||||
) ?? null,
|
||||
return_types:
|
||||
report.binary == null ? null : (report.return_types?.length ?? null),
|
||||
field_types:
|
||||
report.binary == null ? null : (report.field_types?.length ?? null),
|
||||
parameter_types:
|
||||
report.binary == null ? null : (report.parameter_types?.length ?? null),
|
||||
},
|
||||
};
|
||||
if (reusable) checkedReports.set(key, checked);
|
||||
@@ -231,10 +331,27 @@ const selection = (view: Request, report: Report) => {
|
||||
!("declaring_types" in view) || view.declaring_types === undefined
|
||||
? undefined
|
||||
: new Set(view.declaring_types);
|
||||
const addresses =
|
||||
view.collection === "method_addresses" && view.addresses !== undefined
|
||||
? new Set(view.addresses.map((value) => BigInt(value).toString(16)))
|
||||
: undefined;
|
||||
const states =
|
||||
view.collection === "method_addresses" && view.states !== undefined
|
||||
? new Set<string>(view.states)
|
||||
: undefined;
|
||||
const methods =
|
||||
view.collection === "parameters" && view.method_indices !== undefined
|
||||
(view.collection === "parameters" ||
|
||||
view.collection === "parameter_types") &&
|
||||
view.method_indices !== undefined
|
||||
? new Set(view.method_indices)
|
||||
: undefined;
|
||||
// A parameter type row is owned by the method of the parameter at its index.
|
||||
const parameterOwner = (row: Row, index: number) =>
|
||||
"method" in row
|
||||
? row.method
|
||||
: view.collection === "parameter_types"
|
||||
? report.parameters?.[index]?.method
|
||||
: undefined;
|
||||
const namespaces =
|
||||
view.collection !== "types" || view.namespaces === undefined
|
||||
? undefined
|
||||
@@ -263,7 +380,38 @@ const selection = (view: Request, report: Report) => {
|
||||
else ranges.push(range);
|
||||
}
|
||||
}
|
||||
const moduleImages =
|
||||
view.collection === "code_gen_modules" && view.image_indices !== undefined
|
||||
? new Set(view.image_indices)
|
||||
: undefined;
|
||||
if (
|
||||
[
|
||||
moduleImages,
|
||||
addresses,
|
||||
states,
|
||||
names,
|
||||
owners,
|
||||
namespaces,
|
||||
methods,
|
||||
imageIndices,
|
||||
].every((filter) => filter === undefined)
|
||||
)
|
||||
return undefined;
|
||||
return (row: Row, index: number) => {
|
||||
if (
|
||||
moduleImages !== undefined &&
|
||||
(!("image" in row) || !moduleImages.has(row.image))
|
||||
)
|
||||
return false;
|
||||
if (
|
||||
addresses !== undefined &&
|
||||
(!("address" in row) ||
|
||||
row.address === null ||
|
||||
!addresses.has(BigInt(row.address).toString(16)))
|
||||
)
|
||||
return false;
|
||||
if (states !== undefined && (!("state" in row) || !states.has(row.state)))
|
||||
return false;
|
||||
if (
|
||||
names !== undefined &&
|
||||
(!("name" in row) || row.name === null || !names.has(row.name))
|
||||
@@ -279,10 +427,7 @@ const selection = (view: Request, report: Report) => {
|
||||
(!("namespace" in row) || !namespaces.has(row.namespace))
|
||||
)
|
||||
return false;
|
||||
if (
|
||||
methods !== undefined &&
|
||||
(!("method" in row) || !methods.has(row.method))
|
||||
)
|
||||
if (methods !== undefined && !methods.has(parameterOwner(row, index) ?? -1))
|
||||
return false;
|
||||
if (imageIndices === undefined) return true;
|
||||
let lower = 0;
|
||||
@@ -309,7 +454,7 @@ export const projectIl2cppMetadataView = (
|
||||
path: ["view"],
|
||||
reason: "invalid_value",
|
||||
message:
|
||||
"IL2CPP metadata Evidence supports summary and selected sections, images, types, methods, member, parameter and string literal pages.",
|
||||
"IL2CPP metadata Evidence supports summary and selected sections, images, types, methods, member, parameter, string literal, method address and type reference pages.",
|
||||
},
|
||||
]),
|
||||
);
|
||||
@@ -342,9 +487,14 @@ export const projectIl2cppMetadataView = (
|
||||
events,
|
||||
parameters,
|
||||
string_literals,
|
||||
method_addresses,
|
||||
return_types,
|
||||
field_types,
|
||||
parameter_types,
|
||||
...identity
|
||||
} = report;
|
||||
const allRows = {
|
||||
code_gen_modules: report.binary?.code_gen_modules,
|
||||
sections,
|
||||
images,
|
||||
types,
|
||||
@@ -354,6 +504,10 @@ export const projectIl2cppMetadataView = (
|
||||
events,
|
||||
parameters,
|
||||
string_literals,
|
||||
method_addresses,
|
||||
return_types,
|
||||
field_types,
|
||||
parameter_types,
|
||||
};
|
||||
const shared = {
|
||||
parent_evidence_id: parent.evidenceId,
|
||||
@@ -362,13 +516,23 @@ export const projectIl2cppMetadataView = (
|
||||
artifact: { path: report.artifact.path, sha256: report.artifact.sha256 },
|
||||
limitations: [...parent.limitations],
|
||||
unknowns: [
|
||||
...il2cppMetadataMemberCollections
|
||||
...(report.binary != null && report.binary.code_gen_modules_field === null
|
||||
? [
|
||||
"Module-array association is inferred from executable byte patterns; instruction boundaries and runtime use remain unverified.",
|
||||
]
|
||||
: []),
|
||||
...[
|
||||
...il2cppMetadataMemberCollections,
|
||||
...il2cppMetadataBinaryCollections,
|
||||
]
|
||||
.filter((name) => report[name] === undefined)
|
||||
.map(
|
||||
(name) =>
|
||||
`Retained Evidence did not collect ${name}; its count is unknown. Reinspect the metadata to acquire these facts.`,
|
||||
),
|
||||
"Names come from unencrypted metadata only; native code addresses and runtime registration remain unknown.",
|
||||
report.binary == null
|
||||
? "Names come from unencrypted metadata only; native code addresses and runtime registration remain unknown."
|
||||
: "Addresses come from static binary tables; runtime behavior and generic instance code remain unknown.",
|
||||
...(parent.subject == null
|
||||
? [
|
||||
"The parent has no recorded subject; its normalized artifact identity cannot be cross-checked against a subject digest.",
|
||||
@@ -376,15 +540,32 @@ export const projectIl2cppMetadataView = (
|
||||
: []),
|
||||
],
|
||||
};
|
||||
if (view.kind === "summary")
|
||||
if (view.kind === "summary") {
|
||||
const binary =
|
||||
report.binary == null
|
||||
? report.binary
|
||||
: (({ code_gen_modules: _modules, ...facts }) => facts)(report.binary);
|
||||
return ok({
|
||||
kind: "summary",
|
||||
view,
|
||||
...shared,
|
||||
summary: { ...identity, counts },
|
||||
summary: {
|
||||
...identity,
|
||||
...(binary === undefined ? {} : { binary }),
|
||||
counts,
|
||||
},
|
||||
coverage: completeWithinViewCoverage(1, 1),
|
||||
});
|
||||
const rows = allRows[view.collection];
|
||||
}
|
||||
const nativeUnavailable =
|
||||
report.binary == null &&
|
||||
[
|
||||
"method_addresses",
|
||||
"return_types",
|
||||
"field_types",
|
||||
"parameter_types",
|
||||
].includes(view.collection);
|
||||
const rows = nativeUnavailable ? undefined : allRows[view.collection];
|
||||
if (rows === undefined)
|
||||
return err(
|
||||
analysisViewInputError([
|
||||
@@ -395,17 +576,39 @@ export const projectIl2cppMetadataView = (
|
||||
},
|
||||
]),
|
||||
);
|
||||
if (
|
||||
view.collection === "parameter_types" &&
|
||||
view.method_indices !== undefined &&
|
||||
report.parameters === undefined
|
||||
)
|
||||
return err(
|
||||
analysisViewInputError([
|
||||
{
|
||||
path: ["view", "method_indices"],
|
||||
reason: "invalid_value",
|
||||
message:
|
||||
"Retained Evidence did not collect parameters, so parameter owners are unknown; inspect the metadata again to select parameter types by method.",
|
||||
},
|
||||
]),
|
||||
);
|
||||
const matches = selection(view, report);
|
||||
const selectedFields =
|
||||
view.fields === undefined ? undefined : new Set<string>(view.fields);
|
||||
const items = [];
|
||||
let total = 0;
|
||||
for (const index of rowIndices(rows.length, view.indices)) {
|
||||
const row = rows[index]!;
|
||||
if (!matches(row, index)) continue;
|
||||
if (total >= view.offset && items.length < view.limit)
|
||||
items.push(selectFields(row, index, selectedFields));
|
||||
total++;
|
||||
if (matches === undefined && view.indices === undefined) {
|
||||
total = rows.length;
|
||||
const end = Math.min(rows.length, view.offset + view.limit);
|
||||
for (let index = Math.min(view.offset, rows.length); index < end; index++)
|
||||
items.push(selectFields(rows[index]!, index, selectedFields));
|
||||
} else {
|
||||
for (const index of rowIndices(rows.length, view.indices)) {
|
||||
const row = rows[index]!;
|
||||
if (matches !== undefined && !matches(row, index)) continue;
|
||||
if (total >= view.offset && items.length < view.limit)
|
||||
items.push(selectFields(row, index, selectedFields));
|
||||
total++;
|
||||
}
|
||||
}
|
||||
return ok({
|
||||
kind: "il2cpp-metadata",
|
||||
@@ -413,6 +616,16 @@ export const projectIl2cppMetadataView = (
|
||||
...shared,
|
||||
items,
|
||||
metadata_version: report.metadata_version,
|
||||
...(report.binary == null
|
||||
? {}
|
||||
: {
|
||||
binary_source: {
|
||||
artifact: report.binary.artifact,
|
||||
format: report.binary.format,
|
||||
architecture: report.binary.architecture,
|
||||
image_base: report.binary.image_base,
|
||||
},
|
||||
}),
|
||||
counts,
|
||||
coverage: pageViewCoverage(view.offset, items.length, total),
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user