Como posso contar o número de vezes que uma sequência de bytes ocorre em um arquivo?

15

Eu quero contar quantas vezes uma certa seqüência de bytes acontece dentro de um arquivo que eu tenho. Por exemplo, quero descobrir quantas vezes o número \Xnnxdeadbeef ocorre dentro de um arquivo executável. Agora eu estou fazendo isso usando o grep:

#/usr/bin/fish
grep -c \Xef\Xbe\Xad\Xde my_executable_file

(Os bytes são escritos na ordem inversa porque minha CPU é little-endian)

No entanto, tenho dois problemas com a minha abordagem:

  • Essas seqüências de escape %code% só funcionam na casca do peixe.
  • grep está realmente contando o número de linhas que contêm meu número mágico. Se o padrão ocorrer duas vezes na mesma linha, só contará uma vez.

Existe uma maneira de corrigir esses problemas? Como posso fazer este liner rodar no Bash shell e contar com precisão o número de vezes que o padrão ocorre dentro do arquivo?

    
por hugomg 16.12.2016 / 19:16

7 respostas

14

Esta é a solução de uma linha solicitada (para shells recentes que possuem "substituição de processo"):

grep -o "ef be ad de" <(hexdump -v -e '/1 "%02x "' infile.bin) | wc -l

Se nenhuma "substituição de processo" <(…) estiver disponível, use grep como filtro:

hexdump -v -e '/1 "%02x "' infile.bin  | grep -o "ef be ad de" | wc -l

Abaixo está a descrição detalhada de cada parte da solução.

Valores de bytes de números hexadecimais:

Seu primeiro problema é fácil de resolver:

Those \Xnn escape sequences only work in the fish shell.

Altere o% superiorX para umx inferior e use printf (para a maioria dos shells):

$ printf -- '\xef\xbe\xad\xde'

Ou use:

$ /usr/bin/printf -- '\xef\xbe\xad\xde'

Para as camadas que optam por não implementar a representação '\ x'.

É claro que traduzir hex para octal funcionará em (quase) qualquer shell:

$ "$sh" -c 'printf '\''%b'\'' "$(printf '\''\0%o'\'' $((0xef)) $((0xbe)) $((0xad)) $((0xde)) )"'

Onde "$ sh" é qualquer shell (razoável). Mas é muito difícil mantê-lo corretamente citado.

Arquivos binários.

A solução mais robusta é transformar o arquivo e a seqüência de bytes (ambos) em alguma codificação que não tenha problemas com valores de caracteres ímpares como (nova linha) 0x0A ou (byte nulo) 0x00 . Ambos são muito difíceis de gerenciar corretamente com ferramentas projetadas e adaptadas para processar "arquivos de texto".

Uma transformação como base64 pode parecer válida, mas apresenta a questão de que todo byte de entrada pode ter até três representações de saída, dependendo se é o primeiro, segundo ou terceiro byte da posição mod 24 (bits). / p>

$ echo "abc" | base64
YWJjCg==

$ echo "-abc" | base64
LWFiYwo=

$ echo "--abc" | base64
LS1hYmMK

$ echo "---abc" | base64        # Note that YWJj repeats.
LS0tYWJjCg==

Transformada hexadecimal.

É por isso que a transformação mais robusta deve ser aquela que inicia em cada limite de bytes, como a simples representação HEX.
Podemos obter um arquivo com a representação hexadecimal do arquivo com qualquer uma dessas ferramentas:

$ od -vAn -tx1 infile.bin | tr -d '\n'   > infile.hex
$ hexdump -v -e '/1 "%02x "' infile.bin  > infile.hex
$ xxd -c1 -p infile.bin | tr '\n' ' '    > infile.hex

A sequência de bytes a pesquisar já está em hexadecimal neste caso.
:

$ var="ef be ad de"

Mas também pode ser transformado. Um exemplo de um round-hex-bin-hex segue:

$ echo "ef be ad de" | xxd -p -r | od -vAn -tx1
ef be ad de

A cadeia de pesquisa pode ser definida a partir da representação binária. Qualquer uma das três opções apresentadas acima, hexdump ou xxd são equivalentes. Apenas certifique-se de incluir os espaços para garantir que a correspondência esteja nos limites de byte (sem desvio de nibble permitido):

$ a="$(printf "\xef\xbe\xad\xde" | hexdump -v -e '/1 "%02x "')"
$ echo "$a"
ef be ad de

Se o arquivo binário se parece com isto:

$ cat infile.bin | xxd
00000000: 5468 6973 2069 7320 efbe adde 2061 2074  This is .... a t
00000010: 6573 7420 0aef bead de0a 6f66 2069 6e70  est ......of inp
00000020: 7574 200a dead beef 0a66 726f 6d20 6120  ut ......from a 
00000030: 6269 0a6e 6172 7920 6669 6c65 2e0a 3131  bi.nary file..11
00000040: 3232 3131 3232 3131 3232 3131 3232 3131  2211221122112211
00000050: 3232 3131 3232 3131 3232 3131 3232 3131  2211221122112211
00000060: 3232 0a

Em seguida, uma pesquisa grep simples fornecerá a lista de sequências correspondentes:

$ grep -o "$a" infile.hex | wc -l
2

Uma linha?

Tudo pode ser realizado em uma linha:

$ grep -o "ef be ad de" <(xxd -c 1 -p infile.bin | tr '\n' ' ') | wc -l

