mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 11:13:21 +00:00
fix(windows): validate declared PE header identity
This commit is contained in:
1 parent
a116f2a7ae
commit
a486cfe518
3 files changed
+56
-12
No files matched your search
@@ -8,11 +8,14 @@ export interface PeDataDirectory {
|
||||
readonly offset: number;
|
||||
}
|
||||
|
||||
/** Structural facts copied from one declared PE optional header. */
|
||||
export interface PeOptionalHeaderFixedFields {
|
||||
export interface PeOptionalHeaderIdentity {
|
||||
readonly magic: 0x10b | 0x20b;
|
||||
readonly bits: 32 | 64;
|
||||
readonly format: "pe32" | "pe32-plus";
|
||||
}
|
||||
|
||||
/** Structural facts copied from one declared PE optional header. */
|
||||
export interface PeOptionalHeaderFixedFields extends PeOptionalHeaderIdentity {
|
||||
readonly imageBase: bigint;
|
||||
readonly sectionAlignment: number;
|
||||
readonly fileAlignment: number;
|
||||
@@ -37,10 +40,10 @@ export interface PeOptionalHeaderFailure {
|
||||
readonly message: string;
|
||||
}
|
||||
|
||||
/** Decode mapping fields without requiring a directory count or table. */
|
||||
export const readPeOptionalHeaderFixedFields = (
|
||||
/** Decode format identity without requiring mapping fields or directories. */
|
||||
export const readPeOptionalHeaderIdentity = (
|
||||
bytes: Uint8Array,
|
||||
): Result<PeOptionalHeaderFixedFields, PeOptionalHeaderFailure> => {
|
||||
): Result<PeOptionalHeaderIdentity, PeOptionalHeaderFailure> => {
|
||||
if (bytes.byteLength < 2)
|
||||
return err({
|
||||
kind: "malformed",
|
||||
@@ -56,6 +59,16 @@ export const readPeOptionalHeaderFixedFields = (
|
||||
message: "Only PE32 and PE32+ image optional headers are supported",
|
||||
});
|
||||
const bits = magic === 0x20b ? 64 : 32;
|
||||
return ok({ magic, bits, format: bits === 64 ? "pe32-plus" : "pe32" });
|
||||
};
|
||||
|
||||
/** Decode mapping fields without requiring a directory count or table. */
|
||||
export const readPeOptionalHeaderFixedFields = (
|
||||
bytes: Uint8Array,
|
||||
): Result<PeOptionalHeaderFixedFields, PeOptionalHeaderFailure> => {
|
||||
const identity = readPeOptionalHeaderIdentity(bytes);
|
||||
if (!identity.ok) return identity;
|
||||
const { bits } = identity.value;
|
||||
const directoryOffset = bits === 64 ? 112 : 96;
|
||||
if (bytes.byteLength < 64)
|
||||
return err({
|
||||
@@ -63,10 +76,9 @@ export const readPeOptionalHeaderFixedFields = (
|
||||
reason: "truncated-fixed-fields",
|
||||
message: "truncated PE optional header",
|
||||
});
|
||||
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
||||
return ok({
|
||||
magic,
|
||||
bits,
|
||||
format: bits === 64 ? "pe32-plus" : "pe32",
|
||||
...identity.value,
|
||||
imageBase:
|
||||
bits === 64
|
||||
? view.getBigUint64(24, true)
|
||||
|
||||
@@ -7,11 +7,12 @@ import {
|
||||
} from "./windowsNativeArtifact.js";
|
||||
|
||||
const fixture = () => {
|
||||
const bytes = Buffer.alloc(128);
|
||||
const bytes = Buffer.alloc(152);
|
||||
bytes.write("MZ");
|
||||
bytes.writeUInt32LE(64, 0x3c);
|
||||
bytes.write("PE\0\0", 64, "ascii");
|
||||
bytes.writeUInt16LE(0x8664, 68);
|
||||
bytes.writeUInt16LE(64, 84);
|
||||
bytes.writeUInt16LE(0x20b, 88);
|
||||
const manifest = {
|
||||
packageVersion: "1.0.0",
|
||||
@@ -116,3 +117,26 @@ it.each(["offset", "signature", "machine", "optional magic"])(
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it.each([0, 1, 65, 0xffff])(
|
||||
"rejects an unavailable declared optional header of %i bytes",
|
||||
(length) => {
|
||||
const { bytes } = fixture();
|
||||
bytes.writeUInt16LE(length, 84);
|
||||
expect(createWindowsNativeManifest("1.0.0", bytes)).toMatchObject({
|
||||
ok: false,
|
||||
error: { message: expect.stringContaining("PE32+") },
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it.each([2, 32, 63, 64])(
|
||||
"admits PE32+ identity in a present declared header of %i bytes",
|
||||
(length) => {
|
||||
const { bytes } = fixture();
|
||||
bytes.writeUInt16LE(length, 84);
|
||||
expect(createWindowsNativeManifest("1.0.0", bytes)).toMatchObject({
|
||||
ok: true,
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -1,6 +1,7 @@
|
||||
import { observeMzWindowsHeader } from "./dosMz.js";
|
||||
import { createHash } from "node:crypto";
|
||||
import { readPeCoffHeader } from "./peCoffHeader.js";
|
||||
import { readPeOptionalHeaderIdentity } from "./peOptionalHeader.js";
|
||||
import { err, ok, type Result } from "./result.js";
|
||||
|
||||
/** Fixed compatibility contract shared by the artifact producer and readers. */
|
||||
@@ -99,14 +100,21 @@ const validateWindowsX64Artifact = (
|
||||
),
|
||||
);
|
||||
const offset = observed.offset;
|
||||
if (offset > bytes.length - 26 || bytes.readUInt16LE(offset + 24) !== 0x20b)
|
||||
const coff = readPeCoffHeader(bytes.subarray(offset + 4, offset + 24));
|
||||
if (
|
||||
!coff.ok ||
|
||||
coff.value.machine !== 0x8664 ||
|
||||
coff.value.optionalHeaderSize > bytes.length - offset - 24
|
||||
)
|
||||
return err(
|
||||
new WindowsNativeArtifactError(
|
||||
"Windows native artifact is not a Windows x64 PE32+ image",
|
||||
),
|
||||
);
|
||||
const coff = readPeCoffHeader(bytes.subarray(offset + 4, offset + 24));
|
||||
if (!coff.ok || coff.value.machine !== 0x8664)
|
||||
const optional = readPeOptionalHeaderIdentity(
|
||||
bytes.subarray(offset + 24, offset + 24 + coff.value.optionalHeaderSize),
|
||||
);
|
||||
if (!optional.ok || optional.value.bits !== 64)
|
||||
return err(
|
||||
new WindowsNativeArtifactError(
|
||||
"Windows native artifact is not a Windows x64 PE32+ image",
|
||||
|
||||
Reference in new issue
Block a user