Files
reverse-engineering-anything/tests/fixtures/uimage.ts
T
Cipherandgensokyo-dev 18a8e71c9f feat(artifacts): add U-Boot legacy uImage reader (part of #7129) (#7234)
* feat(artifacts): add U-Boot legacy uImage reader (part of #7129)

Recognize legacy uImages by their 0x27051956 magic and inventory them as containers: 64-byte header facts (arch/os/type/compression raw and named, load/entry addresses, name, timestamp) with header and data CRC32 verified, stored payload extractable as payload.bin, and multi-file images split from the stored size table into image-<n>.bin. Compressed payloads stay as stored; nothing is executed or decompressed. Wires the format through classification, the artifact reader dispatch, extraction, and the evidence/snapshot format vocabularies. Related to #7129 (uImage part only); HEX/SREC and FIT are out of scope.

* fix(uimage): preserve aligned component ranges and exact header facts

* fix(uimage): retain buffered members through consumption and cancellation

* fix(uimage): preserve active read and validation failure precedence

---------

Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
2026-10-28 19:11:41 +08:00

72 lines
2.6 KiB
TypeScript

import { crc32 } from "node:zlib";
import {
UIMAGE_HEADER_SIZE,
UIMAGE_MAGIC,
UIMAGE_NAME_LENGTH,
} from "../../src/domain/uimageFormat.js";
export interface UImageFixtureOptions {
readonly arch?: number;
readonly os?: number;
readonly type?: number;
readonly compression?: number;
readonly loadAddress?: number;
readonly entryPoint?: number;
readonly timestamp?: number;
readonly name?: string;
/** Raw stored payload bytes; for multi images pass the size table + parts. */
readonly payload?: Buffer;
/** Corrupt the header CRC after computing it (keeps a valid magic). */
readonly corruptHeaderCrc?: boolean;
/** Corrupt the data CRC after computing it. */
readonly corruptDataCrc?: boolean;
}
/** Build a synthetic legacy uImage with correctly computed CRCs. */
export const buildUImage = (options: UImageFixtureOptions = {}): Buffer => {
const payload = options.payload ?? Buffer.from("uimage-payload");
const header = Buffer.alloc(UIMAGE_HEADER_SIZE);
header.writeUInt32BE(UIMAGE_MAGIC, 0);
header.writeUInt32BE(options.timestamp ?? 1700000000, 8);
header.writeUInt32BE(payload.length, 12);
header.writeUInt32BE(options.loadAddress ?? 0x8000, 16);
header.writeUInt32BE(options.entryPoint ?? 0x8000, 20);
// Data CRC first: corrupting it must keep the header CRC valid, so the
// header CRC is computed over the final header bytes below.
const dataCrc =
(crc32(payload) >>> 0) ^ (options.corruptDataCrc === true ? 0xff : 0);
header.writeUInt32BE(dataCrc >>> 0, 24);
header[28] = options.os ?? 5;
header[29] = options.arch ?? 2;
header[30] = options.type ?? 2;
header[31] = options.compression ?? 0;
const name = Buffer.from(options.name ?? "test-kernel", "utf8");
name.copy(header, 32, 0, Math.min(name.length, UIMAGE_NAME_LENGTH));
const headerCrc = crc32(header) >>> 0;
header.writeUInt32BE(
options.corruptHeaderCrc === true ? (headerCrc ^ 0xff) >>> 0 : headerCrc,
4,
);
return Buffer.concat([header, payload]);
};
/** Size table + parts layout for a multi-file image payload. */
export const buildMultiPayload = (parts: readonly Buffer[]): Buffer => {
const table = Buffer.alloc((parts.length + 1) * 4);
parts.forEach((part, index) => {
table.writeUInt32BE(part.length, index * 4);
});
const stored: Buffer[] = [table];
parts.forEach((part, index) => {
if (part.length === 0)
throw new Error(
"A zero size terminates the uImage table; it cannot describe a component",
);
stored.push(part);
if (index + 1 < parts.length)
stored.push(Buffer.alloc((4 - (part.length % 4)) % 4));
});
return Buffer.concat(stored);
};