Por exemplo, a pesquisa por 11221122 no mesmo arquivo precisará dessas duas etapas:

$ a="$(printf '11221122' | hexdump -v -e '/1 "%02x "')"
$ grep -o "$a" <(xxd -c1 -p infile.bin | tr '\n' ' ') | wc -l
4

Para "ver" as correspondências:

$ grep -o "$a" <(xxd -c1 -p infile.bin | tr '\n' ' ')
3131323231313232
3131323231313232
3131323231313232
3131323231313232

$ grep "$a" <(xxd -c1 -p infile.bin | tr '\n' ' ')

… 0a 3131323231313232313132323131323231313232313132323131323231313232 313132320a

Buffer

Existe uma preocupação de que o grep armazene em buffer todo o arquivo e, se o arquivo for grande, crie uma carga pesada para o computador. Para isso, podemos usar uma solução sed não tamponada:

a='ef be ad de'
hexdump -v -e '/1 "%02x "' infile.bin  | 
    sed -ue 's/\('"$a"'\)/\n\n/g' | 
        sed -n '/^'"$a"'$/p' |
            wc -l

O primeiro sed é sem buffer ( -u ) e é usado apenas para injetar duas novas linhas no fluxo por string correspondente. O segundo sed somente imprimirá as linhas (curtas) correspondentes. O wc -l contará as linhas correspondentes.

Isso armazenará apenas algumas linhas curtas. A (s) string (s) correspondente (s) no segundo sed. Isso deve ser bastante baixo em recursos usados.

Ou, um pouco mais complexo para entender, mas a mesma ideia em um só lugar:

a='ef be ad de'
hexdump -v -e '/1 "%02x "' infile.bin  |
    sed -u '/\n/P;//!s/'"$a"'/\n&\n/;D' |
        wc -l
    
por 17.12.2016 / 02:49
7

Com o sinalizador grep (perl-regexp) do% GNU-P

LC_ALL=C grep -oaP '\xef\xbe\xad\xde' file | wc -l

LC_ALL=C é para evitar problemas em localidades com vários bytes, onde grep tentaria interpretar sequências de bytes como caracteres.

-a trata os arquivos binários equivalentes aos arquivos de texto (em vez do comportamento normal, em que grep apenas imprime se há pelo menos uma correspondência ou não)

    
por 16.12.2016 / 20:27
6
PERLIO=:raw perl -nE '$c++ while m/\xef\xbe\xad\xde/g; END{say $c}' file

Que trata o (s) arquivo (s) de entrada como binário (sem conversão para feeds de linha ou codificações, veja perlrun ) então faz um loop sobre o (s) arquivo (s) de entrada (s) não imprimindo incrementando um contador para todas as correspondências do dado hexadecimal (ou qualquer forma, veja perlre ).

    
por 16.12.2016 / 19:40
5

A tradução mais direta que vejo é:

$ echo $'\xef\xbe\xad\xde' > hugohex
$ echo $'\xef\xbe\xad\xde\xef\xbe\xad\xde' >> hugohex
$ grep -F -a -o -e $'\xef\xbe\xad\xde' hugohex|wc -l
3

Em que usei $'\xef' como o bash ANSI- citando (originalmente um recurso ksh93 , agora suportado por zsh , bash , mksh , FreeBSD sh ) do fish \Xef e usou grep -o ... | wc -l para contar as instâncias. grep -o produz cada partida em uma linha separada. O -a flag faz o grep se comportar em arquivos binários da mesma maneira que em arquivos de texto. -F é para strings fixas, então você não precisa escapar de operadores regex.

Como em você fish case, você não pode usar essa abordagem se a sequência a ser procurada incluir os bytes 0 ou 0xa (nova linha em ASCII).

    
por 16.12.2016 / 20:07
5

Com o GNU awk , você pode fazer:

LC_ALL=C awk -v 'RS=\xef\xbe\xad\xde' 'END{print NR - (NR && RT == "")}'

Se algum dos bytes forem operadores ERE, eles teriam que ser escapados (com \ ). Como 0x2e , que é . , teria que ser inserido como \. ou \\x2e . Além disso, deve funcionar com valores de byte arbitrários, incluindo 0 e 0xa.

Observe que não é tão simples quanto NR-1 , porque há alguns casos especiais:

  • quando a entrada está vazia, NR é 0, NR-1 daria -1.
  • quando a entrada termina no separador de registro, um registro vazio não é criado depois disso. Nós testamos para isso com RT=="" .

Observe também que, no pior dos casos (se o arquivo não contiver o termo de pesquisa), o arquivo terminará sendo carregado inteiro na memória).

    
por 16.12.2016 / 23:30
4

Você pode usar o método bytes.count do Python para obter o número total de substrings não sobrepostas em um bytestring.

python -c "print(open('./myexecutable', 'rb').read().count(b'\xef\xbe\xad\xde'))"

Este one-liner carregará o arquivo inteiro na memória, então não é o mais eficiente, mas funciona e é mais legível que o Perl; D

    
por 16.12.2016 / 23:15
1
tr "$(printf \0xef)\n" \n\0 < infile |
grep -c "^$(printf "
tr "$(printf \0xef)\n" \n\0 < infile |
grep -c "^$(printf "%pre%xbe%pre%xad%pre%xde")"
xbe%pre%xad%pre%xde")"
    
por 18.01.2017 / 12:15