Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
zipdefrag — Presentato a Recon Montreal 2018 | Kitploit
Strumenti/GitHubGitHub/nccgroup/zipdefrag
Sicurezza Sistemi EmbeddedMemory ForensicsReverse EngineeringRecupero DatiDigital ForensicsAnalisi del Firmware
GitHubnccgroup/zipdefrag

zipdefrag

Presentato a Recon Montreal 2018

Vedi Repository
748 anni faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

Questo dump è un puzzle

o Advanced Shotgun Parsing per sciocchi e ribelli

C'era una volta

Tanto tempo fa, caro lettore, in una notte buia e tempestosa, il vostro fedele autore si imbatté in una situazione sconcertante e misteriosa: un sistema in cui l'unica cosa che impediva l'analisi e il reverse engineering della memoria chip-off era un file system proprietario frammentato sconosciuto, in combinazione con la compressione dovuta all'uso di Java embedded, che riduceva l'efficacia degli strumenti esistenti per il file carving.

All'epoca si sarebbe potuta mettere insieme qualche soluzione ad hoc, concatenando manualmente blocchi che sembravano combaciare, con un po' di orribile lavoro da console Python e brutti script bash ad hoc. Era Good Enough, ma richiedeva molto tempo.

Mentre estrarre dati non compressi in un'analisi chip-off è un lavoro piuttosto ordinario nella giornata di un hacker hardware, la compressione pone problemi significativi quando i suoi pezzi sono sparsi ovunque in modo irrazionale e sgradevole, anche quando non ci sono altre vere protezioni contro l'estrazione.

Ma sicuramente deve esserci un modo migliore?

Ci sono alcune cose interessanti sui file Zip in particolare (che è il formato di base usato per i file JAR). Da studente devoto dell'International Journal of PoC||GTFO ormai da parecchio tempo, e in particolare seguendo il lavoro sullo "stunting" dei formati di file di Ange Albertini, ho pensato che dentro un file zip ci potessero essere abbastanza dati sul file zip stesso da poter fare un lavoro decente nel ricucire il tutto.

Ok, lasciamo da parte i riferimenti, entriamo nei dettagli tecnici.

Prima di tutto: potremmo non conoscere le specifiche del file system (e dalla prospettiva della mia ricerca, indipendentemente dal sistema su cui avevo incontrato il problema, ho deciso che era semplicemente meglio non curarsene). Ma sappiamo una cosa o due su come sono implementati la maggior parte dei file system. In particolare sappiamo che tendono a essere scritti a blocchi. I blocchi hanno una dimensione minima di un certo tipo, nota come pagine, e possiamo identificare quella dimensione di pagina esaminando il dump e individuando la dimensione minima dei blocchi scritti.

Alcuni di questi possono essere contigui, altri no, senza uno schema chiaro su quando i blocchi sono contigui.

Tutto ciò per dire: il problema che abbiamo è come riordinare le pagine di dati in modo che ci forniscano immagini valide (o sufficientemente vicine) dei file che vogliamo estrarre.

