软件逆向03

编译、链接、装载与ELF文件

Posted by orgaworl on September 10, 2024

1. 基础

一个标准线程由线程 ID, 指令指针 PC, 寄存器集合, 堆栈组成


2. 广义编译

完整过程

  • 预处理
    • gcc -E .cpp => .i cc1
    • 处理 # 预编译指令
      • 展开 #define 宏定义
      • 处理 #ifdef 等 条件预编译指令
      • 处理 #include 预编译指令
      • 删除注释 //
      • 添加行号和文件名标识
    • 进行文本替换
  • 编译 cc1
    • gcc -S .i => .s cc1
    • 词法分析/扫描
    • 语法分析/ 语法树
    • 语义分析
    • 中间代码生成
    • 源代码优化
    • 汇编代码生成
    • 目标代码优化
  • 汇编 as
    • gcc -c .s => .o as
    • 无任何优化, 根据对照表生成机器代码
  • 链接 ld
    • gcc - .o => .out ld
    • 将多个可重定位目标文件合并为可执行目标文件.

狭义编译

  1. 词法分析/扫描 将源代码的字符序列分割为一系列记号 (Token)
    • 关键词: for while if
    • 标识量: student
    • 字面表: 1 2 3 “h”
    • 特殊符号: +-= 并且还将标识符存放到符号表中,将数字字符串常量存放到文字表中
  2. 语法分析/ 语法树 对由扫描器产生的记号进行语法分析, 产生语法树 .
    • 对于任意子树, 先计算其左子树和右子树的结果, 再计算根节点结果.
    • 以符号和数字作为叶节点 ![[grammar-tree.png|575]]
  3. 语义分析 检查表达式有效性,处理类型转换。 编译器进行静态语义分析 (编译器即可确定的语义)
    • 上下文相关性审查(比如,使用了未赋值的变量)
    • 声明和类型的匹配
    • 类型转换 该阶段为语法树的表达式标识了类型,对于隐式转化会在树中插入相应的转换节点
  4. 源代码优化,产生中间代码生成 Intermediate Code
    • 编译器前端将语法树转换为中间语言, 即语法树的顺序表示, 与运行环境无关
    • 包含类型
      • 三地址码 x = y op z
      • P-代码
  5. 目标代码生成
    • 编译器后端(中的代码生成器)将中间语言转为目标机器汇编代码.
  6. 目标代码优化
    • 编译器后端 (中的目标代码优化器)

编译器前后端划分 前端负责生成中间代码 后端负责将中间代码转换为目标机器代码,包括:

  • 目标代码生成器
  • 目标代码优化器

优点:

  • 不同平台使用同一个前端和多个针对性后端

两次优化


3. 链接

符号 子程序或变量的起始地址

模块间通信:

  • 模块间函数调用
  • 模块间变量访问
  • 两者都是”模块间符号的引用”

    3.1 静态链接

    原理: 将指令对其他符号地址的引用进行修正;

编译目标文件时, 将目标地址设为 0, 由链接器在进行链接时将其修正. 地址修正的过程称为重定位, 每个修正的地方为 重定位入口.

链接前目标文件中所有节的VMA都为0,虚拟地址尚未分配, 链接过程中计算对应值. 链接后各个节被分配到相应的虚拟地址

合并方式

  • 按序叠加
  • 合并相似节: 将目标文件中的相似节合并为一个段segment.

两步链接

合并相似节对应两步链接方法

地址和空间分配

扫描所有输入的目标文件, 记录所有节的长度、属性、位置, 将所有的符号定义和符号引用统一存放到全局符号表中,合并所有节为多个段, 计算合并后段的长度和位置, 建立映射关系.

此过程中生成程序头表 Program-Header-Table. ![[ELF-file-structure.png|475]]

产生的段

  • 代码段 text (只读内存段)
    • ELF-Header Program-Header-Table .init .text .rodata
  • 数据段 data (读/写内存段)
    • .data .bss
  • 不加载到内存中的符号表、调试信息
    • section-head-table .symtab .debug .strtab
符号解析(符号决议)

读取节的数据和重定位数据, 进行符号解析和重定位、调整代码、调整代码中地址

符号解析: 将“每个引用”和”可重定位目标文件符号表中一个确定的符号定义“关联起来.

局部符号解析:静态局部变量

  • 要求每个模块中每个局部符号有一个定义
  • 并要求每个定义有唯一名字

多重定义的全局符号解析: 全局变量和函数

  • 不允许出现多个强符号,可以出现多个弱符号
  • 定义函数和初始化全局变量为强符号
  • 未初始化全局变量为弱符号.
  • 当存在强符号时,必选强符号;如果此时存在弱符号占用空间更大则警告
  • 当不存在强符号时, 从弱符号中任意选择; 类型不同时选择空间最大符号.
重定位

在链接前两步已经确定所有符号的虚拟地址,据此对每个需要重定位的符号进行地址修正。

