readelf-lWlnstatElffiletypeisDYN (Position-Independent Executablefile)Entrypoint0x1c00Thereare9programheaders,startingatoffset64ProgramHeaders:TypeOffsetVirtAddrPhysAddrFileSizMemSizFlgAlignPHDR0x0000400x00000000000000400x00000000000000400x0001f80x0001f8R0x8INTERP0x0002380x00000000000002380x00000000000002380x00001b0x00001bR0x1[Requesting program interpreter: /lib/ld-linux-aarch64.so.1]LOAD0x0000000x00000000000000000x00000000000000000x003f7c0x003f7cRE0x10000LOAD0x00fc480x000000000001fc480x000000000001fc480x0005280x001190RW0x10000DYNAMIC0x00fc580x000000000001fc580x000000000001fc580x0002000x000200RW0x8NOTE0x0002540x00000000000002540x00000000000002540x0000e00x0000e0R0x4GNU_EH_FRAME0x0036100x00000000000036100x00000000000036100x0001b40x0001b4R0x4GNU_STACK0x0000000x00000000000000000x00000000000000000x0000000x000000RW0x10GNU_RELRO0x00fc480x000000000001fc480x000000000001fc480x0003b80x0003b8R0x1SectiontoSegmentmapping:SegmentSections...0001.interp02.interp.note.gnu.build-id.note.ABI-tag.note.package.gnu.hash.dynsym.dynstr.gnu.version.gnu.version_r.rela.dyn.rela.plt.init.plt.text.fini.rodata.eh_frame_hdr.eh_frame03.init_array.fini_array.dynamic.got.data.bss04.dynamic05.note.gnu.build-id.note.ABI-tag.note.package06.eh_frame_hdr0708.init_array.fini_array.dynamic.got
The previous program has 9 program headers, then, the segment mapping indicates in which program header (from 00 to 08) each section is located.
PHDR - Program HeaDeR
Contains the program header tables and metadata itself.
INTERP
Indicates the path of the loader to use to load the binary into memory.
LOAD
These headers are used to indicate how to load a binary into memory.
Each LOAD header indicates a region of memory (size, permissions and alignment) and indicates the bytes of the ELF binary to copy in there.
For example, the second one has a size of 0x1190, should be located at 0x1fc48 with permissions read and write and will be filled with 0x528 from the offset 0xfc48 (it doesn't fill all the reserved space). This memory will contain the sections .init_array .fini_array .dynamic .got .data .bss.
DYNAMIC
This header helps to link programs to their library dependencies and apply relocations. Check the .dynamic section.
NOTE
This stores vendor metadata information about the binary.
GNU_EH_FRAME
Defines the location of the stack unwind tables, used by debuggers and C++ exception handling-runtime functions.
GNU_STACK
Contains the configuration of the stack execution prevention defense. If enabled, the binary won't be able to execute code from the stack.
GNU_RELRO
Indicates the RELRO (Relocation Read-Only) configuration of the binary. This protection will mark as read-only certain sections of the memory (like the GOT or the init and fini tables) after the program has loaded and before it begins running.
In the previous example it's copying 0x3b8 bytes to 0x1fc48 as read-only affecting the sections .init_array .fini_array .dynamic .got .data .bss.
Note that RELRO can be partial or full, the partial version do not protect the section .plt.got, which is used for lazy binding and needs this memory space to have write permissions to write the address of the libraries the first time their location is searched.
TLS
Defines a table of TLS entries, which stores info about thread-local variables.
Section Headers
Section headers gives a more detailed view of the ELF binary
It also indicates the location, offset, permissions but also the type of data it section has.
Meta Sections
String table: ELF 파일에 필요한 모든 문자열을 포함하고 있습니다(하지만 프로그램에서 실제로 사용되는 문자열은 아닙니다). 예를 들어, .text 또는 .data와 같은 섹션 이름을 포함합니다. 그리고 문자열 테이블에서 .text가 45의 오프셋에 있다면 name 필드에서 숫자 45를 사용합니다.
문자열 테이블이 어디에 있는지 찾기 위해 ELF는 문자열 테이블에 대한 포인터를 포함합니다.
Symbol table: 이름(문자열 테이블의 오프셋), 주소, 크기 및 기호에 대한 더 많은 메타데이터와 같은 기호에 대한 정보를 포함합니다.
Main Sections
.text: 실행할 프로그램의 명령어입니다.
.data: 프로그램에서 정의된 값을 가진 전역 변수입니다.
.bss: 초기화되지 않은 전역 변수(또는 0으로 초기화됨). 여기의 변수는 자동으로 0으로 초기화되므로 이진 파일에 쓸모없는 0이 추가되는 것을 방지합니다.
.rodata: 상수 전역 변수(읽기 전용 섹션)입니다.
.tdata 및 .tbss: 스레드 로컬 변수가 사용될 때 .data 및 .bss와 같습니다(__thread_local in C++ 또는 __thread in C).
.dynamic: 아래를 참조하십시오.
Symbols
Symbols는 프로그램 내의 명명된 위치로, 함수, 전역 데이터 객체, 스레드 로컬 변수 등이 될 수 있습니다.
readelf -s lnstat
Symbol table '.dynsym' contains 49 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000001088 0 SECTION LOCAL DEFAULT 12 .init
2: 0000000000020000 0 SECTION LOCAL DEFAULT 23 .data
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strtok@GLIBC_2.17 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND s[...]@GLIBC_2.17 (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strlen@GLIBC_2.17 (2)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fputs@GLIBC_2.17 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.17 (2)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _[...]@GLIBC_2.34 (3)
9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND perror@GLIBC_2.17 (2)
10: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...]
11: 0000000000000000 0 FUNC WEAK DEFAULT UND _[...]@GLIBC_2.17 (2)
12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND putc@GLIBC_2.17 (2)
[...]
각 심볼 항목은 다음을 포함합니다:
이름
바인딩 속성 (약한, 로컬 또는 전역): 로컬 심볼은 프로그램 자체에서만 접근할 수 있으며, 전역 심볼은 프로그램 외부에서 공유됩니다. 약한 객체는 예를 들어 다른 함수에 의해 재정의될 수 있는 함수입니다.
유형: NOTYPE (유형이 지정되지 않음), OBJECT (전역 데이터 변수), FUNC (함수), SECTION (섹션), FILE (디버거용 소스 코드 파일), TLS (스레드 로컬 변수), GNU_IFUNC (재배치를 위한 간접 함수)
프로그램이 선호하는 주소(보통 0x400000)와 다른 위치에 로드되면(주소가 이미 사용 중이거나 ASLR 또는 기타 이유로), 정적 재배치는 포인터를 수정하여 이진 파일이 선호하는 주소에 로드될 것으로 예상했던 값을 수정합니다.
예를 들어, R_AARCH64_RELATIV 유형의 섹션은 재배치 편향과 추가 값의 합으로 주소를 수정해야 합니다.
동적 재배치 및 GOT
재배치는 외부 기호(종속성의 함수와 같은)를 참조할 수도 있습니다. 예를 들어, libC의 malloc 함수입니다. 그런 다음 로더는 libC를 로드할 때 malloc 함수가 로드된 주소를 확인하고, 이 주소를 GOT(전역 오프셋 테이블) 테이블(재배치 테이블에 표시됨)에 기록합니다. 여기서 malloc의 주소가 지정되어야 합니다.
프로시저 링크 테이블
PLT 섹션은 지연 바인딩을 수행할 수 있게 해주며, 이는 함수의 위치 해석이 처음 접근할 때 수행된다는 것을 의미합니다.
따라서 프로그램이 malloc을 호출할 때, 실제로는 PLT의 malloc에 해당하는 위치(malloc@plt)를 호출합니다. 처음 호출될 때 malloc의 주소를 해석하고 저장하므로 다음에 malloc이 호출될 때는 PLT 코드 대신 그 주소가 사용됩니다.
프로그램 초기화
프로그램이 로드된 후 실행할 시간입니다. 그러나 실행되는 첫 번째 코드는 항상 main 함수가 아닙니다. 예를 들어 C++에서 전역 변수가 클래스의 객체인 경우, 이 객체는 main이 실행되기 전에초기화되어야 합니다.
이 전역 변수들은 .data 또는 .bss에 위치하지만, __CTOR_LIST__와 __DTOR_LIST__ 목록에서는 초기화 및 소멸할 객체들이 추적을 위해 저장됩니다.
C 코드에서는 GNU 확장을 사용하여 동일한 결과를 얻을 수 있습니다:
__attributte__((constructor)) //Add a constructor to execute before__attributte__((destructor)) //Add to the destructor list
From a compiler perspective, to execute these actions before and after the main function is executed, it's possible to create a init function and a fini function which would be referenced in the dynamic section as INIT and FIN. and are placed in the init and fini sections of the ELF.
컴파일러 관점에서, main 함수가 실행되기 전과 후에 이러한 작업을 실행하기 위해 init 함수와 fini 함수를 생성할 수 있으며, 이는 동적 섹션에서 INIT 및 **FIN**으로 참조됩니다. 그리고 ELF의 init 및 fini 섹션에 배치됩니다.
The other option, as mentioned, is to reference the lists __CTOR_LIST__ and __DTOR_LIST__ in the INIT_ARRAY and FINI_ARRAY entries in the dynamic section and the length of these are indicated by INIT_ARRAYSZ and FINI_ARRAYSZ. Each entry is a function pointer that will be called without arguments.
언급된 다른 옵션은 동적 섹션의 INIT_ARRAY 및 FINI_ARRAY 항목에서 __CTOR_LIST__ 및 __DTOR_LIST__ 목록을 참조하는 것이며, 이들의 길이는 INIT_ARRAYSZ 및 **FINI_ARRAYSZ**로 표시됩니다. 각 항목은 인수 없이 호출될 함수 포인터입니다.
Moreover, it's also possible to have a PREINIT_ARRAY with pointers that will be executed before the INIT_ARRAY pointers.
또한 INIT_ARRAY 포인터보다 먼저 실행될 포인터가 있는 **PREINIT_ARRAY**를 가질 수도 있습니다.
Initialization Order
The program is loaded into memory, static global variables are initialized in .data and unitialized ones zeroed in .bss.
All dependencies for the program or libraries are initialized and the the dynamic linking is executed.
PREINIT_ARRAY functions are executed.
INIT_ARRAY functions are executed.
If there is a INIT entry it's called.
If a library, dlopen ends here, if a program, it's time to call the real entry point (main function).
초기화 순서
프로그램이 메모리에 로드되고, 정적 전역 변수가 **.data**에서 초기화되며, 초기화되지 않은 변수는 **.bss**에서 0으로 설정됩니다.
프로그램 또는 라이브러리에 대한 모든 종속성이 초기화되고 동적 링크가 실행됩니다.
PREINIT_ARRAY 함수가 실행됩니다.
INIT_ARRAY 함수가 실행됩니다.
INIT 항목이 있으면 호출됩니다.
라이브러리인 경우, dlopen은 여기서 종료되고, 프로그램인 경우 실제 진입점(main 함수)을 호출할 시간입니다.
Thread-Local Storage (TLS)
They are defined using the keyword __thread_local in C++ or the GNU extension __thread.
__thread_local 키워드를 사용하여 C++에서 정의되거나 GNU 확장 **__thread**를 사용합니다.
Each thread will maintain a unique location for this variable so only the thread can access its variable.
각 스레드는 이 변수에 대해 고유한 위치를 유지하므로 오직 해당 스레드만 자신의 변수를 접근할 수 있습니다.
When this is used the sections .tdata and .tbss are used in the ELF. Which are like .data (initialized) and .bss (not initialized) but for TLS.
이것이 사용되면 ELF에서 .tdata 및 .tbss 섹션이 사용됩니다. 이는 .data(초기화됨) 및 .bss(초기화되지 않음)와 유사하지만 TLS에 해당합니다.
Each variable will hace an entry in the TLS header specifying the size and the TLS offset, which is the offset it will use in the thread's local data area.
각 변수는 TLS 헤더에 크기와 TLS 오프셋을 지정하는 항목을 가지며, 이는 스레드의 로컬 데이터 영역에서 사용할 오프셋입니다.
The __TLS_MODULE_BASE is a symbol used to refer to the base address of the thread local storage and points to the area in memory that contains all the thread-local data of a module.
__TLS_MODULE_BASE는 스레드 로컬 저장소의 기본 주소를 참조하는 데 사용되는 기호이며, 모듈의 모든 스레드 로컬 데이터를 포함하는 메모리 영역을 가리킵니다.