二进制基础
二进制
源代码的编译和链接
编译:由C语言代码生成汇编语言
汇编:由汇编代码生成机器码
链接:将多个机器码的目标链接成一个可执行文件
可执行文件
什么是可执行文件
可执行文件(Executable file)是一种计算机文件,它包含了一组计算机指令和数据,可以直接在特定的操作系统中运行。可执行文件通常用于执行特定任务或应用程序。
Windows:PE
可执行文件:.exe
动态链接库:.dll
静态链接库:.lib
Linux:ELF
可执行文件:.out
动态链接库:.so
静态链接库:.a
名称 | 内容 |
---|---|
ELF头部(ELF Header) | 位于文件的开头,包含了描述整个ELF文件的基本信息。其中包括魔数、ELF文件类型、架构类型、入口地址、段表和节表的偏移等。 |
程序头表(Program Header Table) | 包含了描述程序运行时所需的各个段(Segment)的信息。例如,代码段、数据段、动态链接段等。每个段的大小、虚拟内存地址、文件偏移等信息都存储在程序头表中。 |
节头表(Section Header Table) | 包含了描述各个节(Section)的信息。节是ELF文件中各个具有特定功能的区域,如代码节、数据节、符号表节等。节头表记录了每个节的名称、大小、偏移等信息。 |
节数据(Section Data) | 即各个节的实际数据,例如代码、数据、符号表等。ELF文件中的程序和数据都存储在不同的节中。 |
符号表(Symbol Table) | 包含了程序中定义和引用的符号(Symbol)信息,如变量、函数、全局变量等。符号表可以用于进行符号解析和动态链接。 |
动态节(Dynamic Section) | 包含了动态链接所需的信息,如共享库依赖、动态链接器的名称等。 |
除了以上主要部分外,ELF文件还包含其他一些部分,如字符串表、重定位表、调试信息等。这些部分提供了更多的元数据和调试信息,以支持程序的动态链接、调试和分析等功能。
段和节
Section称为节,是指在汇编源码中经由关键字section或segment修饰、逻辑划分的指令或数据区域,汇编器
会将这两个关键字修饰的区域在目标文件中编译成节,也就是说"节"最初诞生于目标文件
中。
Segment称为段,是链接器根据目标文件中属性相同的多个Section合并后的Section集合
,这个集合称为Segment,也就是段,链接器把目标文件链接成可执行文件,因此段最终诞生于可执行文件
中。我们平时所说的可执行程序内存空间中的代码段
和数据段
就是指的Segment。
代码段
.text节
.rodata节
.dynsym节
.dynstr节
.plt节
.rel.got节
.line节
...........
数据段
.data节
.dynamic节
.got节
.got.plt节
.bss节
...........
段视图常用于执行时的内存区域权限划分,而节视图常用于链接编译或内存存储。
名称 | 内容 |
---|---|
.text | 此节区包含程序的可执行指令。 |
.data | 这些节区包含初始化了的数据,将出现在程序的内存映像中。 |
.bss | 它存储了程序中未初始化的全局变量和静态变量的数据。在编译和链接过程中,所有位于.bss节中的变量都被初始化为零或空值。 |
.dynsym | 此节区包含了动态链接符号表。 |
.dynstr | 此节区包含用于动态链接的字符串 |
.dynamic | 此节区包含动态链接信息。 |
.got | 此节用于存储全局变量的地址。 |
.got.plt | 此节用于存储动态链接的全局偏移表和过程链接表相关的信息。 |
... | ............ |
程序的装载与运行
静态链接过程
加载可执行文件:操作系统负责加载可执行文件到内存中,并创建进程。加载过程中,操作系统会为程序分配内存空间,并将可执行文件的代码段、数据段等内容加载到相应的地址空间。
初始化:在加载完成后,操作系统会执行一些初始化操作,包括设置栈帧、初始化全局变量和静态变量等。
程序执行:操作系统会将控制权交给程序的入口点(通常是
main()
函数),程序开始执行。程序按照顺序执行代码,调用不同的函数和执行各种指令。符号解析和重定位:在程序执行过程中,如果遇到对函数或变量的引用,需要进行符号解析和重定位。符号解析是通过查找符号表来确定引用的符号地址,而重定位是将该地址修正为正确的值。
调用函数和跳转:当程序执行到函数调用或跳转指令时,需要进行相关处理。对于函数调用,会保存当前函数的状态,包括返回地址和局部变量等;然后跳转到被调用函数的入口点,并传递参数。函数执行完毕后,返回到调用点继续执行。
数据访问:程序可能需要读取或修改数据,包括全局变量、静态变量和常量等。对于全局变量和静态变量,可以直接通过相应的地址进行访问。对于常量,通常会将其存储在只读的数据段中。
程序结束:当程序执行到结束点或遇到退出指令时,会执行相应的清理操作,并将控制权交还给操作系统。操作系统回收程序所占用的内存,并终止进程的执行。
动态链接执行过程
加载可执行文件:操作系统负责加载可执行文件到内存中,并创建进程。加载过程中,操作系统会为程序分配内存空间,并将可执行文件的代码段、数据段等内容加载到相应的地址空间。
初始化:在加载完成后,操作系统会执行一些初始化操作,包括设置栈帧、初始化全局变量和静态变量等。
程序执行:操作系统会将控制权交给程序的入口点(通常是
main()
函数),程序开始执行。程序按照顺序执行代码,调用不同的函数和执行各种指令。符号解析和重定位:在程序执行过程中,如果遇到对函数或变量的引用,需要进行符号解析和重定位。与静态链接不同的是,动态链接过程中符号解析是在运行时进行的,通过动态链接器(如动态链接库)来完成。动态链接器会根据需要加载相应的共享库文件,并解析其中的符号表,确定引用的符号地址,然后进行重定位。
函数调用和跳转:当程序执行到函数调用或跳转指令时,会进行相关处理。对于动态链接库中的函数,程序会通过跳转到库文件中的入口点来执行相应的代码。参数传递和返回值处理等操作也会参考约定和调用规则进行。
数据访问:程序可能需要读取或修改数据,包括全局变量、静态变量和常量等。对于全局变量和静态变量,可以直接通过相应的地址进行访问。对于常量,通常会将其存储在只读的数据段中。
程序结束:当程序执行到结束点或遇到退出指令时,会执行相应的清理操作,并将控制权交还给操作系统。操作系统回收程序所占用的内存,并终止进程的执行。
x86架构下的寄存器
通用寄存器:
EAX(累加器):用于存放函数返回值或一般性的计算结果。
EBX(基址寄存器):一般用作指针的基地址,也可以用于存放通用数据。
ECX(计数器):用于循环计数或其他计数操作。
EDX(数据寄存器):用于存放通用数据。
ESI(源变址寄存器):通常用作源操作数的指针。
EDI(目标变址寄存器):通常用作目标操作数的指针。
ESP(栈指针):指向栈顶元素。
EBP(基址指针):在函数调用时用于保存旧的栈帧。指向栈底元素。
段寄存器:
CS(代码段寄存器):存放当前执行代码所在的代码段。
DS(数据段寄存器):存放数据段的起始地址。
SS(堆栈段寄存器):存放当前的堆栈段的起始地址。
ES(附加段寄存器):作为附加的数据段寄存器。
FS 和 GS(附加段寄存器):作为附加的数据段寄存器,用于扩展地址空间。
标志寄存器:
EFLAGS:存放各种标志位的状态,包括进位标志、零标志、符号标志、溢出标志等。
指令指针寄存器:
EIP:存放下一条将要执行的指令的地址。
控制寄存器和调试寄存器:
CR0、CR2、CR3、CR4:控制寄存器,用于控制和管理处理器的特性和行为。
DR0、DR1、DR2、DR3、DR6、DR7:调试寄存器,用于调试和跟踪代码的执行。
栈
栈在程序运行时起着至关重要的作用。函数调用栈在内存中连续,用来存储函数运行时的状态信息,包括函数参数与局部变量。调用函数时,函数的状态会被保存在栈中,函数结束即从栈中弹出。函数调用栈在内存中从高地址向低地址变化,所以栈顶对应的内存地址在进栈时变小,弹出时变大。
相关寄存器
名称 | 作用 |
---|---|
EBP | 存储当前函数状态的基地址,即栈底元素地址。 |
ESP | 指向栈顶元素 |
EIP | 存放下一条将要执行的指令的地址。 |
C语言函数调用栈
在调用一个函数之前,首先会将调用函数的下一条指令压入栈中,即EIP先被压入栈中。
先创建一个栈帧,然后依次存放父函数的基地址(EBP)、函数的参数。
当有子函数时,再开辟一个栈帧,先将该子函数下一指令压入栈中,然后将该子函数的父函数的基地址压入栈中,并且使寄存器EBP指向ESP。
随后将子函数的变量压入栈中。
调用完成之后,子函数参数弹出。
弹出父函数的EBP并将其赋值给寄存器EBP
弹出返回地址(子函数EIP)并赋值给寄存器EIP
参数的传递
x86
使用栈来传递参数
使用eax存放返回值
amd64
前6个参数依次存放在rdi、rsi、rdx、rcx、r8、r9
7个以后的参数存放在栈中 [[栈溢出]] [[格式化字符串]]