-
ELF 파일 포맷(2)Linux System 2019. 3. 11. 13:06
ELF Program Header
: 프로그램 로딩에 필요한 바이너리 세그먼트를 정의.
세그먼트
디스크에 저장된 실행파일이 커널에 의해 로드되는 과정에서 어떤 메모리 구조로 매핑될 것인지를 정의
(즉, 프로그램을 메모리에 로드하는 로더가 ELF 파일을 다룰 때의 단위. 각 구성은 Read Only, Read and Write, Write Only등의 속성에 세그먼트를 분리)
- 실행 가능 파일에만 존재 한다. (물론 공유 Obejct 파일에도 존재한다.)
- 여러 Linkable 파일들의 섹션들을 링커가 돌면서 통합해주고, 이것들에 대한 정보를 ELF Program Header에 갖는다.
-> 즉, 섹션과 세그먼트는 완전히 서로 별개의 것이 아니다.
일종의 유사한 섹션들을 모아 놓은 것이 세그먼트
섹션 : 링킹 가능 파일을 링킹이 편리하게 하기 위해 사용.
세그먼트 : 실행 가능 파일을 ELF-loader에 의해 로드 혹은 링크를 쉽게 하기 위해 사용
즉, ELF는 ABI(Application Binary Interface)로써 각 용도에 따라 섹션이나 세그먼트를 나누어 두고 프로그램의 실행 또는 링킹을 원할하게 하기 위해 사용.
- ELF Program Header는 ELF Header의 e_phoff 멤버(Program header offset)를 통해 접근이 가능.
- 이 테이블은 세그먼트의 타입, 파일 오프셋, 물리 주소, 가상 주소, 파일 크기, 메모리 이미지 크기, 정렬 방식 등을 정의.
구조체 형식
typedef strcut {
uint32_t p_type; // 세그먼트 형식
Elf32_Off p_offset; // 세그먼트 오프셋
Elf32_Addr p_vaddr; // 세그먼트 가상 주소
Elf32_Addr p_paddr; // 세그먼트 물리 주소
uint32_t p_filesz; // 파일에서의 세그먼트 크기
uint32_t p_memsz; // 메모리에서의 세그먼트 크기
uint32_t p_flags; // 세그먼트 플래그 (실행 | 읽기 | 쓰기)
uint32_t p_align; // 메모리에서의 세그먼트 정렬
}Elf32_Phdr
세그먼트 형식
1) PT_LOAD (0x01)
: 실행파일에는 PT_LOAD 형식의 세그먼트가 하나 이상 필요.
로드 가능한 세그먼트 형식으로 메모리에 로드 또는 매핑된다.
EX) 텍스트 세그먼트, 데이터 세그먼트
- 이 형식의 세그먼트들은 p_align 값을 이용해 정렬된 후 메모리에 매핑된다.
- 텍스트 세그먼트(코드 세그먼트)는 일반적으로 PF_X | PF_R (Read + Execute)라는 퍼미션을 가진다.
-데이터 세그먼트는 일반적으로 PF_W | PF_R (Read + Write)라는 세그먼트퍼미션을 가진다.
(여기서 악성코드에 감염된 경우, 텍스트 세그먼트의 퍼미션이 변경될 수 있다.
예를 들어, PF_W 플래그가 p_flags에 추가되어 쓰기 가능한 상태가 될 수 잇다.)
2) PT_DYNAMIC (0x02)
: Dynamic 세그먼트 - Dynamic 링킹되는 실행파일에서 사용되며 동적 링커가 사용하는 정보가 담긴다.
- 주로 사용하는 값
ㄱ. 런타임 중에 링크되는 공유라이브러리 목록
ㄴ. 전역 오프셋 테이블(GOT)의 주소/위치
ㄷ. 재배열 엔트리 정보
- 주로 사용되는 태그들.
0x00. DT_NULL : 동적 세그먼트 리스트의 끝을 의미 (Ignored)
0x01. DT_NEEDED : 필요한 라이브러리의 이름의 문자열 테이블 오프셋(d_val)
(이름은 STRTAB에 존재. 인덱스로 참조)
0x02. DT_PLTRELSZ : PLT 재배열 테이블의 크기 (bytes)(d_val)
0x03. DT_PLTGOT (d_ptr)
0x04. DT_HASH : 심볼 해시 테이블 주소 (d_ptr)
0x05. DT_STRTAB : 문자열 테이블 주소 (d_ptr)
0x06. DT_SYMTAB : 심볼 테이블 주소 (d_ptr)
0x07. DT_RELA : RELA 상대 주소 재배열 테이블 주소 (d_ptr)
0x08. DT_RELASZ : RELA 상대 주소 재배열 테이블 크기 (d_val)
0x09. DT_RELAENT : RELA 상대 주소 재배열 테이블 엔트리 크기 (d_val)
0x0A. DT_STRSZ : 문자열 테이블 크기 (d_val)
0x0B. DT_SYMENT : 심볼 테이블 엔트리 크기 (d_val)
0x0C. DT_INIT : 초기화 함수 주소 (d_ptr)
0x0D. DT_FINI : 종료 함수 주소 (d_ptr)
0x0E. DT_SONAME : 공유 오브젝트 이름의 문자열 테이블 오프셋 (d_val)
0x0F. DT_RPATH : 라이브러리 탐색 경로 문자열 테이블 (d_val)
0x10. DT_SYMBOLIC : 프로그램 심볼 실행 전 링커의 공유 라이브러리 탐색 알람. (ignored)
0x11. DT_REL : REL 상대 주소 재배열 테이블 주소 (d_ptr)
0x12. DT_RELSZ : REL 상대 주소 재배열 테이블 크기 (d_val)
0x13. DT_RELENT : REL 상대 주소 재배열 테이블 엔트리 크기 (d_val)
0x14. DT_PLTREL : PTL(RELA or REL)가 가리키는 재배열 형식 (d_val)
0x15. DT_DEBUG : 정의되지 않은 사용(디버깅용) (d_ptr)
0x16. DT_TEXTREL : 이 태그가 없으면 모든 재배열 가능 세그먼트는 쓰기 가능한 상태 여야 한다. (ignored)
0x17. DT_JMPREL : PLT가 사용하는 재배열 엔트리 주소 (d_ptr)
0x18. DT_BIND_NOW : 프로그램이 실행되기 전 동적 링커가 모든 재배열을 처리하라고 명령. (ignored)
- 각 Dynamic 세그먼트에는 Dynamic 링킹 정보(구조체)가 담겨 있다.
-> ELF Dynamic 구조체
typedef struct {
Elf32_Sword d_tag;
union {
Elf32_Word d_val;Elf32_Addr d_ptr;
}d_un;
}Elf32_Dyn;
3. PT_INTERP (0x03)
: 프로그램 인터프리터의 정보를 가지고 있다.
프로그램 인터프리터 : 동적 링커라고도 하며, 일반적으로 /lib/linux-ld.so.2에 위치한다.
4. PT_NOTE (0x04)
: PT_NOTE 형식의 세그먼트는 특정 벤더나 시스템에 관한 부가적인 정보를 포함한다.
-> 벤더나 시스템 빌더는 SHT_NOTE 섹션 형식이나 PT_NOTE 프로그램 헤더를 통해 오브젝트 파일의 호환성 등의 정보를 기록할 수 있다.
- 이 세그먼트는 OS에서만 참조하기 때문에, 실행파일이 실행되는 동안 꼭 필요한 정보가 아니며, 누락되어 있는 경우, 추정한 값을 이용한다.
5. PT_PHDR (0x06)
: ELF Program Header의 주소와 크기를 나타낸다.
※ 재배열 가능 객체 (ET_REL 형식)에는 프로그램 헤더가 존재하지 않는다.
.o 파일은 실행 파일로 링크되는 파일이기 때문에, 그 자체로는 직접 메모리로 로드될 수가 없다.
예외) 리눅스의 로드 가능한 커널 모듈은 ET_REL 객체이지만, 모듈 자체가 커널 메모리로 로드되고, 재배열되어 실행이 된다.
예제를 통해 살펴보자
- PHDR 세그먼트를 보면 offset 0x34에서 부터 Program Header가 시작됨을 알 수 있고, 전체 Size는 0x20 * 0xB = 0x160이 됨을 확인 할 수 있다.
- INTERP 세그먼트를 보면 offset은 0x194에 존재한다.
동적 링커가 '/lib/ld-linux.so.2'임을 알 수 있다.
size도 문자열 총 길이가 0x13이 됨을 알 수 있다.
- LOAD 세그먼트의 경우 여러 개 존재하는데, 플래그와 readelf의 매핑 정보를 통해 각 LOAD 세그먼트의 역할을 알 수 있다.
-> 두번 째 LOAD 세그먼트를 보면, 플래그가 Read + Execute이고 mapping 정보를 보아도 .text가 매핑이 되어있다. 따라서 해당 세그먼트가 텍스트 세그먼트임을 알 수 있다.
-> 네번 째 LOAD 세그먼트의 경우, 플래그가 Read + Write 이고, mapping 정보를 보아도 .data, .bss가 매핑이 되어 있다. 따라서 해당 세그먼트가 데이터 세그먼트임을 알 수 있다.
-> 텍스트 세그먼트를 살펴본 결과, 0x1000에서 부터 instruction 코드가 있음을 알 수 있다. 그리고, .init 섹션에서부터 코드가 시작함을 알 수 있다.
- Dynamic 세그먼트의 경우 .dynamic 섹션 하나에만 매핑이 된다. 그냥 .dynamic 섹션으로 봐도 무리가 없을 것 같다.
위의 그림을 보면, d_tag - d_val 또는 d_tag - d_ptr 형태로 dynamic 구조체가 마지막에 NULL이 나올때까지 리스트로 존재하게 된다.
'Linux System' 카테고리의 다른 글
ELF 파일 포맷(3) (0) 2019.03.14 ELF 파일 포맷(1) (0) 2019.03.11 라이브러리 (0) 2019.03.11 파일 디스크립터 (0) 2019.03.10 댓글