ELF文件使用重定位表用以保存重定位相关信息,每个要被重定位的节都有一个对应的重定位表.

  • .rel.text
  • .rel.data

每个重定位位置即为一个重定位入口.


3.2 动态链接

程序运行时进行链接,需要OS支持,需要同时映射可执行文件和共享目标文件

  • 静态库
    • 浪费内存和磁盘空间
    • 更新必须重新链接,更新困难

根据共享目标文件在内存中的地址分配可划分:

  • 静态共享库(地址固定)
    • OS在某个特定地址划分处一些地址块, 为模块预留空间
    • 会导致地址冲突、升级困难等问题,基本不采用.
  • 动态共享库(地址不固定)
    • 又称装载时重定位,在链接时对所有绝对地址的引用不做重定位,而是在装载时进行重定位。
    • 当模块装载地址确定,即目标地址确定,对所有绝对地址引用进行重定位

地址无关代码PIC 将指令中需要被修改的部分分离出来,与数据部分放在一起,数据部分可以在每个进程中拥有一个副本。

模块内部函数调用与数据访问,使用相对地址调用,无需重定位 模块外部函数调用与数据访问,使用代码无关地址技术,将地址相关部分放到数据段,以便能够同时读写,且每个进程都有独立副本互不影响。

在数据段中创建全局偏移表GOT,用以指向需要访问的外部符号,代码引用外部符号时使用GOT进行间接引用

链接器在装载模块时会查找每个变量所在的地址,随后填充GOT中各个项。


4. 目标文件

发展变革:

  • a.out
  • COFF
  • PE & ELF
![[object-file-evolution.png 575]]

5. PE 格式目标文件

  1. DOS HEADER
  2. DOS Stub
  3. IMAGE NT HEADERS
  4. SECTION Table
  5. Section

6. ELF 格式目标文件

  • 分类
    • 可重定位文件: 包括静态链接库
    • 可执行文件
    • 共享目标文件
    • 核心转储文件

目标文件按照属性不同将数据划分为多个节 section

文件内容

  • ELF Header
  • Program-head-table 链接后存在
  • Sections
    • .text(只读)
    • .data 有初值全局变量和静态变量
    • .bss 未初始化局部静态变量编译时一定存放在 .bss 节中 未初始化全局变量编译时不一定存放在 .bss 节中, 与编译器有关. 不存放则预留一个未定义全局变量符号,等待链接时在.bss节分配空间
    • .rodata(只读) 只读变量 (const 修饰), 字符串常量
    • .comment 编译器版本信息字符串
    • .debug 调试信息
    • .dynamic 动态链接信息
    • .hash 符号哈希表
  • Section-head-table 描述所有节的属性
    • 有n个节描述符 Elf32_Shdr,其中第一个为无效节描述符,共(n-1)个有效节.
  • Symbol table
    • .symtab 符号表
      • Elf32_Sym数组, 每个元素对应一个符号, 第一个元素无效.
      • 符号值st_value: 函数或变量地址
    • 包含符号分类
      • 定义在本目标文件中全局符号
      • 在本目标文件中引用的外部符号
      • 节名
      • 局部符号
      • 行号信息(可选)
    • 链接时只关注全局符号和外部符号
  • String table
    • .strtab 字符串表 : 保存普通字符串
    • .shstrtab 节头字符串表 : 保存节头表中用到的字符串(如, 节名)
  • Relocation table
    • 每个需要重定位的代码节或数据节都需要单独的重定位节
    • .rel.text
    • .rel.*

![[ELF-file-content.png]]

6.1 文件头 ELF Header

