Files
reverse-engineering-anything/tests/fixtures/go/rodataStringImage.ts
T
Ryan S 94182f331e feat(go): recover bounded Go string literals from (ptr, len) headers (#9206)
* 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
2026-11-08 22:50:32 +00:00

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);
}
};