From dd2cbbf1a27a021f0eacb2250a3f53ba1d58c5ca Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Tue, 27 Oct 2026 12:31:35 +0800 Subject: [PATCH] fix(il2cpp): require FAT container identity --- src/il2cpp/Il2cppBinaryImage.ts | 12 ++++++++- .../boundary/il2cpp/il2cppBinaryFile.test.ts | 26 ++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/il2cpp/Il2cppBinaryImage.ts b/src/il2cpp/Il2cppBinaryImage.ts index e8ff9fd516..abde218167 100644 --- a/src/il2cpp/Il2cppBinaryImage.ts +++ b/src/il2cpp/Il2cppBinaryImage.ts @@ -1,4 +1,5 @@ import { + classifyFatContainerEvidence, readFatContainerHeader, validateFatSlice, } from "../domain/apple/machoContainer.js"; @@ -34,7 +35,16 @@ const selectImage = ( architecture: string | undefined, ): Buffer => { const fat = readFatContainerHeader(bytes, bytes.length); - if (fat.state === "not-fat") { + const identity = + fat.state === "parsed" + ? classifyFatContainerEvidence(fat, bytes) + : undefined; + if (identity?.state === "malformed") + throw new Il2cppBinaryFormatFailure("malformed-container", identity.reason); + if ( + fat.state === "not-fat" || + (fat.state === "parsed" && identity?.state !== "mach-o") + ) { if (architecture !== undefined) throw new Il2cppBinaryFormatFailure( "slice-not-found", diff --git a/tests/boundary/il2cpp/il2cppBinaryFile.test.ts b/tests/boundary/il2cpp/il2cppBinaryFile.test.ts index b287db7dd4..96877525f8 100644 --- a/tests/boundary/il2cpp/il2cppBinaryFile.test.ts +++ b/tests/boundary/il2cpp/il2cppBinaryFile.test.ts @@ -1,3 +1,4 @@ +import { javaClassFile } from "../../fixtures/javaClassFile.js"; import { Il2cppBinaryFormatFailure } from "../../../src/il2cpp/Il2cppBinaryFormat.js"; import { readBinaryContainer } from "../../../src/native/metadata/readBinaryContainer.js"; import { @@ -191,10 +192,33 @@ it("rejects reused MethodDef tokens within an image but preserves cross-image re ); }); +it.each([0, 4000, 9000])( + "reports a valid JVM class with %i unused constant bytes as unsupported", + (constantBytes) => { + expect(failure(javaClassFile(constantBytes, true))).toEqual({ + reason: "unsupported-container", + kind: "unsupported", + }); + }, +); + +it("does not infer universal Mach-O identity from a FAT-shaped non-Mach-O table", () => { + // CAFEBABE also identifies JVM classes. A plausible FAT table and known CPU + // do not prove Mach-O unless the declared slice contains a Mach-O header. + const bytes = createUniversalFixture([ + { cpu: 0x0100000c, bytes: Buffer.alloc(0x100) }, + ]); + expect(failure(bytes)).toEqual({ + reason: "unsupported-container", + kind: "unsupported", + }); + expect(failure(bytes, "arm64").reason).toBe("slice-not-found"); +}); + it("requires an explicit slice of a universal Mach-O and reads only that slice", () => { const thin = createIl2cppBinaryFixture(); const universal = createUniversalFixture([ - { cpu: 0x01000007, bytes: Buffer.alloc(0x100) }, + { cpu: 0x01000007, bytes: createIl2cppBinaryFixture() }, { cpu: 0x0100000c, bytes: thin }, ]); expect(failure(universal)).toEqual({