Collect the notes of ELF learning and write them into a blog, which will be improved over time

Think about:

  1. What does it really mean for links when you define a variable or function that has a static attribute?
  2. When an SO is first loaded, what is the loading process like?
  3. For an SO, it defines some global variables. The code of a process linked to this SO modifies the values of these global variables without affecting other processes linked to this SO. How to achieve this?
  4. Libfoo. so is linked to libfoo.so at compile time and calls its add function. If libfoo.so is dynamically linked to loadLibrary,dlopen, and dlsym at runtime instead of at compile time, does the program still have A global offset table? If not, what is the logic for positionindependent code?

Compilation basis:

A hello.c file goes through the process of compiling into an executable:

  1. Pretreatment stage:

Hello. C is first converted into a file, usually an. I, by the preprocessor CPP program, which modiates the original C program based on commands beginning with the character #. C -o hello. I or GCC -e hello.c -o hello. I

  1. Compilation stage:

Hello. I is converted by the compiler to a hello.s text file, which is an assembly language program, assuming GCC is version 4.8.5: /usr/lib/ GCC /x86_64-linux-gnu/4.8.5/cc1 hello. I or GCC -s hello. I -o hello. S Extern functions add and printf are used in hello.c, and the generated assembler will generate the corresponding “call add” and “call printf” directives, but the add and printf directives need to be linked.

  1. Assembly stage:

The assembler as translates hello.s into machine language instructions: as hello.s -o hello.o or GCC -c hello.s -o hello.o

O is in the format of relocatable Object Program. On my machine, it will be printed out in the format of hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

  1. Link stage:

Because hello.c calls the printf function, which exists in an object file called printf.o, this file must somehow be merged into the hello.o program and the executable is generated, which is done by calling ld. Linking mainly includes the steps of address and space allocation, symbol resolution and relocation. It can be executed at compile time (statically linked), load time (dynamically linked), or even run time by the application. The command is as follows: Ld [System Object Files and args] hello.o Note that the compiler and assembler generate code and data sections starting at address 0, and the linker modifs all references to these symbols by associating each symbol definition with a memory location, Redirect the sections by pointing them to the memory location.

GCC -v hello.c: /usr/lib/gcc/x86_64-linux-gnu/4.8/collect2: /usr/lib/gcc/x86_64-linux-gnu/4.8/collect2: /usr/lib/gcc/x86_64-linux-gnu/4.8/collect2 It calls the LD linker to complete the link to the object file, and then does some processing on the link result, mainly collecting all the information related to the program initialization and constructing the initialization structure. It can be seen that at least the following link to process some libraries and object files are linked in: crt1. O, crti. O, crtbegin. CRTN. O, o, crtend. O


There are three forms of object files:

Relocatable target files: various. O files, for example. ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

Executable object file: can be copied directly to storage for execution. ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, For the GNU/Linux 2.6.32, BuildID [sha1] = d123665a1af8a2d4c17ecda841072aac01e434f9, not stripped

Shared object file SO: A special relocatable object file that can be dynamically loaded into storage and linked at load or run time. ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, BuildID[sha1]=799a037243ce66d67e34983bef8928c90457dbf4, stripped

The object file not only contains the machine instruction code and data, but also includes some information needed for linking, such as symbol table, debugging information, string, etc. For the bare board program, the generated executable file needs to keep only the instruction code and data.


Format of the target file:

Since the object file participates in the linking and execution processes, it provides different views of the same file content to illustrate this, divided into the linking view and the execution view, as follows:

The order of the sections shown in the figure above is not absolute, only the ELF header is fixed at the beginning of the file, and the locations of the other sections may vary depending on the file.

The ELF header is at the head of the file and describes the organization of the entire file. Section: Text Section, data Section, symtab Section, etc. The Section Header table contains information that describes the sections in the file. Each Section in the table has an entry(also called the Section header). Each entry gives information such as Section names, Section size and so on. The linked object file must have a Section header table. Other object files may or may not have a Section header table. Program Header Table tells the system how to create an image for the process. The target file used for execution must have Program Header Table. Multiple Section sections are merged into one segment during loading, as in: .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt.plt.got.text.fini.rodata.eh_frame_hdr. eh_frame belong to the text segment of the executable file. Init_array.fini_array.jcr.dynamic.got.got.plt.data.bss belong to the data section of the executable file.

Elf-related data structures are in the /usr/include/linux/elf.h file and are defined as follows:

