main()는 리눅스에서 어떻게 실행이 될까?
What is ELF?
게임 또는 애니메이션이나 소설에 나오는 엘프를 말하는 걸까요?
네, 하지만 여러분들이 생각하는 그런 엘프는 아니라 유감,
그렇다면 ELF에 대해 알아봅시다!
1999년 86open 프로젝트에 x86기반 유닉스 계열 시스템들의 표준 바이너리 파일 형식입니다.
ELF (Executable and Linkable Format)은 실행파일, 목적파일, 공유 라이브러리 그리고 코어 덤프를 위한 표준 파일 형식인데요.
음 그냥 간단히 윈도우에서의 .exe 파일과 비슷하다고 보시면 될 것 같습니다.
ELF 포맷 구조를 보도록 하겠습니다.
그림판으로 그려서 못생긴 기분이 들지만...
대략 이렇게 생겨먹었습니다.
ELF파일은 ELF 헤더가 맨 앞에 위치하며, 프로그램 헤더 테이블과 섹션 헤더 테이블은 그 뒤에 위치합니다.
그렇다면 이제 elf 파일 형식을 직접 봐보도록 하겠습니다.
ELF Header View
ELF 파일 형식을 보기 위해서는 readelf 명령어를 이용합니다.
readelf 명령어 옵션에 대해 알아보고 싶으신 분은 아래의 링크를 참조바랍니다.
readelf - https://sourceware.org/binutils/docs/binutils/readelf.html
저희는 ELF Header를 보려고 하는 중이기 때문에
아래와 같은 옵션으로 넘겨줍니다.
-h
--file-header
Displays the information contained in the ELF header at the start of the file.
$ readelf -h ./hello
ELF 헤더는 다음과 같은 구조를 가지고 있습니다.
#define EI_NIDENT 16
typedef struct {
unsigned char e_ident[EI_NIDENT]; // Magic number and other info
Elf32_Half e_type; // Object file type
Elf32_Half e_machine; // Architecture
Elf32_Word e_version; // Object file version
Elf32_Addr e_entry; // Entry point virtual address
Elf32_Off e_phoff; // Program header table file offset
Elf32_Off e_shoff; // Section headr table file offset
Elf32_Word e_flags; // Processor-specific flags
Elf32_Half e_ehsize; // ELF header size in bytes
Elf32_Half e_phentsize; // Program header table entry size
Elf32_Half e_phnum; // Program header entry count
Elf32_Half e_shentsize; // Section header table entry size
Elf32_Half e_shnum; // Sectino header table entry count
Elf32_Half e_shstrndx; // Sectino header string table index
} Elf32_Ehdr; // x86
typedef struct {
unsigned char e_ident[EI_NIDENT]; // Magic number and other info
Elf64_Half e_type; // Object file type
Elf64_Half e_machine; // Architecture
Elf64_Word e_version; // Object file version
Elf64_Addr e_entry; // Entry point virtual address
Elf64_Off e_phoff; // Program header table file offset
Elf64_Off e_shoff; // Section headr table file offset
Elf64_Word e_flags; // Processor-specific flags
Elf64_Half e_ehsize; // ELF header size in bytes
Elf64_Half e_phentsize; // Program header table entry size
Elf64_Half e_phnum; // Program header entry count
Elf64_Half e_shentsize; // Section header table entry size
Elf64_Half e_shnum; // Sectino header table entry count
Elf64_Half e_shstrndx; // Sectino header string table index
} Elf64_Ehdr; // x86_64
참고 : http://www.sco.com/developers/gabi/2003-12-17/ch4.eheader.html
Program Header table
프로그램 헤더 테이블은 ELF헤더의 e_phoff로 지정된 오프셋에서 시작하고 e_phentsize와 e_phnum으로 정해진 크기를 갖는 테이블입니다.
프로그램 헤더 테이블의 전체 크기는 e_phnum * e_phentsize byte 입니다.
지금 현재는 프로그램 헤더 테이블을 보려는 중이니 아래와 같은 옵션으로 넘겨줍니다.
-l
--program-headers
--segments
Displays the information contained in the file's segment headers, if it has any.
$ readelf -l ./hello
프로그램 헤더는 다음과 같은 구조를 갖고 있습니다.
typedef struct {
Elf32_Word p_type; // Segment type
Elf32_Off p_offset; // Segment file offset
Elf32_Addr p_vaddr; // Segment virtual address
Elf32_Addr p_paddr; // Segment physical address
Elf32_Word p_filesz; // Segment size in file
Elf32_Word p_memsz; // Segment size in memory
Elf32_Word p_flags; // Segment flags
Elf32_Word p_align; // Segment alignment
} Elf32_Phdr; // x86
typedef struct {
Elf64_Word p_type; // Segment type
Elf64_Word p_flags; // Segment flags
Elf64_Off p_offset; // Segment file offset
Elf64_Addr p_vaddr; // Segment virtual address
Elf64_Addr p_paddr; // Segment physical address
Elf64_Xword p_filesz; // Segment size in file
Elf64_Xword p_memsz; // Segment size in memory
Elf64_Xword p_align; // Segment alignment
} Elf64_Phdr; // x86_64
세그 먼트 타입에는 다음과 같이 있습니다.
PT_NULL 0
PT_LOAD 1
PT_DYNAMIC 2
PT_INTERP 3
PT_NOTE 4
PT_SHLIB 5
PT_PHDR 6
PT_TLS 7
PT_LOOS 0x60000000
PT_HIOS 0x6fffffff
PT_LOPROC 0x70000000
PT_HIPROC 0x7fffffff
참고 : http://www.sco.com/developers/gabi/latest/ch5.pheader.html
Section header table
이 곳은 Section&Segment 오프셋이나 크기, 타입 등의 정보를 담고 있는 테이블입니다.
이 테이블은 오브젝트 파일 같이 relocation 가능한 ELF 오브젝트에는 반드시 있어야되는 녀석입니다.
지금 현재는 Section header table을 보려는 중이니 아래와 같은 옵션을 넘겨줍니다.
-S
--sections
--section-headers
Displays the information contained in the file's section headers, if it has any.
$ readelf -S ./hello
섹션 헤더는 다음과 같은 구조를 갖고 있습니다.
typedef struct {
uint32_t sh_name; // Section name
uint32_t sh_type; // Section Type
uint32_t sh_flags; // Section Flag
Elf32_Addr sh_addr; // Section virtual address
Elf32_Off sh_offset; // Section offset
uint32_t sh_size; // section size
uint32_t sh_link; // section index link
uint32_t sh_info; // extra information
uint32_t sh_addralign; // section alignment constraints
uint32_t sh_entsize; // hold a table of fixed-sized
} Elf32_Shdr; // x86
typedef struct {
uint32_t sh_name; // Section name
uint32_t sh_type; // Section Type
uint64_t sh_flags; // Section Flag
Elf64_Addr sh_addr; // Section virtual address
Elf64_Off sh_offset; // Section offset
uint64_t sh_size; // section size
uint32_t sh_link; // section index link
uint32_t sh_info; // extra information
uint64_t sh_addralign; // section alignment constraints
uint64_t sh_entsize; // hold a table of fixed-sized
} Elf64_Shdr; // x86_64
참고 : http://linux.die.net/man/5/elf
ps. 오 세상에... 여기까지 귀찮게 다 작성했더니 마지막에 섹션 헤더에 대해 구글링을 하다가 엄청난 자료를 발견하였다. 무려 한글로 된 ... 고로 첨부합니다.
이렇게 리눅스에 ELF 포맷에 대해 알아보았습니다.
그렇다면 이제 어떻게 main을 불러오는지 알아보도록 하겠습니다!
오 할렐루야 이제야...
I want go to main() !
먼저 바이너리의 엔트리 포인트를 먼저 보도록 하겠습니다.
Entry point address는 0x8048320 이라고 합니다.
이제 이 주소에 뭐가 있는지 디버깅을 하면서 찬찬히 들어가보도록 하겠습니다.
Entry point address는 _start함수의 주소였습니다.
그렇다면 프로그램이 실행되면 _start 함수가 먼저 실행이 되겠네요.
_start 함수가 실행되면 스택 프레임은 아래와 같습니다.
Stack Top |
0x804841d |
... |
esi |
ecx |
|
0x8048440 |
|
0x80484b0 |
|
edx |
|
esp | |
eax |
현재 이렇게 스택 프레임이 생겨먹었습니다.
여기서 그렇다면 궁금증이 몇가지가 생기셨을꺼라 생각됩니다.
뭐 저만 생긴 걸 수도 있지만요.
- 스택에 push된 저 값들은 무엇인가요?
- _start에서 0x8048310을 호출하는데 뭐지?
스택에 push된 저 값들은 무엇인가요?
먼저 스택에 push된 값들은 아래와 같습니다.
0x80484b0, 0x8048440, 0x804841d
0x804841d : main() 함수의 주소
0x8048440 : __libc_csu_init() 함수 주소
0x80484b0 : ??
들이라는 것을 알 수 있었습니다.
_start에서 0x8048310를 호출하는데 뭐지??
음 뭐 사실 뭐하는 녀석인지는 옆에 함수 명이 보이기 때문에 크게 생각하실 것은 없습니다.
0x0804833c <_start+28>: call 0x8048310 <__libc_start_main@plt>
plt를 호출하는 이유는 __libc_start_main를 처음 호출하는 것이라 바로 호출하지 않고 plt에서 got를 다이나믹 링킹하고 호출을 합니다.
아니 그러면 __libc_start_main은 뭘까요?
깊숙히 들어가보도록 하겠습니다.
- __libc_start_main은 뭔가요?
__libc_start_main은 뭔가요?
음...
현재 이 흐름은 libc의 손에 달려있습니다...!
__libc_start_main함수는 libc.so.6 안에 있거든요!
glibc 소스 코드에서 __libc_start_main를 발견할 수 있습니다.
코드는 아래와 같습니다!
int __cdecl __libc_start_main(int (__cdecl *main)(int, char **, char **),
int argc,
char **ubp_av, void (*init)(void),
void (*fini)(void),
void (*rtld_fini)(void),
void *stack_end)
__libc_csu_init 호출
_init 호출
호로록
이 부분에선 gmon 프로파일링 시스템 초기화가 필요하다면 gmon_start를 호출하여 초기화합니다.
그 뒤에 다시 __libc_start_main으로 돌아오고 저희가 만든 main을 호출하게 됩니다.
이상입니다.
뭔가 덤성덤성 뛰어넘은 부분이 많지만 ...
도움이 되었으면 좋겠습니다!
길고 긴 글을 읽어주셔서 감사합니다.
'0x20 Security > 0x21 System' 카테고리의 다른 글
About LD_PRELOAD (0) | 2016.07.20 |
---|---|
Use After Free (UAF) (1) | 2016.07.13 |
System Hacking :: Memory Protection (0) | 2016.06.28 |
[SSA] 시스템 해킹 스터디 6주차 (349) | 2015.11.09 |
[SSA] 시스템 해킹 스터디 5주차 (155) | 2015.11.05 |