Linux 명령줄에서 바이너리 파일 내부를 엿보는 방법

미스터리 파일이 있습니까? Linux 파일 명령은 파일 유형을 빠르게 알려줍니다. 하지만 바이너리 파일이라면 더 자세히 알아볼 수 있습니다. 파일에는 분석하는 데 도움이 되는 여러 마구간이 있습니다. 이러한 도구 중 일부를 사용하는 방법을 보여 드리겠습니다.

파일 형식 식별

파일에는 일반적으로 소프트웨어 패키지가 파일의 유형과 그 안의 데이터가 나타내는 내용을 식별할 수 있는 특성이 있습니다. MP3 음악 플레이어에서 PNG 파일을 열려고 시도하는 것은 이치에 맞지 않습니다. 따라서 파일에 어떤 형태의 ID가 수반되는 것은 유용하고 실용적입니다.

이것은 파일의 맨 처음에 있는 몇 개의 서명 바이트일 수 있습니다. 이렇게 하면 파일이 형식과 내용에 대해 명시적일 수 있습니다. 때때로 파일 유형은 파일 아키텍처로 알려진 데이터 자체의 내부 조직의 고유한 측면에서 유추됩니다.

Windows와 같은 일부 운영 체제는 파일 확장자에 따라 완전히 안내됩니다. 속기 쉽다거나 신뢰할 수 있다고 부를 수 있지만 Windows는 DOCX 확장자를 가진 모든 파일이 실제로 DOCX 워드 프로세싱 파일이라고 가정합니다. 곧 알게 되겠지만 Linux는 그렇지 않습니다. 증거를 원하고 파일 내부를 살펴보고 찾습니다.

여기에 설명된 도구는 이 기사를 조사하는 데 사용한 Manjaro 20, Fedora 21 및 Ubuntu 20.04 배포판에 이미 설치되어 있습니다. 다음을 사용하여 조사를 시작하겠습니다. 파일 명령.

파일 명령 사용

현재 디렉토리에 다양한 파일 유형 모음이 있습니다. 문서, 소스 코드, 실행 파일 및 텍스트 파일이 혼합되어 있습니다.

ls 명령은 디렉토리에 무엇이 있는지 보여주고 -hl(사람이 읽을 수 있는 크기, 긴 목록) 옵션은 각 파일의 크기를 보여줍니다.

ls -hl

이들 중 몇 가지에 대해 파일을 시도하고 우리가 얻는 것을 봅시다:

file build_instructions.odt
file build_instructions.pdf
file COBOL_Report_Apr60.djvu

세 가지 파일 형식이 올바르게 식별됩니다. 가능한 경우 파일은 우리에게 조금 더 많은 정보를 제공합니다. PDF 파일은 버전 1.5 형식.

임의의 값이 XYZ인 확장자를 갖도록 ODT 파일의 이름을 바꾸더라도 파일은 파일 파일 브라우저와 파일을 사용하는 명령줄 모두에서 여전히 올바르게 식별됩니다.

파일 파일 브라우저 내에서 올바른 아이콘이 제공됩니다. 명령줄에서 file은 확장자를 무시하고 파일 내부를 살펴보고 유형을 확인합니다.

file build_instructions.xyz

이미지 및 음악 파일과 같은 미디어의 파일을 사용하면 일반적으로 형식, 인코딩, 해상도 등에 관한 정보를 얻을 수 있습니다.

file screenshot.png
file screenshot.jpg
file Pachelbel_Canon_In_D.mp3

흥미롭게도 일반 텍스트 파일의 경우에도 파일은 확장자로 파일을 판단하지 않습니다. 예를 들어, 표준 일반 텍스트를 포함하지만 소스 코드는 포함하지 않는 확장자가 “.c”인 파일이 있는 경우 파일은 이를 정품 C로 오인하지 않습니다. 소스 코드 파일:

file function+headers.h
file makefile
file hello.c

file은 헤더 파일(“.h”)을 파일의 C 소스 코드 컬렉션의 일부로 올바르게 식별하고 makefile이 스크립트임을 알고 있습니다.

  Pi-Hole Linux 도구를 사용하여 광고를 차단하는 방법

바이너리 파일과 함께 파일 사용

바이너리 파일은 다른 것보다 “블랙박스”에 가깝습니다. 이미지 파일을 볼 수 있고, 사운드 파일을 재생할 수 있으며, 문서 파일은 적절한 소프트웨어 패키지로 열 수 있습니다. 그러나 바이너리 파일은 더 어려운 문제입니다.

예를 들어, “hello” 및 “wd” 파일은 바이너리 실행 파일입니다. 그들은 프로그램입니다. “wd.o”라는 파일은 오브젝트 파일입니다. 소스 코드가 컴파일러에 의해 컴파일되면 하나 이상의 개체 파일이 생성됩니다. 여기에는 링커에 대한 정보와 함께 완성된 프로그램이 실행될 때 컴퓨터가 결국 실행할 기계어 코드가 포함됩니다. 링커는 라이브러리에 대한 함수 호출에 대해 각 개체 파일을 확인합니다. 프로그램이 사용하는 모든 라이브러리에 링크합니다. 이 프로세스의 결과는 실행 파일입니다.

