mirror of
https://github.com/morluto/rea.git
synced 2026-11-14 10:43:30 +00:00
Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
183 lines
6.5 KiB
TypeScript
183 lines
6.5 KiB
TypeScript
/** Build a minimal ELF header for binary-target parser tests. */
|
|
export const elf = (
|
|
class_: number,
|
|
endian: number,
|
|
machine: number,
|
|
): Buffer => {
|
|
const bytes = Buffer.alloc(class_ === 1 ? 52 : 64);
|
|
bytes.set([0x7f, 0x45, 0x4c, 0x46, class_, endian, 1]);
|
|
if (endian === 1) {
|
|
bytes.writeUInt16LE(machine, 18);
|
|
bytes.writeUInt32LE(1, 20);
|
|
bytes.writeUInt16LE(class_ === 1 ? 52 : 64, class_ === 1 ? 40 : 52);
|
|
} else {
|
|
bytes.writeUInt16BE(machine, 18);
|
|
bytes.writeUInt32BE(1, 20);
|
|
bytes.writeUInt16BE(class_ === 1 ? 52 : 64, class_ === 1 ? 40 : 52);
|
|
}
|
|
return bytes;
|
|
};
|
|
|
|
/** Build a minimal thin Mach-O header for binary-target parser tests. */
|
|
export const thinMach = (magic: number, cpu: number): Buffer => {
|
|
const bytes = Buffer.alloc(
|
|
magic === 0xfeedfacf || magic === 0xcffaedfe ? 32 : 28,
|
|
);
|
|
bytes.writeUInt32BE(magic, 0);
|
|
if (magic === 0xcefaedfe || magic === 0xcffaedfe) bytes.writeUInt32LE(cpu, 4);
|
|
else bytes.writeUInt32BE(cpu, 4);
|
|
return bytes;
|
|
};
|
|
|
|
/** Build a FAT Mach-O with in-bounds thin slices, or an explicitly truncated table. */
|
|
export const fat = (
|
|
cpus: readonly number[],
|
|
declared = cpus.length,
|
|
): Buffer => {
|
|
const tableSize = 8 + cpus.length * 20;
|
|
const slices = cpus.map((cpu) => {
|
|
const bytes = thinMach(0xfeedfacf, cpu);
|
|
bytes.writeUInt32BE(2, 12);
|
|
return bytes;
|
|
});
|
|
const sliceStart = Math.ceil(tableSize / 16) * 16;
|
|
const bytes = Buffer.alloc(
|
|
declared === cpus.length ? sliceStart + cpus.length * 32 : tableSize,
|
|
);
|
|
bytes.writeUInt32BE(0xcafebabe, 0);
|
|
bytes.writeUInt32BE(declared, 4);
|
|
cpus.forEach((cpu, index) => {
|
|
const entry = 8 + index * 20;
|
|
const offset = sliceStart + index * 32;
|
|
bytes.writeUInt32BE(cpu, entry);
|
|
bytes.writeUInt32BE(offset, entry + 8);
|
|
bytes.writeUInt32BE(32, entry + 12);
|
|
bytes.writeUInt32BE(4, entry + 16);
|
|
if (declared === cpus.length) slices[index]?.copy(bytes, offset);
|
|
});
|
|
return bytes;
|
|
};
|
|
|
|
export interface PeCodeViewFixture {
|
|
readonly type?: "RSDS" | "NB10";
|
|
readonly guid?: string;
|
|
readonly age?: number;
|
|
readonly pdbPath?: string;
|
|
}
|
|
|
|
/** Build a bounded PE image with optional managed metadata or CodeView debug data. */
|
|
export const pe = (
|
|
machine: number,
|
|
offset = 64,
|
|
characteristics = 0x0002,
|
|
managed = false,
|
|
codeView?: PeCodeViewFixture,
|
|
): Buffer => {
|
|
const optionalHeaderSize =
|
|
machine === 0x014c || machine === 0x01c0 ? 224 : 240;
|
|
const sectionTableOffset = offset + 24 + optionalHeaderSize;
|
|
const sectionCount = codeView !== undefined ? 1 : 0;
|
|
const rawDataOffset = 512;
|
|
|
|
let codeViewData: Buffer | undefined;
|
|
if (codeView !== undefined) {
|
|
const type = codeView.type ?? "RSDS";
|
|
const pdbPath = codeView.pdbPath ?? "app.pdb";
|
|
const age = codeView.age ?? 1;
|
|
const pathBuf = Buffer.from(`${pdbPath}\0`, "utf8");
|
|
if (type === "RSDS") {
|
|
codeViewData = Buffer.alloc(24 + pathBuf.length);
|
|
codeViewData.write("RSDS", 0, "ascii");
|
|
const guid = codeView.guid ?? "01234567-89ab-cdef-0123-456789abcdef";
|
|
const parts = guid.split("-");
|
|
if (parts.length === 5) {
|
|
codeViewData.writeUInt32LE(parseInt(parts[0]!, 16), 4);
|
|
codeViewData.writeUInt16LE(parseInt(parts[1]!, 16), 8);
|
|
codeViewData.writeUInt16LE(parseInt(parts[2]!, 16), 10);
|
|
Buffer.from(parts[3]!, "hex").copy(codeViewData, 12);
|
|
Buffer.from(parts[4]!, "hex").copy(codeViewData, 14);
|
|
}
|
|
codeViewData.writeUInt32LE(age, 20);
|
|
pathBuf.copy(codeViewData, 24);
|
|
} else {
|
|
codeViewData = Buffer.alloc(16 + pathBuf.length);
|
|
codeViewData.write("NB10", 0, "ascii");
|
|
codeViewData.writeUInt32LE(0, 4);
|
|
codeViewData.writeUInt32LE(0, 8);
|
|
codeViewData.writeUInt32LE(age, 12);
|
|
pathBuf.copy(codeViewData, 16);
|
|
}
|
|
}
|
|
|
|
const minSize =
|
|
codeViewData !== undefined
|
|
? rawDataOffset + 28 + codeViewData.length + 32
|
|
: Math.max(512, offset + 24 + optionalHeaderSize);
|
|
const bytes = Buffer.alloc(minSize);
|
|
bytes.write("MZ", 0, "ascii");
|
|
bytes.writeUInt32LE(offset, 0x3c);
|
|
bytes.write("PE\0\0", offset, "binary");
|
|
bytes.writeUInt16LE(machine, offset + 4);
|
|
bytes.writeUInt16LE(sectionCount, offset + 6);
|
|
bytes.writeUInt16LE(optionalHeaderSize, offset + 20);
|
|
bytes.writeUInt16LE(characteristics, offset + 22);
|
|
const optionalHeader = offset + 24;
|
|
const pe32 = machine === 0x014c || machine === 0x01c0;
|
|
bytes.writeUInt16LE(pe32 ? 0x10b : 0x20b, optionalHeader);
|
|
const directoryOffset = pe32 ? 96 : 112;
|
|
bytes.writeUInt32LE(16, optionalHeader + directoryOffset - 4);
|
|
if (managed) {
|
|
bytes.writeUInt32LE(0x2000, optionalHeader + directoryOffset + 14 * 8);
|
|
bytes.writeUInt32LE(72, optionalHeader + directoryOffset + 14 * 8 + 4);
|
|
}
|
|
if (codeViewData !== undefined) {
|
|
const debugDirRva = 0x1000;
|
|
const debugDirSize = 28;
|
|
bytes.writeUInt32LE(debugDirRva, optionalHeader + directoryOffset + 6 * 8);
|
|
bytes.writeUInt32LE(
|
|
debugDirSize,
|
|
optionalHeader + directoryOffset + 6 * 8 + 4,
|
|
);
|
|
|
|
bytes.write(".rdata\0\0", sectionTableOffset, "ascii");
|
|
bytes.writeUInt32LE(0x1000, sectionTableOffset + 8);
|
|
bytes.writeUInt32LE(0x1000, sectionTableOffset + 12);
|
|
bytes.writeUInt32LE(512, sectionTableOffset + 16);
|
|
bytes.writeUInt32LE(rawDataOffset, sectionTableOffset + 20);
|
|
bytes.writeUInt32LE(0x40000040, sectionTableOffset + 36);
|
|
|
|
bytes.writeUInt32LE(2, rawDataOffset + 12);
|
|
bytes.writeUInt32LE(codeViewData.length, rawDataOffset + 16);
|
|
bytes.writeUInt32LE(debugDirRva + 28, rawDataOffset + 20);
|
|
bytes.writeUInt32LE(rawDataOffset + 28, rawDataOffset + 24);
|
|
|
|
codeViewData.copy(bytes, rawDataOffset + 28);
|
|
}
|
|
return bytes;
|
|
};
|
|
|
|
/** Build a DOS MZ image with a 32-byte header and a 16-bit entry point. */
|
|
export const dosMz = (moduleSize = 128): Buffer => {
|
|
const size = 32 + moduleSize;
|
|
const bytes = Buffer.alloc(size);
|
|
bytes.write("MZ", 0, "ascii");
|
|
bytes.writeUInt16LE(size % 512, 2);
|
|
bytes.writeUInt16LE(Math.ceil(size / 512), 4);
|
|
bytes.writeUInt16LE(2, 8);
|
|
bytes.writeUInt16LE(0xffff, 12);
|
|
bytes.writeUInt16LE(28, 24);
|
|
bytes.set([0xb8, 0x00, 0x4c, 0xcd, 0x21], 32);
|
|
// This is module data, not an e_lfanew field in a Windows DOS stub.
|
|
if (size >= 64) bytes.writeUInt32LE(0xfedcba98, 60);
|
|
return bytes;
|
|
};
|
|
|
|
/** MZ stub declaring a non-PE Windows signature; not a complete NE/LE/LX image. */
|
|
export const nonPeWindowsMz = (signature: "NE" | "LE" | "LX"): Buffer => {
|
|
const bytes = dosMz(480);
|
|
bytes.writeUInt16LE(4, 8);
|
|
bytes.writeUInt32LE(128, 60);
|
|
bytes.write(signature, 128, "ascii");
|
|
return bytes;
|
|
};
|