Come posso contare il numero di volte in cui si verifica una sequenza di byte in un file?


16

Voglio contare quante volte accade una determinata sequenza di byte all'interno di un file che ho. Ad esempio, voglio scoprire quante volte si \0xdeadbeefverifica il numero all'interno di un file eseguibile. In questo momento lo sto facendo usando grep:

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

(I byte sono scritti in ordine inverso perché la mia CPU è little-endian)

Tuttavia, ho due problemi con il mio approccio:

  • Quelle \Xnnsequenze di fuga funzionano solo nella conchiglia di pesce.
  • grep sta effettivamente contando il numero di righe che contengono il mio numero magico. Se il motivo si presenta due volte nella stessa riga, verrà conteggiato una sola volta.

C'è un modo per risolvere questi problemi? Come posso far funzionare questo liner nella shell Bash e contare con precisione il numero di volte che si verifica il pattern all'interno del file?


qualche aiuto: unix.stackexchange.com/q/231213/117549 - in particolare,grep -o
Jeff Schaller

1
grep è lo strumento sbagliato da usare. Prendi in considerazione bgrep o bgrep2.
fpmurphy,

3
Se la sequenza da cercare è 11221122, come dovrebbe essere restituito un input simile 112211221122? 1 o 2?
Stéphane Chazelas,

Sarei d'accordo con la segnalazione di 2 o 3 partite in quel caso. Qualunque sarebbe più semplice da implementare.
hugomg,

Risposte:


15

Questa è la soluzione one-liner richiesta (per shell recenti che hanno "sostituzione di processo"):

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

Se non <(…)è disponibile alcuna "sostituzione di processo" , basta usare grep come filtro:

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

Di seguito è la descrizione dettagliata di ogni parte della soluzione.

Valori byte da numeri esadecimali:

Il tuo primo problema è facile da risolvere:

Quelle sequenze di escape \ Xnn funzionano solo nel guscio di pesce.

Cambia la parte superiore Xcon una inferiore xe usa printf (per la maggior parte delle shell):

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

Oppure usa:

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

Per quelle shell che scelgono di non implementare la rappresentazione '\ x'.

Ovviamente, tradurre hex in ottale funzionerà su (quasi) qualsiasi shell:

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

Dove "$ sh" è qualsiasi shell (ragionevole). Ma è abbastanza difficile tenerlo correttamente citato.

File binari.

La soluzione più efficace è trasformare il file e la sequenza di byte (entrambi) in una codifica che non abbia problemi con valori di caratteri dispari come (nuova riga) 0x0Ao (byte null) 0x00. Entrambi sono piuttosto difficili da gestire correttamente con strumenti progettati e adattati per elaborare "file di testo".

Una trasformazione come base64 può sembrare valida, ma presenta il problema che ogni byte di input può avere fino a tre rappresentazioni di output a seconda che si tratti del primo, secondo o terzo byte della posizione mod 24 (bit).

$ echo "abc" | base64
YWJjCg==

$ echo "-abc" | base64
LWFiYwo=

$ echo "--abc" | base64
LS1hYmMK

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

Trasformazione esadecimale.

Ecco perché la trasformazione più affidabile dovrebbe essere quella che inizia su ogni limite di byte, come la semplice rappresentazione HEX.
Possiamo ottenere un file con la rappresentazione esadecimale del file con uno di questi strumenti:

$ 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

In questo caso la sequenza di byte da cercare è già in esadecimale.
:

$ var="ef be ad de"

Ma potrebbe anche essere trasformato. Segue un esempio di hex-bin-hex esadecimale di andata e ritorno:

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

La stringa di ricerca può essere impostata dalla rappresentazione binaria. Una delle tre opzioni presentate sopra od, hexdump o xxd sono equivalenti. Assicurati solo di includere gli spazi per assicurarti che la corrispondenza si trovi sui confini dei byte (non è consentito alcuno spostamento nibble):

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