I file Zip sono scritti in modo da implementare una sorta di gerarchia inversa. Prima i dati del file compresso (avvolti in intestazioni di file locali che li descrivono). Poi una directory centrale (che elenca gli offset delle intestazioni dei file locali) e poi un record di fine directory centrale (che, tra le altre cose, descrive il numero di file memorizzati nello zip, l'offset in cui inizia la directory centrale e la dimensione della directory centrale).

Mettiamolo al contrario, scavando un po' più nei dettagli:

  • L'End of Central Directory ci dice:

    • La posizione esatta, all'interno del file Zip, del record EOCD (l'offset della CD, più la lunghezza della CD, che precede l'EOCD)
    • Quanti file (e quindi record CD) cercare.
    • La posizione precisa nel file Zip del primo record CD.
  • Ogni record della Central Directory ci dice:

    • CRC32 dei dati del file compresso
    • Timestamp
    • Molti altri metadati (metodo di compressione, flag, versione del SO usata/richiesta...)
    • Un indice all'interno del file per il corrispondente blocco LF
    • Fondamentale: dati sufficienti per costruire un'immagine del corrispondente blocco LF.
  • Ogni record di file locale ci dice:

    • La posizione nel nostro dump dell'inizio di un file
    • Se è un file abbastanza piccolo, otteniamo l'intero file nella stessa pagina, o grazie all'intestazione del file successivo che appare nel successivo blocco paginato del file zip!
    • se un numero sufficiente di file piccoli è impacchettato in un numero sufficiente di pagine, possiamo usare la posizione delle pagine e i valori noti della directory per creare un ordinamento delle pagine (con lacune note!)

Tutto quanto sopra ci porta ad aver ricostruito la stragrande maggioranza del file.

Colpo di scena - Dobbiamo realisticamente avere a che fare con più di un firmware JAR!

Prima di tutto, abbiamo bisogno di un passaggio per distinguere i dati provenienti da firmware diversi. La ragione è che tutti gli offset sono rilevanti solo all'interno dei rispettivi file zip -- qualsiasi conflitto porterà a stream zip non corrispondenti e a corruzione, e vogliamo assolutamente ottenere quanti più dati non corrotti possibile. Inoltre vogliamo buone garanzie che, ad esempio, eventuali vulnerabilità diagnosticate nel firmware target riguardino quello che vediamo normalmente in esecuzione e non qualche altro file che è semplicemente rimasto lì in giro.

La soluzione necessaria qui è l'algoritmo kmeans (noto anche come "Algoritmo di Lloyd"). C'è un ottimo video qui che spiega come funziona. SciPy aveva una buona versione pronta all'uso, ma ho dovuto identificare/patchare l'unica crate di clustering/analytics che implementa l'algoritmo per farlo funzionare con l'implementazione Rust. Fortunatamente non ho dovuto scriverlo da zero.

Dopo quel lavoro, è fatta.

Possiamo usare una serie di caratteristiche per fare ciò. I campi Flags, Method e Version variano tutti in base allo stack Zip usato per comprimere il file. Inoltre, le intestazioni hanno timestamp, ed è generalmente improbabile che tutti i firmware siano stati compilati e compressi esattamente nello stesso momento.

Per inciso, vale la pena notare che i file Zip usano timestamp in formato MS-DOS, che sono short impacchettati a bit che rappresentano anno-mese-giorno e ora-minuto-due secondi. Se questi non venissero convertiti in un valore scalare assoluto prima di usarli come dati di classificazione, potreste dare lo stesso peso alla differenza di un anno come a quella di un secondo, e questo non va affatto bene!

Li convertiamo in un vettore euclideo (che è una parola elegante per un array n-dimensionale di valori in ℝ, o coordinate float, ma il video linkato sopra è probabilmente la spiegazione più diretta) e l'algoritmo di clustering fa praticamente tutto il resto per noi, raccogliendo tutte le intestazioni parse nel numero di bucket che ci aspettiamo.

Una breve nota sul parsing

Anche se ho scritto questo in uno script Python abbastanza abbozzato per prototipare questo metodo, avendo raggiunto circa il 70-80% di tasso di recupero del contenuto JAR con il PoC, ho deciso di fermarmi lì e passare a implementare una versione veloce in Rust.

Rust ha una crate chiamata nom che è assolutamente fantastica per scrivere parser-verificatori. Questa è stata una delle principali attrazioni per riscriverlo in Rust, tra l'altro. La capacità di scrivere parser chiari ed estremamente rigorosi rende tutto questo per certi versi molto più facile rispetto al tentativo di gestire tutto in Python (che tende a essere molto più tollerante, tanto che a volte è un po' una sfida essere certi di non sorvolare su errori sfuggiti nel tentativo di intercettare un caso limite).

