ELF文件解析(ELF File Parsing)
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] |
名称 | 长度 | 对齐方式 | 用途 |
---|---|---|---|
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头部表以外,其它部分都没有严格的顺序。
|
readelf
└─$ readelf -h ./ls |
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 |
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 |
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). */字符串表地址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位存储着重定位类型信息,后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
加载到内存重定位后最后一个segment还会指导进行只读改写
从0x232b0开始memsz 3208个byte为只读
Section Header Table
- 节头表(Section header table)
- 记录了ELF文件的节区信息
- 用于链接的目标文件必须拥有此结构
- 其它类型目标文件不一定需要
section Header Table内容对程序执行完全不影响,可以完全改掉,正常执行(这个也和动态linker版本有关)
typedef struct { |