ELF 파일 형식 이해

Understanding Elf File Format



소스 코드에서 바이너리 코드로

프로그래밍은 기발한 아이디어를 갖고 선택한 프로그래밍 언어(예: C)로 소스 코드를 작성하고 소스 코드를 파일에 저장하는 것으로 시작됩니다. 적절한 컴파일러(예: GCC)의 도움으로 소스 코드가 먼저 개체 코드로 변환됩니다. 결국 링커는 개체 코드를 참조 라이브러리와 연결하는 이진 파일로 개체 코드를 변환합니다. 이 파일은 단일 명령어를 CPU가 이해하는 기계어 코드로 포함하고 있으며 컴파일된 프로그램이 실행되는 즉시 실행됩니다.

위에서 언급한 이진 파일은 특정 구조를 따르며 가장 일반적인 파일 중 하나는 Executable 및 Linkable Format의 약어인 ELF입니다. 실행 파일, 재배치 가능한 개체 파일, 공유 라이브러리 및 코어 덤프에 널리 사용됩니다.







20년 전인 1999년에 86open 프로젝트는 x86 프로세서의 Unix 및 Unix 계열 시스템을 위한 표준 바이너리 파일 형식으로 ELF를 선택했습니다. 운 좋게도 ELF 형식은 이전에 System V Application Binary Interface와 Tool Interface Standard[4]에 모두 문서화되어 있었습니다. 이 사실은 Unix 기반 운영 체제의 다른 공급업체와 개발자 간의 표준화에 대한 합의를 크게 단순화했습니다.



그 결정의 이유는 다양한 엔디안 형식 및 주소 크기에 대한 유연성, 확장성 및 플랫폼 간 지원인 ELF의 설계였습니다. ELF의 디자인은 특정 프로세서, 명령어 세트 또는 하드웨어 아키텍처에 국한되지 않습니다. 실행 파일 형식에 대한 자세한 비교는 여기를 참조하십시오[3].



그 이후로 ELF 형식은 여러 운영 체제에서 사용됩니다. 여기에는 Linux, Solaris/Illumos, Free-, Net- 및 OpenBSD, QNX, BeOS/Haiku 및 Fuchsia OS가 포함됩니다[2]. 또한 Android, Maemo 또는 Meego OS/Sailfish OS를 실행하는 모바일 장치와 PlayStation Portable, Dreamcast 및 Wii와 같은 게임 콘솔에서 찾을 수 있습니다.





사양은 ELF 파일의 파일 이름 확장자를 명확히 하지 않습니다. .axf, .bin, .elf, .o, .prx, .puff, .ko, .so 및 .mod 또는 없음과 같은 다양한 문자 조합이 사용됩니다.

ELF 파일의 구조

Linux 터미널에서 man elf 명령은 ELF 파일 구조에 대한 편리한 요약을 제공합니다.



목록 1: ELF 구조의 맨페이지

$ 맨 일레븐

ELF(5) 리눅스 프로그래머 매뉴얼 ELF(5)

이름
elf - 실행 파일 및 연결 형식(ELF) 파일 형식

개요
#포함하다

설명
헤더 파일은 ELF 실행 바이너리의 형식을 정의합니다.
파일. 이러한 파일 중에는 재배치 가능한 일반 실행 파일이 있습니다.
오브젝트 파일, 코어 파일 및 공유 라이브러리.

ELF 파일 형식을 사용하는 실행 파일은 ELF 헤더,
뒤에 프로그램 헤더 테이블이나 섹션 헤더 테이블, 또는 둘 다 옵니다.
ELF 헤더는 항상 파일의 오프셋 0에 있습니다. 프로그램
헤더 테이블과 파일에서 섹션 헤더 테이블의 오프셋은
ELF 헤더에 정의되어 있습니다. 두 테이블은 나머지를 설명합니다.
파일의 특수성.

...

