Skip to content
KitploitKITPLOIT
FerramentasExploitsBlog
Log in
Enviar
FerramentasExploitsBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

FeedsContatoPrivacidade© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
zipdefrag — Apresentado na Recon Montreal 2018 | Kitploit
Ferramentas/GitHubGitHub/nccgroup/zipdefrag
Segurança de Sistemas EmbarcadosForensia de MemóriaEngenharia ReversaRecuperação de DadosForensia DigitalAnálise de Firmware
GitHubnccgroup/zipdefrag

zipdefrag

Apresentado na Recon Montreal 2018

Ver Repositório
7423há 8 anosAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

Este Dump É Um Quebra-Cabeça

ou Parsing Avançado com Espingarda para Tolos e Rebeldes

era uma vez

Era uma vez, caro leitor, em uma noite escura e tempestuosa, seu fiel autor tropeçou em uma situação intrigante e misteriosa - um sistema onde a única coisa que impedia a análise de memória chip-off e a engenharia reversa era um sistema de arquivos proprietário fragmentado e desconhecido, em conjunto com compressão devido ao uso de Java embarcado, reduzindo a eficácia das ferramentas existentes para file carving.

Na época, alguma solução ad hoc pode ter sido improvisada concatenando manualmente chunks que pareciam se encaixar, com um pouco de trabalho horrível no console do Python e scripts bash ad hoc feios. Era boa o suficiente, mas demorada.

Embora extrair dados simples não compactados em análise chip-off seja um trabalho bastante rotineiro no dia a dia de um hacker de hardware, a compressão impõe problemas significativos quando seus pedaços estão espalhados por toda parte de maneira irracional e desagradável, mesmo quando não há outras proteções reais contra extração.

Mas certamente deve haver um jeito melhor?

Há algumas coisas interessantes sobre arquivos Zip em particular (que é o formato básico usado para arquivos JAR). Sendo um estudante dedicado do International Journal of PoC||GTFO há bastante tempo e particularmente seguindo o trabalho em truques de formatos de arquivo de Ange Albertini, imaginei que poderia haver dados suficientes dentro de um arquivo zip sobre o próprio arquivo zip para que você conseguisse fazer um bom trabalho de remendá-lo novamente.

Então, com as referências fora do caminho, vamos aos detalhes técnicos.

Antes de mais nada, podemos não saber (e, da perspectiva da minha pesquisa, independentemente do sistema no qual eu havia encontrado o problema, decidi que era simplesmente melhor não me importar com isso) os detalhes específicos do sistema de arquivos. Mas sabemos uma coisa ou duas sobre a forma como a maioria dos sistemas de arquivos é implementada. Em particular, sabemos que eles tendem a ser gravados em chunks. Os chunks têm um tamanho mínimo de algum tipo, conhecido como páginas, e podemos identificar esse tamanho de página examinando o dump e identificando o tamanho mínimo de bloco gravado.

Alguns desses podem ser contíguos, outros não, sem um padrão claro para quando os blocos são contíguos.

Ou seja, o problema que temos é como reordenar as páginas de dados de modo que elas nos forneçam imagens válidas (ou quase isso) dos arquivos que queremos extrair.

Arquivos Zip são gravados de uma maneira que implementa uma espécie de hierarquia reversa. Primeiro, os dados compactados do arquivo (envolvidos em cabeçalhos locais de arquivo que os descrevem). Depois, um diretório central (que lista os offsets dos cabeçalhos locais) e, então, um registro de fim de diretório central (que, entre outras coisas, descreve o número de arquivos armazenados no zip, o offset onde o diretório central começa e o tamanho do diretório central).

