introduction of ELF sections
이번글에서는 ELF 파일들의 각 섹션에 대해서 자세히 분석을 해보겠다.
(pwn) amemoyoi ~$ objdump -h test
test: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .interp 0000001c 0000000000000318 0000000000000318 00000318 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .note.gnu.property 00000030 0000000000000338 0000000000000338 00000338 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .note.gnu.build-id 00000024 0000000000000368 0000000000000368 00000368 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .note.ABI-tag 00000020 000000000000038c 000000000000038c 0000038c 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .gnu.hash 00000024 00000000000003b0 00000000000003b0 000003b0 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
5 .dynsym 000000a8 00000000000003d8 00000000000003d8 000003d8 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
6 .dynstr 0000008d 0000000000000480 0000000000000480 00000480 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
7 .gnu.version 0000000e 000000000000050e 000000000000050e 0000050e 2**1
CONTENTS, ALLOC, LOAD, READONLY, DATA
8 .gnu.version_r 00000030 0000000000000520 0000000000000520 00000520 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
9 .rela.dyn 000000c0 0000000000000550 0000000000000550 00000550 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
10 .rela.plt 00000018 0000000000000610 0000000000000610 00000610 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
11 .init 0000001b 0000000000001000 0000000000001000 00001000 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
12 .plt 00000020 0000000000001020 0000000000001020 00001020 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
13 .plt.got 00000010 0000000000001040 0000000000001040 00001040 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
14 .plt.sec 00000010 0000000000001050 0000000000001050 00001050 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
15 .text 00000107 0000000000001060 0000000000001060 00001060 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
16 .fini 0000000d 0000000000001168 0000000000001168 00001168 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
17 .rodata 00000011 0000000000002000 0000000000002000 00002000 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
18 .eh_frame_hdr 00000034 0000000000002014 0000000000002014 00002014 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
19 .eh_frame 000000ac 0000000000002048 0000000000002048 00002048 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
20 .init_array 00000008 0000000000003db8 0000000000003db8 00002db8 2**3
CONTENTS, ALLOC, LOAD, DATA
21 .fini_array 00000008 0000000000003dc0 0000000000003dc0 00002dc0 2**3
CONTENTS, ALLOC, LOAD, DATA
22 .dynamic 000001f0 0000000000003dc8 0000000000003dc8 00002dc8 2**3
CONTENTS, ALLOC, LOAD, DATA
23 .got 00000048 0000000000003fb8 0000000000003fb8 00002fb8 2**3
CONTENTS, ALLOC, LOAD, DATA
24 .data 00000010 0000000000004000 0000000000004000 00003000 2**3
CONTENTS, ALLOC, LOAD, DATA
25 .bss 00000008 0000000000004010 0000000000004010 00003010 2**0
ALLOC
26 .comment 0000002b 0000000000000000 0000000000000000 00003010 2**0
CONTENTS, READONLY우리가 일반적으로 objdump를 하면 나오는 섹션들이다.
디버깅 심볼을 붙이면
27 .debug_aranges 00000030 0000000000000000 0000000000000000 0000303b 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
28 .debug_info 0000008c 0000000000000000 0000000000000000 0000306b 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
29 .debug_abbrev 00000043 0000000000000000 0000000000000000 000030f7 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
30 .debug_line 00000052 0000000000000000 0000000000000000 0000313a 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
31 .debug_str 000000d9 0000000000000000 0000000000000000 0000318c 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
32 .debug_line_str 00000020 0000000000000000 0000000000000000 00003265 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS다음과 같은 섹션들이 추가된다.
바로가기 링크들
5 .dynsym
이 섹션은 동적으로 참조되는 심볼들의 테이블 이다. Elf64_Sym 구조체들의 나열로 되어 있다.
즉, 한개의 심볼이 한개의 구조체로 되어있다는 뜻
https://elixir.bootlin.com/glibc/glibc-2.27/source/elf/elf.h#L536
typedef struct
{
Elf64_Word st_name; /* Symbol name (string tbl index) */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf64_Section st_shndx; /* Section index */
Elf64_Addr st_value; /* Symbol value */
Elf64_Xword st_size; /* Symbol size */
} Elf64_Sym;다음과 같다. readelf 명령어로 읽어보면
(pwn) amemoyoi ~$ readelf -s Notepad
Symbol table '.dynsym' contains 18 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [...]@GLIBC_2.2.5 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [...]@GLIBC_2.2.5 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [...]@GLIBC_2.2.5 (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [...]@GLIBC_2.2.5 (2)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND read@GLIBC_2.2.5 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [...]@GLIBC_2.2.5 (2)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND ftell@GLIBC_2.2.5 (2)
9: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
10: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fseek@GLIBC_2.2.5 (2)
11: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [...]@GLIBC_2.2.5 (2)
12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fopen@GLIBC_2.2.5 (2)
13: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [...]@GLIBC_2.2.5 (2)
14: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [...]@GLIBC_2.2.5 (2)
15: 00000000006020a0 8 OBJECT GLOBAL DEFAULT 26 [...]@GLIBC_2.2.5 (2)
16: 00000000006020b0 8 OBJECT GLOBAL DEFAULT 26 stdin@GLIBC_2.2.5 (2)
17: 00000000006020c0 8 OBJECT GLOBAL DEFAULT 26 [...]@GLIBC_2.2.5 (2)
다음과 같이 동적심볼에 대한 정보들이 나오는 것을 알 수 있다.
필드 중 가장 중요한 st_name 값은 .dynstr base 위치로 부터의 심볼 str이 있는 위치까지의 오프셋을 가지고 있다.
얘를 들어 offset 값이 0x1f면
pwndbg> x/s 0x000000000400478 + 0x1f
0x400497: "puts"0x000000000400478 .dynstr 위치에서 0x1f 에는 다음과 같이 함수의 심볼str이 있다.
각 필드에 대한 설명은 다음과 같다.
| st_name | .dynstr에서 이름 문자열 위치 | 0x20 → "puts" |
| st_info | 바인딩 & 타입 (4비트씩) | STB_GLOBAL + STT_FUNC |
| st_other | 가시성 | STV_DEFAULT, STV_HIDDEN |
| st_shndx | 어떤 섹션에 정의되었는지 | .text, SHN_UNDEF |
| st_value | 심볼 주소/오프셋 | 함수 주소, 변수 주소 |
| st_size | 크기 (바이트) | 함수의 길이, 변수 크기 |
각 필드의 크기는 다음과 같다.
| st_name | Elf64_Word | 4 |
| st_info | unsigned char | 1 |
| st_other | unsigned char | 1 |
| st_shndx | Elf64_Section | 2 |
| st_value | Elf64_Addr | 8 |
| st_size | Elf64_Xword | 8 |
| 합계 | 24 |
근데 헷갈리면 안되는 것이 .dynsym 은 일종의 api 같은 것이다. 우리의 바이너리는 libc의 심볼을 사용하기 위해 .dynsym 에 심볼들을 내놓았고 그에 반해 반대로 libc는 자신의 함수들을 사용하라고 .dynsym 에 심볼들을 내놓는다.
6 .dynstr
5 .dynsym 와 연관된 섹션으로 심볼에 대한 문자열들이 저장 되어 있다. 이 문자열을 통해 relocation 항목들이 resolve 된다.
gdb 상으로 보면
pwndbg> x/10s 0x000000000400478
0x400478: ""
0x400479: "libc.so.6"
0x400483: "sprintf"
0x40048b: "fopen"
0x400491: "ftell"
0x400497: "puts"
0x40049c: "stdin"
0x4004a2: "strstr"
0x4004a9: "fseek"
0x4004af: "read"다음과 같이 문자열들이 많은 것을 볼 수 있다.
9 , 10 .rela.dyn .rela.plt
got와 같은 재배치가 필요한 영역이 존재하며, dynsym 와 같은 심볼들의 정보에 대한 테이블이 존재한다. 그럼 이 둘을 이을 존재가 있어야 한다. 어떤 got가 어떤 symdol하고 연관 되어 있는지. 이런 역할을 하는 곳이 이 .rela.dyn .rela.plt 둘이다.
익숙하겠지만, .got .got.plt 와 대응한다고 보면된다.
.got 영역에 대한 이을 재배치 정보가 .rela.dyn
.got.plt 영역에 대한 재배치 정보는 .rela.plt 이다.
여기는 Elf64_Rela 구조체의 나열이다.
https://elixir.bootlin.com/glibc/glibc-2.27/source/elf/elf.h#L664
typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
Elf64_Sxword r_addend; /* Addend */
} Elf64_Rela;한번 직접 gdb로 봐보자면
9 .rela.plt 00000138 00000000004005b8 00000000004005b8 000005b8 23 CONTENTS, ALLOC, LOAD, READONLY, DATA
다음과 같을때 00000000004005b8 이 .rela.plt의 시작지점이 된다.
pwndbg> x/3gx 0x00000000004005b8
0x4005b8: 0x0000000000602018 0x0000000100000007
0x4005c8: 0x0000000000000000따라서 한번 구조체를 보자면
0x0000000000602018 이 r_offset
0x0000000100000007 이 r_info
0x0000000000000000 이 r_addend
처럼 된다.
pwndbg> info symbol 0x0000000000602018
remove@got[plt] in section .got.plt of /home/amemoyoi/1day_2pwn/Notepadr_offset은 말 그대로 재배치를 해야하는 주소를 담고 있다.
#define ELF64_R_INFO(sym,type) ((((Elf64_Xword) (sym)) << 32) + (type))r_info 는 상위 4바이트가 재배치함수에 대한 인덱스를 의미한다. 자세한 내용은 아래 블록링크 참조
그리고 하위 4바이트는 R_X86_64_JUMP_SLOT 와 같은 타입을 의미한다.
마지막으로 r_addend은
*r_offset = resolve(sym) + r_addend
다음과 같이 resolve한 값에 실제 추가값을 더해서 더 정밀하게 하기 위해서 사용된다.
위에 gdb상에서도 0이 듯이 주로 0 값이다.
12 .plt
상당히 중요한 섹션이다. 우리가 libc와 라이브러리등 외부의 코드를 사용할때 거쳐야 하는 징검다리 역할을 하며, got 섹션하고 밀접한 연관이 있다. 또한 lazy binding 을 통해 바로 함수의 주소를 아는 것이 아닌 특정한 과정을 거쳐서 외부 함수의 주소를 알아낸다.
pwndbg> disass 0x555555555030
Dump of assembler code for function printf@plt:
0x0000555555555030 <+0>: jmp *0x2fca(%rip) # 0x555555558000 <printf@got.plt>
0x0000555555555036 <+6>: push $0x0
0x000055555555503b <+11>: jmp 0x555555555020기본적인 형태는 다음과 같다.
자세한 함수 위치를 가져오는 과정은 다음 글의 정리해놓았다.
13 .plt.got
plt 영역과의 차이점
얘내는 12번의 plt 영역과 유사하지만 많은 차이를 가진다.
https://stackoverflow.com/questions/58076539/plt-plt-got-what-is-different
해당 사이트를 참고해서 분석했다.
.plt 영역은 함수를 호출할때 그 주소값을 got에 가져온뒤 점프하는 lazy binding 을 진행한다.
하지만, 모든 함수가 그렇지는 않다. 특정한 이유로 non-lazy binding 을 해야한다면 .plt.got 영역이 사용된다.