Se il file binario è simile al seguente:

$ 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

Quindi, una semplice ricerca grep fornirà l'elenco delle sequenze corrispondenti:

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

Una linea?

Tutto può essere eseguito su una riga:

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

Ad esempio, la ricerca 11221122nello stesso file richiede questi due passaggi:

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

Per "vedere" le partite:

$ 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


buffering

C'è il timore che grep esegua il buffer dell'intero file e, se il file è grande, crea un carico pesante per il computer. Per questo, possiamo usare una soluzione sed senza buffer:

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

Il primo sed è senza buffer ( -u) e viene utilizzato solo per iniettare due nuove righe sullo stream per stringa corrispondente. Il secondo sedstamperà solo le linee (brevi) corrispondenti. Il wc -l conterà le linee corrispondenti.

Questo buffererà solo alcune righe brevi. Le stringhe corrispondenti nella seconda seduta. Questo dovrebbe essere abbastanza basso nelle risorse utilizzate.

O, un po 'più complesso da capire, ma la stessa idea in una sed:

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

2
Nota che se metti tutto il testo su una riga, ciò significa grepche finirà per caricarlo intero in memoria (qui il doppio della dimensione del file originale + 1 a causa della codifica esadecimale), quindi alla fine, finisce per essere più sovraccarico pythondell'approccio o di perlquello con -0777. È inoltre necessaria grepun'implementazione che supporti linee di lunghezza arbitraria (quelle che supportano -ogeneralmente lo fanno). Buona risposta altrimenti.
Stéphane Chazelas il

1
Le tue versioni esadecimali corrispondono a valori spostati da un punto all'altro? E fb ea dd e? oltre ai byte desiderati. od -An -tx1 | tr -d '\n'o hexdump -v -e '/1 " %02x"'con una stringa di ricerca che contiene anche spazi evitatelo, ma non vedo una tale correzione per xxd.
dave_thompson_085,

@ dave_thompson_085 Risposta modificata. Credo che la risposta corrisponderà solo ai limiti dei byte ora, grazie ancora.
sorontar

@ StéphaneChazelas Potresti rivedere l'opzione proposta di utilizzare un sed senza buffer. Grazie.
sorontar,

sed -u(ove disponibile) è per non buffer. Ciò significa che leggerà un byte alla volta sull'input e ne emetterà immediatamente l'output senza buffering. In ogni caso, dovrà comunque caricare l'intera linea nello spazio del motivo, quindi non sarà d'aiuto qui.
Stéphane Chazelas il

7

Con GNU grep's -P(perl-regexp) flag

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

LC_ALL=Cè evitare problemi in locali multibyte dove grepaltrimenti si proverebbe a interpretare sequenze di byte come caratteri.

