mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
refactor(pe): share CodeView range interpretation
This commit is contained in:
1 parent
a7d71f47ee
commit
92bdb4443f
3 files changed
+104
-45
No files matched your search
@@ -82,6 +82,7 @@ import {
|
||||
parseExecutableHeader,
|
||||
parsePeRecordFacts,
|
||||
peDebugDirectoryEntry,
|
||||
peCodeViewDataRanges,
|
||||
peRvaToFileOffset,
|
||||
type ExecutableMetadata,
|
||||
} from "../domain/binaryTarget.js";
|
||||
@@ -1347,28 +1348,15 @@ const readPeCodeView = async (
|
||||
debugFileOffset,
|
||||
);
|
||||
throwIfTargetResolutionCancelled(signal);
|
||||
const numEntries = Math.floor(debugDirRead.bytesRead / 28);
|
||||
for (let j = 0; j < numEntries; j++) {
|
||||
const entryOffset = j * 28;
|
||||
const type = debugDirectoryBuffer.readUInt32LE(entryOffset + 12);
|
||||
if (type !== 2) continue; // IMAGE_DEBUG_TYPE_CODEVIEW
|
||||
const sizeOfData = debugDirectoryBuffer.readUInt32LE(entryOffset + 16);
|
||||
let pointerToRawData = debugDirectoryBuffer.readUInt32LE(entryOffset + 24);
|
||||
if (pointerToRawData === 0) {
|
||||
const addressOfRawData = debugDirectoryBuffer.readUInt32LE(
|
||||
entryOffset + 20,
|
||||
);
|
||||
const mapped = peRvaToFileOffset(
|
||||
addressOfRawData,
|
||||
sectionTable,
|
||||
numberOfSections,
|
||||
debugDir.sizeOfHeaders,
|
||||
Math.min(sizeOfData, 4096),
|
||||
);
|
||||
if (mapped === undefined) continue;
|
||||
pointerToRawData = mapped;
|
||||
}
|
||||
if (sizeOfData < 16) continue;
|
||||
for (const {
|
||||
fileOffset: pointerToRawData,
|
||||
declaredSize: sizeOfData,
|
||||
} of peCodeViewDataRanges(
|
||||
debugDirectoryBuffer.subarray(0, debugDirRead.bytesRead),
|
||||
sectionTable,
|
||||
numberOfSections,
|
||||
debugDir.sizeOfHeaders,
|
||||
)) {
|
||||
const maxCodeViewRead = Math.min(sizeOfData, 4096);
|
||||
const codeViewBuffer = Buffer.alloc(maxCodeViewRead);
|
||||
const codeViewRead = await handle.read(
|
||||
|
||||
+39
-23
@@ -630,6 +630,35 @@ export const peRvaToFileOffset = (
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/** CodeView RVA admission uses the same bounded prefix as target file reads. */
|
||||
const PE_CODEVIEW_MAPPING_BYTES = 4096;
|
||||
|
||||
/** Interpret complete debug entries once; callers own directory and payload read bounds. */
|
||||
export function* peCodeViewDataRanges(
|
||||
directory: Buffer,
|
||||
sectionTable: Buffer,
|
||||
numberOfSections: number,
|
||||
sizeOfHeaders: number,
|
||||
): Generator<{ readonly fileOffset: number; readonly declaredSize: number }> {
|
||||
for (let offset = 0; offset + 28 <= directory.length; offset += 28) {
|
||||
if (directory.readUInt32LE(offset + 12) !== 2) continue;
|
||||
const declaredSize = directory.readUInt32LE(offset + 16);
|
||||
if (declaredSize < 16) continue;
|
||||
const rawPointer = directory.readUInt32LE(offset + 24);
|
||||
const fileOffset =
|
||||
rawPointer !== 0
|
||||
? rawPointer
|
||||
: peRvaToFileOffset(
|
||||
directory.readUInt32LE(offset + 20),
|
||||
sectionTable,
|
||||
numberOfSections,
|
||||
sizeOfHeaders,
|
||||
Math.min(declaredSize, PE_CODEVIEW_MAPPING_BYTES),
|
||||
);
|
||||
if (fileOffset !== undefined) yield { fileOffset, declaredSize };
|
||||
}
|
||||
}
|
||||
|
||||
/** Parse CodeView metadata from a bounded in-memory PE image buffer. */
|
||||
export const parsePeCodeViewFromBuffer = (
|
||||
fileBytes: Buffer,
|
||||
@@ -673,29 +702,16 @@ export const parsePeCodeViewFromBuffer = (
|
||||
debugDir.debugSize,
|
||||
fileBytes.length - debugFileOffset,
|
||||
);
|
||||
const numEntries = Math.floor(maxDebugBytes / 28);
|
||||
for (let j = 0; j < numEntries; j++) {
|
||||
const entryOffset = debugFileOffset + j * 28;
|
||||
const type = fileBytes.readUInt32LE(entryOffset + 12);
|
||||
if (type !== 2) continue; // IMAGE_DEBUG_TYPE_CODEVIEW
|
||||
const sizeOfData = fileBytes.readUInt32LE(entryOffset + 16);
|
||||
let pointerToRawData = fileBytes.readUInt32LE(entryOffset + 24);
|
||||
if (pointerToRawData === 0) {
|
||||
const addressOfRawData = fileBytes.readUInt32LE(entryOffset + 20);
|
||||
const mapped = peRvaToFileOffset(
|
||||
addressOfRawData,
|
||||
sectionTable,
|
||||
numberOfSections,
|
||||
debugDir.sizeOfHeaders,
|
||||
Math.min(sizeOfData, 4096),
|
||||
);
|
||||
if (mapped === undefined) continue;
|
||||
pointerToRawData = mapped;
|
||||
}
|
||||
if (
|
||||
sizeOfData < 16 ||
|
||||
fileBytes.length < pointerToRawData + Math.min(sizeOfData, 24)
|
||||
)
|
||||
for (const {
|
||||
fileOffset: pointerToRawData,
|
||||
declaredSize: sizeOfData,
|
||||
} of peCodeViewDataRanges(
|
||||
fileBytes.subarray(debugFileOffset, debugFileOffset + maxDebugBytes),
|
||||
sectionTable,
|
||||
numberOfSections,
|
||||
debugDir.sizeOfHeaders,
|
||||
)) {
|
||||
if (fileBytes.length < pointerToRawData + Math.min(sizeOfData, 24))
|
||||
continue;
|
||||
const codeViewData = fileBytes.subarray(
|
||||
pointerToRawData,
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { peCodeViewDataRanges } from "./binaryTarget.js";
|
||||
|
||||
const entry = (type: number, size: number, rva: number, raw: number) => {
|
||||
const bytes = Buffer.alloc(28);
|
||||
bytes.writeUInt32LE(type, 12);
|
||||
bytes.writeUInt32LE(size, 16);
|
||||
bytes.writeUInt32LE(rva, 20);
|
||||
bytes.writeUInt32LE(raw, 24);
|
||||
return bytes;
|
||||
};
|
||||
|
||||
const section = () => {
|
||||
const bytes = Buffer.alloc(40);
|
||||
bytes.writeUInt32LE(0x2000, 12);
|
||||
bytes.writeUInt32LE(4096, 16);
|
||||
bytes.writeUInt32LE(0x400, 20);
|
||||
return bytes;
|
||||
};
|
||||
|
||||
it("prefers physical pointers and maps only missing pointers through section or header RVAs", () => {
|
||||
const directory = Buffer.concat([
|
||||
entry(2, 32, 0x2000, 0x900),
|
||||
entry(2, 32, 0x2020, 0),
|
||||
entry(2, 32, 0x100, 0),
|
||||
entry(2, 32, 0x8000, 0),
|
||||
]);
|
||||
expect([...peCodeViewDataRanges(directory, section(), 1, 0x400)]).toEqual([
|
||||
{ fileOffset: 0x900, declaredSize: 32 },
|
||||
{ fileOffset: 0x420, declaredSize: 32 },
|
||||
{ fileOffset: 0x100, declaredSize: 32 },
|
||||
]);
|
||||
});
|
||||
|
||||
it("ignores other debug types, undersized records and incomplete trailing entries", () => {
|
||||
const directory = Buffer.concat([
|
||||
entry(1, 32, 0, 0x500),
|
||||
entry(2, 15, 0, 0x600),
|
||||
entry(2, 16, 0, 0x700),
|
||||
entry(2, 32, 0, 0x800).subarray(0, 27),
|
||||
]);
|
||||
expect([...peCodeViewDataRanges(directory, Buffer.alloc(0), 0, 0)]).toEqual([
|
||||
{ fileOffset: 0x700, declaredSize: 16 },
|
||||
]);
|
||||
});
|
||||
|
||||
it("maps the bounded payload prefix while retaining its full declared size", () => {
|
||||
const directory = Buffer.concat([
|
||||
entry(2, 8192, 0x2000, 0),
|
||||
entry(2, 8192, 0x2001, 0),
|
||||
]);
|
||||
expect([...peCodeViewDataRanges(directory, section(), 1, 0x400)]).toEqual([
|
||||
{ fileOffset: 0x400, declaredSize: 8192 },
|
||||
]);
|
||||
});
|
||||
Reference in new issue
Block a user