ELF 구조부터 로딩까지

ELF format

ELF(Executable and Linking Format)는 실행 가능한 코드를 위한 표준화된 파일 형식이다.

ELF는 주로 실행 파일(ET_EXEC)이나 공유 라이브러리(ET_DYN) 에 사용된다. 현재 거의 모든 현대적인 Unix 계열 운영체제는 ELF 의 이식성, 표준화된 기능, 처음부터 깔끔하게 설계된 구조 덕분에 ELF 형식을 지원한다.

ELF 표준의 실제 버전은 1.2 이다. ELF 표준을 다루는 여러 문서가 있으며 자세한 내용은 Tool Interface Standard, Executeable and Linking Format, Version 1.2 를 참고하면 된다.

ELF 바이너리 형식은 다음 두가지 요구사항을 모두 만족하도록 설계되었다.

  • linker : 일반적으로 컴파일 과정에서 사용된다.
  • loader : 일반적으로 프로그램 실행 시점에서 사용된다.
이 두가지 요구사항을 만족하기 위해 ELF 바이너리 파일 내부의 데이터를 설명하는 서로 구별되는 두가지 인터페이스가 포함되었다.

이 두 인터페이스는 서로 의존하지 않는다.

ELF headers

ELF 파일은 최소한 ELF header를 반드시 포함해야 한다.

ELF 헤더에는 바이너리 파일의 내용을 어떻게 해석해야 하는지에 대한 정보와 바이너리를 설명하는 다른 구조체들이 파일의 어디에 위치하는지에 대한 정보가 들어있다.

ELF 헤더는 파일 내부의 offset 0에서 시작하며 다음과 같은 형식을 가진다.

#define EI_NIDENT (16)

typedef struct
{
    unsigned char e_ident[EI_NIDENT];
    Elf32_Half    e_type;
    Elf32_Half    e_machine;
    Elf32_Word    e_version;
    Elf32_Addr    e_entry;
    Elf32_Off     e_phoff;
    Elf32_Off     e_shoff;
} Elf32_Ehdr;


[e_ident]

e_ident 의 특정 오프셋에는 바이너리를 어떻게 처리하고 해석해야 하는지에 관한 정보가 들어있다.

Linux 에서는 SysV ABI 에 포함되지 않은 추가적인 인덱스와 값도 정의한다는 점에 주의해야한다.

이러한 값들은 이식성이 없을 수 있다.


다음은 공식적으로 알려진 오프셋과 각 오프셋에서 가질 수 있는 값들이다.

#define EI_MAG0         0       /* 파일 식별 바이트 0의 인덱스 */
#define ELFMAG0         0x7f    /* 매직 넘버 바이트 0 */

#define EI_MAG1         1       /* 파일 식별 바이트 1의 인덱스 */
#define ELFMAG1         'E'     /* 매직 넘버 바이트 1 */

#define EI_MAG2         2       /* 파일 식별 바이트 2의 인덱스 */
#define ELFMAG2         'L'     /* 매직 넘버 바이트 2 */

#define EI_MAG3         3       /* 파일 식별 바이트 3의 인덱스 */
#define ELFMAG3         'F'     /* 매직 넘버 바이트 3 */

#define EI_CLASS        4       /* 파일 클래스 바이트의 인덱스 */
#define ELFCLASSNONE    0       /* 잘못된 클래스 */
#define ELFCLASS32      1       /* 32비트 오브젝트 */
#define ELFCLASS64      2       /* 64비트 오브젝트 */

#define EI_DATA         5       /* 데이터 인코딩 바이트의 인덱스 */
#define ELFDATANONE     0       /* 잘못된 데이터 인코딩 */
#define ELFDATA2LSB     1       /* 2의 보수, 리틀 엔디언 */
#define ELFDATA2MSB     2       /* 2의 보수, 빅 엔디언 */

