linux 恶意软件研究相关工具 及下载地址:https://remnux.org/docs/distro/tools/
ELF文件(目标文件)格式主要三种:
1)可重定向文件:文件保存着代码和适当的数据,用来和其他的目标文件一起来创建一个可执行文件或者是一个共享目标文件。(目标文件或者静态库文件,即linux通常后缀为.a和.o的文件)
2)可执行文件:文件保存着一个用来执行的程序。(例如bash,gcc等)
3)共享目标文件:共享库。文件保存着代码和合适的数据,用来被下连接编辑器和动态链接器链接。(linux下后缀为.so的文件。)
1)ELF header:在文件的开始,保存了路线图,描述了该文件的组织情况。
2)Program header table:告诉系统如何创建进程映像。用来构造进程映像的目标文件必须具有程序头部表,可重定位文件不需要这个表。
3)Section header table :包含了描述文件节区的信息,每个节区在表中都有一项,每一项给出诸如节区名称、节区大小这类信息。用于链接的目标文件必须包含节区头部表,其他目标文件可以有,也可以没有这个表。
文件头(ELF Header) |
程序头表(Program Header Table) |
代码段(.text) |
数据段(.data) |
Bss段(.bss)//有存放data |
段表字符串表(.shstrtab) |
段表(Section Header Table) |
符号表(.symtab) |
字符串表(.strtab) |
重定位表(.rel.text) |
重定位表(.rel.data) |
Elf 在文件中与在内存中被执行视图对比
链接视图 |
执行视图 |
ELF头部 |
ELF头部 |
程序头部表(可选) |
程序头部表 |
节区1 |
段1 |
… |
|
节区n |
段2 |
… |
|
… |
… |
节区头部表 |
节区头部表(可选) |
例题:Readelf -e a.out (例题为gcc下编写的简单的helloword程序编译生成的.out后缀的 elf文件)
查看文件头数据
源码下载
which readelf
结果 /路径
Dpkg -S 路径
结果:binutils: /路径
下载源码到本地 apt-get source binutils
Elf文件头解析
查看文件头:Readelf -h
16进制查看工具:hexdump -C filename|more
Readelf.c中 process_elf函数调用process_object函数,解析整个文件
process_object函数中 get_file_header用于解析文件头
并存放到Elf_Internal_Ehdr elf_header静态变量中,Elf_Internal_Ehdr elf_header在internal.h中
工具readelf查看文件头:Readelf -h filename
工具查看文件16进制文件头:hexdump -n 52 -C Filename
文件头解析:
查看定义:Usr/include/elf.h
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;
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf64_Half e_type; /* Object file type */
Elf64_Half e_machine; /* Architecture */
Elf64_Word e_version; /* Object file version */
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff; /* Program header table file offset */
Elf64_Off e_shoff; /* Section header table file offset */
Elf64_Word e_flags; /* Processor-specific flags */
Elf64_Half e_ehsize; /* ELF header size in bytes */
Elf64_Half e_phentsize; /* Program header table entry size */
Elf64_Half e_phnum; /* Program header table entry count */
Elf64_Half e_shentsize; /* Section header table entry size */
Elf64_Half e_shnum; /* Section header table entry count */
Elf64_Half e_shstrndx; /* Section header string table index */
} Elf64_Ehdr;
程序头表占据空间:
程序头表文件内的偏移地址e_phoff+e_phentsize(表项大小)*e_phnum(表项个数)
段头表项占据空间:
段表项内的文件偏移地址e_shoff_e_shentsize(表项大小)*e_shnum(表项数量)
Hexdump -C -s 地址偏移 fileName
Hexdump -C -s 6088 a.out (17c8)
Readelf -S filename
特殊节区:
在分析这些节区的时候,需要注意如下事项:
以“.”开头的节区名称是系统保留的。应用程序可以使用没有前缀的节区名称,以避 免与系统节区冲突。
目标文件格式允许人们定义不在上述列表中的节区。
目标文件中也可以包含多个名字相同的节区。
保留给处理器体系结构的节区名称一般构成为:处理器体系结构名称简写 + 节区名称。
处理器名称应该与 e_machine 中使用的名称相同。例如 .FOO.psect 街区是由FOO 体系结构定义的 psect 节区。
Sh_name记录字符串表(.shstrtab段)内偏移。即0x1b,.shstrtab段的偏移等于0x16c2.
段名偏移=0x16c2+0x1b=0x16dd
Readelf -x [x] filename 对应索引号查看的section的内容查看:
下面用 hexdump 的方法去读取.text这个 section 中的内容,通过看section header中.text中offset和size分别是0x3e0和0x1e2,通过16进制向10进制转换得到offset:992和size:482。
输入 hexdump –s 992 –n 482 –C a.out,输出与上面一样内容
Sh_link和sh_info表段的链接信息
Sh_info记录的是符号表内最后一个局部的符号表项在符号表内的索引+1.
符号表信息:Readelf -s a.out
STB_WEAK:与全局变量绑定类似,不过比STB_GLOBAL的优先级低,被标记为STB_WEAK的符号有可能会被同名的未被标记为STB_WEAK的符号覆盖。
重定位表段,sh_link记录重定位所作用的符号表段对应段表所在的段内的索引,而sh_info记录重定位所作用的段表项在段表中的索引。
其他类型的信息:
Sh_addralign段对齐方式
段地址对齐(sh_addralign) 由于对齐方式均以2的幂为基准,因此该字段表示2的幂数 当sh_addralign为1或0时 对齐无要求
对齐值 |
对齐方式 |
说明 |
0 |
无对齐要求 |
|
1 |
无对齐要求 |
|
4 |
对齐4 |
满足sh_offset%4=0 |
16 |
对齐16 |
满足sh_offset%16=0 |
32 |
对齐32 |
满足sh_offset%32=0 |
Sh_entsize一般保存如符号表段,重定位表段,表示段内保存表的表项大小。
ELF程序头表(Program Header Table)
程序头表与段表互相独立,有ELF文件头同一管理。
typedef struct elf32_phdr{
Elf32_Word p_type;
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
Elf32_Word p_filesz;
Elf32_Word p_memsz;
Elf32_Word p_flags;
Elf32_Word p_align;
}Elf32_Phdr;
(1) p_type表示当前描述的段的种类。常见有以下常数。
#define PT_NULL 0 //空段
#define PT_LOAD 1 //可装载段
#define PT_DYNAMIC 2 //表示该段包含了用于动态连接器的信息
#define PT_INTERP 3 //表示当前段指定了用于动态连接的程序解释器,通常是ld-linux.so
#define PT_NOTE 4 //该段包含有专有的编译器信息
#define PT_SHLIB 5 //该段包含有共享库
(2) p_offset给出了该段在二进制文件中的偏移量,单位为字节。
(3) p_vaddr给出了该段需要映射到进程虚拟地址空间中的位置。
(4) p_paddr在只支持物理寻址,不支持虚拟寻址的系统当中才使用。
(5) p_filesz给出了该段在二进制文件当中的长度,单位为字节。
(6) p_memsz给出了段在虚拟地址空间当中的长度,单位为字节。与p_filesz不等时会通过截断数据或者以0填充的方式处理。
(7) p_flags保存了标志信息,定义了该段的访问权限。有如下值
#define PF_R 0x4 //该段可读
#define PF_W 0x2 //该段可写
#define PF_X 0x1 //该段可执行
(8) p_align指定了段在内存和二进制文件当中的对齐方式,即p_offset和p_vaddr必须是p_align的整数倍。
Readelf -l filename
目标文件的符号表中包含用来定位、重定位程序中符号定义和引用的信息。符号表 索引是对此数组的索引。索引 0 表示表中的第一表项,同时也作为 定义符号的索引。
符号是对某些类型的数据或者代码(如全局变量或函数)的符号引用。 例如,printf()函数会在动态符号表.dynsym 中存有一个指向该函数的 符号条目。在大多数共享库和动态链接可执行文件中,存在两个符号表。 如前面使用readelf –S命令输出的内容中,可以看到有两个节:.dynsym 和.symtab。
.dynsym保存了引用来自外部文件符号的全局符号,如printf这样的 库函数,.dynsym保存的符号是.symtab所保存符号的子集,.symtab中 还保存了可执行文件的本地符号,如全局变量,或者代码中定义的本地函数 等。因此,.symtab保存了所有的符号,而.dynsym只保存动态/全局符号。
因此,就存在这样一个问题:既然.symtab 中保存了.dynsym 中所有的 符号,那么为什么还需要两个符号表呢?使用readelf –S命令查看可执行文 件的输出,可以看到一部分节被标记为了 A(ALLOC) 、WA(WRITE/ALLOC) 或者 AX(ALLOC/EXEC) 。.dynsym 是被标记了 ALLOC 的,而.symtab 则没有标记。 ALLOC 表示有该标记的节会在运行时分配并装载进入内存,而.symtab 不是在运行时必需的,因此不会被装载到内存中。.dynsym保存的符号只能在运行时被解析,因此是运行时动态链接器所需要的唯一符号。.dynsym符号表 对于动态链接可执行文件的执行来说是必需的,而.symtab符号表只是用来进 行调试和链接的,有时候为了节省空间,会将.symtab符号表从生产二进制文 件中删掉。
Gcc -nostdlib file.c -o te
/* Symbol Table Entry */
typedef struct elf32_sym {
Elf32_Word st_name; /* name - index into string table */
Elf32_Addr st_value; /* symbol value */
Elf32_Word st_size; /* symbol size */
unsigned char st_info; /* type and binding */
unsigned char st_other; /* 0 - no defined meaning */
Elf32_Half st_shndx; /* section header index */
} Elf32_Sym;
typedef struct {
Elf64_Half st_name; /* Symbol name index in str table */
Elf_Byte st_info; /* type / binding attrs */
Elf_Byte st_other; /* unused */
Elf64_Quarter st_shndx; /* section index of symbol */
Elf64_Xword st_value; /* value of symbol */
Elf64_Xword st_size; /* size of symbol */
} Elf64_Sym;
Readelf -s filenme | more
符号类型
STT_NOTYPE:符号类型未定义。
STT_FUNC:表示该符号与函数或者其他可执行代码关联。
STT_OBJECT:表示该符号与数据目标文件关联。
符号绑定
STB_LOCAL:本地符号在目标文件之外是不可见的,目标文件包含 了符号的定义,如一个声明为 static 的函数。
STB_GLOBAL:全局符号对于所有要合并的目标文件来说都是可见 的。一个全局符号在一个文件中进行定义后,另外一个文件可以对这 个符号进行引用。
STB_WEAK:与全局绑定类似,不过比 STB_GLOBAL 的优先级低。 被标记为 STB_WEAK 的符号有可能会被同名的未被标记为 STB_WEAK的符号覆盖。
下面是对绑定和类型字段进行打包和解包的宏指令。
ELF32_ST_BIND(info)或者 ELF64_ST_BIND(info):从 st_info 值中提取出一个绑定。 ELF32_ST_TYPE(info)或者 ELF64_ST_TYPE(info):从 st_info 值中提取类型。 ELF32_ST_TYPE(bind,type)或者ELF64_ST_INFO(bind,type): 将一个绑定和类型转换成st_info值。
字符串表节区包含以 NULL(ASCII 码 0)结尾的字符序列,通常称为字符串。ELF 目标文件通常使用字符串来表示符号和节区名称。对字符串的引用通常以字符串在字符 串表中的下标给出。
一般,第一个字节(索引为 0)定义为一个空字符串。类似的,字符串表的最后一 个字节也定义为 NULL,以确保所有的字符串都以 NULL 结尾。索引为 0 的字符串在 不同的上下文中可以表示无名或者名字为 NULL 的字符串。
允许存在空的字符串表节区,其节区头部的 sh_size 成员应该为 0。对空的字符串 表而言,非 0 的索引值是非法的。
在使用、分析字符串表时,要注意以下几点:
字符串表索引可以引用节区中任意字节。
字符串可以出现多次
可以存在对子字符串的引用
同一个字符串可以被引用多次。
字符串表中也可以存在未引用的字符串。
虽然字符串内成为串表,但并非表的形式。而是一个文件领域。
输入 hexdump –s 文件偏移–n 文件大小 –C filename
静态链接的整个过程分两步,第一步是空间与地址分配,第二步是符号解析与重定位;其中第二步是链接过程的核心,特别是重定位。在重定位过程中,重定位表和符号表起着至关重要的作用,重定位表确定了需要被重定位的地址,符号表决定了被置换成什么值。
可重定位表
连接器在处理目标文件时,须要对目标文件中某些部位进行重定位,即代码段和数据段中那些对绝对地址的引用的位置。这些重定位的信息都记录在ELF文件表里面,对于每个须要重定位的代码段和数据段,都会有一个相应的重定位表,例如 .rel.text 表对应.text段。也就是说,重定位表记录了须要被重定位的地址都在相应段的哪些地方。
r_offset:
该字段指明重定位所作用的位置;对于重定位文件来说,该值是受重定位所作用的存储单元在节中的字节偏移量;对于可执行文件或共享目标文件来说,该值是受重定位所作用的存储单元的虚拟地址;
r_info:
该字段指明重定位所作用的符号表索引和重定位的类型;比如,如果是一个函数需要重定位,则该字段的值就是被调函数所对应的符号表索引;如果索引值为STN_UNDEF,则未定义索引,那么重定位过程中将使用0作为符号值;重定位的类型依据处理器的不同而不同;如果一种处理器规定自己引用了一个重定位项的类型或者符号表索引,则表明这种处理器应用了ELF32_R_TYPE/ELF64_R_TYPE或ELF32_R_SYM/ELF64_R_SYM到其重定位项的r_info字段;
解析重定位
静态链接的整个过程分两步,第一步是空间与地址分配,第二步是符号解析与重定位;其中第二步是链接过程的核心,特别是重定位。在重定位过程中,重定位表和符号表起着至关重要的作用,重定位表确定了需要被重定位的地址,符号表决定了被置换成什么值。
示例:
执行命令gcc -c hello.c -o test.o 生成目标文件 test.o
一、可重定位表
连接器在处理目标文件时,须要对目标文件中某些部位进行重定位,即代码段和数据段中那些对绝对地址的引用的位置。这些重定位的信息都记录在ELF文件表里面,对于每个须要重定位的代码段和数据段,都会有一个相应的重定位表,例如 .rel.text 表对应.text段。也就是说,重定位表记录了须要被重定位的地址都在相应段的哪些地方。
执行命令 readelf -a test.o 可以看到 .text 的重定位表 .rel.text
执行 objdump -dS test.o 可以得到test.o 的反汇编代码,如下
1> offset 是重定位入口的偏移。例如,在图一中,符号printf的offset值为021,在,.text偏移地址0x21存储的内容为0xfc ff ff ff 。
2> info 字段表示重定位入口的类型和符号。该值的低8位表示重定位入口的类型, 高24位表示重定位入口的符号在符号表中的下标。
重定位入口的类型:
宏定义 |
值 |
重定位修正方法 |
R_386_32 |
1 |
绝对地址修正 S+A |
R_386_PC32 |
2 |
相对地址修正 S+A-P |
A = 保存在被修正位置的值(例如本例中printf重定位处的值是0xfc ff ff ff)
P = 被修正的位置(相对于段开始的偏移量或者虚拟地址)
S = 符号的实际地址, 即由info的高24位指定的符号的实际地址
高24位表示在符号表中的下标:
在本例中,符号printf的info值的高24位是0x00000E,对应的十进制是14;由可知,下标是14的表项对应的符号是printf,由此可验证info的高24位是符号在符号表中的下标。重定位表只是能指定什么地方需要被重定位,但需要被置换成什么数值,这个是由符号表决定的)。