“watch.exe” 파일은 Windows에서 실행되도록 크로스 컴파일된 바이너리 실행 파일입니다.

file wd
file wd.o
file hello
file watch.exe

마지막 파일을 먼저 취하면 파일은 “watch.exe” 파일이 Microsoft Windows의 x86 프로세서 제품군을 위한 PE32+ 실행 가능한 콘솔 프로그램임을 알려줍니다. PE는 이식 가능한 실행 형식을 나타내며, 32비트 및 64비트 버전이 있습니다.. PE32는 32비트 버전이고 PE32+는 64비트 버전입니다.

다른 세 파일은 모두 다음과 같이 식별됩니다. 실행 및 연결 가능한 형식 (ELF) 파일. 이것은 실행 파일 및 라이브러리와 같은 공유 개체 파일에 대한 표준입니다. 곧 ELF 헤더 형식을 살펴보겠습니다.

눈을 사로잡을 수 있는 것은 두 개의 실행 파일(“wd” 및 “hello”)이 다음과 같이 식별된다는 것입니다. Linux 표준 기반 (LSB) 공유 개체 및 개체 파일 “wd.o”는 LSB 재배치 가능으로 식별됩니다. 실행 가능이라는 단어는 없는 경우에 분명합니다.

개체 파일은 재배치 가능합니다. 즉, 그 안에 있는 코드를 어느 위치에서나 메모리로 로드할 수 있습니다. 실행 파일은 이 기능을 상속하는 방식으로 개체 파일에서 링커에 의해 생성되었기 때문에 공유 개체로 나열됩니다.

이것은 허용 주소 공간 레이아웃 무작위화 (ASMR) 시스템이 선택한 주소의 메모리에 실행 파일을 로드합니다. 표준 실행 파일에는 헤더에 코딩된 로드 주소가 있으며, 이는 메모리에 로드되는 위치를 나타냅니다.

  Linux 데스크탑을 위한 6가지 최고의 시스템 백업 도구

ASMR은 보안 기술입니다. 예측 가능한 주소의 메모리에 실행 파일을 로드하면 공격에 취약합니다. 이는 진입점과 해당 기능의 위치가 항상 공격자에게 알려지기 때문입니다. 위치 독립 실행 파일 임의의 주소에 위치한 (PIE)는 이러한 취약성을 극복합니다.

만약 우리가 우리 프로그램을 컴파일 gcc 컴파일러를 사용하여 -no-pie 옵션을 제공하면 일반적인 실행 파일을 생성합니다.

-o(출력 파일) 옵션을 사용하면 실행 파일의 이름을 제공할 수 있습니다.

gcc -o hello -no-pie hello.c

새 실행 파일의 파일을 사용하고 변경된 사항을 확인합니다.

file hello

실행 파일의 크기는 이전과 동일합니다(17KB).

ls -hl hello

바이너리는 이제 표준 실행 파일로 식별됩니다. 데모용으로만 이 작업을 수행합니다. 이런 식으로 응용 프로그램을 컴파일하면 ASMR의 모든 이점을 잃게 됩니다.

실행 파일이 왜 그렇게 큰가요?

우리의 예제 hello 프로그램은 17KB이므로 크다고 할 수는 없지만 모든 것이 상대적입니다. 소스 코드는 120바이트입니다.

cat hello.c

하나의 문자열을 터미널 창에 인쇄하는 것뿐이라면 바이너리를 대량화하는 것은 무엇입니까? ELF 헤더가 있다는 것을 알고 있지만 64비트 바이너리의 경우 길이가 64바이트에 불과합니다. 분명히, 그것은 다른 것이어야 합니다:

ls -hl hello

하자 다음을 사용하여 바이너리를 스캔하십시오. strings 명령은 그 안에 무엇이 들어 있는지 찾기 위한 간단한 첫 번째 단계입니다. 우리는 그것을 더 적게 파이프할 것입니다:

strings hello | less

바이너리 내부에는 “Hello, Geek world!” 외에 많은 문자열이 있습니다. 우리의 소스 코드에서. 대부분은 바이너리 내의 영역에 대한 레이블과 공유 객체의 이름 및 연결 정보입니다. 여기에는 라이브러리와 바이너리가 의존하는 라이브러리 내의 함수가 포함됩니다.

그만큼 ldd 명령 바이너리의 공유 객체 종속성을 보여줍니다.

ldd hello

출력에는 세 개의 항목이 있으며 그 중 두 개에는 디렉토리 경로가 포함됩니다(첫 번째 항목에는 없음).

