二进制

源代码的编译和链接

  • 编译:由C语言代码生成汇编语言

  • 汇编:由汇编代码生成机器码

  • 链接:将多个机器码的目标链接成一个可执行文件

image-20230710075946173

可执行文件

什么是可执行文件

可执行文件(Executable file)是一种计算机文件,它包含了一组计算机指令和数据,可以直接在特定的操作系统中运行。可执行文件通常用于执行特定任务或应用程序。

Windows:PE

  • 可执行文件:.exe

  • 动态链接库:.dll

  • 静态链接库:.lib

Linux:ELF

  • 可执行文件:.out

  • 动态链接库:.so

  • 静态链接库:.a

image-20230710083914310

名称

内容

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

此节用于存储动态链接的全局偏移表和过程链接表相关的信息。

...

............

程序的装载与运行

静态链接过程

  1. 加载可执行文件:操作系统负责加载可执行文件到内存中,并创建进程。加载过程中,操作系统会为程序分配内存空间,并将可执行文件的代码段、数据段等内容加载到相应的地址空间。

  2. 初始化:在加载完成后,操作系统会执行一些初始化操作,包括设置栈帧、初始化全局变量和静态变量等。

  3. 程序执行:操作系统会将控制权交给程序的入口点(通常是main()函数),程序开始执行。程序按照顺序执行代码,调用不同的函数和执行各种指令。

  4. 符号解析和重定位:在程序执行过程中,如果遇到对函数或变量的引用,需要进行符号解析和重定位。符号解析是通过查找符号表来确定引用的符号地址,而重定位是将该地址修正为正确的值。

  5. 调用函数和跳转:当程序执行到函数调用或跳转指令时,需要进行相关处理。对于函数调用,会保存当前函数的状态,包括返回地址和局部变量等;然后跳转到被调用函数的入口点,并传递参数。函数执行完毕后,返回到调用点继续执行。

  6. 数据访问:程序可能需要读取或修改数据,包括全局变量、静态变量和常量等。对于全局变量和静态变量,可以直接通过相应的地址进行访问。对于常量,通常会将其存储在只读的数据段中。

  7. 程序结束:当程序执行到结束点或遇到退出指令时,会执行相应的清理操作,并将控制权交还给操作系统。操作系统回收程序所占用的内存,并终止进程的执行。

动态链接执行过程

  1. 加载可执行文件:操作系统负责加载可执行文件到内存中,并创建进程。加载过程中,操作系统会为程序分配内存空间,并将可执行文件的代码段、数据段等内容加载到相应的地址空间。

  2. 初始化:在加载完成后,操作系统会执行一些初始化操作,包括设置栈帧、初始化全局变量和静态变量等。

  3. 程序执行:操作系统会将控制权交给程序的入口点(通常是main()函数),程序开始执行。程序按照顺序执行代码,调用不同的函数和执行各种指令。

  4. 符号解析和重定位:在程序执行过程中,如果遇到对函数或变量的引用,需要进行符号解析和重定位。与静态链接不同的是,动态链接过程中符号解析是在运行时进行的,通过动态链接器(如动态链接库)来完成。动态链接器会根据需要加载相应的共享库文件,并解析其中的符号表,确定引用的符号地址,然后进行重定位。

  5. 函数调用和跳转:当程序执行到函数调用或跳转指令时,会进行相关处理。对于动态链接库中的函数,程序会通过跳转到库文件中的入口点来执行相应的代码。参数传递和返回值处理等操作也会参考约定和调用规则进行。

  6. 数据访问:程序可能需要读取或修改数据,包括全局变量、静态变量和常量等。对于全局变量和静态变量,可以直接通过相应的地址进行访问。对于常量,通常会将其存储在只读的数据段中。

  7. 程序结束:当程序执行到结束点或遇到退出指令时,会执行相应的清理操作,并将控制权交还给操作系统。操作系统回收程序所占用的内存,并终止进程的执行。

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先被压入栈中。

image-20230711174212534

  • 先创建一个栈帧,然后依次存放父函数的基地址(EBP)、函数的参数。

image-20230711181532515

  • 当有子函数时,再开辟一个栈帧,先将该子函数下一指令压入栈中,然后将该子函数的父函数的基地址压入栈中,并且使寄存器EBP指向ESP。

image-20230711181936368

  • 随后将子函数的变量压入栈中。

image-20230711182114513

  • 调用完成之后,子函数参数弹出。

image-20230711181936368

  • 弹出父函数的EBP并将其赋值给寄存器EBP

image-20230711182648410.png

  • 弹出返回地址(子函数EIP)并赋值给寄存器EIP

image-20230711182931625

参数的传递

x86

  • 使用栈来传递参数

  • 使用eax存放返回值

amd64

  • 前6个参数依次存放在rdi、rsi、rdx、rcx、r8、r9

  • 7个以后的参数存放在栈中 [[栈溢出]] [[格式化字符串]]