elf文件结构解读以及plt节got节的理解
前言:
熟悉elf文件结构是一件很不错的事,因为安卓中的so加固以及修复都是需要这些知识的,包括pwn里面的rop之类的,也都是
和got节,plt节息息相关的,个人建议是在搞懂elf文件结构后,自己实现一个解析器,把注释写好,方便忘了再进一步重温,写的不好
见谅。
一. elf文件概述
elf文件包括了可执行文件,共享文件,目标文件这三类,其中安卓中涉及到的就是so文件,这个其实就是一个共享文件,类似
windows上的dll文件,目标文件是汇编文件,后缀为.o的文件,与可执行文件不同的是,并没有段头表,因为段是由相同功能的
节组合成的,而目标文件只是一个模块,并没有和其他模块进行链接,也就是节也没有合并,所以不存在段这个概念,程序的入口点
地址也是为空的,可执行文件和共享文件的话,大体结构和目标文件相同,多了段的概念,然后提供了两种视图(链接视图和装载视图)
二.elf文件结构
看起来其实不复杂,文件头,程序头表,节头表,节,段(其实就是相同功能节的组合体),接下来单独说说各个部分
三.elf文件头
这里搬出来010editor来看,这里是我导入之前做的crackme的so文件
好像名字奇奇怪怪的,我直接搬上,elf结构体定义
typedef struct { unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ Elf32_Half e_type; /* Object file type */ Elf32_Half e_machine; /* Architecture */ Elf32_Word e_version; /* Object file version */ Elf32_Addr e_entry; /* Entry point virtual address */ Elf32_Off e_phoff; /* Program header table file offset */ Elf32_Off e_shoff; /* Section header table file offset */ Elf32_Word e_flags; /* Processor-specific flags */ Elf32_Half e_ehsize; /* ELF header size in bytes */ Elf32_Half e_phentsize; /* Program header table entry size */ Elf32_Half e_phnum; /* Program header table entry count */ Elf32_Half e_shentsize; /* Section header table entry size */ Elf32_Half e_shnum; /* Section header table entry count */ Elf32_Half e_shstrndx; /* Section header string table index */ } Elf32_Ehdr;
重点说说几个字段,没说的说明比较简单易懂
1. e_ident:魔数,标识是哪个文件
2. e_phoff:程序头表在文件中的偏移
3. e_shoff: 程序节表在文件中的编译
4.e_phentsize: elf头占多少字节,32位的so,一般为52个字节
5. e_phnum: 程序头表中有关程序头的结构体个数,类似一个int数组,中int的个数
6. e_shnum: 和e_phnum差不多,把主语换成节头表
7. e_shstrndx: .strtab节在节头表的下标,因为里面存着所有节的名字
四.节头表解析
typedef struct { Elf32_Word sh_name; /* Section name (string tbl index) */ Elf32_Word sh_type; /* Section type */ Elf32_Word sh_flags; /* Section flags */ Elf32_Addr sh_addr; /* Section virtual addr at execution */ Elf32_Off sh_offset; /* Section file offset */ Elf32_Word sh_size; /* Section size in bytes */ Elf32_Word sh_link; /* Link to another section */ Elf32_Word sh_info; /* Additional section information */ Elf32_Word sh_addralign; /* Section alignment */ Elf32_Word sh_entsize; /* Entry size if section holds table */ } Elf32_Shdr;
节头表的结构如上图,实际上节头表就是上图结构体的数组,里面的数量是上面elf文件头中的e_shnum决定的,但是注意这个结构体并不是我们所想的节
也只是一个中间过渡的东西,只是定义了每个节在文件的哪个位置,名字叫什么,大小,类型是什么,具体的内容,还要根据其中定义好的偏移和大小再
去查找,接下来说说每个字段的含义
1.sh_name 是字符串节的下表,通常是先根据文件头中的strndx字段找到字符串节,然后再根据这个sh_name,找到节的名字
2. sh_type 表明这个节的类型是什么,内容比较多,直接上图
3.sh_flag 表明这个节是否可读可写可执行,记忆性的东西,直接上图
4.sh_addr 将会映射到虚拟内存空间中的地址
5.sh_size 和sh_off: 前一个是节的大小,后一个是节在文件中的偏移
在linux上也可以通过readelf -S xxx(文件名)进行查看
五. 特殊的节
五.1 .symtab
符号表的节,一般的so文件都会被抹去,怕被反编译,直接还原出符号名,也是一个结构体数组
st_name: 在字符串表中的下标
st_value :真正的值
st_size: 大小
st_info: 符号类别
毕竟符号分为局部符号,全局符号,还得标记是不是动态链接的
st_stndx: 符号属于哪个段,那个段在节头表的下标
挑了个so文件,发现里面的符号表已经被抹去了,-s只能查看.dynsym这个动态链接的节了
2..dynsym和.dynstr
动态链接符号表,里面主要存放着动态链接的符号,.dynstr里面主要存放动态链接符号的字符串名,
3. .rel.节名
重定位的表,很重要,因为这个表需要告诉linker哪个符号需要重定位
4. .plt节和.got节
出现.plt节的原因是有延迟绑定机制,因为动态链接中,符号很多,而且有些符号还没用到
那么重定位的负担就很重,所以就出现了延迟绑定,只有用到了该符号再进行绑定(这里的绑定主要说的是got表中填入符号的地址)
所以汇编代码大概是这样的:
jmp 后面的地址,是got表中的内容,但是如果没用过这个符号,里面填写的是push n的地址,也就是跳转到下一条指令了,
然后在把got表的下标和模块的id入栈,调用符号绑定的函数,实现延迟绑定,所以.plt节实际上就是一个got表的跳板,
六.程序头表(segment)
elf可分为两种视图,一种是链接视图,还有一种是装载视图
实际上不需要想的很复杂,段实际上就是相同功能的节的集合,本质上还是节,不过装载过程中所需的信息只和段有关,这也是为什么so加固中,可以去动节的一些信息,为我们解壳提供便利
typedef struct
{
Elf32_Word p_type;
/
*
Segment
type
*
/
Elf32_Off p_offset;
/
*
Segment
file
offset
*
/
Elf32_Addr p_vaddr;
/
*
Segment virtual address
*
/
Elf32_Addr p_paddr;
/
*
Segment physical address
*
/
Elf32_Word p_filesz;
/
*
Segment size
in
file
*
/
Elf32_Word p_memsz;
/
*
Segment size
in
memory
*
/
Elf32_Word p_flags;
/
*
Segment flags
*
/
Elf32_Word p_align;
/
*
Segment alignment
*
/
} Elf32_Phdr;
也是一个结构体数组,注释也写得很清楚了。略
总结: 重心还是在节那块,参考了程序员自我修养,和看雪的几篇文章,但是看雪文章明显不如书有精华了,建议还是看看书