mirror of
https://github.com/morluto/rea.git
synced 2026-11-09 00:07:18 +00:00
* feat(go): recover bounded Go string literals from (ptr, len) headers * fix(go): deduplicate header scan spans, bound rejected cache, and reject Cf * fix(go): allow U+200C ZWNJ and U+200D ZWJ in Go string literal recovery * fix(go): index Mach-O load regions, validate MCP schema, and add feature specs * test(go): cover Mach-O region-index cancellation and load-region boundaries
384 lines
11 KiB
TypeScript
384 lines
11 KiB
TypeScript
export interface GoStringHeaderFixtureSpec {
|
|
readonly section: "rodata" | "data" | "text";
|
|
readonly offsetInSection: number;
|
|
readonly ptr: bigint;
|
|
readonly len: bigint;
|
|
}
|
|
|
|
export interface GoRodataStringFixtureOptions {
|
|
readonly format: "elf" | "pe" | "macho";
|
|
readonly bits?: 32 | 64;
|
|
readonly byteOrder?: "little" | "big";
|
|
readonly rodata: Buffer;
|
|
readonly secondRodata?: Buffer;
|
|
readonly headers: readonly GoStringHeaderFixtureSpec[];
|
|
readonly dataSize?: number;
|
|
readonly sectionless?: boolean;
|
|
readonly corruptElfShstrtab?: boolean;
|
|
readonly elfSplitReversedDataSections?: boolean;
|
|
readonly machoChainedFixups?: boolean;
|
|
}
|
|
|
|
interface LayoutContext {
|
|
readonly bytes: Buffer;
|
|
readonly wide: boolean;
|
|
readonly little: boolean;
|
|
readonly base: bigint;
|
|
readonly textOffset: number;
|
|
readonly textSize: number;
|
|
readonly textAddr: bigint;
|
|
readonly rodataOffset: number;
|
|
readonly rodataSize: number;
|
|
readonly rodataAddr: bigint;
|
|
readonly secondOffset: number;
|
|
readonly secondSize: number;
|
|
readonly secondRodataAddr: bigint;
|
|
readonly dataOffset: number;
|
|
readonly dataSize: number;
|
|
readonly dataAddr: bigint;
|
|
readonly bssSize: number;
|
|
readonly u16: (offset: number, value: number) => void;
|
|
readonly u32: (offset: number, value: number) => void;
|
|
readonly word: (offset: number, value: bigint) => void;
|
|
}
|
|
|
|
/**
|
|
* Authored multi-section ELF, PE and Mach-O images with separate `.text`,
|
|
* `.rodata` / `__rodata` / `.rdata`, and `.data` / `__data` spans.
|
|
*/
|
|
export const createGoRodataStringFixture = (
|
|
options: GoRodataStringFixtureOptions,
|
|
) => {
|
|
const bits = options.bits ?? 64;
|
|
const wide = bits === 64;
|
|
const little = (options.byteOrder ?? "little") === "little";
|
|
const base = 0x100000n;
|
|
const textOffset = 0x1000;
|
|
const textSize = 0x200;
|
|
const rodataOffset = 0x2000;
|
|
const rodataSize = Math.max(16, options.rodata.length);
|
|
const secondOffset = rodataOffset + rodataSize;
|
|
const secondSize = options.secondRodata?.length ?? 0;
|
|
const dataOffset =
|
|
Math.ceil((secondOffset + secondSize + 0x100) / 0x1000) * 0x1000;
|
|
const dataSize = options.dataSize ?? 0x1000;
|
|
const bssSize = 0x200;
|
|
const bytes = Buffer.alloc(dataOffset + dataSize);
|
|
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
const u16 = (at: number, v: number) => view.setUint16(at, v, little);
|
|
const u32 = (at: number, v: number) => view.setUint32(at, v, little);
|
|
const word = (at: number, v: bigint) => {
|
|
if (wide) view.setBigUint64(at, v, little);
|
|
else view.setUint32(at, Number(v), little);
|
|
};
|
|
const layout: LayoutContext = {
|
|
bytes,
|
|
wide,
|
|
little,
|
|
base,
|
|
textOffset,
|
|
textSize,
|
|
textAddr: base + BigInt(textOffset),
|
|
rodataOffset,
|
|
rodataSize,
|
|
rodataAddr: base + BigInt(rodataOffset),
|
|
secondOffset,
|
|
secondSize,
|
|
secondRodataAddr: base + BigInt(secondOffset),
|
|
dataOffset,
|
|
dataSize,
|
|
dataAddr: base + BigInt(dataOffset),
|
|
bssSize,
|
|
u16,
|
|
u32,
|
|
word,
|
|
};
|
|
options.rodata.copy(bytes, rodataOffset);
|
|
options.secondRodata?.copy(bytes, secondOffset);
|
|
const width = wide ? 8 : 4;
|
|
for (const header of options.headers) {
|
|
const sectionBase =
|
|
header.section === "text"
|
|
? textOffset
|
|
: header.section === "rodata"
|
|
? rodataOffset
|
|
: dataOffset;
|
|
const at = sectionBase + header.offsetInSection;
|
|
word(at, header.ptr);
|
|
word(at + width, header.len);
|
|
}
|
|
if (options.format === "elf") writeElfStringContainer(layout, options);
|
|
else if (options.format === "pe") writePeStringContainer(layout);
|
|
else writeMachoStringContainer(layout, options.machoChainedFixups === true);
|
|
|
|
return {
|
|
bytes,
|
|
textAddr: layout.textAddr,
|
|
rodataAddr: layout.rodataAddr,
|
|
rodataOffset,
|
|
secondRodataAddr: layout.secondRodataAddr,
|
|
dataAddr: layout.dataAddr,
|
|
dataOffset,
|
|
};
|
|
};
|
|
|
|
const writeElfStringContainer = (
|
|
ctx: LayoutContext,
|
|
options: GoRodataStringFixtureOptions,
|
|
): void => {
|
|
const { bytes, wide, little, u16, u32, word } = ctx;
|
|
bytes.set([0x7f, 0x45, 0x4c, 0x46, wide ? 2 : 1, little ? 1 : 2, 1]);
|
|
u16(16, 2);
|
|
u16(18, wide ? 62 : 3);
|
|
u32(20, 1);
|
|
const phoff = 128;
|
|
const shoff = options.sectionless === true ? 0 : 512;
|
|
const phentsize = wide ? 56 : 32;
|
|
const shentsize = wide ? 64 : 40;
|
|
const phdrs: readonly [number, number, bigint, number, number][] = [
|
|
[5, ctx.textOffset, ctx.textAddr, ctx.textSize, ctx.textSize],
|
|
[
|
|
4,
|
|
ctx.rodataOffset,
|
|
ctx.rodataAddr,
|
|
ctx.rodataSize + ctx.secondSize,
|
|
ctx.rodataSize + ctx.secondSize,
|
|
],
|
|
[6, ctx.dataOffset, ctx.dataAddr, ctx.dataSize, ctx.dataSize + ctx.bssSize],
|
|
];
|
|
phdrs.forEach(([flags, off, vaddr, filesz, memsz], idx) => {
|
|
const p = phoff + idx * phentsize;
|
|
u32(p, 1);
|
|
if (wide) {
|
|
u32(p + 4, flags);
|
|
word(p + 8, BigInt(off));
|
|
word(p + 16, vaddr);
|
|
word(p + 24, vaddr);
|
|
word(p + 32, BigInt(filesz));
|
|
word(p + 40, BigInt(memsz));
|
|
word(p + 48, 0x1000n);
|
|
} else {
|
|
word(p + 4, BigInt(off));
|
|
word(p + 8, vaddr);
|
|
word(p + 12, vaddr);
|
|
word(p + 16, BigInt(filesz));
|
|
word(p + 20, BigInt(memsz));
|
|
u32(p + 24, flags);
|
|
word(p + 28, 0x1000n);
|
|
}
|
|
});
|
|
const shstrOffset = 0xc00;
|
|
const shstr = Buffer.from(
|
|
"\0.shstrtab\0.text\0.rodata\0.rodata.2\0.data\0.bss\0",
|
|
);
|
|
shstr.copy(bytes, shstrOffset);
|
|
const corrupt = options.corruptElfShstrtab === true;
|
|
if (corrupt) u32(shstrOffset, 3);
|
|
const sections: readonly (readonly [
|
|
number,
|
|
number,
|
|
bigint,
|
|
bigint,
|
|
number,
|
|
number,
|
|
])[] = [
|
|
[1, 3, corrupt ? 0x800n : 0n, 0n, shstrOffset, corrupt ? 24 : shstr.length],
|
|
[11, 1, 6n, ctx.textAddr, ctx.textOffset, ctx.textSize],
|
|
[17, 1, 2n, ctx.rodataAddr, ctx.rodataOffset, ctx.rodataSize],
|
|
...(ctx.secondSize > 0
|
|
? ([
|
|
[25, 1, 2n, ctx.secondRodataAddr, ctx.secondOffset, ctx.secondSize],
|
|
] as const)
|
|
: []),
|
|
...(options.elfSplitReversedDataSections === true
|
|
? ([
|
|
[
|
|
35,
|
|
1,
|
|
3n,
|
|
ctx.dataAddr + 0x200n,
|
|
ctx.dataOffset + 0x200,
|
|
ctx.dataSize - 0x200,
|
|
],
|
|
[35, 1, 3n, ctx.dataAddr + 0x40n, ctx.dataOffset + 0x40, 0x180],
|
|
] as const)
|
|
: ([[35, 1, 3n, ctx.dataAddr, ctx.dataOffset, ctx.dataSize]] as const)),
|
|
[41, 8, 3n, ctx.dataAddr + BigInt(ctx.dataSize), bytes.length, ctx.bssSize],
|
|
];
|
|
if (options.sectionless !== true) {
|
|
sections.forEach(([nameOff, type, flags, addr, off, size], idx) => {
|
|
const s = shoff + (idx + 1) * shentsize;
|
|
u32(s, nameOff);
|
|
u32(s + 4, type);
|
|
word(s + 8, flags);
|
|
word(s + (wide ? 16 : 12), addr);
|
|
word(s + (wide ? 24 : 16), BigInt(off));
|
|
word(s + (wide ? 32 : 20), BigInt(size));
|
|
});
|
|
}
|
|
word(wide ? 32 : 28, BigInt(phoff));
|
|
word(wide ? 40 : 32, BigInt(shoff));
|
|
u16(wide ? 52 : 40, wide ? 64 : 52);
|
|
u16(wide ? 54 : 42, phentsize);
|
|
u16(wide ? 56 : 44, 3);
|
|
u16(wide ? 58 : 46, shentsize);
|
|
u16(wide ? 60 : 48, options.sectionless === true ? 0 : sections.length + 1);
|
|
u16(wide ? 62 : 50, options.sectionless === true ? 0 : 1);
|
|
};
|
|
|
|
const writePeStringContainer = (ctx: LayoutContext): void => {
|
|
const { bytes, wide, base, u16, u32, word } = ctx;
|
|
bytes.write("MZ", 0, "ascii");
|
|
u32(60, 128);
|
|
bytes.write("PE\0\0", 128, "ascii");
|
|
u16(132, wide ? 0x8664 : 0x14c);
|
|
const peSections: readonly (readonly [
|
|
string,
|
|
number,
|
|
number,
|
|
number,
|
|
number,
|
|
number,
|
|
])[] = [
|
|
[
|
|
".text",
|
|
ctx.textSize,
|
|
ctx.textOffset,
|
|
ctx.textSize,
|
|
ctx.textOffset,
|
|
0x60000020,
|
|
],
|
|
[
|
|
".rdata",
|
|
ctx.rodataSize,
|
|
ctx.rodataOffset,
|
|
ctx.rodataSize,
|
|
ctx.rodataOffset,
|
|
0x40000040,
|
|
],
|
|
...(ctx.secondSize > 0
|
|
? ([
|
|
[
|
|
".rdata2",
|
|
ctx.secondSize,
|
|
ctx.secondOffset,
|
|
ctx.secondSize,
|
|
ctx.secondOffset,
|
|
0x40000040,
|
|
],
|
|
] as const)
|
|
: []),
|
|
[
|
|
".data",
|
|
ctx.dataSize + ctx.bssSize,
|
|
ctx.dataOffset,
|
|
ctx.dataSize,
|
|
ctx.dataOffset,
|
|
0xc0000040,
|
|
],
|
|
];
|
|
u16(134, peSections.length);
|
|
const optSize = wide ? 112 : 96;
|
|
u16(148, optSize);
|
|
u16(152, wide ? 0x20b : 0x10b);
|
|
word(152 + (wide ? 24 : 28), base);
|
|
u32(152 + 32, 0x1000);
|
|
u32(152 + 36, 0x200);
|
|
u32(152 + 56, bytes.length + 0x1000);
|
|
u32(152 + 60, 0x400);
|
|
peSections.forEach(([name, vsize, rva, rawSize, rawOff, chars], index) => {
|
|
const s = 152 + optSize + index * 40;
|
|
bytes.write(name, s, "ascii");
|
|
u32(s + 8, vsize);
|
|
u32(s + 12, rva);
|
|
u32(s + 16, rawSize);
|
|
u32(s + 20, rawOff);
|
|
u32(s + 36, chars);
|
|
});
|
|
};
|
|
|
|
const writeMachoStringContainer = (
|
|
ctx: LayoutContext,
|
|
chainedFixups: boolean,
|
|
): void => {
|
|
const { bytes, wide, base, u32, word } = ctx;
|
|
u32(0, wide ? 0xfeedfacf : 0xfeedface);
|
|
u32(4, wide ? 0x0100000c : 7);
|
|
u32(8, 0);
|
|
u32(12, 2);
|
|
const segHdr = wide ? 72 : 56;
|
|
const secHdr = wide ? 80 : 68;
|
|
const textSecs: readonly (readonly [
|
|
string,
|
|
bigint,
|
|
number,
|
|
number,
|
|
number,
|
|
])[] = [
|
|
["__text", ctx.textAddr, ctx.textSize, ctx.textOffset, 0x80000400],
|
|
["__rodata", ctx.rodataAddr, ctx.rodataSize, ctx.rodataOffset, 0],
|
|
...(ctx.secondSize > 0
|
|
? ([
|
|
[
|
|
"__const",
|
|
ctx.secondRodataAddr,
|
|
ctx.secondSize,
|
|
ctx.secondOffset,
|
|
0,
|
|
],
|
|
] as const)
|
|
: []),
|
|
];
|
|
const dataSecs: readonly (readonly [
|
|
string,
|
|
bigint,
|
|
number,
|
|
number,
|
|
number,
|
|
])[] = [["__data", ctx.dataAddr, ctx.dataSize, ctx.dataOffset, 0]];
|
|
const cmd0Size = segHdr + textSecs.length * secHdr;
|
|
const cmd1Size = segHdr + dataSecs.length * secHdr;
|
|
const fixupSize = chainedFixups ? 16 : 0;
|
|
u32(16, 2 + (fixupSize > 0 ? 1 : 0));
|
|
u32(20, cmd0Size + cmd1Size + fixupSize);
|
|
let cursor = wide ? 32 : 28;
|
|
for (const [segName, vmaddr, vmsize, fileoff, filesize, prot, secs] of [
|
|
["__TEXT", base, BigInt(ctx.dataOffset), 0, ctx.dataOffset, 5, textSecs],
|
|
[
|
|
"__DATA",
|
|
ctx.dataAddr,
|
|
BigInt(ctx.dataSize + ctx.bssSize),
|
|
ctx.dataOffset,
|
|
ctx.dataSize,
|
|
3,
|
|
dataSecs,
|
|
],
|
|
] as const) {
|
|
const size = segHdr + secs.length * secHdr;
|
|
u32(cursor, wide ? 0x19 : 1);
|
|
u32(cursor + 4, size);
|
|
bytes.write(segName, cursor + 8, "ascii");
|
|
word(cursor + 24, vmaddr);
|
|
word(cursor + (wide ? 32 : 28), vmsize);
|
|
word(cursor + (wide ? 40 : 32), BigInt(fileoff));
|
|
word(cursor + (wide ? 48 : 36), BigInt(filesize));
|
|
u32(cursor + (wide ? 56 : 40), prot);
|
|
u32(cursor + (wide ? 60 : 44), prot);
|
|
u32(cursor + (wide ? 64 : 48), secs.length);
|
|
secs.forEach(([secName, addr, secSize, off, flags], idx) => {
|
|
const s = cursor + segHdr + idx * secHdr;
|
|
bytes.write(secName, s, "ascii");
|
|
bytes.write(segName, s + 16, "ascii");
|
|
word(s + 32, addr);
|
|
word(s + (wide ? 40 : 36), BigInt(secSize));
|
|
u32(s + (wide ? 48 : 40), off);
|
|
u32(s + (wide ? 64 : 56), flags);
|
|
});
|
|
cursor += size;
|
|
}
|
|
if (fixupSize > 0) {
|
|
u32(cursor, 0x80000034);
|
|
u32(cursor + 4, fixupSize);
|
|
}
|
|
};
|