From 88420926717121a018f717eb1eda08a6da31dfcb Mon Sep 17 00:00:00 2001 From: ShuaixinHuang <1836656514@qq.com> Date: Sat, 7 Nov 2026 22:14:29 +0800 Subject: [PATCH] fix(pe): reject misaligned certificate table offsets (#9276) * fix(pe): reject misaligned certificate table offsets * test: align ghidra limitation wording with main * style: format ghidra limitation assertion --- src/native/pe/PeCertificateParser.test.ts | 15 +++++++++++++++ src/native/pe/PeCertificateParser.ts | 2 ++ tests/boundary/mcp/ghidraEvidenceMcp.test.ts | 4 +--- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/native/pe/PeCertificateParser.test.ts b/src/native/pe/PeCertificateParser.test.ts index a7bde68945..15aa911a67 100644 --- a/src/native/pe/PeCertificateParser.test.ts +++ b/src/native/pe/PeCertificateParser.test.ts @@ -105,6 +105,21 @@ it("reports unsigned attribute OIDs and incomplete nested coverage", () => { } }); +it.each([false, true])( + "rejects a misaligned certificate table (PE32+ = %s)", + (plus) => { + const aligned = peCertificateFixture(plus, undefined, undefined, true); + expect(parse(aligned).coverage.status).toBe("complete"); + const shifted = Buffer.concat([ + aligned.subarray(0, 0x200), + Buffer.alloc(1), + aligned.subarray(0x200), + ]); + shifted.writeUInt32LE(0x201, 0x98 + (plus ? 112 : 96) + 4 * 8); + expect(() => parse(shifted)).toThrow(/certificate table offset.*aligned/u); + }, +); + it("accepts only zero eight-byte alignment padding included in dwLength", () => { for (const plus of [false, true]) { const bytes = peCertificateFixture(plus, undefined, undefined, true); diff --git a/src/native/pe/PeCertificateParser.ts b/src/native/pe/PeCertificateParser.ts index a73dfddb38..53dd393395 100644 --- a/src/native/pe/PeCertificateParser.ts +++ b/src/native/pe/PeCertificateParser.ts @@ -79,6 +79,8 @@ export const parsePeCertificates = ( const directoryAt = optionalHeaderOffset + security.offset; if (tableSize === 0 || tableOffset < headerSize) fail("PE certificate table offset overlaps headers or has zero size."); + if (tableOffset % 8 !== 0) + fail("PE certificate table offset must be aligned to eight bytes."); requireRange(bytes, tableOffset, tableSize); const table = bytes.subarray(tableOffset, tableOffset + tableSize); const entries: PeCertificates["entries"] = []; diff --git a/tests/boundary/mcp/ghidraEvidenceMcp.test.ts b/tests/boundary/mcp/ghidraEvidenceMcp.test.ts index 8bcb4d4d4e..0232798225 100644 --- a/tests/boundary/mcp/ghidraEvidenceMcp.test.ts +++ b/tests/boundary/mcp/ghidraEvidenceMcp.test.ts @@ -703,9 +703,7 @@ describe("Ghidra MCP evidence parity", () => { }, limitations: expect.arrayContaining([ expect.stringContaining("default auto-analysis complete"), - expect.stringContaining( - "original executable bytes are never written", - ), + expect.stringContaining("original executable bytes are unchanged"), ]), }); const directAnalyzed = await new EnhancedTools(session).execute(