Se dei parser fantastici, veloci e leggibili ti interessano, dai un'occhiata a

  • Writing Parsers like it's 2017
  • Nom Benchmarks - in cui qualcuno ha scritto un parser http da zero in Rust, leggermente più veloce di un'implementazione C molto veloce, senza buffer overflow.

Oltre a tutto il resto, eseguire analisi di questo tipo in Python è intrinsecamente lento, e non è mai stato realmente inteso come qualcosa di più di una strada verso un PoC per esplorare la fattibilità di questo approccio.

Comunque, basta così...

Andando avanti

Gli approcci per ricostruire i blocchi rimanenti includono filtrare prima le pagine rimanenti per candidati ad alta entropia, riempire prima i gap più piccoli (eliminando quante più pagine possibile dalla lista di ricerca, dato che testare le permutazioni su questi è un'operazione a tempo esponenziale nel caso peggiore, quindi risolvere rapidamente i casi più facili è una priorità e semplifica esponenzialmente il nostro problema man mano che procediamo!).

Possiamo anche ottenere una vittoria veloce trovando i casi in cui un'intestazione di file locale non è parsabile a causa di un confine di pagina (dovremmo essere in grado di abbinarla a una controparte allineata in modo identico, almeno nei casi in cui allineamenti simili sono unici e non collidono con altri artefatti di corruzione).

Come controlliamo i candidati per le pagine mancanti? Beh, abbiamo i checksum CRC32 per i file proprio lì nella nostra directory centrale! Piuttosto che calcolare il CRC32 sull'intero file, probabilmente il modo migliore è calcolare il CRC32 sui blocchi che già conosciamo (in avanti dai dati alla fine della pagina prima del nostro gap, e all'indietro dai dati (o dal blocco DataDescriptor dopo lo stream deflate) e ricavare da questi quale CRC32 intermedio dovremmo aspettarci per ogni blocco di pagine mancanti.

In pratica, ogni volta che scegliamo il problema più semplice/veloce da risolvere, rendiamo i problemi più difficili significativamente più semplici eliminando gli scarti. Questo è il motivo per cui si usa l'entropia di Shannon per buttare via direttamente le pagine vuote o quasi vuote: non è garantito che avremo solo pagine zip ad alta entropia, ma anche se ci sono valori anomali è un'enorme accelerazione evitare di gestire quella complicazione all'inizio.

Volevo solo compilare questa dannata cosa, che succede?

Se vuoi armeggiarci, installa Rust (consigliato con il fantastico rustup nightly. Poi:

root@kitploit:~
$ git clone [repo]
...
$ cd zipdefrag
...
$ cargo build --release

Puoi evitare il flag release per abilitare il debug.

Gli artefatti di build saranno in /target/{debug,release}

Compila la documentazione con cargo doc (Questa crate è ampiamente documentata. Mi piace scrivere.)

Attualmente non c'è output da terminale di default per la versione Rust; se vuoi eseguire l'harness CLI devi impostare la variabile d'ambiente RUST_LOG=zipdefrag, che abilita il logging verboso da terminale che mostra l'analisi finora.

Altro in arrivo:

  • Un eseguibile nativo veloce e portabile (con hook Python) per risolvere dump zip "puzzle" da file system sconosciuti.

  • Un dump dimostrativo

Problemi noti

  • Le prestazioni sono attualmente compromesse per l'implementazione Rust a causa del comportamento dispendioso nella ricerca dei blocchi LFH corrispondenti. Sistemerò questa cosa e imparerò la lezione.

  • Questa tecnica non funziona bene quando molti dei file nel JAR sono significativamente più grandi della dimensione di pagina. Poiché si basa su un uso intensivo della struttura intrinseca dei file zip, i file pesanti in termini di dati non funzionano così bene.

Convenientemente, i file delle classi tendono a essere piuttosto piccoli in generale per i midlet J2ME, ma i binari grandi impacchettati all'interno saranno probabilmente irrecuperabili.

Inoltre, il PoC Python contiene un certo numero di bug aritmetici.

Scarica lo strumento