From 5c4a6c467cf8766071383e57de19edca005506a6 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Mon, 26 Oct 2026 14:04:45 +0800 Subject: [PATCH] refactor(pe): share optional header image and alignment facts --- src/domain/peOptionalHeader.test.ts | 13 +++++++++++++ src/domain/peOptionalHeader.ts | 9 +++++++++ src/native/metadata/PeImage.test.ts | 21 +++++++++++++++++++++ src/native/metadata/PeImage.ts | 13 +++++++++---- 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/domain/peOptionalHeader.test.ts b/src/domain/peOptionalHeader.test.ts index c019995951..295048f110 100644 --- a/src/domain/peOptionalHeader.test.ts +++ b/src/domain/peOptionalHeader.test.ts @@ -14,6 +14,10 @@ const header = ( ): Buffer => { const bytes = Buffer.alloc(byteLength); bytes.writeUInt16LE(format.magic); + if (format.bits === 64) bytes.writeBigUInt64LE(0xfedcba9876540000n, 24); + else bytes.writeUInt32LE(0x87650000, 28); + bytes.writeUInt32LE(0x1000, 32); + bytes.writeUInt32LE(0x200, 36); bytes.writeUInt32LE(0x400, 60); bytes.writeUInt32LE(directoryCount, format.directoryOffset - 4); return bytes; @@ -32,6 +36,9 @@ it.each(formats)( if (!result.ok) throw new Error(result.error.message); expect(result.value).toMatchObject({ ...format, + imageBase: format.bits === 64 ? 0xfedcba9876540000n : 0x87650000n, + sectionAlignment: 0x1000, + fileAlignment: 0x200, sizeOfHeaders: 0x400, directoryCount: 5, }); @@ -54,6 +61,9 @@ it.each(formats)( ok: true, value: { ...format, + imageBase: format.bits === 64 ? 0xfedcba9876540000n : 0x87650000n, + sectionAlignment: 0x1000, + fileAlignment: 0x200, sizeOfHeaders: 0xffffffff, directoryCount: 0, dataDirectories: [], @@ -157,6 +167,9 @@ it("honors a Uint8Array window and detaches facts from later source changes", () backing.fill(0); expect(result.value).toEqual({ ...formats[1], + imageBase: 0xfedcba9876540000n, + sectionAlignment: 0x1000, + fileAlignment: 0x200, sizeOfHeaders: 0x400, directoryCount: 1, dataDirectories: [{ address: 0x12345678, size: 0x90, offset: 112 }], diff --git a/src/domain/peOptionalHeader.ts b/src/domain/peOptionalHeader.ts index 9d27350468..9d4446690c 100644 --- a/src/domain/peOptionalHeader.ts +++ b/src/domain/peOptionalHeader.ts @@ -13,6 +13,9 @@ export interface PeOptionalHeaderStructure { readonly magic: 0x10b | 0x20b; readonly bits: 32 | 64; readonly format: "pe32" | "pe32-plus"; + readonly imageBase: bigint; + readonly sectionAlignment: number; + readonly fileAlignment: number; readonly sizeOfHeaders: number; readonly directoryOffset: number; readonly directoryCount: number; @@ -74,6 +77,12 @@ export const readPeOptionalHeaderStructure = ( magic, bits, format: bits === 64 ? "pe32-plus" : "pe32", + imageBase: + bits === 64 + ? view.getBigUint64(24, true) + : BigInt(view.getUint32(28, true)), + sectionAlignment: view.getUint32(32, true), + fileAlignment: view.getUint32(36, true), sizeOfHeaders: view.getUint32(60, true), directoryOffset, directoryCount, diff --git a/src/native/metadata/PeImage.test.ts b/src/native/metadata/PeImage.test.ts index 44fcfcc2a7..d6461011f5 100644 --- a/src/native/metadata/PeImage.test.ts +++ b/src/native/metadata/PeImage.test.ts @@ -68,6 +68,27 @@ it.each([32, 64] as const)( }, ); +it.each([32, 64] as const)( + "retains PE%d declared image base and alignments in native image facts", + (bits) => { + const { bytes } = createGoBinaryFixture({ format: "pe", bits }); + const optional = bytes.readUInt32LE(0x3c) + 24; + const imageBase = bits === 64 ? 0xfedcba9876540000n : 0x87650000n; + if (bits === 64) bytes.writeBigUInt64LE(imageBase, optional + 24); + else bytes.writeUInt32LE(Number(imageBase), optional + 28); + // These remain declarations; alignment policy belongs to image consumers. + bytes.writeUInt32LE(0, optional + 32); + bytes.writeUInt32LE(0xffffffff, optional + 36); + const container = readPeImage(bytes); + expect(container.image_base).toBe(imageBase); + expect(container.pe_header_mapping.address).toBe(imageBase); + expect(container.mappings[0]?.address).toBe(imageBase + 0x400n); + expect(container.pe_section_alignment).toBe(0); + expect(container.pe_file_alignment).toBe(0xffffffff); + expect(container.sections[0]?.file_alignment).toBe(0xffffffff); + }, +); + it("keeps Go fallback candidates when a PE section has no printable name", () => { const fixture = createGoBinaryFixture({ format: "pe", bits: 64 }); const expected = readGoBinaryImage(fixture.bytes).build_info; diff --git a/src/native/metadata/PeImage.ts b/src/native/metadata/PeImage.ts index 6524b37068..0a06425a20 100644 --- a/src/native/metadata/PeImage.ts +++ b/src/native/metadata/PeImage.ts @@ -82,9 +82,14 @@ export const readPeImage = (bytes: Buffer): PeImage => { ); if (!facts.ok) throw new BinaryFormatFailure(facts.error.kind, facts.error.message); - const { bits, dataDirectories, sizeOfHeaders } = facts.value; - const imageBase = reader.word(optional + (bits === 64 ? 24 : 28), bits); - const fileAlignment = reader.u32(optional + 36); + const { + bits, + dataDirectories, + sizeOfHeaders, + imageBase, + sectionAlignment, + fileAlignment, + } = facts.value; const headerMapping: FileMapping = { address: imageBase, offset: 0, @@ -153,7 +158,7 @@ export const readPeImage = (bytes: Buffer): PeImage => { format: "pe", image_base: imageBase, pe_machine: machine, - pe_section_alignment: reader.u32(optional + 32), + pe_section_alignment: sectionAlignment, pe_file_alignment: fileAlignment, pe_data_directories: dataDirectories, pe_header_mapping: headerMapping,