위의 설명에서 알 수 있듯이 ELF 파일은 ELF 헤더와 파일 데이터의 두 섹션으로 구성됩니다. 파일 데이터 섹션은 0개 이상의 세그먼트를 설명하는 프로그램 헤더 테이블, 프로그램 헤더 테이블의 항목이 참조하는 데이터가 뒤따르는 0개 이상의 섹션을 설명하는 섹션 헤더 테이블 및 섹션 헤더 테이블로 구성될 수 있습니다. 각 세그먼트에는 파일의 런타임 실행에 필요한 정보가 포함되고 섹션에는 링크 및 재배치에 대한 중요한 데이터가 포함됩니다. 그림 1은 이를 개략적으로 보여줍니다.

ELF 헤더

ELF 헤더는 길이가 32바이트이며 파일 형식을 식별합니다. 0x7F 다음에 0x45, 0x4c 및 0x46이 오는 4개의 고유 바이트 시퀀스로 시작하여 E, L 및 F 세 글자로 변환됩니다. 다른 값 중에서 헤더는 32 또는 32에 대한 ELF 파일인지 여부도 나타냅니다. 리틀 또는 빅 엔디안을 사용하는 64비트 형식은 ELF 버전과 올바른 ABI(애플리케이션 바이너리 인터페이스) 및 CPU 명령어 세트와 상호 운용하기 위해 파일이 컴파일된 운영 체제를 보여줍니다.

이진 파일 터치의 hexdump는 다음과 같습니다.

.Listing 2: 바이너리 파일의 hexdump