-atratta i file binari equivalenti ai file di testo (invece del normale comportamento, dove grepstampa solo se c'è almeno una corrispondenza o meno)


Questa soluzione mi dà sempre 0 corrispondenze anziché il numero corretto.
hugomg,

@hugomg, potrebbe essere necessario invertire i byte passati grep per farlo corrispondere?
Iruvar,

Non penso che sia l'ordine. Le altre due risposte a questa domanda funzionano correttamente.
hugomg,

2
@hugomg, è la locale. Vedi modifica.
Stéphane Chazelas,

2
Suggerirò di includere l' -aopzione, altrimenti grep risponderà Binary file file.bin matchesper ogni file che grep rileva come binario.
sorontar

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

Il che tratta i file di input come binari (nessuna traduzione per avanzamenti di riga o codifiche, vedi perlrun ) quindi passa sopra i file di input senza stampare incrementando un contatore per tutte le corrispondenze dell'esagono dato (o qualunque forma, vedi perlre ) .


2
Nota che non puoi usarlo se la sequenza da cercare contiene il byte 0xa. In tal caso, è possibile utilizzare un separatore di record diverso (con -0ooo).
Stéphane Chazelas,

1
@ StéphaneChazelas è possibile utilizzare la sequenza di interesse stessa poiché $/, con un compromesso leggermente diverso (utilizzo della memoria proporzionale alla distanza massima tra tali sequenze):perl -nE 'BEGIN { $/ = "\xef\xbe\xad\xde" } chomp; $c++ unless eof && length; END { say $c }'
hobbs

@ StéphaneChazelas Per favore leggi la mia risposta per una soluzione per qualsiasi valore di byte.
sorontar

1
@hobbs, in ogni caso, anche qui, l'utilizzo della memoria sarà proporzionale alla distanza massima tra due byte 0xa che per i file non di testo potrebbe essere arbitrariamente grande.
Stéphane Chazelas,

5

Con GNU awkpuoi fare:

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

Se uno qualsiasi dei byte è un operatore ERE, dovrebbe comunque essere evitato (con \\). Come 0x2equale .dovrebbe essere inserito come \\.o \\\x2e. Oltre a ciò, dovrebbe funzionare con valori di byte arbitrari inclusi 0 e 0xa.

Nota che non è semplice come solo NR-1perché ci sono un paio di casi speciali:

  • quando l'ingresso è vuoto, NR è 0, NR-1 darebbe -1.
  • quando l'input termina nel separatore di record, dopo di ciò non viene creato un record vuoto. Lo testiamo con RT=="".

Si noti inoltre che nel caso peggiore (se il file non contiene il termine di ricerca), il file verrà caricato completamente in memoria).


5

La traduzione più diretta che vedo è:

$ 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

Dove ho usato $'\xef'come bash ANSI-citando (in origine una ksh93funzione, ora supportato da zsh, bash, mksh, FreeBSD sh) versione del pesce di \Xef, e usato grep -o ... | wc -lper contare le istanze. grep -ogenera ogni corrispondenza su una riga separata. Il -aflag fa in modo che grep si comporti sui file binari nello stesso modo in cui lo fa sui file di testo. -Fè per stringhe fisse, quindi non è necessario sfuggire agli operatori regex.

Come nel tuo fishcaso, non puoi usare quell'approccio se la sequenza da cercare include i byte 0 o 0xa (newline in ASCII).


L'uso printf '%b' $(printf '\\%o ' $((0xef)) $((0xbe)) $((0xad)) $((0xde))) > hugohex'sarebbe il metodo più portatile "pure shell". Certo: printf "efbeadde" | xxd -p -r > hugohexsembra il metodo più pratico.
sorontar

4

È possibile utilizzare il bytes.countmetodo Python per ottenere il numero totale di sottostringhe non sovrapposte in un bytestring.

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

Questo one-liner caricherà l'intero file in memoria, quindi non il più efficiente, ma funziona ed è più leggibile di Perl; D


'più leggibile di Perl' è solo un passo avanti da TECO - che IINM è: 239I$ 190I$ 173I$ 222I$ HXA ERfile$Y 0UC <:S^EQA$; %C$> QC=(gd & r)
dave_thompson_085

È possibile mmap()un file in Python ; ciò ridurrebbe il commit della memoria.
Toby Speight,

1
tr "$(printf \\0xef)\n" \\n\\0 < infile |
grep -c "^$(printf "\0xbe\0xad\0xde")"

1

Penso che tu possa usare Perl, provalo:

perl -0777ne 'CORE::say STDOUT s/\xef\xbe\xad\xde//g' file_name  

Il comando Sostituisci sfornisce il numero di sostituzioni effettuate, -0777 significa che non tratta la nuova riga come carattere speciale, e- esegui il comando, sayper stampare ciò che segue e quindi stampare il nuovo carattere di riga, nnon avevo capito bene, ma non funziona senza - da docs:

fa sì che Perl assuma il seguente ciclo attorno al tuo programma, il che lo fa scorrere su argomenti di file un po 'come sed -n o awk: LINE: while (<>) {... # il tuo programma va qui}

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.