Files
reverse-engineering-anything/src/domain/artifactFormatMetadata.ts
T
Entrpi 546119b63e feat(artifacts): inventory and extract ESP32 firmware images
ESP32-family app and bootloader images (magic 0xE9) had no reader, so
open_binary refused OTA downloads, app partitions and bootloaders.

Add the esp-image container format behind inspect_artifact and
extract_artifact. A pure decoder reads each byte once and keeps only
fixed-size fields. Derived facts cover the chip, from a table cited to
ESP-IDF v6.1; segment classification as ESP-IDF's should_map and
should_load; checksum and appended SHA-256 results; app and bootloader
descriptors; Secure Boot v2 signature blocks, observed and unverified;
and undescribed trailing byte ranges. Truncation and undescribed bytes
make coverage unknown. ESP8266 images are refused with a specific
reason, and ciphertext stays unrecognized.

Add the format to every target, Evidence, snapshot, live-summary and
artifact vocabulary, document it, and add an optional verify:esp-images
lane that compares esptool-built images with esptool image-info and
espsecure signature-info-v2.

Refs #3615
2026-10-27 22:54:29 +11:00

285 lines
9.6 KiB
TypeScript

import { z } from "zod";
const offset = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
const uint32 = z.number().int().min(0).max(0xffff_ffff);
/** Parsed source fields owned by the PCK adapter, independent of content hashes. */
export const godotPckMetadataSchema = z.strictObject({
kind: z.literal("godot-pck"),
format_version: uint32,
engine_major: uint32,
engine_minor: uint32,
engine_patch: uint32,
pack_start: offset,
pack_end: offset,
embedded: z.boolean(),
pack_flags: uint32,
file_base: offset,
directory_start: offset,
declared_file_count: uint32,
directory_status: z.enum(["available", "encrypted", "sparse-bundle"]),
});
export type GodotPckMetadata = z.infer<typeof godotPckMetadataSchema>;
export const godotPckEntryMetadataSchema = z.strictObject({
kind: z.literal("godot-pck-entry"),
index: uint32,
source_path: z.string(),
declared_offset: offset,
flags: uint32,
md5_status: z.enum(["declared", "all-zero"]),
});
/** Source cookie fields and derived directory counts without Python execution. */
export const pyInstallerMetadataSchema = z.strictObject({
kind: z.literal("pyinstaller"),
layout_version: z.enum(["2.0", "2.1"]),
archive_start: offset,
archive_size: offset,
cookie_offset: offset,
toc_offset: offset,
toc_size: offset,
entry_count: offset,
runtime_option_count: offset,
external_dependency_count: offset,
python_version_raw: uint32,
python_major: uint32.nullable(),
python_minor: uint32.nullable(),
python_library: z.string().nullable(),
encryption_marker_present: z.boolean(),
});
export type PyInstallerMetadata = z.infer<typeof pyInstallerMetadataSchema>;
/** Primitive facts preserve source spelling and keep metadata copies independent. */
export const pyInstallerEntryMetadataSchema = z.strictObject({
kind: z.literal("pyinstaller-entry"),
container: z.enum(["carchive", "pyz"]),
container_name: z.string().nullable(),
index: offset,
source_name: z.string(),
type_code: z.union([z.string().length(1), uint32]),
relative_offset: offset,
directory_record_offset: offset.nullable(),
directory_record_size: offset.nullable(),
compression_flag: z.union([z.literal(0), z.literal(1)]),
bytecode_header: z.enum(["not-bytecode", "unknown", "derived"]),
bytecode_magic: z
.string()
.regex(/^[0-9a-f]{8}$/u)
.nullable(),
pyz_status: z.enum(["not-pyz", "decoded", "opaque"]),
pyz_sha256: z
.string()
.regex(/^[0-9a-f]{64}$/u)
.nullable(),
pyz_python_magic: z
.string()
.regex(/^[0-9a-f]{8}$/u)
.nullable(),
pyz_python_version: z.string().nullable(),
pyz_entry_count: offset.nullable(),
encryption_source: z.literal("carchive-marker").nullable(),
dependency_status: z
.enum(["unresolved", "invalid-reference"])
.exactOptional(),
dependency_archive: z.string().nullable().exactOptional(),
dependency_member: z.string().nullable().exactOptional(),
});
export type PyInstallerEntryMetadata = z.infer<
typeof pyInstallerEntryMetadataSchema
>;
/**
* Node.js single-executable blob facts. Lengths follow the Node 24 64-bit
* little-endian `size_t` reader. The fuse sentinel is a separate carrier scan.
*/
export const nodeSeaMetadataSchema = z.strictObject({
kind: z.literal("node-sea"),
magic: z.literal(0x143da20),
flags: uint32,
disable_experimental_sea_warning: z.boolean(),
use_snapshot: z.boolean(),
use_code_cache: z.boolean(),
include_assets: z.boolean(),
include_exec_argv: z.boolean(),
exec_argv_extension: z.enum(["none", "env", "cli"]),
exec_argv: z.array(z.string()),
code_path: z.string(),
carrier: z.enum(["mach-o", "elf", "pe"]),
blob_offset: offset,
blob_length: offset,
fuse_state: z.enum(["set", "unset", "absent", "conflicting"]),
fuse_sentinel: z.string().nullable(),
fuse_contradiction: z.string().nullable(),
});
export type NodeSeaMetadata = z.infer<typeof nodeSeaMetadataSchema>;
/** One injected script, snapshot, code cache, or asset. Bytes stay opaque. */
export const nodeSeaEntryMetadataSchema = z.strictObject({
kind: z.literal("node-sea-entry"),
role: z.enum(["main-script", "snapshot", "code-cache", "asset"]),
source_path: z.string(),
index: uint32,
});
/** Runtime member identity, independent of blob layout and directory order. */
export const nodeSeaEntryDeclaration = (
metadata: z.infer<typeof nodeSeaEntryMetadataSchema>,
) => ({ role: metadata.role, source_path: metadata.source_path });
const uint8 = z.number().int().min(0).max(0xff);
const uint16 = z.number().int().min(0).max(0xffff);
const sha256Hex = z.string().regex(/^[0-9a-f]{64}$/u);
const byteRange = z.strictObject({ offset, length: offset });
/** `esp_app_desc_t` fields, as ESP-IDF names them; strings stop at NUL. */
const espAppDescriptorSchema = z.strictObject({
kind: z.literal("app"),
offset,
secure_version: uint32,
version: z.string().nullable(),
project_name: z.string().nullable(),
time: z.string().nullable(),
date: z.string().nullable(),
idf_ver: z.string().nullable(),
app_elf_sha256: sha256Hex,
min_efuse_blk_rev_full: uint16,
max_efuse_blk_rev_full: uint16,
/** Stored as log2; 0 in images built before ESP-IDF v5.4. */
mmu_page_size: uint8,
});
/** `esp_bootloader_desc_t` fields, as ESP-IDF names them. */
const espBootloaderDescriptorSchema = z.strictObject({
kind: z.literal("bootloader"),
offset,
secure_version: uint8,
version: uint32,
idf_ver: z.string().nullable(),
date_time: z.string().nullable(),
});
/**
* ESP32-family image facts. `header` and the descriptor fields are observed;
* the descriptor kind, `chip`, the integrity statuses and the trailing-byte
* ranges are derived.
*/
export const espImageMetadataSchema = z.strictObject({
kind: z.literal("esp-image"),
/** `esp_image_header_t` fields, as ESP-IDF names them. */
header: z.strictObject({
segment_count: uint8,
spi_mode: uint8,
spi_speed: uint8,
spi_size: uint8,
entry_addr: uint32,
wp_pin: uint8,
spi_pin_drv: z.string().regex(/^[0-9a-f]{6}$/u),
chip_id: uint16,
min_chip_rev: uint8,
min_chip_rev_full: uint16,
max_chip_rev_full: uint16,
reserved: z.string().regex(/^[0-9a-f]{8}$/u),
hash_appended: z.boolean(),
}),
/** Derived from `chip_id` through the cited table; null for unknown IDs. */
chip: z
.strictObject({
target: z.string(),
isa: z.enum(["xtensa", "riscv"]),
source: z.string(),
})
.nullable(),
checksum: z.strictObject({
offset: offset.nullable(),
stored: uint8.nullable(),
computed: uint8.nullable(),
status: z.enum(["valid", "invalid", "unavailable"]),
}),
/** The SHA-256 the header's `hash_appended` adds after the checksum. */
appended_sha256: z.strictObject({
offset: offset.nullable(),
stored: sha256Hex.nullable(),
computed: sha256Hex.nullable(),
status: z.enum(["valid", "invalid", "unavailable", "not-appended"]),
}),
/** End of the checksum block and any digest; null when truncated before it. */
image_end: offset.nullable(),
signature_sector_offset: offset.nullable(),
/** Bytes after the image that no padding, signature or erased flash describes. */
undescribed_trailing: z.array(byteRange),
/** Its kind classifies the image as an app or a bootloader. */
descriptor: z
.discriminatedUnion("kind", [
espAppDescriptorSchema,
espBootloaderDescriptorSchema,
])
.nullable(),
});
export type EspImageMetadata = z.infer<typeof espImageMetadataSchema>;
/** One header-declared segment; `data_len` is declared, not available. */
export const espImageSegmentMetadataSchema = z.strictObject({
kind: z.literal("esp-image-segment"),
index: uint8,
load_addr: uint32,
data_len: uint32,
header_offset: offset,
/** ESP-IDF's cold-boot `should_map` and `should_load`; derived. */
classification: z.enum(["mapped", "loaded", "not-loaded", "unknown"]),
});
export type EspImageSegmentMetadata = z.infer<
typeof espImageSegmentMetadataSchema
>;
/** One Secure Boot v2 block with its 0xE7 magic and a matching CRC32. */
export const espImageSignatureBlockMetadataSchema = z.strictObject({
kind: z.literal("esp-image-signature-block"),
/** Slot in the signature sector, 0 to 2. */
index: uint8,
version: uint8,
scheme: z.enum(["rsa", "ecdsa"]).nullable(),
/** Third block byte for ECDSA: 0 selects SHA-256, 1 SHA-384. */
sha_version: uint8.nullable(),
curve_id: uint8.nullable(),
digest_algorithm: z.enum(["sha256", "sha384"]).nullable(),
image_digest: z
.string()
.regex(/^(?:[0-9a-f]{64}|[0-9a-f]{96})$/u)
.nullable(),
/** Whether `image_digest` equals the digest of the bytes before the sector. */
image_digest_matches: z.boolean().nullable(),
/** SHA-256 of the block's public key, the value eFuse key digests hold. */
public_key_sha256: sha256Hex.nullable(),
/** REA checks no key, signature or eFuse. */
signature_status: z.literal("unverified"),
});
export type EspImageSignatureBlockMetadata = z.infer<
typeof espImageSignatureBlockMetadataSchema
>;
/** Declaration identity that is stable across archive layout changes. */
export const pyInstallerEntryDeclaration = (
metadata: PyInstallerEntryMetadata,
) => ({
container: metadata.container,
container_name: metadata.container_name,
source_name: metadata.source_name,
type_code: metadata.type_code,
});
export const artifactFormatMetadataSchema = z.discriminatedUnion("kind", [
godotPckMetadataSchema,
godotPckEntryMetadataSchema,
pyInstallerMetadataSchema,
pyInstallerEntryMetadataSchema,
nodeSeaMetadataSchema,
nodeSeaEntryMetadataSchema,
espImageMetadataSchema,
espImageSegmentMetadataSchema,
espImageSignatureBlockMetadataSchema,
]);
export type ArtifactFormatMetadata = z.infer<
typeof artifactFormatMetadataSchema
>;