
Ich möchte zählen, wie oft eine bestimmte Bytefolge in einer meiner Dateien vorkommt. Beispielsweise möchte ich herausfinden, wie oft diese Zahl \0xdeadbeef
in einer ausführbaren Datei vorkommt. Im Moment mache ich das mit grep:
#/usr/bin/fish
grep -c \Xef\Xbe\Xad\Xde my_executable_file
(Die Bytes werden in umgekehrter Reihenfolge geschrieben, da meine CPU Little-Endian ist)
Allerdings habe ich mit meinem Ansatz zwei Probleme:
- Diese
\Xnn
Escape-Sequenzen funktionieren nur in der Fish-Shell. - grep zählt tatsächlich die Anzahl der Zeilen, die meine magische Zahl enthalten. Wenn das Muster zweimal in derselben Zeile vorkommt, wird es nur einmal gezählt.
Gibt es eine Möglichkeit, diese Probleme zu beheben? Wie kann ich diesen Einzeiler in der Bash-Shell ausführen und die Anzahl der Vorkommen des Musters in der Datei genau zählen?
Antwort1
Dies ist die angeforderte Einzeilerlösung (für neuere Shells mit „Prozesssubstitution“):
grep -o "ef be ad de" <(hexdump -v -e '/1 "%02x "' infile.bin) | wc -l
Wenn keine „Prozesssubstitution“ <(…)
verfügbar ist, verwenden Sie einfach grep als Filter:
hexdump -v -e '/1 "%02x "' infile.bin | grep -o "ef be ad de" | wc -l
Nachfolgend finden Sie eine detaillierte Beschreibung der einzelnen Teile der Lösung.
Bytewerte aus Hexadezimalzahlen:
Ihr erstes Problem lässt sich leicht lösen:
Diese \Xnn-Escapesequenzen funktionieren nur in der Fish-Shell.
Ersetzen Sie das obere X
durch ein unteres x
und verwenden Sie printf (für die meisten Shells):
$ printf -- '\xef\xbe\xad\xde'
Oder verwenden Sie:
$ /usr/bin/printf -- '\xef\xbe\xad\xde'
Für die Shells, die die Darstellung „\x“ nicht implementieren möchten.
Natürlich funktioniert die Übersetzung von Hex in Oktal auf (fast) jeder Shell:
$ "$sh" -c 'printf '\''%b'\'' "$(printf '\''\\0%o'\'' $((0xef)) $((0xbe)) $((0xad)) $((0xde)) )"'
Wobei "$sh" eine beliebige (sinnvolle) Shell ist. Es ist jedoch ziemlich schwierig, die korrekten Anführungszeichen beizubehalten.
Binärdateien.
Die stabilste Lösung besteht darin, die Datei und die Bytefolge (beide) in eine Kodierung umzuwandeln, die keine Probleme mit ungeraden Zeichenwerten wie (neue Zeile) 0x0A
oder (Nullbyte) hat 0x00
. Beides ist mit Tools, die für die Verarbeitung von „Textdateien“ entwickelt und angepasst wurden, ziemlich schwierig zu handhaben.
Eine Transformation wie Base64 scheint gültig zu sein, bringt aber das Problem mit sich, dass jedes Eingabebyte bis zu drei Ausgabedarstellungen haben kann, abhängig davon, ob es das erste, zweite oder dritte Byte der Mod 24 (Bits)-Position ist.
$ echo "abc" | base64
YWJjCg==
$ echo "-abc" | base64
LWFiYwo=
$ echo "--abc" | base64
LS1hYmMK
$ echo "---abc" | base64 # Note that YWJj repeats.
LS0tYWJjCg==
Hex-Transformation.
Deshalb sollte die robusteste Transformation eine sein, die an jeder Bytegrenze beginnt, wie die einfache HEX-Darstellung.
Wir können eine Datei mit der Hex-Darstellung der Datei mit einem dieser Tools erhalten:
$ 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
Die zu suchende Bytefolge liegt in diesem Fall bereits im Hex-Format vor.
:
$ var="ef be ad de"
Es könnte aber auch transformiert werden. Es folgt ein Beispiel für einen Roundtrip Hex-Bin-Hex:
$ echo "ef be ad de" | xxd -p -r | od -vAn -tx1
ef be ad de
Der Suchstring kann aus der binären Darstellung festgelegt werden. Alle drei oben aufgeführten Optionen od, hexdump oder xxd sind gleichwertig. Achten Sie nur darauf, die Leerzeichen einzuschließen, um sicherzustellen, dass die Übereinstimmung innerhalb der Byte-Grenzen liegt (keine Nibble-Verschiebung zulässig):
$ a="$(printf "\xef\xbe\xad\xde" | hexdump -v -e '/1 "%02x "')"
$ echo "$a"
ef be ad de
Wenn die Binärdatei folgendermaßen aussieht:
$ 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
Anschließend liefert eine einfache Grep-Suche die Liste der übereinstimmenden Sequenzen:
$ grep -o "$a" infile.hex | wc -l
2
Eine Linie?
Dies alles kann in einer Zeile ausgeführt werden:
$ grep -o "ef be ad de" <(xxd -c 1 -p infile.bin | tr '\n' ' ') | wc -l
Um beispielsweise 11221122
in derselben Datei zu suchen, sind diese beiden Schritte erforderlich:
$ a="$(printf '11221122' | hexdump -v -e '/1 "%02x "')"
$ grep -o "$a" <(xxd -c1 -p infile.bin | tr '\n' ' ') | wc -l
4
So „sehen“ Sie die Übereinstimmungen:
$ grep -o "$a" <(xxd -c1 -p infile.bin | tr '\n' ' ')
3131323231313232
3131323231313232
3131323231313232
3131323231313232
$ grep "$a" <(xxd -c1 -p infile.bin | tr '\n' ' ')
… 0a3131323231313232313132323131323231313232313132323131323231313232Nr. 313132320a
Pufferung
Es besteht die Sorge, dass grep die gesamte Datei puffert und bei großen Dateien eine hohe Belastung für den Computer darstellt. Dafür können wir eine ungepufferte sed-Lösung verwenden:
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
Der erste sed ist ungepuffert ( -u
) und wird nur verwendet, um pro übereinstimmender Zeichenfolge zwei Zeilenumbrüche in den Stream einzufügen. Der zweite sed
druckt nur die (kurzen) übereinstimmenden Zeilen. wc -l zählt die übereinstimmenden Zeilen.
Dadurch werden nur einige kurze Zeilen gepuffert. Die passenden Zeichenfolgen im zweiten sed. Dies sollte recht ressourcenschonend sein.
Oder, etwas komplexer zu verstehen, aber dieselbe Idee in einem Sed:
a='ef be ad de'
hexdump -v -e '/1 "%02x "' infile.bin |
sed -u '/\n/P;//!s/'"$a"'/\n&\n/;D' |
wc -l
Antwort2
Mit GNUs grep
( -P
perl-regexp) Flagge
LC_ALL=C grep -oaP '\xef\xbe\xad\xde' file | wc -l
LC_ALL=C
dient dazu, Probleme in Mehrbyte-Gebietsschemas zu vermeiden, bei denen grep
andernfalls versucht würde, Bytefolgen als Zeichen zu interpretieren.
-a
behandelt Binärdateien gleichwertig wie Textdateien (anstelle des normalen Verhaltens, bei dem grep
nur ausgegeben wird, ob mindestens eine Übereinstimmung vorliegt oder nicht)
Antwort3
PERLIO=:raw perl -nE '$c++ while m/\xef\xbe\xad\xde/g; END{say $c}' file
Dadurch werden die Eingabedateien als binär behandelt (keine Übersetzung für Zeilenumbrüche oder Kodierungen, sieheperlrun) durchläuft dann die Eingabedateien, die nicht gedruckt werden, und erhöht einen Zähler für alle Übereinstimmungen mit dem angegebenen Hex-Wert (oder einer beliebigen anderen Form, sieheperlmutt).
Antwort4
Mit GNU awk
können Sie Folgendes tun:
LC_ALL=C awk -v 'RS=\xef\xbe\xad\xde' 'END{print NR - (NR && RT == "")}'
Wenn eines der Bytes ein ERE-Operator ist, muss es jedoch maskiert werden (mit \\
). Beispielsweise müsste 0x2e
which als oder eingegeben werden . Ansonsten sollte es mit beliebigen Bytewerten einschließlich 0 und 0xa funktionieren..
\\.
\\\x2e
Beachten Sie, dass es nicht so einfach ist, NR-1
da es einige Sonderfälle gibt:
- Wenn die Eingabe leer ist und NR 0 ist, würde NR-1 -1 ergeben.
- Wenn die Eingabe im Datensatztrennzeichen endet, wird danach kein leerer Datensatz erstellt. Dies testen wir mit
RT==""
.
Beachten Sie auch, dass im schlimmsten Fall (wenn die Datei den Suchbegriff nicht enthält) die Datei vollständig in den Speicher geladen wird.