$ hd /usr/bin/touch | 머리 -5
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF.......................|
00000010 02 00 3e 00 01 00 00 00 e3 25 40 00 00 00 00 00 | ..> ......% @ ..... |
00000020 40 00 00 00 00 00 00 00 28 e4 00 00 00 00 00 00 | @ ....... (....... |
00000030 00 00 00 00 40 00 38 00 09 00 40 00 1b 00 1a 00 | [이메일 보호] @.....|
00000040 06 00 00 00 05 00 00 00 40 00 00 00 00 00 00 00 | [이메일 보호] |

데비안 GNU/리눅스는 GNU 'binutils' 패키지에서 제공되는 readelf 명령을 제공합니다. 스위치 -h(-file-header의 짧은 버전)와 함께 ELF 파일의 헤더를 멋지게 표시합니다. Listing 3은 터치 명령에 대해 설명한다.

.Listing 3: ELF 파일의 헤더 표시하기

$ readelf -h /usr/bin/touch
ELF 헤더:
마법: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
클래스: ELF64
데이터: 2의 보수, 리틀 엔디안
버전: 1(현재)
OS / ABI: UNIX - 시스템 V
ABI 버전: 0
유형: EXEC(실행 파일)
기계: Advanced Micro Devices X86-64
버전: 0x1
진입점 주소: 0x4025e3
프로그램 헤더 시작: 64(파일에 바이트)
섹션 헤더 시작: 58408(파일의 바이트 수)
플래그: 0x0
이 헤더의 크기: 64(바이트)
프로그램 헤더 크기: 56(바이트)
프로그램 헤더 수: 9
섹션 헤더 크기: 64(바이트)
섹션 헤더 수: 27
섹션 헤더 문자열 테이블 인덱스: 26

프로그램 헤더

프로그램 헤더는 런타임에 사용된 세그먼트를 표시하고 시스템에 프로세스 이미지를 생성하는 방법을 알려줍니다. 목록 2의 헤더는 ELF 파일이 각각 크기가 56바이트인 9개의 프로그램 헤더로 구성되어 있으며 첫 번째 헤더는 바이트 64에서 시작함을 보여줍니다.

다시, readelf 명령은 ELF 파일에서 정보를 추출하는 데 도움이 됩니다. 스위치 -l(-program-headers 또는 –segments의 약어)은 목록 4와 같이 더 자세한 정보를 표시합니다.

.Listing 4: 프로그램 헤더에 대한 정보 표시

$ readelf -l /usr/bin/touch

Elf 파일 형식은 EXEC(실행 파일)입니다.
진입점 0x4025e3
오프셋 64에서 시작하는 9개의 프로그램 헤더가 있습니다.

프로그램 헤더:
유형 오프셋 VirtAddr PhysAddr
FileSiz MemSiz 플래그 정렬
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001f8 0x00000000000001f8 R E 8
인터프 0x0000000000000238 0x0000000000400238 0x0000000000400238
0x000000000000001c 0x000000000000001c R 1
[프로그램 인터프리터 요청: /lib64/ld-linux-x86-64.so.2]
로드 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x000000000000d494 0x000000000000d494 R E 200000
로드 0x000000000000de10 0x000000000060de10 0x000000000060de10
0x0000000000000524 0x0000000000000748 RW 200000
동적 0x000000000000de28 0x000000000060de28 0x000000000060de28
0x00000000000001d0 0x00000000000001d0 RW 8
참고 0x000000000000254 0x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x000000000000bc40 0x000000000040bc40 0x000000000040bc40
0x00000000000003a4 0x00000000000003a4 R 4
GNU_STACK 0x0000000000000000 0x000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 10
GNU_RELRO 0x000000000000de10 0x000000000060de10 0x000000000060de10
0x00000000000001f0 0x00000000000001f0 R 1

섹션에서 세그먼트로 매핑:
세그먼트 섹션...
00
01 .인터프
02 .interp .note.ABI 태그 .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini . 로데이터 .eh_frame_hdr .eh_frame
03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
04 .다이나믹
05 .note.ABI-태그 .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .jcr .dynamic .got

섹션 헤더

ELF 구조의 세 번째 부분은 섹션 헤더입니다. 바이너리의 단일 섹션을 나열하기 위한 것입니다. 스위치 -S(-section-headers 또는 -sections의 약자)는 다른 헤더를 나열합니다. 터치 명령의 경우 27개의 섹션 헤더가 있으며 Listing 5는 그 중 처음 4개와 마지막 헤더만 보여줍니다. 각 행은 섹션 크기, 섹션 유형, 주소 및 메모리 오프셋을 다룹니다.

.Listing 5: readelf가 공개한 섹션 세부 정보

$ readelf -S /usr/bin/touch
오프셋 0xe428에서 시작하는 27개의 섹션 헤더가 있습니다.

섹션 헤더:
[Nr] 이름 유형 주소 오프셋
크기 EntSize 플래그 링크 정보 정렬
[ 0] 널 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[1] .interp PROGBITS 0000000000400238 00000238
000000000000001c 0000000000000000 A 0 0 1
[2] .note.ABI 태그 노트 0000000000400254 00000254
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i 참고 0000000000400274 00000274
...
...
[26] .shstrtab STRTAB 0000000000000000 0000e334
00000000000000ef 0000000000000000 0 0 1
플래그 키:
W(쓰기), A(할당), X(실행), M(병합), S(문자열), l(대형)
I(정보), L(링크 순서), G(그룹), T(TLS), E(제외), x(알 수 없음)
O(추가 OS 처리 필요) o(OS별), p(프로세서별)

ELF 파일 분석 도구

위의 예에서 언급했듯이 GNU/Linux에는 ELF 파일을 분석하는 데 도움이 되는 여러 유용한 도구가 포함되어 있습니다. 우리가 살펴볼 첫 번째 후보는 파일 유틸리티입니다.

파일은 재배치 가능, 실행 가능 또는 공유 개체 파일의 코드가 사용되는 명령 집합 아키텍처를 포함하여 ELF 파일에 대한 기본 정보를 표시합니다. 목록 6에서 /bin/touch는 GNU/Linux 커널 버전 2.6.32용으로 동적으로 링크되고 빌드된 Linux Standard Base(LSB)를 따르는 64비트 실행 파일임을 알려줍니다.

.Listing 6: 파일을 사용한 기본 정보

$ 파일 /bin/touch
/bin/touch: ELF 64비트 LSB 실행 파일, x86-64, 버전 1(SYSV), 동적으로 링크됨, 인터프리터 /lib64/l,
GNU/Linux 2.6.32용, BuildID[sha1]=ec08d609e9e8e73d4be6134541a472ad0ea34502, 제거됨
$

두 번째 후보는 readelf입니다. ELF 파일에 대한 자세한 정보를 표시합니다. 스위치 목록은 비교적 길고 ELF 형식의 모든 측면을 다룹니다. -n(-notes의 약자) 스위치 사용 목록 7은 파일 터치에 존재하는 메모 섹션(ABI 버전 태그 및 빌드 ID 비트스트링)만 보여줍니다.

.Listing 7: ELF 파일의 선택된 섹션 표시

$ readelf -n /usr/bin/touch

길이가 0x00000020인 파일 오프셋 0x00000254에서 찾은 메모 표시:
소유자 데이터 크기 설명
GNU 0x00000010 NT_GNU_ABI_TAG(ABI 버전 태그)
OS: 리눅스, ABI: 2.6.32

길이가 0x00000024인 파일 오프셋 0x00000274에서 찾은 메모 표시:
소유자 데이터 크기 설명
GNU 0x00000014 NT_GNU_BUILD_ID(고유한 빌드 ID 비트 문자열)
빌드 ID: ec08d609e9e8e73d4be6134541a472ad0ea34502

Solaris 및 FreeBSD에서 유틸리티 elfdump [7]는 readelf에 해당합니다. 2019년 현재 2003년 이후 새로운 릴리스나 업데이트가 없습니다.

세 번째는 Linux에서만 사용할 수 있는 elfutils[6]라는 패키지입니다. GNU Binutils에 대한 대체 도구를 제공하고 ELF 파일의 유효성 검사도 허용합니다. 패키지에 제공된 모든 유틸리티 이름은 'elf utils'에 대해 eu로 시작합니다.

마지막으로 objdump에 대해 언급하겠습니다. 이 도구는 readelf와 유사하지만 개체 파일에 중점을 둡니다. ELF 파일 및 기타 개체 형식에 대한 유사한 범위의 정보를 제공합니다.

.Listing 8: objdump로 추출한 파일 정보

$ objdump -f /bin/touch

/bin/touch: 파일 형식 elf64-x86-64
아키텍처: i386:x86-64, 플래그 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
시작 주소 0x00000000004025e3

$

ELF 파일의 내용을 읽고 조작하는 도구가 포함된 'elfkickers'[9]라는 소프트웨어 패키지도 있습니다. 불행히도 릴리스의 수가 다소 적기 때문에 언급만 하고 더 이상의 예는 보여주지 않습니다.

개발자로서 대신 'pax-utils'[10,11]를 볼 수 있습니다. 이 유틸리티 세트는 ELF 파일의 유효성을 검사하는 데 도움이 되는 여러 도구를 제공합니다. 예를 들어, dumpelf는 ELF 파일을 분석하고 세부 정보가 포함된 C 헤더 파일을 반환합니다(그림 2 참조).

결론

영리한 디자인과 뛰어난 문서 덕분에 ELF 형식은 매우 잘 작동하며 20년 후에도 여전히 사용됩니다. 위에 표시된 유틸리티를 사용하면 ELF 파일에 대한 통찰력을 얻을 수 있으며 프로그램이 수행하는 작업을 파악할 수 있습니다. 이것이 소프트웨어 분석의 첫 번째 단계입니다. 해피 해킹입니다!

링크 및 참조
감사의 말

작가는 이 기사를 준비하는 데 도움을 준 Axel Beckert에게 감사를 전하고 싶습니다.