Hexter

Dentro i formati di file binari: intestazioni PNG, ZIP e Mach-O spiegate

Ogni formato di file che usi ogni giorno, immagine, archivio, eseguibile, ha una struttura definita fino al byte, e quasi nulla di quella struttura è visibile attraverso gli strumenti che di solito usi per aprire quei file. Anteprima ti mostra un’immagine. Utility Archivio ti mostra i file estratti. Nessuno dei due ti mostra i byte che hanno reso possibili quei risultati. Ecco come appaiono davvero tre formati comuni sotto la superficie, e come guardarli tu stesso.

PNG: una sequenza di chunk etichettati

Ogni file PNG inizia con gli stessi otto byte: 89 50 4E 47 0D 0A 1A 0A. Non sono dati dell’immagine, sono una firma, scelta deliberatamente per includere un byte che diventa inutilizzabile se il file viene trasferito per sbaglio come testo (0x0A e 0x0D sono caratteri di a capo, quindi un trasferimento in modalità testo difettoso li corrompe in un modo facile da rilevare).

Dopo la firma, tutto in un PNG è un chunk, e ogni chunk segue la stessa disposizione in quattro parti:

Diagramma della struttura di un chunk PNG: un campo lunghezza di 4 byte, un campo tipo di 4 byte come IHDR o IDAT, i dati del chunk stessi, e un checksum CRC di 4 byte alla fine.
Ogni chunk PNG, dal primo (IHDR) all'ultimo (IEND), segue questa stessa disposizione a quattro campi.
  • Lunghezza (4 byte): quanti byte di dati seguono, senza contare il tipo o il CRC.
  • Tipo (4 byte, sempre ASCII): un codice di quattro lettere come IHDR (intestazione dell’immagine, sempre per primo), IDAT (dati dei pixel compressi, ce ne possono essere diversi), o IEND (segna la fine del file).
  • Dati: qualunque cosa specifichi quel tipo di chunk. I dati di IHDR sono larghezza, altezza, profondità di bit e tipo di colore, sempre la stessa disposizione fissa.
  • CRC (4 byte): un checksum del tipo e dei dati, così un decoder può rilevare un chunk corrotto invece di leggerlo male in silenzio.

È l’intero formato. Un decoder PNG semplicemente percorre chunk dopo chunk, leggendo la lunghezza per sapere quanto saltare, finché non incontra IEND.

ZIP: intestazioni locali, poi una directory alla fine

Un archivio ZIP memorizza ogni file con la propria intestazione di file locale, contrassegnata dalla firma 50 4B 03 04 (il PK all’inizio è un riferimento a Phil Katz, il creatore del formato). Quell’intestazione include il nome del file, il metodo di compressione, e sia la dimensione compressa sia quella non compressa, immediatamente seguite dai dati compressi del file.

La parte che manda in confusione: quelle dimensioni e checksum per singolo file nell’intestazione locale sono a volte zero, con i valori reali memorizzati più avanti in un “data descriptor”, perché lo ZIP è stato progettato per supportare scritture in streaming dove la dimensione finale non è nota finché i dati non sono già stati scritti. L’elenco definitivo di cosa c’è davvero nell’archivio si trova in una central directory alla fine del file, il che è anche il motivo per cui alcuni ZIP corrotti o troncati possono sembrare avere contenuto quando li scandagli dall’inizio, ma non riescono ad aprirsi, perché lo strumento che stai usando legge prima la central directory ed è mancante o danneggiata.

Mach-O: magic number, poi load command

Mach-O è il formato eseguibile che macOS usa per i binari compilati. Si apre con un magic number che dice a un loader due cose contemporaneamente: se è a 32 o a 64 bit, e quale ordine di byte usa il resto del file. 0xFEEDFACE è a 32 bit, 0xFEEDFACF è a 64 bit, e se vedi i byte invertiti (0xCEFAEDFE o 0xCFFAEDFE), significa che il file è stato scritto nell’ordine di byte opposto rispetto alla macchina che lo legge.

Dopo il magic number viene un’intestazione fissa (tipo di CPU, tipo di file, e un conteggio dei load command), seguita da quel numero di load command in sequenza. I load command sono il modo in cui un file Mach-O descrive tutto ciò di cui il loader ha bisogno: quali segmenti mappare in memoria, quali librerie dinamiche collegare, dove si trova l’entry point. È un’idea simile a quella dei chunk del PNG: un’intestazione fissa, poi una sequenza di record auto-descrittivi, ognuno dei quali dice al lettore quanto saltare per arrivare al successivo.

Guardare tutto questo tu stesso

Nulla di ciò richiede di scrivere un parser. Apri un qualsiasi file PNG, ZIP o Mach-O in Hexter e la scheda Analyze riconosce il formato automaticamente, percorrendo la struttura reale dei chunk o dei load command e mostrandola come un albero accanto all’esadecimale grezzo, così puoi cliccare su un campo e vedere esattamente da quali byte proviene. È in sola lettura per impostazione predefinita, quindi è davvero sicuro farlo su un file reale, un binario di un’app, uno screenshot, un archivio scaricato, senza alcun rischio di alterarlo.

Se invece vuoi verificare a mano quanto sopra: apri un PNG e controlla che i primi otto byte corrispondano alla firma, poi guarda i byte da 8 a 11 (la lunghezza del primo chunk) e i byte da 12 a 15 (il suo tipo, che dovrebbe leggersi IHDR). È un esercizio abbastanza piccolo da fare in un paio di minuti, ed è un modo davvero utile per capire un formato di file che hai usato tutta la vita ma non hai mai guardato davvero.