linux-vdso.so: 가상 동적 공유 개체(VDSO) 사용자 공간 바이너리가 커널 공간 루틴 세트에 액세스할 수 있도록 하는 커널 메커니즘입니다. 이것 컨텍스트 전환의 오버헤드를 방지합니다. 사용자 커널 모드에서. VDSO 공유 개체는 ELF(Executable and Linkable Format) 형식을 준수하므로 런타임 시 바이너리에 동적으로 연결할 수 있습니다. VDSO는 동적으로 할당되며 ASMR을 활용합니다. VDSO 기능은 표준에 의해 제공됩니다. GNU C 라이브러리 커널이 ASMR 체계를 지원하는 경우.
libc.so.6: GNU C 라이브러리 공유 객체.
/lib64/ld-linux-x86-64.so.2: 바이너리가 사용하려는 동적 링커입니다. 동적 링커 바이너리가 어떤 종속성을 가지고 있는지 찾기 위해 바이너리를 조사합니다.. 공유 개체를 메모리로 시작합니다. 바이너리를 실행하고 메모리의 종속성을 찾고 액세스할 수 있도록 준비합니다. 그런 다음 프로그램을 시작합니다.

  Linux lsof 명령을 사용하는 방법

ELF 헤더

우리는 할 수 있습니다 ELF 헤더 검사 및 디코딩 readelf 유틸리티 및 -h(파일 헤더) 옵션 사용:

readelf -h hello

헤더는 우리를 위해 해석됩니다.

모든 ELF 바이너리의 첫 번째 바이트는 16진수 값 0x7F로 설정됩니다. 다음 세 바이트는 0x45, 0x4C 및 0x46으로 설정됩니다. 첫 번째 바이트는 파일을 ELF 바이너리로 식별하는 플래그입니다. 이 수정을 명확하게 하기 위해 다음 3바이트는 “ELF”로 표기합니다. 아스키:

클래스: 바이너리가 32비트 또는 64비트 실행 파일인지 여부를 나타냅니다(1=32, 2=64).
데이터: 다음을 나타냅니다. 엔디안 사용. 엔디안 인코딩은 멀티바이트 숫자가 저장되는 방식을 정의합니다. 빅 엔디안 인코딩에서 숫자는 최상위 비트부터 먼저 저장됩니다. 리틀 엔디안 인코딩에서 숫자는 최하위 비트부터 먼저 저장됩니다.
버전: ELF의 버전(현재는 1)입니다.
OS/ABI: 유형을 나타냅니다. 애플리케이션 바이너리 인터페이스 사용. 이것은 프로그램과 공유 라이브러리와 같은 두 개의 바이너리 모듈 사이의 인터페이스를 정의합니다.
ABI 버전: ABI의 버전입니다.
유형: ELF 바이너리의 유형입니다. 공통 값은 재배치 가능한 리소스(예: 개체 파일)의 경우 ET_REL, -no-pie 플래그로 컴파일된 실행 파일의 경우 ET_EXEC, ASMR 인식 실행 파일의 경우 ET_DYN입니다.
기계: 명령어 세트 아키텍처. 바이너리가 생성된 대상 플랫폼을 나타냅니다.
버전: 이 버전의 ELF에 대해 항상 1로 설정됩니다.
진입점 주소: 실행이 시작되는 바이너리 내의 메모리 주소입니다.

다른 항목은 위치를 계산할 수 있도록 바이너리 내의 영역 및 섹션의 크기와 수입니다.

바이너리의 처음 8바이트를 간단히 살펴봅니다. 헥스 덤프와 함께 파일의 처음 4바이트에 서명 바이트와 “ELF” 문자열이 표시됩니다. -C(표준) 옵션은 16진수 값과 함께 바이트의 ASCII 표현을 제공하고 -n(숫자) 옵션을 사용하면 보고 싶은 바이트 수를 지정할 수 있습니다.

hexdump -C -n 8 hello

objdump 및 세분화된 보기

핵심 세부 사항을 보려면 objdump 명령을 -d(분해) 옵션과 함께 사용할 수 있습니다.

objdump -d hello | less

이것은 실행 가능한 기계 코드를 분해하고 어셈블리 언어와 함께 16진수 바이트로 표시합니다. 각 줄의 첫 번째 bye의 주소 위치는 맨 왼쪽에 표시됩니다.

이것은 어셈블리 언어를 읽을 수 있거나 커튼 뒤에서 무슨 일이 일어나는지 궁금한 경우에만 유용합니다. 출력이 많기 때문에 더 적게 파이프했습니다.

컴파일 및 링크

바이너리를 컴파일하는 방법에는 여러 가지가 있습니다. 예를 들어 개발자는 디버깅 정보를 포함할지 여부를 선택합니다. 바이너리가 링크되는 방식도 그 내용과 크기에 영향을 미칩니다. 이진 참조가 외부 종속성으로 개체를 공유하는 경우 종속성이 정적으로 연결되는 것보다 작습니다.

대부분의 개발자는 여기에서 다룬 명령을 이미 알고 있습니다. 그러나 다른 사람들에게는 주변을 뒤지고 바이너리 블랙 박스 안에 무엇이 있는지 볼 수 있는 몇 가지 쉬운 방법을 제공합니다.