来自十六进制数字的字节值:

来自十六进制数字的字节值:

我想计算某个特定字节序列在我拥有的文件中发生的次数。例如,我想找出该数字\0xdeadbeef在可执行文件中出现了多少次。现在我正在使用 grep 执行此操作:

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

(字节以相反的顺序写入,因为我的CPU是little-endian)

但是,我的方法有两个问题:

  • 这些\Xnn转义序列仅在鱼壳中起作用。
  • grep 实际上是在计算包含我的幻数的行数。如果该模式在同一行中出现两次,则仅计算一次。

有办法解决这些问题吗?如何使这一行在 Bash shell 中运行并准确计算该模式在文件内出现的次数?

答案1

这是所请求的单行解决方案(对于最近具有“进程替换”的 shell):

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

如果没有可用的“进程替换” <(…),只需使用 grep 作为过滤器:

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

下面是该解决方案各部分的详细说明。

来自十六进制数字的字节值:

你的第一个问题很容易解决:

这些 \Xnn 转义序列仅在 Fish shell 中有效。

将上部更改X为下部x并使用 printf (对于大多数 shell):

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

或者使用:

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

对于那些选择不实现 '\x' 表示的 shell。

当然,将十六进制转换为八进制将适用于(几乎)任何 shell:

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

其中“$sh”是任何(合理的)shell。但要正确引用它是相当困难的。

二进制文件。

0x0A最可靠的解决方案是将文件和字节序列(两者)转换为某种编码,这种编码对于奇数字符值(如 (new line)或 (null byte) )没有问题0x00。使用设计和适应处理“文本文件”的工具来正确管理两者都相当困难。

像 base64 这样的转换可能看起来是有效的,但它提出了一个问题,即每个输入字节可能有最多三个输出表示,具体取决于它是 mod 24(位)位置的第一个、第二个还是第三个字节。

$ echo "abc" | base64
YWJjCg==

$ echo "-abc" | base64
LWFiYwo=

$ echo "--abc" | base64
LS1hYmMK

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

十六进制变换。

这就是为什么最强大的转换应该是从每个字节边界开始的转换,就像简单的十六进制表示一样。
我们可以使用以下任何工具获取具有文件十六进制表示形式的文件:

$ 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

在这种情况下,要搜索的字节序列已经是十六进制。
:

$ var="ef be ad de"

但它也可以转变。往返十六进制-二进制-十六进制的示例如下:

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

搜索字符串可以根据二进制表示来设置。上面提供的三个选项中的任何一个 od、hexdump 或 xxd 都是等效的。只需确保包含空格以确保匹配位于字节边界上(不允许半字节移位):

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

如果二进制文件如下所示:

$ 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

然后,简单的 grep 搜索将给出匹配序列的列表:

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

一条线?

这一切都可以在一行中执行:

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

例如,11221122在同一个文件中搜索将需要以下两个步骤:

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

要“查看”比赛:

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

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

… 0a3131323231313232313132323131323231313232313132323131323231313232313132320a


缓冲

人们担心 grep 会缓冲整个文件,如果文件很大,会给计算机带来沉重的负载。为此,我们可以使用无缓冲的 sed 解决方案:

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

第一个 sed 是无缓冲的 ( -u),仅用于在每个匹配字符串的流上注入两个换行符。第二个sed只会打印(短)匹配行。 wc -l 将计算匹配的行数。

这只会缓冲一些短行。第二个 sed 中的匹配字符串。这所使用的资源应该相当低。

或者,理解起来有些复杂,但在一个 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

使用 GNUgrep-P(perl-regexp) 标志

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

LC_ALL=C是为了避免多字节语言环境中出现问题,grep否则会尝试将字节序列解释为字符。

-a将二进制文件视为等同于文本文件(而不是正常行为,其中grep仅打印出是否至少有一个匹配项)

答案3

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

它将输入文件视为二进制文件(没有换行或编码的翻译,请参阅佩尔伦)然后循环遍历不打印的输入文件,为给定十六进制的所有匹配项递增计数器(或任何形式,请参阅佩尔雷)。

答案4

使用 GNU awk,您可以执行以下操作:

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

如果任何字节是 ERE 运算符,则必须对它们进行转义(使用\\)。就像0x2ewhich is.必须输入为\\.or \\\x2e。除此之外,它应该适用于任意字节值,包括 0 和 0xa。

请注意,这并不只是NR-1因为有一些特殊情况那么简单:

  • 当输入为空时,NR为0,NR-1将给出-1。
  • 当输入以记录分隔符结束时,此后不会创建空记录。我们用 来测试这一点RT==""

另请注意,在最坏的情况下(如果文件不包含搜索词),文件最终将被整个加载到内存中)。

相关内容