feat(android): link native declarations to static JNI candidates (#7472)

Link original DEX native declarations to per-ABI function exports and relocated static JNI table candidates through shared CLI/MCP workflows. Preserve global attribution ambiguity, exact provenance, partial coverage and retained Evidence; default MCP delivery to bounded summaries with filtered/batch pages.

Use one immutable ELF relocation decoder with streaming IL2CPP materialization, cooperative bounded export/version parsing and bounded JNI state. Reuse validated session inventories without loading target libraries or executing JNI code.

Validation: relevant type/lint/build and parser/domain/public CLI/MCP regressions; genuine four-ABI NDK fixtures and independent library/slot oracles; actual JDK/JADX Conscrypt workflows with retained-source removal. Full measurements and limitations are recorded in docs/verification/android-jni-bindings-2026-10-27.md.

Closes #6403.
This commit is contained in:
gensokyo-dev authored and GitHub committed 2026-10-27 18:53:22 +08:00
1 parent 740c0f5615
commit 570abe95f2
60 files changed
+8414 -330

No files matched your search

+79
View File
@@ -16,6 +16,10 @@ import {
} from "./analysisEvidenceDelivery.js";
import { androidPackageDeliverySchema } from "../domain/android/androidPackageQuery.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import { AnalysisInputError } from "../domain/analysisErrorCore.js";
import { parseAnalysisInput } from "../application/AnalysisInput.js";
import { androidJniSelectionSchema } from "../domain/android/androidJniBindings.js";
import { androidJniDeliverySchema } from "../domain/android/androidJniQuery.js";
/** Expose the same explicit APK requests through one-shot CLI commands. */
export const registerAndroidCommands = (
@@ -67,6 +71,81 @@ export const registerAndroidCommands = (
);
const mappingInput = (path: string | undefined) =>
path === undefined ? {} : { mapping_path: path };
cli.command(CLI_COMMANDS.inspectAndroidJniBindings, {
description:
"Link raw DEX native declarations to static JNI export/table candidates without executing the APK",
args: z.object({ path }),
options: z.object({
...analysisEvidenceDeliveryOptions,
query: z
.string()
.optional()
.describe(
"JSON selection: class_name, method_names, descriptors, abis, library_paths; arrays preserve exact names",
),
view: z
.string()
.optional()
.describe(
"JSON android-jni collection page; requires --detail summary and --evidence_output",
),
}),
run: ({ args, options }) =>
withCommandCancellation((signal) =>
logCliCommand(
logger,
CLI_COMMANDS.inspectAndroidJniBindings,
async () => {
const operation = "inspect_android_jni_bindings";
const delivery = parseCliAnalysisEvidenceDelivery(
operation,
options,
androidJniDeliverySchema,
);
if (!delivery.ok) return projectAnalysisError(delivery.error);
let query: unknown = {};
try {
if (options.query !== undefined)
query = JSON.parse(options.query);
} catch (cause: unknown) {
return projectAnalysisError(
new AnalysisInputError(operation, { cause }, [
{
path: ["query"],
reason: "invalid_format",
message: "query must be a JSON selection object.",
},
]),
);
}
const selected = parseAnalysisInput(
operation,
query,
androidJniSelectionSchema,
signal,
);
if (!selected.ok) return projectAnalysisError(selected.error);
return deliverCliAnalysisEvidence({
run: () =>
service.execute(
operation,
{ ...selected.value, path: args.path },
{ signal },
),
service: { close: () => provider.close() },
providerId: "jadx",
operation,
signal,
detail: delivery.value.detail,
evidenceOutput: delivery.value.evidenceOutput,
...(delivery.value.view === undefined
? {}
: { view: delivery.value.view }),
});
},
),
),
});
cli.command(CLI_COMMANDS.inspectAndroidPackage, {
description:
"Inspect APK declarations and decoded manifest without executing it",