다음은 내가 ida 직접 본 .plt.got 섹션인데 다음과 같이 바로
jmp cs:__cxa_finalize_ptr한줄만 존재한다. 즉, 이미 값이 해당 영역에 넣어져 있음을 가정하고 바로 점프하는 부분이다.
22 .dynamic
(pwn) amemoyoi ~$ readelf -d ./Notepad
Dynamic section at offset 0x1e28 contains 24 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000c (INIT) 0x4006f0
0x000000000000000d (FINI) 0x400c84
0x0000000000000019 (INIT_ARRAY) 0x601e10
0x000000000000001b (INIT_ARRAYSZ) 8 (bytes)
0x000000000000001a (FINI_ARRAY) 0x601e18
0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x400298
0x0000000000000005 (STRTAB) 0x400478
0x0000000000000006 (SYMTAB) 0x4002c8
0x000000000000000a (STRSZ) 155 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000015 (DEBUG) 0x0
0x0000000000000003 (PLTGOT) 0x602000
0x0000000000000002 (PLTRELSZ) 312 (bytes)
0x0000000000000014 (PLTREL) RELA
0x0000000000000017 (JMPREL) 0x4005b8
0x0000000000000007 (RELA) 0x400558
0x0000000000000008 (RELASZ) 96 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000006ffffffe (VERNEED) 0x400538
0x000000006fffffff (VERNEEDNUM) 1
0x000000006ffffff0 (VERSYM) 0x400514
0x0000000000000000 (NULL) 0x0
다음과 같이 readelf 명령어로 값을 해석할 수 있다. 딱봐도 알겠지만 이 섹션은 로더가 elf에 대해 일을 수행하기 위해 필요한 영역이며 각각의 필요한 값들이 정리 되어 있다. 예를 들어 영역의 위치라던가
.dynamic 섹션은 Elf64_Dyn 구조체로 되어 있다.
typedef struct {
Elf64_Xword d_tag;
union {
Elf64_Xword d_val;
Elf64_Addr d_ptr;
} d_un;
} Elf64_Dyn;해당 구조체는 무슨 정보를 갖고 있는지를 나타내는 d_tag 와 그에대한 값 d_val 이나 주소 d_ptr를 가지고 있다.
(pwn) amemoyoi ~$ objdump -s -j .dynamic ./Notepad
./Notepad: file format elf64-x86-64
Contents of section .dynamic:
601e28 01000000 00000000 01000000 00000000 ................
601e38 0c000000 00000000 f0064000 00000000 ..........@.....
601e48 0d000000 00000000 840c4000 00000000 ..........@.....
601e58 19000000 00000000 101e6000 00000000 ..........`.....
601e68 1b000000 00000000 08000000 00000000 ................
601e78 1a000000 00000000 181e6000 00000000 ..........`.....
601e88 1c000000 00000000 08000000 00000000 ................
601e98 f5feff6f 00000000 98024000 00000000 ...o......@.....
601ea8 05000000 00000000 78044000 00000000 ........x.@.....
601eb8 06000000 00000000 c8024000 00000000 ..........@.....
601ec8 0a000000 00000000 9b000000 00000000 ................
601ed8 0b000000 00000000 18000000 00000000 ................
601ee8 15000000 00000000 00000000 00000000 ................
601ef8 03000000 00000000 00206000 00000000 ......... `.....
601f08 02000000 00000000 38010000 00000000 ........8.......
601f18 14000000 00000000 07000000 00000000 ................
601f28 17000000 00000000 b8054000 00000000 ..........@.....
601f38 07000000 00000000 58054000 00000000 ........X.@.....
601f48 08000000 00000000 60000000 00000000 ........`.......
601f58 09000000 00000000 18000000 00000000 ................
601f68 feffff6f 00000000 38054000 00000000 ...o....8.@.....
601f78 ffffff6f 00000000 01000000 00000000 ...o............
601f88 f0ffff6f 00000000 14054000 00000000 ...o......@.....
601f98 00000000 00000000 00000000 00000000 ................
601fa8 00000000 00000000 00000000 00000000 ................
601fb8 00000000 00000000 00000000 00000000 ................
601fc8 00000000 00000000 00000000 00000000 ................
601fd8 00000000 00000000 00000000 00000000 ................
601fe8 00000000 00000000 00000000 00000000 ................
헥스로 보았을때도 앞에 8바이트가 테그고 그 뒤 8바이트가 값임을 알 수 있다.
23 .got
먼저 got는 GLOBAL_OFFSET_TALBE 의 약자로 로더가 재배치할 항목들의 주소들을 가지는 테이블이다.
좀더 자세히 파보면 .got 와 .got.plt가 존재한다.
우리가 흔지 아는 got는 .got.plt 를 말한다.
이 둘의 차이점은 다음 글에서 다룬다.
got 영역에 존재할 항목들을 보고 싶다면,
(pwn) amemoyoi ~$ objdump -R Notepad다음과 같은 명령어로 확인할 수 있다. R_X86_64_JUMP_SLOT 타입이 .got.plt 속하고
R_X86_64_GLOB_DAT 타입들이 .got 에 속한다.
got는 다음과 같이 이루어져 있다.
테이블 구조
// 이하 0x10은 .got section
0x600ff0: 0x00007ffff7a03b10 // & __libc_start_main()
0x600ff8: 0x0000000000000000
// 이하 0x38은 .got.plt section
0x601000: 0x0000000000600e20 // & .dynamic section
0x601008: 0x00007ffff7ffe170 // & link_map
0x601010: 0x00007ffff7dea8f0 // & _dl_runtime_resolve_xsavec()
0x601018: ?????????????????? // 우리가 아는 got 시작
0x601020: ??????????????????
0x601028: ??????????????????
0x601030: ??????????????????근데 여기서 항상 .got.plt section 제외하고 .got 섹션은 항상 유동적으로 바뀐다. 가끔은
.got.plt section 뒤에 위치하기도 한다. 따라서 유동적으로 생각을 해주었으면 좋겠다.