typedef __u32 Elf32_Addr;
typedef __u16 Elf32_Half;
typedef __u32 Elf32_Off;
typedef __s32 Elf32_Sword;
typedef __u32 Elf32_Word;
Copy the code

ELF header structure: 32-bit ELF header structure:

#define EI_NIDENT 16 typedef struct elf32_hdr{ unsigned char e_ident[EI_NIDENT]; // The first 16 bytes identify the ELF file, also known as magic Elf32_Half E_type; // Indicate the type of the target file, such as EXEC executable Elf32_Half e_machine; //ELF file CPU platform attribute Elf32_Word e_version; //ELF version number, usually 0x1 Elf32_Addr e_entry; Elf32_Off E_phoff = 0 Elf32_Off e_phoff = 0 Elf32_Off e_phoff = 0 Elf32_Off e_phoff = 0 Elf32_Off e_phoff Elf32_Off E_shoff; Elf32_Off e_shoff; // Indicates the section header table offset in the file. If there is no item, this value is 0 Elf32_Word e_flags; // Flag Elf32_Half e_ehsize; // ELF header size Elf32_Half e_phentsize; Elf32_Half E_phnum; // Program Header Specifies the size of a program header(also called entry) in the table. Elf32_Half e_shentsize; // Program header Table number of program headers Elf32_Half e_shentsize; Elf32_Half E_shnum; // Section Header Table Specifies the size of a section header(also called entry) in bytes. //section header The number of section headers in the table Elf32_Half e_shstrndx; //. Shstrtab the subscript of this section entry in the section Header table} Elf32_Ehdr;Copy the code

You can use readelf-h to see the ELF header details. The ELF header can be used to find the section header table. The ELF header can be used to iterate through each entry and index information for each specific section.

ELF section header table is an array of Elf32_Shdr structures. ELF header table objdump -h: ELF header table objdump -h: ELF header table objdump -h: ELF header

