Files
reverse-engineering-anything/src/native/elf/ElfNotes.fixture.ts
T
morluto 5f702dd6cc fix(parsers): preserve format boundaries and query occurrence identity
Normalize parsed facts at their owning boundaries across PE exports and certificates, ELF section names, GPU bundles, IL2CPP generic types, dyld slice selection, source maps, environment replay, and JavaScript mapped arguments. Preserve partial facts and explicit unsupported cases; clarify the corresponding documentation.

Validation: 831 affected tests passed with one existing skip across 23 files; build, typecheck/lint, documentation, formatting, and 2133-file test discovery passed. Format controls are synthetic; real Unity, GPU, and ARM-device execution is unverified.

Fixes #6709
Fixes #6688
Fixes #6719
Fixes #6718
Fixes #6704
Fixes #6703
Fixes #6692
Fixes #6723
Fixes #6687
Fixes #6726
Fixes #6696
Fixes #6691
Fixes #6727
2026-10-23 22:01:16 +08:00

320 lines
10 KiB
TypeScript

import { deflateSync } from "node:zlib";
const concat = (...parts: Buffer[]): Buffer => Buffer.concat(parts);
const alignTo = (value: number, alignment: number): number =>
Math.ceil(value / alignment) * alignment;
/** Effective namesz/descsz padding: 8 stays 8; 0, 1, 4, and other values use 4. */
const notePadding = (declared: number): number => (declared === 8 ? 8 : 4);
const writeWord = (
bytes: Buffer,
offset: number,
value: number,
bits: 32 | 64,
little: boolean,
): void => {
if (bits === 64) {
if (little) bytes.writeBigUInt64LE(BigInt(value), offset);
else bytes.writeBigUInt64BE(BigInt(value), offset);
return;
}
if (little) bytes.writeUInt32LE(value, offset);
else bytes.writeUInt32BE(value, offset);
};
const write16 = (
bytes: Buffer,
offset: number,
value: number,
little: boolean,
): void => {
if (little) bytes.writeUInt16LE(value, offset);
else bytes.writeUInt16BE(value, offset);
};
const write32 = (
bytes: Buffer,
offset: number,
value: number,
little: boolean,
): void => {
if (little) bytes.writeUInt32LE(value, offset);
else bytes.writeUInt32BE(value, offset);
};
/** Encode one ELF note, padding name/descriptor to the table alignment. */
export const elfNote = (
owner: string,
type: number,
descriptor: Buffer,
little = true,
alignment = 4,
): Buffer => {
const name = Buffer.from(`${owner}\0`, "latin1");
const pad = notePadding(alignment);
const descOff = alignTo(12 + name.length, pad);
const bytes = Buffer.alloc(alignTo(descOff + descriptor.length, pad));
write32(bytes, 0, name.length, little);
write32(bytes, 4, descriptor.length, little);
write32(bytes, 8, type, little);
name.copy(bytes, 12);
descriptor.copy(bytes, descOff);
return bytes;
};
/** GNU NT_GNU_BUILD_ID note with a 20-byte descriptor. */
export const gnuBuildIdNote = (
hex = "0123456789abcdef0123456789abcdef01234567",
little = true,
): Buffer => elfNote("GNU", 3, Buffer.from(hex, "hex"), little);
/** GNU NT_GNU_ABI_TAG note: Linux 3.2.0. */
export const gnuAbiTagNote = (little = true): Buffer => {
const descriptor = Buffer.alloc(16);
write32(descriptor, 0, 0, little);
write32(descriptor, 4, 3, little);
write32(descriptor, 8, 2, little);
write32(descriptor, 12, 0, little);
return elfNote("GNU", 1, descriptor, little);
};
/** GNU NT_GNU_PROPERTY_TYPE_0 note with 8-byte table padding. */
export const gnuPropertyNote = (little = true): Buffer => {
const descriptor = Buffer.alloc(32);
write32(descriptor, 0, 0xc0000002, little);
write32(descriptor, 4, 4, little);
write32(descriptor, 8, 3, little);
write32(descriptor, 16, 0xc0008002, little);
write32(descriptor, 20, 4, little);
write32(descriptor, 24, 1, little);
return elfNote("GNU", 5, descriptor, little, 8);
};
export interface ElfNoteImageOptions {
readonly bits?: 32 | 64;
readonly little?: boolean;
readonly notes?: Buffer;
readonly ownership?: "program" | "section" | "both" | "none";
readonly machine?: number;
readonly type?: number;
readonly alignment?: number;
readonly sectionName?: string;
}
/** Minimal ELF32/ELF64 image declaring PT_NOTE and/or SHT_NOTE. */
export const elfNoteFixture = (options: ElfNoteImageOptions = {}): Buffer => {
const bits = options.bits ?? 64;
const little = options.little ?? true;
const ownership = options.ownership ?? "both";
const notes =
options.notes ??
concat(gnuBuildIdNote(undefined, little), gnuAbiTagNote(little));
const declaredAlign = options.alignment ?? 4;
const pad = notePadding(declaredAlign);
const header = bits === 64 ? 64 : 52;
const phent = bits === 64 ? 56 : 32;
const shent = bits === 64 ? 64 : 40;
const program = ownership === "program" || ownership === "both";
const section = ownership === "section" || ownership === "both";
const phnum = program ? 1 : 0;
const sectionName = options.sectionName ?? ".note.gnu.build-id";
const shstrtab = Buffer.from(`\0${sectionName}\0.shstrtab\0`, "latin1");
const shnum = section ? 3 : 0;
const phoff = header;
const shoff = phoff + phnum * phent;
const noteOff = alignTo(shoff + shnum * shent, pad);
const strOff = noteOff + notes.length;
const bytes = Buffer.alloc(strOff + (section ? shstrtab.length : 0));
bytes.writeUInt32LE(0x464c457f, 0);
bytes[4] = bits === 64 ? 2 : 1;
bytes[5] = little ? 1 : 2;
bytes[6] = 1;
write16(bytes, 0x10, options.type ?? 3, little);
write16(bytes, 0x12, options.machine ?? 62, little);
write32(bytes, 0x14, 1, little);
write16(bytes, bits === 64 ? 0x34 : 0x28, header, little);
writeWord(
bytes,
bits === 64 ? 0x20 : 0x1c,
phnum === 0 ? 0 : phoff,
bits,
little,
);
write16(bytes, bits === 64 ? 0x36 : 0x2a, phent, little);
write16(bytes, bits === 64 ? 0x38 : 0x2c, phnum, little);
writeWord(
bytes,
bits === 64 ? 0x28 : 0x20,
shnum === 0 ? 0 : shoff,
bits,
little,
);
write16(bytes, bits === 64 ? 0x3a : 0x2e, shent, little);
write16(bytes, bits === 64 ? 0x3c : 0x30, shnum, little);
write16(bytes, bits === 64 ? 0x3e : 0x32, section ? 2 : 0, little);
if (program) {
const ph = phoff;
write32(bytes, ph, 4, little);
if (bits === 64) {
write32(bytes, ph + 4, 4, little);
writeWord(bytes, ph + 8, noteOff, bits, little);
writeWord(bytes, ph + 32, notes.length, bits, little);
writeWord(bytes, ph + 40, notes.length, bits, little);
writeWord(bytes, ph + 48, declaredAlign, bits, little);
} else {
writeWord(bytes, ph + 4, noteOff, bits, little);
writeWord(bytes, ph + 16, notes.length, bits, little);
writeWord(bytes, ph + 20, notes.length, bits, little);
write32(bytes, ph + 24, 4, little);
writeWord(bytes, ph + 28, declaredAlign, bits, little);
}
}
if (section) {
const noteName = 1;
const strName = `${sectionName}\0`.length + 1;
const noteSh = shoff + shent;
const strSh = shoff + 2 * shent;
write32(bytes, noteSh, noteName, little);
write32(bytes, noteSh + 4, 7, little);
writeWord(bytes, noteSh + (bits === 64 ? 24 : 16), noteOff, bits, little);
writeWord(
bytes,
noteSh + (bits === 64 ? 32 : 20),
notes.length,
bits,
little,
);
writeWord(
bytes,
noteSh + (bits === 64 ? 48 : 32),
declaredAlign,
bits,
little,
);
write32(bytes, strSh, strName, little);
write32(bytes, strSh + 4, 3, little);
writeWord(bytes, strSh + (bits === 64 ? 24 : 16), strOff, bits, little);
writeWord(
bytes,
strSh + (bits === 64 ? 32 : 20),
shstrtab.length,
bits,
little,
);
shstrtab.copy(bytes, strOff);
}
notes.copy(bytes, noteOff);
return bytes;
};
/** ELF note fixture with a zlib-compressed section-name string table. */
export const compressedElfNoteFixture = (
bits: 32 | 64 = 64,
little = true,
compressionType = 1,
sectionName = ".note.gnu.build-id",
): Buffer => {
const image = elfNoteFixture({
bits,
little,
ownership: "section",
sectionName,
});
const wide = bits === 64;
const u16 = (offset: number) =>
little ? image.readUInt16LE(offset) : image.readUInt16BE(offset);
const word = (offset: number) =>
wide
? Number(
little
? image.readBigUInt64LE(offset)
: image.readBigUInt64BE(offset),
)
: little
? image.readUInt32LE(offset)
: image.readUInt32BE(offset);
const shoff = word(wide ? 40 : 32);
const shentsize = u16(wide ? 58 : 46);
const namesIndex = u16(wide ? 62 : 50);
const namesHeader = shoff + namesIndex * shentsize;
const namesOffset = word(namesHeader + (wide ? 24 : 16));
const namesSize = word(namesHeader + (wide ? 32 : 20));
const names = image.subarray(namesOffset, namesOffset + namesSize);
const compressed = deflateSync(names);
const compressionHeaderBytes = wide ? 24 : 12;
const replacement = Buffer.alloc(compressionHeaderBytes);
if (little) replacement.writeUInt32LE(compressionType, 0);
else replacement.writeUInt32BE(compressionType, 0);
if (wide) {
if (little) {
replacement.writeBigUInt64LE(BigInt(names.length), 8);
replacement.writeBigUInt64LE(1n, 16);
} else {
replacement.writeBigUInt64BE(BigInt(names.length), 8);
replacement.writeBigUInt64BE(1n, 16);
}
} else {
if (little) {
replacement.writeUInt32LE(names.length, 4);
replacement.writeUInt32LE(1, 8);
} else {
replacement.writeUInt32BE(names.length, 4);
replacement.writeUInt32BE(1, 8);
}
}
const result = concat(
image.subarray(0, namesOffset),
replacement,
compressed,
image.subarray(namesOffset + namesSize),
);
const writeWord = (offset: number, value: number) => {
if (wide) {
if (little) result.writeBigUInt64LE(BigInt(value), offset);
else result.writeBigUInt64BE(BigInt(value), offset);
} else if (little) result.writeUInt32LE(value, offset);
else result.writeUInt32BE(value, offset);
};
const flagsAt = namesHeader + 8;
writeWord(flagsAt, word(flagsAt) | 0x800);
writeWord(
namesHeader + (wide ? 32 : 20),
compressionHeaderBytes + compressed.length,
);
writeWord(namesHeader + (wide ? 48 : 32), 1);
return result;
};
/**
* ELF64 whose only PT_NOTE declares 20 bytes. The file holds the 12-byte
* header, 4-byte name, and 1-byte descriptor; the 3 alignment bytes are absent.
*/
export const elfNoteTablePastEnd = (): Buffer => {
const note = Buffer.alloc(17);
write32(note, 0, 4, true);
write32(note, 4, 1, true);
write32(note, 8, 1, true);
note.write("GNU\0", 12, "latin1");
note[16] = 0x2a;
const header = Buffer.alloc(64);
header.writeUInt32LE(0x464c457f, 0);
header[4] = 2;
header[5] = 1;
header[6] = 1;
write16(header, 0x10, 3, true);
write16(header, 0x12, 62, true);
write32(header, 0x14, 1, true);
write16(header, 0x34, 64, true);
writeWord(header, 0x20, 64, 64, true);
write16(header, 0x36, 56, true);
write16(header, 0x38, 1, true);
const program = Buffer.alloc(56);
write32(program, 0, 4, true);
writeWord(program, 8, 120, 64, true);
writeWord(program, 32, 20, 64, true);
writeWord(program, 48, 4, 64, true);
return concat(header, program, note);
};