描述整个文件属性.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define EI_NIDENT(16)
typedef struct{
  unsigned char	e_ident[EI_NIDENT];	/* Magic number和其它信息 */
  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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
![[ELF-header.png 575]]

取值:

  • MagicNum 中
    • Magic:
      • 0x7F, 0x45 ,0x4c, 0x46
      • 对应:DEL ‘E’ ‘L’ ‘F’
    • ELF文件类型
      • 0 : 无效;1 : 32bit;2 : 64bit
    • 字节序: 大小端
      • 0: 无效;1: 小端;2: 大端
    • ELF版本
      • 01
    • OS/ABI
    • ABI 版本
  • etype
    • 1可重定位; 2可执行; 3共享
  • emachine
    • 3 intelx86

readelf -h file

![[readelf-result.png|600]]

6.2 程序头表 Program-head-table

主要描述了程序执行时在内存中的布局以及如何映射到内存中, 是对可执行二进制文件中段的描述, 程序装载时必需。

在可执行ELF文件中必需, 可链接文件中可选,通过program header来获得每个segment的属性, 通过section header来获得每个section的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef struct {
    Elf32_Word    p_type;     /* segment type */
    Elf32_Off     p_offset;   /* segment offset */
    Elf32_Addr    p_vaddr;    /* virtual address of segment */
    Elf32_Addr    p_paddr;    /* physical address - ignored? */
    Elf32_Word    p_filesz;   /* number of bytes in file for seg. */
    Elf32_Word    p_memsz;    /* number of bytes in mem. for seg. */
    Elf32_Word    p_flags;    /* flags */
    Elf32_Word    p_align;    /* memory alignment */
} Elf32_Phdr;
typedef struct {
    Elf64_Half    p_type;     /* entry type */
    Elf64_Half    p_flags;    /* flags */
    Elf64_Off     p_offset;   /* offset */
    Elf64_Addr    p_vaddr;    /* virtual address */
    Elf64_Addr    p_paddr;    /* physical address */
    Elf64_Xword   p_filesz;   /* file size */
    Elf64_Xword   p_memsz;    /* memory size */
    Elf64_Xword   p_align;    /* memory & file alignment */
} Elf64_Phdr;

链接前每个节的VMA、LMA都为0, 链接过程中计算对应值.

readelf ![[Pasted image 20240630141320.png]]

常见段类型:

  • PT_LOAD 会被装载或映射到内存中
  • PT_DYNAMIC 动态段,动态链接可执行文件特有的段,包含
    • 运行时需要链接的共享库列表
    • 全局偏移表GOT
    • 重定位条目相关信息

6.3 节 Sections

重定位表 .rel.*

.rel.text

1
2
3
4
5
6
7
8
9
10
typedef struct{  
Elf64_Addr r_offset;  
Elf64_Xword r_info;  
} Elf64_Rel;

typedef struct{  
Elf64_Addr r_offset; /* Address */  
Elf64_Xword r_info; /* Relocation type and symbol index */  
Elf64_Sxword r_addend; /* Addend */  
} Elf64_Rela;

符号表 .symtab

记录了目标文件中用到的所有符号, 每个符号拥有对应符号值, 即地址.

符号表中符号:

  • 定义在本目标文件中全局符号
  • 本目标文件引用的外部符号
  • 节名, 值为该节的起始地址
  • 局部符号, 编译内部可见, 对链接无任何作用
  • 行号信息, 可选

链接只考虑全局符号和外部符号.

符号表由多个 Elf32_Sym 结构体组成, 第一个元素为无效的未定义符号.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Elf32_Sym  
{  
  Elf32_Word    st_name;   /* Symbol name (string tbl index) */  
  Elf32_Addr    st_value;  /* Symbol value */  
  Elf32_Word    st_size;   /* Symbol size */  
  unsigned char st_info;   /* Symbol type and binding */  
  unsigned char st_other;  /* Symbol visibility */  
  Elf32_Section st_shndx;  /* Section index */  
};  
struct Elf64_Sym  
{  
  Elf64_Word    st_name;   /* Symbol name (string tbl index) */  
  unsigned char st_info;   /* Symbol type and binding */  
  unsigned char st_other;  /* Symbol visibility */  
  Elf64_Section st_shndx;  /* Section index */  
  Elf64_Addr    st_value;  /* Symbol value */  
  Elf64_Xword   st_size;   /* Symbol size */  
};

readelf ![[Pasted image 20240630141450.png]]

字符串表 .strtab

将所有字符串集中到一个表中, 利用偏移使用不同的字符串。 保存普通字符串,包括:

  • 符号名

节头表字符串表 .shstrtab

保存节头表中使用的字符串:

  • 节名

6.4 节头表 Section-head-table

包含多个节描述符 ( Elf32_Shdr 结构体), 从中可以得到每个节的所有信息,第一个元素是无效节描述符。

  • 节名
  • 节长度
  • 文件中的偏移
  • 读写权限
  • 其他节属性

可链接文件必有, 可执行文件可选,通过program header来获得每个segment的属性, 通过section header来获得每个section的属性。

1
2
3
4
5
6
7
8
9
10
11
12
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;
![[Pasted image 20240603232545.png 625]]

readelf ![[Pasted image 20240630141057.png]] ![[Pasted image 20240630141154.png]]


7. 程序的装载

一、装载的方式

页映射:将内存和磁盘中的数据和指令按照页为单位划分,以后装载和操作的单位就是页。4kb

二、程序的装载运行步骤

  1. 创建一个独立的虚拟地址空间;(虚拟页到物理页的映射关系,此时为空即可,缺页错误时会自动设置)
  2. 读取可执行文件头,检查文件有效性
  3. 读取可执行文件程序头表,建立虚拟地址空间和可执行文件的映射关系;
  4. 动态链接的过程;
    • 启动动态链接器,将控制权交给动态链接器入口
    • 动态链接器根据环境参数对可执行文件进行动态链接工作
  5. 控制权转交给可执行文件
![[Pasted image 20240630175347.png 500]]

内存读写权限配置

.rodata .text 只读