#define EI_VERSION      6       /* 파일 버전 바이트의 인덱스 */
#define EV_CURRENT      1       /* 값은 EV_CURRENT여야 함 */


즉 ELF 파일의 맨 처음은 대략 다음과 같은 의미를 갖는다.

e_ident[0] = 0x7f
e_ident[1] = 'E'
e_ident[2] = 'L'
e_ident[3] = 'F'

e_ident[4] = 32/64비트 여부
e_ident[5] = little/big endian 여부
e_ident[6] = ELF 버전


[e_type]

e_type 은 해당 바이너리가 어떤 용도로 사용되도록 만들어졌는지를 나타낸다.

사용가능한 값은 다음과 같다.

#define ET_NONE         0       /* 파일 종류 없음 */
#define ET_REL          1       /* 재배치 가능한 파일 */
#define ET_EXEC         2       /* 실행 파일 */
#define ET_DYN          3       /* 공유 오브젝트 파일 */
#define ET_CORE         4       /* Core 파일 */

즉 .o 같은 relocatable object는 ET_REL, 실행 파일은 ET_EXEC, 공유 오브젝트는 ET_DYN으로 구분된다.


[e_machine]

e_machine은 해당 오브젝트 파일이 어떤 아키텍쳐를 대상으로 만들어졌는지를 나타낸다.

대표적인 값으로 다음을 제시한다.

#define EM_SPARC         2      /* SUN SPARC */
#define EM_386           3      /* Intel 80386 */
#define EM_SPARCV9      43      /* SPARC v9 64-bit */
#define EM_IA_64        50      /* Intel Merced */


[e_version]

e_version은 오브젝트 파일이 어떤 ELF 버전을 따르는지를 나타낸다.

현재 이 값은 EV_CURRENT 로 설정되어 있어야 하며 e_ident[EI_VERSION] 의 값과 동일하다.


[e_entry]

e_entry 에는 바이너리의 entry point 의 가상 주소가 들어있다.

.text 섹션 시작 부분에 위치한 _start() 함수의 주소이다.

즉 프로그램을 메모리에 모두 올린 다음 CPU가 어디에서 실행을 시작할 지 알려주는 값이다.


[e_phoff]

e_phoff 에는 파일 시작 지점으로부터 첫번째 Program Header까지의 오프셋이 들어있다.

이 필드는 ET_EXEC와 ET_DYN 오브텍트에서 의미가 있다. 예를 들어

ELF 파일

offset 0x0000
┌──────────────────────┐
│ ELF Header           │
│                      │
│ e_phoff = 0x34 ──────┼──┐
└──────────────────────┘  │
                          │
offset 0x0034             │
┌──────────────────────┐  │
│ Program Header #0    │◀─┘
├──────────────────────┤
│ Program Header #1    │
├──────────────────────┤
│ Program Header #2    │
└──────────────────────┘


[e_shoff]


ELF sections

여러개의 object file을 서로 링크할 때 사용되는 인터페이스가 Section 인터페이스다. 

바이너리 파일은 여러 Section의 집합으로 볼 수 있으며 각 Section은 바이트 배열로 구성된다.

하나의 바이트가 둘 이상의 Section에 동시에 속할 수 없다.


Section의 내용은 이를 검사하는 어플리케이션이 원하는 방식으로 해석할 수 있지만 어플리케이션이 Section의 내용을 올바르게 해석할 수 있도록 도와주는 정보도 함께 제공된다.

각 Section은 Section Header에 의해 설명된다. Section Header 들은 일반적으로 오브젝트 파일의 끝부분에 위치하는 Section Header Table에 저장된다.



ELF segments


ELF loading


Linux 에서의 ELF 로딩


Linux ELF - 보조 벡터


ELF 메모리 매핑


Packing / Userspace ELF loader


References


https://phrack.org/issues/58/runtime-binary-encryption

댓글

이 블로그의 인기 게시물

L2 Wireguard

VM escape - QEMU의 메모리를 읽는 두 가지 방법