Bytewerte aus Hexadezimalzahlen:

Bytewerte aus Hexadezimalzahlen:

Ich möchte zählen, wie oft eine bestimmte Bytefolge in einer meiner Dateien vorkommt. Beispielsweise möchte ich herausfinden, wie oft diese Zahl \0xdeadbeefin 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 \XnnEscape-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 Xdurch ein unteres xund 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) 0x0Aoder (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 11221122in 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 seddruckt 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( -Pperl-regexp) Flagge

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

LC_ALL=Cdient dazu, Probleme in Mehrbyte-Gebietsschemas zu vermeiden, bei denen grepandernfalls versucht würde, Bytefolgen als Zeichen zu interpretieren.

-abehandelt Binärdateien gleichwertig wie Textdateien (anstelle des normalen Verhaltens, bei dem grepnur 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 awkkö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 0x2ewhich 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-1da 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.

verwandte Informationen