Vamos inverter isso, mergulhando em um pouco mais de detalhe:

  • O End of Central Directory nos diz:

    • A localização exata, dentro do arquivo Zip, do registro EOCD (o offset do CD, mais o comprimento do CD, que precede o EOCD)
    • Quantos arquivos (e, portanto, registros CD) procurar.
    • A localização precisa no arquivo Zip do primeiro registro CD.
  • Cada registro do Central Directory nos diz:

    • CRC32 dos dados compactados do arquivo
    • Timestamp
    • Muitos outros metadados (método de compressão, flags, versão do SO usada/necessária...)
    • Um índice dentro do arquivo para o chunk LF correspondente
    • Criticamente: dados suficientes para construir uma imagem do chunk LF correspondente.
  • Cada registro Local File nos diz:

    • A localização, em nosso dump, do início de um arquivo
    • Se for um arquivo pequeno o suficiente, obtemos o arquivo inteiro dentro da mesma página, ou, graças ao cabeçalho do próximo arquivo que aparece no próximo chunk paginado do arquivo zip!
    • Se arquivos pequenos suficientes são empacotados em páginas suficientes, podemos usar a localização das páginas e os valores conhecidos do diretório para criar uma ordenação das páginas (com lacunas conhecidas!)

Tudo isso nos leva a ter reconstruída a grande maioria do arquivo.

Reviravolta - Precisamos lidar realisticamente com mais de um firmware JAR!

Antes de tudo, precisamos de uma etapa para distinguir dados de firmwares diferentes. A razão para isso é que todos os offsets só são relevantes dentro de seus respectivos arquivos zip — qualquer conflito levará a fluxos zip incompatíveis e corrupção, e queremos enfaticamente extrair o máximo de dados não corrompidos possível. Também queremos boas garantias de que, por exemplo, quaisquer vulnerabilidades que diagnosticarmos no firmware alvo afetem aquele que normalmente vemos rodando, e não algum outro arquivo que ficou apenas jogado por aí.

A solução necessária aqui é o algoritmo kmeans (também conhecido como "Algoritmo de Lloyd"). Há um ótimo vídeo aqui explicando como ele funciona. O SciPy tinha uma boa versão pronta para uso, mas tive que identificar/ corrigir a única crate de clustering/análise que implementa o algoritmo para fazer isso funcionar na implementação em Rust. Felizmente, não precisei escrevê-lo do zero.

Depois disso, o serviço é uma beleza.

Podemos usar uma série de características para isso. Os campos Flags, Method e Version variam todos de acordo com a pilha Zip usada para comprimir o arquivo. Além disso, os cabeçalhos possuem timestamps, e geralmente é improvável que todos os firmwares tenham sido compilados e comprimidos exatamente ao mesmo tempo.

Como um aparte, vale notar que arquivos Zip usam timestamps no formato MS-DOS, que são shorts empacotados em bits representando ano-mês-dia e hora-minuto-dois-segundos. Se eles não fossem convertidos em um valor escalar absoluto antes de usar isso como dados de classificação, você poderia muito bem dar tanto peso à diferença de um ano quanto dá à de um segundo, e isso não é nada bom!

Nós os convertemos em um Vetor Euclidiano (que é uma palavra chique para um array n-dimensional de valores em ℝ, ou coordenadas de ponto flutuante, mas o vídeo linkado acima é provavelmente a explicação mais direta) e o algoritmo de clustering praticamente faz todo o resto por nós, reunindo todos os cabeçalhos analisados no número de buckets que esperamos.

Uma observação rápida sobre o parsing

Embora eu tenha escrito isso em um script Python bastante tosco para prototipar este método, tendo chegado a uma taxa de recuperação de conteúdo JAR de cerca de 70-80% com o PoC, decidi parar por aí e seguir para a implementação de uma versão rápida em Rust.

Rust tem uma crate chamada nom que é absolutamente fantástica para escrever parsers-verificadores. Essa foi uma das principais atrações para reescrevê-lo em Rust, pelo que vale. A capacidade de escrever parsers claros e extremamente rígidos torna isso muito mais fácil em alguns aspectos do que tentar lidar com tudo isso em Python (que tende a ser muito mais tolerante, tanto que às vezes é um pouco desafiador ter certeza de que você não está ignorando falhas errôneas para capturar um caso extremo).

Se a ideia de parsers rápidos, legíveis e incríveis parece interessante, vá conferir

  • Escrevendo Parsers como se fosse 2017
  • Nom Benchmarks - onde alguém escreveu um parser HTTP do zero em Rust, ligeiramente mais rápido do que uma implementação em C muito rápida, sem estouros de buffer.
Baixar ferramenta