一、简介
ELF 文件是一种用于存储可执行程序、目标文件、共享库等的标准文件格式,在 Linux 及其他类 Linux 操作系统(如 Unix 等)中广泛应用。它定义了一种规范的结构,使得操作系统能够准确地加载程序并使其在内存中正确执行。
简单来说,当我们编写一段代码(比如用 C 语言编写)并经过编译后,生成的可执行文件往往就是以 ELF 格式存在的。它包含了程序的代码段(存放可执行的机器指令)、数据段(存放程序中定义的各种变量等数据)、符号表(记录程序中定义的函数、变量等符号信息,用于链接等操作)等重要部分。
什么是可执行文件:
广义:文件中的数据是可执行代码的文件,例如:.out .exe .py .sh
狭义:文件中的数据是机器码的文件,例如:.out .exe .dll .so
可执行文件分类
Windows:PE 可执行程序:.exe 动态链接库:.dll 静态链接库:.lib
Linux:ELF 可执行程序:.out 动态链接库:.so 静态链接库:.a
文件格式
ELF(Executable and Linkable Format)是一种常见的可执行文件和共享库格式,其结构如下:
ELF header:
包含了 ELF 文件的基本属性信息。
头部(Header):ELF文件的头部包含了整个文件的基本信息,如文件类型、机器架构、入口点地址等。可以将头部看作是文件的元数据,就像书的封面上包含了书的基本信息。
可以用readelf查看,命令如下
readelf -h
16字节ELF标识,前4字节是ELF文件标识"\x7fELF",不可修改
size of this header 即ELF文件头本身的大小,这里是 ELF文件头是64个字节
start of program headers 即程序表 segment在文件中的偏移,即56个字节
size of section headers 即段表section在文件中的偏移,即第64个字节
Number of program headers 即程序表的个数,为36个,说明这个文件中有36个程序表
Program header table:
描述了程序在内存中的加载情况,包括可执行代码段、数据段等。
Section header table:
描述了 ELF 文件中所有节区的信息,如代码节区、数据节区等。
节区数据:包含了 ELF 文件中的实际数据,如代码、数据等。
ELF 文件的结构可以根据不同的操作系统和体系结构而有所差异。
节区表(Section Table):节区表是一个数据结构,它描述了ELF文件中各个节区的位置和属性。类似于书的目录,它告诉你在书中的哪个位置可以找到某个章节或内容。
代码节区(Code Section):代码节区包含了程序的实际指令,即可执行的机器码。这部分就像是一本书中的具体章节内容,包含了实际的文字和故事情节。
数据节区(Data Section):数据节区存储了程序中使用的全局变量、静态变量和常量等数据。可以把它看作是书中的附录或者注释部分,包含了与故事情节相关的补充信息。
符号表(Symbol Table):符号表包含了程序中定义的函数和变量的信息,比如名称、地址等。可以将符号表类比为书的索引,你可以通过索引查找到书中特定关键字或人物的出现位置。
动态链接表(Dynamic Linking Table):动态链接表包含了程序在运行时所需的动态链接信息,比如依赖的共享库和符号解析的过程。它就像是一本参考书,提供了额外的信息和解释。
可以用readelf查看,命令如下
readelf -S
段的类型(sh_type):标识这个段的类型,例如:NULL(代表 无效段), PROGBITS(存储了程序的数据和代码),SYMTAB(代表 符号表),STRTAB(代表 字符串表),RELA(代表 重定位表),HASH(符号表的hash表),DYNAMIC(代表 动态链接信息),NOBITS(代表 程序空间内没有数据),REL(重定位信息),DNYSYM(动态链接的符号表)。
段的地址(address): 表示执行时候的虚拟地址,这估计在程序装载的时候才有,待学习。
段的偏移(sh_offset)和段的大小(sh_size):表示该段相对于文件的偏移量和该段的存储大小,这里以十六进制体现
段的ES(sh_entsize): 表示该段如果存在一个表table,那么这个ES属性则显示每个entry的字节的大小,否则为0。
段的标志位(sh_flags): 表示该段在进程虚拟地址空间的属性,比如是否可写、要分配空间、可执行等。
二、ELF文件在IDA中展现形式
ELF文件本身是一个二进制文件,对应IDA中 HEX VIEW 界面中内容,但关注更多的内容可能是 IDA VIEW。IDA VIEW中是对 HEX VIEW 界面中内容从头到尾的解析,并且内容一一对应(HEX VIEW内容偏移和IDA VIEW的左侧地址对应,不管鼠标在IDA VIEW还是HEX VIEW的哪个位置停留,点击进另一个界面时,地址或偏移都是对应的)。不管二进制文件有多大,解析的汇编内容都只是在IDA VIEW,只是文件很大时,解析会比较慢。
如果二进制内容是文件头或节头或程序头,IDA VIEW中会以从LOAD段解析ELF文件,或是字符串或是顺序排列形式呈现,直到.plt节或.text节,IDA VIEW中开始以汇编代码的形式呈现。
IDA解析ELF格式时,会同时参考节头表和程序头(段)表。如果将节头表内容抹除或改偏移让IDA解析不到节表时,IDA VIEW会完全根据LOAD段来解析内容,原先现实的节全部变成LOAD。如果将程序头表内容抹除或改偏移让IDA解析不到程序头表时,IDA VIEW只会解析节表的内容,正常情况下的LOAD段那一部分内容不会在IDA VIEW和HEX VIEW中出现。
1.节表含义
1>节表名称信息
IDA没有展示elf文件所有节的内容,在IDA中使用Ctrl + s可显示节表名称等信息
全部的节表在上面已经展示过了,就不说了
2>节对应功能
.text: 该节中包含程序的指令代码;
.init: 该节包含进程初始化时要执行的程序指令;当程序开始运行时,系统会在进程进入主函数之前先执行这一个节中的指令代码;
.fini: 该节中包含进程终止时要执行的指令代码;当程序退出时,系统会执行这个节中的指令代码;
.bss : 该节中包含目标文件中未初始化的全局变量;一般情况下,可执行程序在开始执行时,系统会把这一段内容清零;但是在运行期间的.bss段是由系统动态初始化而成的,目标文件中的.bss节中并不包含任何内容,其长度为0,所以它的节类型为SHT_NOBITS;
.data:这两个节用于存放程序中已被初始化过的全局变量;在目标文件中,它们是要占用实际的存储空间的,这一点与.bss节不同;如果全局变量是整形 .rodata:这两个节中包含程序中的只读数据,例如函数体内,只读的字符串会存储在该节中;
.dynamic: 该节中包含动态链接信息,并且可能有SHF_ALLOC和SHF_WRITE等属性;
.dynstr : 该节中包含用于动态链接的字符串,一般是那些与符号表相关的动态符号的名字;
.dynsym : 该节中包含动态链接符号表;
.got : 该节中包含全局偏移表(global offset table),存放的是类似相对_GLOBAL_OFFSET_TABLE_的偏移;
.plt : 该节中包含函数链接表(program link table);
.hash : 该节中包含一张哈希表,用于动态段中查找动态符号;
.interp : 该节中包含ELF文件解析器的路径名;如果该节被包含在某个可装载的段中,那么该节的属性中应设置SHF_ALLOC标志位;
.strtab : 该节用于存放字符串,主要是那些符号表项的名字;如果一个目标文件中有一个可装载的段,并且其中含有符号表,则该节的属性中应该有SHF_ALLOC属性;
.symtab : 该节用于存放符号表;如果一个目标文件中有一个可装载的段,并且其中含有符号表,则该节的属性中应该有SHF_ALLOC属性;
.shstrtab: 该节是节名字表,含有所有其它节的名字;
.comment: 该节中包含版本控制信息;
.line : 该节中包含调试信息,包括哪些调试符号的行号,为程序指令码与源文件的行号建立联系;
.note : 该节中包含注释;
rel.dyn节的每个表项对应了除了外部过程调用的符号以外的所有重定位对象,
.rel.plt节的每个表项对应了所有外部过程调用符号的重定位信息。
三、步骤
1.查看 ELF 文件基本信息
使用file
命令可以快速查看一个文件是否为 ELF 文件以及它的一些基本类型信息。例如,如果有一个名为pwn4的文件,在终端中执行
file pwn4
2.查看 ELF 文件详细结构(使用readelf
工具)
安装readelf
工具(在大多数 Linux 发行版中,它通常已经预装,如果没有,可以通过包管理器安装)。
1>要查看 ELF 文件的头部信息,可以执行:
readelf -h pwn4
这将显示 ELF 文件的头部结构,包括文件类型、目标机器架构、入口点地址等重要信息
2>要查看节区头表信息,可以执行:
readelf -S pwn4
这将显示各个节区的名称、大小、偏移量等详细信息
3>要查看程序头表信息,可以执行:
readelf -l pwn4
这将显示各个段在内存中的映射情况等信息
3.分析 ELF 文件中的符号表(使用nm
工具)
安装nm
工具(同样,在大多数 Linux 发行版中通常已预装)。
执行以下命令来分析符号表:
nm pwn4
这将显示出文件中所有的符号(包括函数、变量等)及其地址等信息,有助于了解程序内部的结构和函数调用关系
四、IDA使用简记
1.快捷键
F5:查看伪代码
D:Options——Setup Data Types便可选定D时设置的数据类型;
A:可以将图形字符由十六进制数转化成图形,如果一个字节不行,可多选一些字节;
C:将数据转换成代码;
G:跳转至相应的地址处;
Ctrl+F:寻找相应名称的内容;
Shift+F9:打开结构体类型窗口;
在结构体类型窗口中,
Insert(Fn+PgDn得到):创建新的结构体类型;
Delete:删除某一个结构体类型;
在结构体end处,按‘D’便可插入成员变量;
Alt+Q将某个变量转成一个结构体类型,如果代码区有符号变红,说明该地址处原先的也是一个构体,但后来被其它的定义给覆盖,按两下‘K’即可打散数据;
K:打散数据;
T:将某一符号转成结构体成员变量;
对于一个函数起始处,D、C、右键——CreateFunction即可还原。
2.ida中查找数组类型及个数的方法
通过Edit——Operand——Number——选择转换的类型。
Y键可以让变量的类型重新更改;
写一个*.h文件,然后通过File——LoadFile——Parse C Header File (Ctrl+ F9),然后选择插入结构体,用快捷键INS,会出现一个
窗口界面,左下角出现Add standard Structcture,点击便可从导入的结构体中选取。很多时候如果ida不能识别某些结构体,可以写写头文件,然后导入到ida中。
在汇编中,只要是符合内容属性一致、地址具有连续性,就可以断定这一串数据是数组了,当然可能作者并没有定义数组,但是只要符合以上两个属性,就是了。数据的类型首先是判断字节数,然后根据指令,判断是浮点型数据,还是整型数据,当然通过普通整型赋值指令也可以判别。lea edi, [esp+esi4+1D0h+var_1A4]指令便说明var_1A4为某一数组的首地址,每个元素字节为4,因为比例因子为4,lea edx, [esp+eax8+1D0h+var_168]指令中var_168是元素字节为8的数组的首地址。
对于数组元素,一般都会有赋值,所以可通过赋值,在内存查找相似数据且相连的一个数据,其中的个数便是数组元素个数。当然对于占很大字节的数组初始化为0内容时,一般会用串拷贝指令,且是rep stosd,如指令mov ecx, 58h xor eax, eax lea edi, [esp+1D0h+var_160] mov [esp+1D0h+var_168], 0 mov [esp+1D0h+var_164], 0 xor ebp, ebp rep stosd