mirror of
https://github.com/morluto/rea
synced 2026-10-26 14:25:49 -04:00
bd4f5e9c19
Retain bounded FAT and offset-tagged PE observations through archive hashing, classification, and content nodes. Preserve PyInstaller member paths according to the observed carrier dialect and share APK v1 signing-name semantics. Read SQLite's implicit sqlite_schema table without inventing a stored schema declaration. Distinguish unsupported ELF numbering and decoded-name admission from malformed data and fixed scan allowances. Preserve Sigrok basenames and classify matching explicit input-format refusals at their owning boundary. Validation: 554 distinct tests passed, 3 skipped across serial focused runs; build, typecheck/lint, docs, formatting, and discovery (2141 files) passed. Sigrok command-boundary simulators do not establish real-tool decoding. Small repeated public PyInstaller CLI runs had equivalent inventory facts; warm median elapsed 0.5431s before / 0.5426s after, with no speed claim. Fixes #6733 Fixes #6741 Fixes #6722 Fixes #6732 Fixes #6736 Fixes #6734 Fixes #6764 Fixes #6766 Refs #6739
274 lines
9.2 KiB
TypeScript
274 lines
9.2 KiB
TypeScript
import type { AsarHeaderAdmission } from "../config/asarHeaderAdmission.js";
|
|
import {
|
|
createAsarHeaderSignatureProbe,
|
|
type FinalAsarHeaderProbeObservation,
|
|
} from "./AsarHeader.js";
|
|
import {
|
|
createXmlPlistSignatureProbe,
|
|
MAX_XML_PLIST_PROBE_BYTES,
|
|
} from "../domain/apple/appleArchiveFormat.js";
|
|
import {
|
|
readFatContainerHeader,
|
|
readFatContainerTable,
|
|
readMachoHeaderPrefixFacts,
|
|
type FatContainerTable,
|
|
type MachoHeaderPrefixFacts,
|
|
type FatSliceHeaderEvidence,
|
|
} from "../domain/apple/machoContainer.js";
|
|
import {
|
|
observeMzWindowsHeader,
|
|
type MzWindowsHeaderSignatureEvidence,
|
|
} from "../domain/dosMz.js";
|
|
|
|
/** Initial bytes required by the other bounded format classifiers. */
|
|
export const ARTIFACT_CLASSIFICATION_PREFIX_BYTES = 8_192;
|
|
|
|
/** Retained prefix plus bounded format-specific observations from the stream. */
|
|
export interface ArtifactClassificationEvidence {
|
|
readonly prefix: Buffer;
|
|
readonly asarHeader: FinalAsarHeaderProbeObservation;
|
|
readonly fatSliceHeaders?: FatSliceHeaderEvidence;
|
|
readonly mzWindowsHeaderSignature?: MzWindowsHeaderSignatureEvidence;
|
|
}
|
|
|
|
const observeFatSliceHeaders = (input: {
|
|
readonly table: FatContainerTable | undefined;
|
|
readonly observedHeaders: Map<number, MachoHeaderPrefixFacts>;
|
|
readonly partialHeaders: Map<
|
|
number,
|
|
{ readonly bytes: Buffer; count: number }
|
|
>;
|
|
readonly streamOffset: number;
|
|
readonly bytes: Uint8Array;
|
|
}): void => {
|
|
const { table, observedHeaders, partialHeaders, streamOffset, bytes } = input;
|
|
if (table?.state !== "parsed") return;
|
|
for (const slice of table.slices) {
|
|
if (observedHeaders.has(slice.offset) && !partialHeaders.has(slice.offset))
|
|
continue;
|
|
const headerLength = Math.min(12, slice.size);
|
|
const headerEnd = slice.offset + headerLength;
|
|
if (!Number.isSafeInteger(headerEnd)) continue;
|
|
const start = Math.max(slice.offset, streamOffset);
|
|
const end = Math.min(headerEnd, streamOffset + bytes.length);
|
|
if (start >= end) continue;
|
|
let pending = partialHeaders.get(slice.offset);
|
|
if (pending === undefined) {
|
|
pending = { bytes: Buffer.alloc(12), count: 0 };
|
|
partialHeaders.set(slice.offset, pending);
|
|
}
|
|
Buffer.from(bytes.subarray(start - streamOffset, end - streamOffset)).copy(
|
|
pending.bytes,
|
|
start - slice.offset,
|
|
);
|
|
pending.count += end - start;
|
|
observedHeaders.set(
|
|
slice.offset,
|
|
readMachoHeaderPrefixFacts(
|
|
pending.bytes.subarray(0, Math.min(pending.count, headerLength)),
|
|
),
|
|
);
|
|
if (pending.count === headerLength) {
|
|
partialHeaders.delete(slice.offset);
|
|
}
|
|
}
|
|
};
|
|
|
|
const finishClassificationEvidence = (
|
|
prefix: Buffer,
|
|
archiveBytes: number,
|
|
finishAsar: (archiveBytes: number) => FinalAsarHeaderProbeObservation,
|
|
sliceHeadersByOffset: Map<number, MachoHeaderPrefixFacts>,
|
|
mzWindowsHeaderSignature: MzWindowsHeaderSignatureEvidence | undefined,
|
|
): ArtifactClassificationEvidence => {
|
|
const finalHeader = readFatContainerHeader(prefix, archiveBytes);
|
|
if (finalHeader.state === "parsed") {
|
|
for (let index = 0; index < finalHeader.slices.length; index += 1) {
|
|
const slice = finalHeader.slices[index];
|
|
if (slice === undefined || sliceHeadersByOffset.has(slice.offset))
|
|
continue;
|
|
const available = Math.max(
|
|
0,
|
|
Math.min(slice.size, prefix.length - slice.offset),
|
|
);
|
|
if (available === 0) continue;
|
|
sliceHeadersByOffset.set(
|
|
slice.offset,
|
|
readMachoHeaderPrefixFacts(
|
|
prefix.subarray(slice.offset, slice.offset + available),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
const fatSliceHeaders =
|
|
finalHeader.state === "parsed"
|
|
? { header: finalHeader, sliceHeadersByOffset }
|
|
: undefined;
|
|
return {
|
|
prefix,
|
|
asarHeader: finishAsar(archiveBytes),
|
|
...(fatSliceHeaders === undefined ? {} : { fatSliceHeaders }),
|
|
...(mzWindowsHeaderSignature === undefined
|
|
? {}
|
|
: { mzWindowsHeaderSignature }),
|
|
};
|
|
};
|
|
|
|
/** Share acquisition bounds and signature observations across files and streams. */
|
|
export const createArtifactClassificationProbe = (
|
|
asarHeaderAdmission?: AsarHeaderAdmission,
|
|
) => {
|
|
const xml = createXmlPlistSignatureProbe();
|
|
const asar = createAsarHeaderSignatureProbe(asarHeaderAdmission);
|
|
const blocks: Buffer[] = [];
|
|
let retainedBytes = 0;
|
|
let observedBytes = 0;
|
|
let fatTable: FatContainerTable | undefined;
|
|
const fatSliceHeadersByOffset = new Map<number, MachoHeaderPrefixFacts>();
|
|
const partialFatHeaders = new Map<
|
|
number,
|
|
{ readonly bytes: Buffer; count: number }
|
|
>();
|
|
let mzWindowsHeaderSignature: MzWindowsHeaderSignatureEvidence | undefined;
|
|
let mzWindowsHeaderOffset: number | undefined;
|
|
let mzHeaderChecked = false;
|
|
const observeSignatureWindow = (
|
|
offset: number,
|
|
sourceOffset: number,
|
|
sourceBytes: Uint8Array,
|
|
): void => {
|
|
if (!Number.isSafeInteger(offset) || offset < 0) return;
|
|
const start = Math.max(offset, sourceOffset);
|
|
const end = Math.min(offset + 4, sourceOffset + sourceBytes.length);
|
|
if (start >= end) return;
|
|
const window = Buffer.alloc(4);
|
|
const previous =
|
|
mzWindowsHeaderSignature?.offset === offset
|
|
? mzWindowsHeaderSignature.bytes
|
|
: undefined;
|
|
previous?.copy(window);
|
|
let observedLength = previous?.length ?? 0;
|
|
if (start < end && start <= offset + observedLength) {
|
|
Buffer.from(
|
|
sourceBytes.subarray(start - sourceOffset, end - sourceOffset),
|
|
).copy(window, start - offset);
|
|
observedLength = Math.max(observedLength, end - offset);
|
|
}
|
|
if (observedLength === 0) return;
|
|
mzWindowsHeaderSignature = {
|
|
offset,
|
|
bytes: window.subarray(0, observedLength),
|
|
};
|
|
};
|
|
const retain = (bytes: Uint8Array): void => {
|
|
let offset = 0;
|
|
while (offset < bytes.length) {
|
|
const blockOffset = retainedBytes % ARTIFACT_CLASSIFICATION_PREFIX_BYTES;
|
|
if (blockOffset === 0)
|
|
blocks.push(Buffer.allocUnsafe(ARTIFACT_CLASSIFICATION_PREFIX_BYTES));
|
|
const count = Math.min(
|
|
ARTIFACT_CLASSIFICATION_PREFIX_BYTES - blockOffset,
|
|
bytes.length - offset,
|
|
);
|
|
blocks[blocks.length - 1]!.set(
|
|
bytes.subarray(offset, offset + count),
|
|
blockOffset,
|
|
);
|
|
retainedBytes += count;
|
|
offset += count;
|
|
}
|
|
};
|
|
const retainedPrefix = (): Buffer => {
|
|
const prefix = Buffer.allocUnsafe(retainedBytes);
|
|
let copied = 0;
|
|
for (const block of blocks) {
|
|
const count = Math.min(block.length, retainedBytes - copied);
|
|
prefix.set(block.subarray(0, count), copied);
|
|
copied += count;
|
|
}
|
|
return prefix;
|
|
};
|
|
return {
|
|
get length(): number {
|
|
return observedBytes;
|
|
},
|
|
get limit(): number {
|
|
if (observedBytes < ARTIFACT_CLASSIFICATION_PREFIX_BYTES)
|
|
return ARTIFACT_CLASSIFICATION_PREFIX_BYTES;
|
|
return Math.max(
|
|
ARTIFACT_CLASSIFICATION_PREFIX_BYTES,
|
|
xml.state === "pending" ? MAX_XML_PLIST_PROBE_BYTES : 0,
|
|
asar.observation.state === "pending" ? asar.limit : 0,
|
|
);
|
|
},
|
|
write(bytes: Uint8Array): void {
|
|
const streamOffset = observedBytes;
|
|
asar.write(bytes);
|
|
let offset = 0;
|
|
while (offset < bytes.length) {
|
|
const limit =
|
|
retainedBytes < ARTIFACT_CLASSIFICATION_PREFIX_BYTES
|
|
? ARTIFACT_CLASSIFICATION_PREFIX_BYTES
|
|
: xml.state === "pending"
|
|
? MAX_XML_PLIST_PROBE_BYTES
|
|
: retainedBytes;
|
|
if (retainedBytes >= limit) break;
|
|
const selected = bytes.subarray(
|
|
offset,
|
|
offset + Math.min(limit - retainedBytes, bytes.length - offset),
|
|
);
|
|
retain(selected);
|
|
xml.write(selected);
|
|
offset += selected.length;
|
|
}
|
|
if (
|
|
(fatTable === undefined || fatTable.state === "needs-data") &&
|
|
retainedBytes >= 8
|
|
)
|
|
fatTable = readFatContainerTable(retainedPrefix());
|
|
observeFatSliceHeaders({
|
|
table: fatTable,
|
|
observedHeaders: fatSliceHeadersByOffset,
|
|
partialHeaders: partialFatHeaders,
|
|
streamOffset,
|
|
bytes,
|
|
});
|
|
if (!mzHeaderChecked && retainedBytes >= 64) {
|
|
mzHeaderChecked = true;
|
|
const prefix = retainedPrefix();
|
|
if (prefix[0] === 0x4d && prefix[1] === 0x5a) {
|
|
const windowsHeader = observeMzWindowsHeader(
|
|
prefix,
|
|
Number.MAX_SAFE_INTEGER,
|
|
);
|
|
if (windowsHeader.state === "signature-needed") {
|
|
mzWindowsHeaderOffset = windowsHeader.offset;
|
|
if (windowsHeader.offset < prefix.length) {
|
|
const inline = prefix.subarray(
|
|
windowsHeader.offset,
|
|
Math.min(windowsHeader.offset + 4, prefix.length),
|
|
);
|
|
mzWindowsHeaderSignature = {
|
|
offset: windowsHeader.offset,
|
|
bytes: inline,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (mzWindowsHeaderOffset !== undefined)
|
|
observeSignatureWindow(mzWindowsHeaderOffset, streamOffset, bytes);
|
|
observedBytes += bytes.length;
|
|
},
|
|
finish(archiveBytes: number): ArtifactClassificationEvidence {
|
|
return finishClassificationEvidence(
|
|
retainedPrefix(),
|
|
archiveBytes,
|
|
(bytes) => asar.finish(bytes),
|
|
fatSliceHeadersByOffset,
|
|
mzWindowsHeaderSignature,
|
|
);
|
|
},
|
|
};
|
|
};
|