introduction of ELF sections

이번글에서는 ELF 파일들의 각 섹션에 대해서 자세히 분석을 해보겠다.

C
(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를 하면 나오는 섹션들이다.

디버깅 심볼을 붙이면

C
 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

C
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 명령어로 읽어보면

C
(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면

C
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_nameElf64_Word4
st_infounsigned char1
st_otherunsigned char1
st_shndxElf64_Section2
st_valueElf64_Addr8
st_sizeElf64_Xword8
합계24

근데 헷갈리면 안되는 것이 .dynsym 은 일종의 api 같은 것이다. 우리의 바이너리는 libc의 심볼을 사용하기 위해 .dynsym 에 심볼들을 내놓았고 그에 반해 반대로 libc는 자신의 함수들을 사용하라고 .dynsym 에 심볼들을 내놓는다.

6 .dynstr

5 .dynsym 와 연관된 섹션으로 심볼에 대한 문자열들이 저장 되어 있다. 이 문자열을 통해 relocation 항목들이 resolve 된다.

gdb 상으로 보면

C
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

C
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의 시작지점이 된다.

C
pwndbg> x/3gx 0x00000000004005b8
0x4005b8:	0x0000000000602018	0x0000000100000007
0x4005c8:	0x0000000000000000

따라서 한번 구조체를 보자면

0x0000000000602018 이 r_offset

0x0000000100000007 이 r_info

0x0000000000000000 이 r_addend

처럼 된다.

C
pwndbg> info symbol 0x0000000000602018
remove@got[plt] in section .got.plt of /home/amemoyoi/1day_2pwn/Notepad

r_offset은 말 그대로 재배치를 해야하는 주소를 담고 있다.

C
#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 을 통해 바로 함수의 주소를 아는 것이 아닌 특정한 과정을 거쳐서 외부 함수의 주소를 알아낸다.

C
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 섹션인데 다음과 같이 바로

C
jmp     cs:__cxa_finalize_ptr

한줄만 존재한다. 즉, 이미 값이 해당 영역에 넣어져 있음을 가정하고 바로 점프하는 부분이다.

22 .dynamic

C
(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 구조체로 되어 있다.

C
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를 가지고 있다.

C
(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 영역에 존재할 항목들을 보고 싶다면,

C
(pwn) amemoyoi ~$ objdump -R Notepad

다음과 같은 명령어로 확인할 수 있다. R_X86_64_JUMP_SLOT 타입이 .got.plt 속하고

R_X86_64_GLOB_DAT 타입들이 .got 에 속한다.

got는 다음과 같이 이루어져 있다.

테이블 구조

C
// 이하 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 뒤에 위치하기도 한다. 따라서 유동적으로 생각을 해주었으면 좋겠다.