내가 가지고 있는 파일에서 특정 바이트 시퀀스가 발생하는 횟수를 세고 싶습니다. 예를 들어 \0xdeadbeef
실행 파일에 이 숫자가 몇 번 나타나는지 알고 싶습니다 . 지금은 grep을 사용하여 이 작업을 수행하고 있습니다.
#/usr/bin/fish
grep -c \Xef\Xbe\Xad\Xde my_executable_file
(내 CPU가 리틀 엔디안이기 때문에 바이트가 역순으로 기록됩니다)
그러나 내 접근 방식에는 두 가지 문제가 있습니다.
- 이러한
\Xnn
이스케이프 시퀀스는 생선 껍질에서만 작동합니다. - grep은 실제로 내 매직 넘버가 포함된 행 수를 계산합니다. 패턴이 같은 줄에 두 번 나타나면 한 번만 계산됩니다.
이러한 문제를 해결할 수 있는 방법이 있나요? Bash 셸에서 이 줄을 실행하고 파일 내에서 이 패턴이 발생하는 횟수를 정확하게 계산하려면 어떻게 해야 합니까?
답변1
요청된 한 줄 솔루션은 다음과 같습니다("프로세스 대체"가 포함된 최근 쉘의 경우).
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
다음은 솔루션의 각 부분에 대한 자세한 설명입니다.
16진수의 바이트 값:
첫 번째 문제는 해결하기 쉽습니다.
이러한 \Xnn 이스케이프 시퀀스는 Fish 쉘에서만 유효합니다.
X
위쪽을 아래쪽으로 변경 x
하고 printf를 사용하십시오(대부분의 쉘에 대해):
$ printf -- '\xef\xbe\xad\xde'
또는 다음을 사용하십시오:
$ /usr/bin/printf -- '\xef\xbe\xad\xde'
'\x' 표현을 구현하지 않기로 선택한 쉘의 경우.
물론 16진수를 8진수로 변환하는 것은 (거의) 모든 쉘에서 작동합니다:
$ "$sh" -c 'printf '\''%b'\'' "$(printf '\''\\0%o'\'' $((0xef)) $((0xbe)) $((0xad)) $((0xde)) )"'
여기서 "$sh"는 (합리적인) 쉘입니다. 하지만 이를 정확하게 인용하는 것은 매우 어렵습니다.
바이너리 파일.
0x0A
가장 신뢰할 수 있는 해결책은 파일과 바이트 시퀀스(둘 다)를 (new line) 또는 (null byte)와 같은 홀수 문자 값에 문제가 없는 일부 인코딩으로 변환하는 것입니다 0x00
. "텍스트 파일"을 처리하도록 설계되고 적용된 도구를 사용하여 적절하게 관리하는 것은 매우 어렵습니다.
base64와 같은 변환은 유효한 것처럼 보일 수 있지만 각 입력 바이트가 첫 번째, 첫 번째 2바이트 또는 3바이트인 mod 24(비트) 위치에 따라 최대 3개의 출력 표현을 가질 수 있다는 문제가 발생합니다.
$ echo "abc" | base64
YWJjCg==
$ echo "-abc" | base64
LWFiYwo=
$ echo "--abc" | base64
LS1hYmMK
$ echo "---abc" | base64 # Note that YWJj repeats.
LS0tYWJjCg==
16진수 변환.
이것이 바로 가장 강력한 변환이 단순한 16진수 표현처럼 모든 바이트 경계에서 시작되는 변환이어야 하는 이유입니다.
다음 도구 중 하나를 사용하여 파일을 16진수로 표현한 파일을 가져올 수 있습니다.
$ 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
이 경우 검색할 바이트 시퀀스는 이미 16진수입니다.
:
$ var="ef be ad de"
하지만 변형도 가능합니다. 왕복 16진수-2진수-16진수의 예는 다음과 같습니다.
$ 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
grep
GNU -P
(perl-regexp) 플래그 사용
LC_ALL=C grep -oaP '\xef\xbe\xad\xde' file | wc -l
LC_ALL=C
grep
이는 바이트 시퀀스를 문자로 해석하려고 시도하는 멀티바이트 로케일의 문제를 방지하기 위한 것입니다 .
-a
grep
이진 파일을 텍스트 파일과 동일하게 취급합니다( 일치하는 항목이 하나 이상 있을 경우에만 인쇄하는 일반적인 동작 대신 ).
답변3
답변4
GNU를 사용하면 awk
다음을 수행할 수 있습니다.
LC_ALL=C awk -v 'RS=\xef\xbe\xad\xde' 'END{print NR - (NR && RT == "")}'
바이트가 ERE 연산자인 경우 이스케이프해야 합니다(사용 \\
). 는 또는 로 입력해야 0x2e
합니다 . 그렇지 않으면 0과 0xa를 포함한 모든 바이트 값에서 작동해야 합니다..
\\.
\\\x2e
NR-1
이는 단순히 몇 가지 특수한 경우 때문 만은 아닙니다 .
- 입력이 비어 있으면 NR은 0이고 NR-1은 -1을 제공합니다.
- 입력이 레코드 구분 기호로 끝나는 경우 이후에는 빈 레코드가 생성되지 않습니다. 우리는 이것을 테스트하곤 했습니다
RT==""
.
또한 최악의 경우(파일에 검색어가 포함되어 있지 않은 경우)에는 파일 전체가 메모리에 로드됩니다.