ELF File Parsing
Executable and Linkable Format
实验材料:010editor readelf objdump linux_ls文件(x86-64)
资料讲解32位elf,实验解析64位elf
ls文件信息
链接:https://pan.baidu.com/s/17ElUYwRhtW0eRRED4SgNDA
提取码:ldss
┌──(kali㉿kali)-[~/Desktop/test]
└─$ checksec ./ls
[*] '/home/kali/Desktop/test/ls'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
┌──(kali㉿kali)-[~/Desktop/test]
└─$ file ./ls
./ls: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=15dfff3239aa7c3b16a71e6b2e3b6e4009dab998, for GNU/Linux 3.2.0, stripped
┌──(kali㉿kali)-[~/Desktop/test]
└─$ ldd ./ls
linux-vdso.so.1 (0x00007ffcd2d86000)
libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007ff70107a000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff700e99000)
libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007ff700dff000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff7010e6000)
名称 | 长度 | 对齐方式 | 用途 |
---|---|---|---|
Elf32_Addr | 4 | 4 | 无符号程序地址 |
Elf32_Half | 2 | 2 | 无符号半整型 |
Elf32_Off | 4 | 4 | 无符号文件偏移 |
Elf32_Sword | 4 | 4 | 有符号大整型 |
Elf32_Word | 4 | 4 | 无符号大整型 |
unsigned char | 1 | 1 | 无符号小整型 |
ELF Header
- ELF文件头表(ELF header)
- 记录了ELF文件的组织结构
除了ELF头部表以外,其它部分都没有严格的顺序。
#define EI_NIDENT 16
typedef struct {
unsigned char e_ident[EI_NIDENT]; /* ELF文件标识 */
Elf32_Half e_type; /* 文件类型 */
Elf32_Half e_machine; /* 机器类型 */
Elf32_Word e_version; /* 文件版本 */
Elf32_Addr e_entry; /* 程序入口地址 */
Elf32_Off e_phoff; /* 程序头表偏移 */
Elf32_Off e_shoff; /* 节头表偏移 */
Elf32_Word e_flags; /* 文件标志 */
Elf32_Half e_ehsize; /* ELF头大小 */
Elf32_Half e_phentsize; /* 程序头表项大小 */
Elf32_Half e_phnum; /* 程序头表项数量 */
Elf32_Half e_shentsize; /* 节头表项大小 */
Elf32_Half e_shnum; /* 节头表项数量 */
Elf32_Half e_shstrndx; /* 节头表字符串表索引 */
} Elf32_Ehdr;
readelf
└─$ readelf -h ./ls
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Position-Independent Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x61d0
Start of program headers: 64 (bytes into file)
Start of section headers: 149360 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 13
Size of section headers: 64 (bytes)
Number of section headers: 31
Section header string table index: 30
e_dient
- 共16个字节
- 头4个字节,被称作 “魔数”,标识该文件是一个 ELF 目标文件。固定为
7f 45 4c 46
(.ELF),和windows下PE(Portable Executable)文件的4D 5A
(MZ)类似,改掉会崩溃 - 下一个字节标识文件的类型
- 0无效类型
- 1 32文件
- 2 64位文件
- 下一个字节标识数据的编码方式
- 0 无效
- 1 小端序 LSB
- 2 大端序 MSB
- 后面字节不看了,因为除了.ELF签名,其他没有用,改掉后程序依旧可以正常运行,只是会迷惑掉解析软件罢了
└─$ readelf -h ./ls
ELF Header:
Magic: 7f 45 4c 46 0e ee ee ee ee ee ee ee ee ee ee ee
Class: <unknown: e>
Data: <unknown: ee>
Version: 238 <unknown>
OS/ABI: <unknown: ee>
ABI Version: 238
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x61d0
Start of program headers: 0 (bytes into file)
Start of section headers: 64 (bytes into file)
Flags: 0x0
Size of this header: 18288 (bytes)
Size of program headers: 2 (bytes)
Number of program headers: 0
Size of section headers: 0 (bytes)
Number of section headers: 0
Section header string table index: 0
readelf: Warning: possibly corrupt ELF file header - it has a non-zero section header offset, but no section headers
e_type
不能随意修改
名称 | 值 | 意义 |
---|---|---|
ET_NONE | 0 | 无文件类型 |
ET_REL | 1 | 可重定位文件 |
ET_EXEC | 2 | 可执行文件 |
ET_DYN | 3 | 共享目标文件 |
ET_CORE | 4 | 核心转储文件 |
ET_LOPROC | 0xff00 | 处理器指定下限 |
ET_HIPROC | 0xffff | 处理器指定上限 |
这里ls头部信息中的类型竟然是共享库文件,而我们查看的是可执行文件,发现开启pie的程序都会被识别为3
PIE
能使程序像共享库一样在主存任何位置装载,这需要将程序编译成位置无关,并链接为ELF
共享对象。
e_machine
可运行的机器架构
不能随意修改
名称 | 值 | 意义 |
---|---|---|
EM_NONE | 0 | 无机器类型 |
EM_M32 | 1 | AT&T WE 32100 |
EM_SPARC | 2 | SPARC |
EM_386 | 3 | Intel 80386 |
EM_68K | 4 | Motorola 68000 |
EM_88K | 5 | Motorola 88000 |
EM_860 | 7 | Intel 80860 |
EM_MIPS | 8 | MIPS RS3000 |
e_version
可修改
系统版本
e_entry
不可修改
系统转交控制权给 ELF 中相应代码的虚拟地址,也就是给pc的值,很重要 ,可以做入口点hook
e_phoff
不可修改
Program Header table OFFset
程序头部表在elf中的偏移
e_shoff
可修改
Section Header table OFFset
节头表在elf中偏移
e_flags
可修改
具体架构版本
e_ehsize
可修改
ELF HEADER 长度
e_phentsize
不可修改
Program Header ENTry SIZE
program header table是个结构体数组,参数描述结构体大小
e_phnum
不可修改
Program Header entry NUMber
program header table结构体个数
e_shentsize
可修改
Section Header ENTry SIZE
section结构体大小
e_shnum
可修改
Section Header NUMber
section结构体个数
e_shstrndx
可修改
关于Program Header table 的项基本都不可以改,Section Header Table基本可改,不影响执行
Program Header table
程序的头部只有对于可执行文件和共享目标文件有意义。
- 程序头表/段表(Program header table)
- 告诉系统如何创建进程
- 生成进程的可执行文件必须拥有此结构
- 重定位文件不一定需要
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;
p_type
PT_LOAD 1 此类型段为一个可加载的段,大小由 p_filesz 和 p_memsz 描述。文件中的字节被映射到相应内存段开始处。如果 p_memsz 大于 p_filesz,“剩余” 的字节都要被置为 0。p_filesz 不能大于 p_memsz。可加载的段在程序头部中按照 p_vaddr 的升序排列。
PT_GNU_RELRO 用于指示 GNU ld linker 链接器如何将某些段放置到进程地址空间中以实现地址空间的保护。PT_GNU_RELRO
中的 RELRO
是 “RELocation Read-Only” 的缩写。它指定了一个区段,该区段包含了程序的只读数据段 (如 .rodata) 中所有的全局偏移表 (Global Offset Table,简称 GOT) 和重定位表 (Relocation Table),并将这个区段设置为只读。这样做的目的是防止程序在运行时被攻击者利用 GOT 篡改,提高程序的安全性。
我们只关注一下需要加载到内存的段就可以
以ls program head table 为例分析
可以看到有四个段需要加载到内存的可加载段,在此之前,需要有两个段(静态链接不需要),一个是Program Header 另一个是 Interpreter Path(解释器路径)
- Program Header
对程序加载又有的只有p_offset_FROM_FILE_BEGIN和p_vaddr_virtual_addrees这两个值又必须和ELF Header里的e_phoff一样,这样做是为了在解析elf时如果给的文件指针是program header table地址,而又需要elf header时(比如需要程序入口点),将该指针减去这个offset就可以找到elf header,毕竟只有elf header位置固定在文件开头
- Interpreter Path
表示在文件偏移offset 为0x318的位置读取LENGTH为28字节的解释器地址,来加载下面的loadable段到进程内存
/lib64/ld-linux-x86-64.so.2
是 Linux x86-64 系统下的动态链接器(dynamic linker)文件,它的主要作用是在程序运行时,将程序所需的共享库(shared library)加载到内存中,并将这些共享库中未定义的符号与程序中定义的符号进行链接,从而使程序能够正常执行。
-
Loadable segment
利用mmap向pie+virtual_address处映射从file_begin开始raw_length大小的数据,前面映射必须从页对齐开始,后面必须按4096对齐(页对齐)结束
-
Dynamic segment
这里面保存了动态链接器所需的基本信息,如依赖于哪些共享对象、动态链接符号表的位置、共享对象初始化代码的地址等
从这里开始我们已经把磁盘上的文件加载到了内存,也就是说我们不会再用到这里的任何和文件相关的地址
这个段标记的数据,也是一个结构体数组,如下
typedef struct { Elf64_Sxword d_tag; /* Dynamic entry type *64位程序占8bit 用于区分各种指定信息类型的标记,该结构中的共用体根据该标志进行解释/ union { Elf64_Xword d_val; /* Integer value */ Elf64_Addr d_ptr; /* Address value */ } d_un; 64位程序占8bit 或者保存一个虚拟地址,或者保存一个整数,可以根据特定的标志进行解释。 } Elf64_Dyn; /* Legal values for d_tag (dynamic entry type). */ #define DT_NULL 0 /* Marks end of dynamic section */ #define DT_NEEDED 1 /* Name of needed library */ #define DT_PLTRELSZ 2 /* Size in bytes of PLT relocs */ #define DT_PLTGOT 3 /* Processor defined value */ #define DT_HASH 4 /* Address of symbol hash table */ #define DT_STRTAB 5 /* Address of string table */ #define DT_SYMTAB 6 /* Address of symbol table */ #define DT_RELA 7 /* Address of Rela relocs */ #define DT_RELASZ 8 /* Total size of Rela relocs */ #define DT_RELAENT 9 /* Size of one Rela reloc */ #define DT_STRSZ 10 /* Size of string table */ #define DT_SYMENT 11 /* Size of one symbol table entry */ #define DT_INIT 12 /* Address of init function */ #define DT_FINI 13 /* Address of termination function */ #define DT_SONAME 14 /* Name of shared object */ #define DT_RPATH 15 /* Library search path (deprecated) */ #define DT_SYMBOLIC 16 /* Start symbol search here */ #define DT_REL 17 /* Address of Rel relocs */ #define DT_RELSZ 18 /* Total size of Rel relocs */ #define DT_RELENT 19 /* Size of one Rel reloc */ #define DT_PLTREL 20 /* Type of reloc in PLT */ #define DT_DEBUG 21 /* For debugging; unspecified */ #define DT_TEXTREL 22 /* Reloc might modify .text */ #define DT_JMPREL 23 /* Address of PLT relocs */ #define DT_BIND_NOW 24 /* Process relocations of object */ #define DT_INIT_ARRAY 25 /* Array with addresses of init fct */ #define DT_FINI_ARRAY 26 /* Array with addresses of fini fct */ #define DT_INIT_ARRAYSZ 27 /* Size in bytes of DT_INIT_ARRAY */ #define DT_FINI_ARRAYSZ 28 /* Size in bytes of DT_FINI_ARRAY */ #define DT_RUNPATH 29 /* Library search path */ #define DT_FLAGS 30 /* Flags for the object being loaded */ #define DT_ENCODING 32 /* Start of encoded range */ #define DT_PREINIT_ARRAY 32 /* Array with addresses of preinit fct*/ #define DT_PREINIT_ARRAYSZ 33 /* size in bytes of DT_PREINIT_ARRAY */ #define DT_SYMTAB_SHNDX 34 /* Address of SYMTAB_SHNDX section */ #define DT_NUM 35 /* Number used */ #define DT_LOOS 0x6000000d /* Start of OS-specific */ #define DT_HIOS 0x6ffff000 /* End of OS-specific */ #define DT_LOPROC 0x70000000 /* Start of processor-specific */ #define DT_HIPROC 0x7fffffff /* End of processor-specific */ #define DT_PROCNUM DT_MIPS_NUM /* Most used by any processor */ #define DT_ADDRRNGLO 0x6ffffe00 #define DT_GNU_HASH 0x6ffffef5 /* GNU-style hash table. */ #define DT_TLSDESC_PLT 0x6ffffef6 #define DT_TLSDESC_GOT 0x6ffffef7 #define DT_GNU_CONFLICT 0x6ffffef8 /* Start of conflict section */ #define DT_GNU_LIBLIST 0x6ffffef9 /* Library list */ #define DT_CONFIG 0x6ffffefa /* Configuration information. */ #define DT_DEPAUDIT 0x6ffffefb /* Dependency auditing. */ #define DT_AUDIT 0x6ffffefc /* Object auditing. */ #define DT_PLTPAD 0x6ffffefd /* PLT padding. */ #define DT_MOVETAB 0x6ffffefe /* Move table. */ #define DT_SYMINFO 0x6ffffeff /* Syminfo table. */ #define DT_ADDRRNGHI 0x6ffffeff #define DT_ADDRTAGIDX(tag) (DT_ADDRRNGHI - (tag)) /* Reverse order! */ #define DT_ADDRNUM 11
- 字符串表地址d_tag=5的地址,后面八位就是地址,其实section里的.dynstr也能找到,但是不一定准确,因为我们程序加载执行时那个setion无用
- 导入库表d_tag=1 后面时字符串表的偏移,可能不止一个导入库表
0x1040+0x0542=0x1582 0x1040+0x0552=0x1592
-
符号表 d_tag=6 对应section .dynsym
typedef struct { Elf64_Word st_name; 4 /* Symbol name (string tbl index) */ unsigned char st_info; 1 /* Symbol type and binding */ unsigned char st_other; 1 /* Symbol visibility */ Elf64_Section st_shndx; 2 /* Section index */ Elf64_Addr st_value; 8 /* Symbol value */ Elf64_Xword st_size; 8 /* Symbol size */ } Elf64_Sym; 24
-
导入表 d_tag=23=0x17
导入表的size在d_tag=2处
typedef struct { Elf64_Addr r_offset; 8 /* Address */,写入导入地址,也就是got表 Elf64_Xword r_info; 8 /* Relocation type and symbol index */前32位2 位存储着重定位类型信息,后32位存储着符号表索引从1开始 Elf64_Sxword r_addend; 8 /* Addend */ } Elf64_Rela;
选取一个分析
符号表
字符串表0x1040+0x344=1384
ida
-
重定位表 d_tag=7 size在d_tag=8
结构和导入表一样
-
哈希表 #define DT_GNU_HASH 0x6ffffef5 /* GNU-style hash table. */
用于加速符号查找,在 DT_GNU_HASH 中,符号名称被哈希到一个桶中,每个桶中保存了一个指向符号表的指针。当需要查找符号时,动态链接器可以使用哈希表来快速定位符号的位置,而不需要遍历整个符号表。
-
Read-only After Relocation
从0x232b0开始memsz 3208个byte为只读
Section Header Table
- 节头表(Section header table)
- 记录了ELF文件的节区信息
- 用于链接的目标文件必须拥有此结构
- 其它类型目标文件不一定需要
section Header Table内容对程序执行完全不影响,可以完全改掉,正常执行(这个也和动态linker版本有关)
typedef struct {
Elf32_Word sh_name; // 节名称在 .shstrtab 节中的索引
Elf32_Word sh_type; // 节类型
Elf32_Word sh_flags; // 节标志
Elf32_Addr sh_addr; // 节的内存地址
Elf32_Off sh_offset; // 节在文件中的偏移量
Elf32_Word sh_size; // 节的大小(字节数)
Elf32_Word sh_link; // 链接到的其他节的索引
Elf32_Word sh_info; // 额外信息
Elf32_Word sh_addralign; // 对齐方式
Elf32_Word sh_entsize; // 节包含实体的大小
} Elf32_Shdr;