typedef struct elf32_shdr { Elf32_Word sh_name; Shstrtab: Elf32_Word sh_type; // Section types, such as SHT_DYNAMIC,SHT_STRTAB,SHT_SYMTAB,SHT_REL, etc. // Section flag bit,SHF_WRITE writable, SHF_ALLOC needs to allocate space, SHF_EXECINSTR indicates executable Elf32_Addr sh_addr; // If this section is loaded into the virtual address space of the process, it represents the virtual address of the section in the process. Elf32_Word sh_size; // This section is offset from the beginning of the file. Elf32_Word sh_link; //section header table subscript link Elf32_Word sh_info; // Section additional information Elf32_Word sh_addralign; //section address alignment value Elf32_Word sh_entsize; } Elf32_Shdr; // Some sections are themselves arrays of fixed-size data types, such as symbol tables.Copy the code

Special sections and their description: Initializing variables is for space efficiency, and uninitialized variables do not need to occupy any real disk space in the object file.

.comment: saves version control information. Data: initializes the global C variable. In contrast, local variables are held on the stack at run time and do not appear in either. Debug the symbol table, which contains local variables and type definitions defined in the program, global variables defined and referenced in the program, and the original C source file. This table is only available when the compiler is called with the -g option. Dynamic: holds dynamic link information. Its type is STRTAB, indicating that it is a string table, but it is actually a dynamic symbol string table, which is used to aid. Dynsym’s (since under dynamic linking, we need to look up symbols at runtime, and there are often auxiliary symbol hash tables to speed up the symbol lookup process). Dynsym: Its type is DYNSYM, it is a dynamic symbol table, it only stores symbols related to dynamic links, for those symbols inside the module, such as module private variables are not saved, most of the dynamic link module has two tables. DYNSYM and.symtab,.symtab often stores all symbols. Dynsym. Fini: holds the code for terminating a process. When a process exits normally, the system executes the code in this section. Interp: Holds the path name of a program interpreter. Line: Holds the path name of a program interpreter. The mapping between the line numbers of the original C program and the machine instructions in the.text section. The tables.rel. XXX and.rela.xxx are only available when the compiler is called with the -g option: The relocation information of the XXX section, such as.rel. Text, represents the list of locations in the.text section. When the linker combines this object file with other files, these locations need to be modified. Instructions to call local functions do not need to be modified, and relocation information is not required in the executable object file. .rodata: read-only data. Shstrtab: string that holds section names. Strtab: a table of strings, a sequence of null-terminated strings. Symbol table, which stores information about functions and global variables defined and referenced in a program. It does not contain entries for local variables. Text: machine code for a compiled program

ELF symbol table structure: For top question 1: What does it actually mean to define a variable or function that has a static attribute? The symbol table section is named. Symtab. The contents of the symbol table are an array of Elf32_Sym structures defined as follows:

typedef struct elf32_sym{ Elf32_Word st_name; Elf32_Addr st_value; Elf32_Addr st_value; Elf32_Word st_size if the symbol is a function or variable definition, the value of the symbol is the address of the function or variable. // Symbol size. For symbols containing data, this value is the size of the data type. // The lower 4 bits are symbol types :STB_LOCAL(local symbol, not visible outside the target file) //STB_GLOBAL(global symbol is visible outside),STB_WEAK(weak symbol) // The higher 28 bits are symbol binding information, STT_NOTYPE(unknown symbol types),STT_OBJECT(data objects such as arrays of variables),STT_FUNC(functions or other executable code) //STT_SECTION unsigned char st_info; // STT_FILE unsigned char st_info; //STT_SECTION unsigned char st_info; // STT_FILE unsigned char st_info; unsigned char st_other; // do not use Elf32_Half st_shndx; // Section} Elf32_Sym;Copy the code

Use readelf-s to view the symbol table. If a global function foo is defined in a source file, use readelf-s to see the information in the symbol table foo:

Num: Value Size Type Bind Vis Ndx Name 9: 0000000000000000 16 FUNC GLOBAL DEFAULT 1 foo and after changing foo to static, the information in the foo symbol table is changed to 6: 0000000000000000 16 FUNC LOCAL DEFAULT 1 fooCopy the code

You can see that foo’s type changes from STB_GLOBAL to STB_LOCAL, so the foo symbol is hidden from other modules during the chaining process. Changing the function to static does not affect the function’s instructions, i.e. the contents of the text section.


Static link:

Combine several input object files into one executable.

If a hello.c file uses an add function defined in another module, compile the hello.c file separately and generate a callq 0 command to call the add function, while the statically linked executable is corrected to callq 400541. This means that the address of the add function is assigned at 0x400541, and all code instructions using the add function are corrected to that location.

Static linking is divided into two parts: space and address allocation, and symbol resolution and relocation.

In ELF files, the relocation table structure is specifically used to store relocation related information. You can use objdump -r to view relocation information for the target file, assuming the hello.c file is as follows:

#include <stdio.h> #include <elf.h> extern int add(int l, int r); int main(){ printf("Hello World! \n"); Add (3, 4); return 0; }Copy the code

Objdump -r hello.o

Elf64-x86-64 RELOCATION RECORDS FOR [.text]: OFFSET TYPE VALUE 0000000000000005 R_X86_64_32 .rodata 000000000000000a R_X86_64_PC32 puts-0x0000000000000004 0000000000000019 R_X86_64_PC32 add-0x0000000000000004 RELOCATION RECORDS FOR [.eh_frame]: OFFSET TYPE VALUE 0000000000000020 R_X86_64_PC32 .textCopy the code

OFFSET is the OFFSET from the section to which it belongs, as shown above. In the text section, the external add function is called at the OFFSET 0x19, which needs to be relocated. Readelf-s hello.o: the offset of text is 0x40, the operand of callq add is 0x40 + 0x19 = 0x59, and XXD hello.o: XXD hello.o: 0x59-1. It is “e8 00 00 00 00”, also known as “callq 0”, that needs to be repositioned to the final add address when linking.

The structure of relocation table is:

typedef struct elf32_rel { Elf32_Addr r_offset; // For executable files or shared object files, this value is the virtual address of the first byte at the location to be relocated. } Elf32_Rel;} Elf32_Rel;} Elf32_Rel;} Elf32_Rel;} Elf32_Rel;} Elf32_Rel;Copy the code

The contents of the.rel. Text section are the Elf32_Rel array.

The address correction method is also mentioned above, which is divided into absolute addressing correction and relative addressing correction. For absolute addressing correction, the address after correction is the actual address of the symbol. For relative addressing correction, the address value after correction plus the address of the next instruction is the actual address of the symbol. Objdump -d a.out

400535: e8 07 00 00 00 callq 400541 <add>
40053a: b8 00 00 00 00 mov $0x0,%eax
Copy the code

E8 07 00 00 00, 0x07 + address of next instruction = 0x07 + 0x40053A = 0x400541 = actual address of add function. The steps of static linking are divided into the following steps: Symbol resolution: Associates each symbol reference in the code with a defined symbol definition (that is, a symbol table entry in one of its input target modules). Relocation section and symbol definitions: After the first step of symbol resolution, the linker to know its input object module of code section and the exact size of the data section, you can proceed to the relocation, the linker will be merged into all of the same section of the same type polymerization section, for example, from the input module. The data section will be merged into one section, this section become output target executable file. The data section, The linker then assigns the runtime storage address to the new aggregation section, to each section defined by the input module, and to each symbol defined by the input module. When this step is complete, each instruction and global variable in the program will have a unique runtime memory address. Symbolic references in relocation section: In this step, the linker modifies the references to each symbol in the code section and data section, using them to point to the correct runtime address.


Loading of executable files:

The loader is invoked by calling the execve function, and thus the system call sys_execve(), and the corresponding load kernel code is executed. The loader copies code and data from the executable file from disk to storage, and then runs the program by jumping to the first instruction or entry point of the program. A memory map of the process at run time can be found in section 7.9 of Understanding Computer Systems in Depth, Second edition.

An executable file needs to be viewed from an execution view. For example, in the top figure, it has program Header table, an array of program headers, which is used to locate segments in the file. And a memory map that contains other information necessary to create the process. In the process of linking, the linker tries to allocate sections with the same permission attributes into the same space. For example, the readable and executable sections are grouped together. Such sections are called segments.

Program header is the structure that describes the segment. Its code is:

typedef struct elf32_phdr{ Elf32_Word p_type; // The main types of segment are PT_LOAD(loadable segment),PT_DYNAMIC(dynamic segment), PT_INTERP(dynamic linker path) Elf32_Off P_offset; //segment offset in file Elf32_Addr p_vaddr; //segment address Elf32_Addr p_paddr; //segment physical address, which is the same as p_vaddr Elf32_Word p_filesz; Elf32_Word p_memsz; Elf32_Word p_memsz; Elf32_Word p_flags; // Segment Size in bytes occupied by the process virtual space. //segment permission attributes such as R, W, X, private P Elf32_Word p_align; //segment alignment attribute} Elf32_Phdr;Copy the code

The command to view the Program Header table is readelf -l. The Program Header table exists only in executable and shared files. As you can see, the loader doesn’t care about the name of the segment. It only cares about the type of the segment. The cat /proc/pidi/maps command is used to query the virtual address space distribution of processes. For example, for a test program, readelf -l a.out reads:

  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4
  INTERP         0x000154 0x08048154 0x08048154 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD           0x000000 0x08048000 0x08048000 0x006f0 0x006f0 R E 0x1000
  LOAD           0x000f04 0x08049f04 0x08049f04 0x00120 0x00124 RW  0x1000
  DYNAMIC        0x000f14 0x08049f14 0x08049f14 0x000e8 0x000e8 RW  0x4
  NOTE           0x000168 0x08048168 0x08048168 0x00044 0x00044 R   0x4
  GNU_EH_FRAME   0x00058c 0x0804858c 0x0804858c 0x00044 0x00044 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
  GNU_RELRO      0x000f04 0x08049f04 0x08049f04 0x000fc 0x000fc R   0x1
Copy the code

Cat /proc/pid/maps = cat /proc/pid/maps

08048000-08049000 r-xp 00000000 08:02 76164164 ~/Sotest/a.out 08049000-0804a000 r--p 00000000 08:02 76164164 ~/Sotest/a.out 0804a000-0804b000 rw-p 00001000 08:02 76164164 ~/Sotest/a.out 09d70000-09d91000 rw-p 00000000 00:00 0 [heap] f7dcd000-f7dce000 rw-p 00000000 00:00 0 f7dce000-f7f7e000 r-xp 00000000 103:02 29693 /lib/i386-linux-gnu/libc-2.23.so f7f7e000-f7f80000 r--p 001af000 103:02 29693 /lib/i386-linux-gnu/libc-2.23.so f7f7e000-f7f80000 r--p 001af000 103:02 29693 /lib/i386-linux-gnu/libc-2.23.so F7f80000-f7f81000 rw-p 001b1000 103:02 29693 /lib/i386-linux-gnu/libc-2.23.so f7f81000-f7f84000 rw-p 00000000 00:00 0 f7fac000-f7fad000 rw-p 00000000 00:00 0 f7fad000-f7fb0000 r--p 00000000 00:00 0 [vvar] f7fb0000-f7fb2000 r-xp 00000000 00:00 0 [vdso] f7fb2000-f7fd5000 r-XP 00000000 103:02 29689 /lib/i386-linux-gnu/ lD-2.23.so f7fd5000-f7FD6000 r--p 00022000 103:02 29689 /lib/i386-linux-gnu/ lD-2.23. so f7fd6000-f7FD7000 rw-p 00023000 103:02 29689 /lib/i386-linux-gnu/ld-2.23.so ffed0000-ffef2000 rw-p 00000000 00:00 0 [stack]Copy the code

The [heap],[vdSO],[stack] above are not mapped to the file, these areas are called VMA(Anonymous Vitrual Memory Area), where the VDSO address is located in the kernel space, it is actually a kernel module, Processes can access this VMA to do some communication with the kernel. Segment loading requires consideration of alignment. Assuming that the default page size is 4096 bytes, the segment loading start address must be an integer multiple of 4096, as well as the segment size itself.


Position-independent code:

Generate so file command: gcc-shared-fpic -o libxxx.so xxx.cyyy.c

Because the code in shared library is shared by multiple processes, the virtual address loaded is different, so the code in shared library cannot assume that it is loaded to any location, using -fpIC to generate shared library location-independent code is necessary. If a shared library is created without location-independent code, different processes would need to make separate copies of the shared library code, determine the addresses of symbols at load time, and modify all instructions that refer to the locations of those symbols, but this would lose the memory-saving benefits of the shared library.

In ubuntu16.04, if foo calls its own add function in a file and attempts to compile the shared library without the -fpic option, an error is reported: the symbol ‘add’ can not be used when making a shared object; recompile with -fPIC

For data, unlike instructions, individual processes can modify global data exposed by shared libraries, so they must have separate copies in their own processes.

In IA32 systems, calls to local functions in the same target module need no special handling, because the reference function is referenced relative to the PC, where it is referenced. The offset to the function definition is known, and this offset is the opcode after the call instruction, so it is PIC in itself. We then call externally defined functions (even if the function is defined in the current source file, as long as static is not added, the compiler assumes it is defined in this module) and global variables, which require special handling to make their references PIC.

The basic idea is to separate out the parts of the instruction that need to be modified and put them next to the data parts, so that the instruction parts can remain unchanged and the data parts can have a copy in each process (the data parts themselves need a separate copy in each process). The address reference methods of various types in SO can be divided into four situations:

  1. Function calls, jumps, etc
  2. Data access within the module, such as global variables defined in the module, static variables
  3. Function calls outside the module, jumps, etc
  4. Data access outside a module, such as access to global variables defined in other modules


  1. For function calls within a module, they can be relative address calls or register-based relative calls where the destination address is the next instruction of the current instruction plus some offset value, which does not require relocation
  2. For data access inside the module, since the relative position between any instruction and the data inside the module it needs to access is fixed, the data inside the module can be accessed by adding the current instruction with a fixed offset. Unlike the call instruction of a function call that directly supports a relative address reference, the program must manually fetch the value of the PC register and add a fixed offset, IA32 does not use $eip to get the PC register value (only indirect means such as writing an __x86.get_pc_thunk.bx function), but x86-64 can use $rip to get the PC register value
  3. For external data access module and other modules the address of the global variable will have to wait until the loading to be sure, if you want to make these modules for external data access is address has nothing to do, it is in the data segment to create a pointer to the variable array, also known as the global offset table (GOT), when the code needs to reference the global variables, It can be referenced indirectly by the corresponding item in the GLOBAL offset. When instruction need to access the variable b, the program will find GOT first, and then according to GOT variables b corresponding items found in the target address, each variable corresponding to a 4-byte address, the linker when load module for each variable’s address, and then fill GOT in each item, in order to ensure that each pointer is pointing to the address is correct, Because the GLOBAL offset itself is in the data segment, it can be modified when modules are loaded, and each process can have its own copy, independent of each other. The offset of the GLOBAL offset table relative to the current instruction is known and can be determined at compile time. Generated in this global variable reference PIC depends on such an interesting fact: no matter where we are in the memory to load an object module (including Shared object module), data segment is always assigned to follow behind the code, so any variable in any instructions and data in the code segment is the distance between a runtime constants, The absolute memory location of code and data segments is independent. This can be described as follows: (1) Create a global table called the “GOT table” at the beginning of the data segment. Each global table referred to by this module has an entry in the global GOT table. This step is position-independent, that is, no matter where the module is loaded, the module will always find the offset table and jump to the global offset table. (2). GOT to jump to the content of the symbol table corresponding to subscript place real load address, of course, has not yet been determined at compile time reference symbol address, loading and confirm when can modify GOT table, thereby changed to real load symbol address, due to GOT the content of the table’s data segment, so that each process has its own independent copy, Modifying the GLOBAL offset table affects this process only.
  4. Function calls between modules are handled similarly to accessing data outside the module, but with a new section.got.plt.

Reference each global variable indirectly through the global offset (GOT structure is similar to pointer array) :

call L1 L1: Movl (%ebx) = movl (%ebx); movl (%ebx) = movl (%ebx); Movl (%eax),%eaxCopy the code

To call an external function indirectly via a global offset function:

Call L1 L1: popl % ebx / / register ebx holds the PC value addl $PROCOFF, % ebx / / register ebx to GOT the address of the proc function call * in the table (% ebx) / / proc function called by indirect GOT sheetCopy the code

As you can see above, the instructions to access variables are now five instructions, and the instructions to call functions are four instructions. The ELF compilation system uses a technique called delayed binding to improve efficiency, which is motivated by the fact that a typical application will use only a fraction of the hundreds of functions output by a shared library like libc.so. Postponing resolution of a function’s address to where it is actually called prevents the dynamic linker from doing hundreds of unnecessary relocations at load time. The first call is expensive at runtime, but each subsequent call costs only one instruction and an indirect memory reference. The idea is that you only do it when you’re actually calling a function, Procedure Linkage Table. If a target module calls any function defined in the shared library, it will have its own GOT and PLT. The GOT is part of the.data section, and the PLT is part of the.text section. It is a 16-byte array structure.

If A wants to call the addvec function in libfoo.so, it should call addvec@plt in the following order:

  1. Call addvec@plt // If the address of addvec@plt is at 0x8048420 and 0x8048420 is at the. PLT section, then the instruction jumps to 0x8048420 and is JMP *0x12345
  2. JMP *0x12345 // execute this command. The value of 0x12345 belongs to the. Got section, and the value of the address is exactly the address of the next addvec@plt instruction. Assume (0x8048420+0x6), so this instruction takes a big detour and finally executes the next instruction for Call addvec@plt
  3. Push $0x0 // This is the next instruction for call addvec@plt, which pushes the function identifier 0x0 onto the stack
  4. JMP 0x8048410 // Next instruction: jump to 0x8048410 and execute at pushl 0x804a004
  5. Pushl 0x804A004 // 0x804A004 is not a common value. It has the linker identity information at its address
  6. JMP *0x804a008 //0x804a008 is the dynamic linker entry point, so the result of this sentence is to jump to the dynamic linker execution

The above execution can be summarized as follows:

  1. Push the id of the addvec function onto the stack
  2. 3. Jump to the dynamic linker for execution

The dynamic linker determines the real address of addvec and populates it to 0x12345. After this step, there are only two instructions left to call the addvec function:

Call addvec@plt JMP *0x12345 //0x12345 is in the. Got section, where addvec is linked to the real addressCopy the code

The hook of the global offset table is to use ptrace or other methods to inject the global offset table, and then modify the address of the global offset table to intercept the function, and change the execution flow of the function.

In practice, there are two tables,”.got” and “.got. PLT “. “.got” is used to store global variable references, and “.got. PLT “is used to store function references. .got. PLT “.got. PLT “.got.

  1. The first holds the address of the “.dynamic” section, which describes information about dynamic links in this module.
  2. The second item holds the ID of the module
  3. The third item holds the address of _dl_runtime_resolve()

Position-independent executable PIE:

$CC app.c -o app-fpie-pie

The location independent executable’s functionality is related to ASLR, which is used for Address space Layout randomization to prevent buffer overflow attacks such as returning liBC. The executable loads random addresses into the virtual Address space. The actual address of each instruction after loading = (module base address + static address seen by a disassembly tool like IDA Pro). Executables compiled for Android 5.0 or higher must be PIE. Because of ASLR, the offset of the base address is added when using FRIda inline hooks:

var base_hello_jni = Module.findBaseAddress("libhello-jni.so");
    Interceptor.attach(base_hello_jni.add(0x1234), {
        onEnter : function(args) {
            this.arg0 = args[0];    
            this.arg1 = args[1];
        }, onLeave : function(retval) {
            
        }
    });
Copy the code

Determine whether to open the ASLR: cat/proc/sys/kernel/randomize_va_space return value on behalf of:

  1. 0 means no randomization, and the loaded runtime address is the static address in the executable
  2. 1 represents conservative randomization, where shared libraries, stacks, MMAP, VDSO and heap are random addresses
  3. 2 represents full randomization, including brK-managed memory

Dynamic linking:

For an SO, it defines some global variables. The code of a process linked to this SO modifies the values of these global variables without affecting other processes linked to this SO. How to achieve this? A: No, for global variables, each process accesses its own separate copy, using a copy-on-write mechanism.

Postponing a link to runtime is called dynamic linking, while static linking, as opposed to static linking, is executed during the linking phase of the compilation process. Dynamic link loading has many similarities with static link loading. First, the operating system reads the Header of the executable file to check the validity of the file, and then reads the virtual address, file address, and attributes of each segment from the “Program Header table” in the Header. In the case of static linking, the operating system transfers control to the entry address of the executable file, and the program starts to execute. However, in the case of dynamic linking, since the executable file also depends on many shared objects, dynamic linking is required. So the operating system launches a dynamic linker (/system/bin/linker for Android). Under Linux, the dynamic linker ld.so is actually a shared object. The dynamic linker has its own particularity and cannot depend on any other shared object. Operating system by mapping the same way it loaded into the process’s address space, the operating system in after loading the dynamic linker, will control to the dynamic linker entry address, perform dynamic link, after the completion of the job as a dynamic linking the dynamic linker will transfer control to the executable file entry address, program formally began.

/lib64/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.

hello.c:

#include <stdio.h> extern int add(int l, int r); int main(){ printf("Hello World! % d \ n ", add (3, 6)); Add (3, 4); return 0; }Copy the code

add.c:

extern int add(int l,int r);

int add(int l,int r){
  return l + r;
}
Copy the code

Add. C is compiled to libadd.so and is linked to hello.c

gcc -shared -fPIC -o libadd.so add.c
gcc hello.c libadd.so
Copy the code

Dynamic linking the ELF should be is one of the most important structure. The dynamic section, it can be seen as dynamic link under the ELF file “header”, it keeps the basic information required for the dynamic linker, depends on what the Shared object, for example, dynamic linking the location of the symbol table, dynamic link relocation table location, the address of the Shared object initialization code, etc., You can use readelf -d a.out to query for information in the.dynamic section, such as showing that the compiled A.out from the example above depends on libadd.so and libc.so.6. The LDD command is used to view all the dependency shared library chains and shared library paths of A. out.

The.dynamic section is an array of Elf32_Dyn structures, where the Elf32_Dyn structure is:

typedef struct dynamic{
  Elf32_Sword d_tag;
  union{
  Elf32_Sword d_val;
  Elf32_Addr d_ptr;
  } d_un;
} Elf32_Dyn;
Copy the code

D_tag indicates the type of the structure. The value d_un represents varies according to the type. DT_SYMTAB dynamic link symbol table address, d_ptr is. Dynsym address DT_STRTAG Dynamic link string table address, D_ptr indicates the address of. Dynstr DT_STRSZ Specifies the size of the dynamically linked string table. D_val indicates the size. DT_SONAME DT_RPATH Specifies the search path for dynamically linked shared objects DT_REL Address of the dynamic link relocation table DT_RELENT Number of entries to the dynamic re-read bit table

Dynamic symbol table (.dynsym) is used to represent the symbol import and export relationship between dynamic links between modules, it is different from.symtab, only save symbols related to dynamic links, use readelf –dyn-syms can view the information in the dynamic symbol table. For the above example, both A.out and libadd.so have their own dynamic symbol tables, and the external function add referenced in A.out appears in the dynamic symbol table:

Num:        Value                 Size    Type       Bind         Vis        Ndx   Name
2: 0000000000000000   0        FUNC GLOBAL DEFAULT  UND  add
Copy the code

The Ndx entry is displayed as UND, indicating that the add function is defined in other modules.

There is also a dynamic symbol string table (.dynstr) to hold dynamically linked strings.

The relocation tables associated with dynamic links are.rel. Dyn and.rel. PLT, which are equivalent to.rel. Text and.rel. .rel. Dyn is a correction to a reference to the.got and data segment, while.rel. PLT is a correction to a function reference to the.got. PLT

For the example above, using the readelf -r command to view a.out’s dynamically linked relocation table yields:

The relocation section '.rela.dyn' at offset 0x500 contains 1 entry: Offset Information Type Symbol Value Symbol Name + Increment 000000600FF8 000500000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_STARt__ + 0 Relocation section '.rela.plt' at offset 0x518 contains three entries: Offset Information Type Symbol Value Symbol Name + Add 000000601018 000200000007 R_X86_64_JUMP_SLO 0000000000000000 Add + 0 000000601020 000300000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0 000000601028 000400000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main @ GLIBC_2. 2.5 + 0Copy the code

You can see that the relocation type is different from that of the static link, R_X86_64_GLOB_DAT and R_X86_64_JUMP_SLO.


Steps for dynamic linking:

  1. Start the dynamic linker, whose path is specified in the.interp section
  2. Load all required shared objects:
  3. Relocation and initialization

After completing the above three steps, the dynamic linker transfers control of the process to the program entry and begins execution. When an SO is first loaded, in which process is it loaded? What is the loading process like? When the first need a Shared library module in the application starts, a single copy of the library would be loaded into memory at runtime (physical memory), when using the same Shared library behind other application starts, they will use has been loaded into memory copies of the library (different virtual memory mapped to the same physical memory). Load DT_NEEDED’s shared library collection in the. Dynamic section: map code and data segments of the shared library into the process space (memory space to disk files, virtual addresses of different processes to the same physical page where the shared library resides). If the shared object also depends on other shared libraries, Is dependent on the Shared library should also be added to the collection, until all depend on the Shared object is loaded, when loading a new Shared objects in the symbol table will be incorporated into the global symbol table, so when all the Shared object is loaded came in in the global symbol table will contain all needed by dynamic link in the process of symbols. If two shared objects define the same symbol, there will be Global symbol interpose for the shared object. Linux’s dynamic linker handles this: When a symbol needs to be added to the global symbol table, if the same symbol name already exists, the newly added symbol is ignored.

Path lookup rules for shared objects: GCC hello.c libadd.so: GCC hello.c libadd.so: GCC hello.c libadd.so: GCC hello.c libadd.so: GCC hello.c libadd.so: GCC hello.c libadd.so: GCC hello.c libadd.so: GCC hello.c libadd.so: GCC hello.c libadd.so: GCC hello.c libadd.so: GCC hello.c libadd.so: GCC hello.c libadd.

  1. Look in DT_RPATH, used at compile timegcc -Wl,-rpath=./ hello.c libadd.soAnd then use it againreadelf -d a.outLibrary RPATH: [./] : Library RPATH: [./] : Library RPATH: [./]

  2. Find the directory specified by the environment variable LD_LIBRARY_PATH
  3. )/lib , /usr/lib

Redirection and initialization: The dynamic linker traverse the executable file and relocation table for each Shared object, will they GOT/PLT in each need to modify the position of the relocation, if a Shared object. The init section, the dynamic linker. The init section of code to achieve a Shared object initialization process, corresponding and possibly. Finit section, The code is executed when the process exits.

Shared libraries and their associated symbolic links are installed in standard directories including: /usr/lib, which is the directory where most standard libraries are installed, should be used to install the libraries used at system startup (since /usr/lib may not have been mounted at system startup) /usr/local/lib, and non-standard or experimental libraries should be installed in this directory Directories listed in /etc/ld.so.conf


Display runtime links:

An application can ask the dynamic linker to load and link arbitrary shared libraries while it is running, without linking those libraries into the application at compile time. Calls to dlopen() open the dynamic library,dlsym() looks for symbols,dlerror() error handling, and dlclose() close the dynamic library. Void *dlopen(const char *filename, int flag) If filename is a relative path, dlopen will try to find the dynamic library in the following order:

  1. Find the directory specified by the environment variable LD_LIBRARY_PATH
  2. Find the shared library path inversely specified in /etc/ld.so.cache
  3. /lib , /usr/lib

This program that links and loads at run time is called a link loader, and it is not much different from a pure loader, the main and most obvious difference being that the link loader puts the output in memory rather than in a file