
这是《【软件与系统安全】笔记与期末复习》系列中的一篇
2022-02-21 第三次课后部分
2022-02-28 第四次课
2022-03-21 第四次课前半部分
x86: 基于 Intel 8086/8088 CPU 的一系列向后兼容的 ISA 的总称
IA-32: 32 位版本的 x86 指令集体系结构
三种主要操作模式:
CISC(Complex Instruction Set Computer)体系结构
x64: 又称 x86-64, 是 x86 的扩展, 是与 x86 兼容的 64 位 ISA
字节序:多字节数据在内存中存储或在网络上传输时各字节的存储/传输顺序
小端序(little endian):低位字节存储在内存中低位地址, 效率较高(Intel CPU 使用)
大端序(big endian):低位字节存储在内存中高位地址, 符合思维逻辑。RISC 架构处理器(如 MIPS, PowerPC)采用
分段内存模型
程序内存由一系列独立的地址空间(称为“段”)组成。代码、数据和栈在不同的段中
逻辑地址=段选择器 + 偏移量
保护模式下的内存管理:分段(必须)+ 分页(可选)

程序线性地址空间 ≤4GB, 物理地址空间 ≤64GB
每个段最大 232 字节, IA-32 程序最多使用 16383 个段


通用寄存器
x86 通用寄存器(GPR): 8 个 32 位

x64 通用寄存器:16 个 64 位
指令指针寄存器
x86 上 32 位的 EIP, 存放当前代码段中将被执行的下一条指令的线性地址偏移
程序运行时, CPU 根据 CS 段寄存器和 EIP 寄存器中的地址偏移读取下一条指令, 将指令传送到指令缓冲区, 并将 EIP 寄存器值自增, 增大的大小即被读取指令的字节数
不能直接修改 EIP, 修改途径:
程序状态与控制寄存器
段寄存器
控制寄存器(CR0-CR4)
调试寄存器(DR0-DR7)
系统表指针寄存器(GDTR, LDTR, IDTR, task register)
MMX(MM0-MM7, XMM0-XMM7)
FPU(ST0-ST7, …)
x64 允许指令在引用数据时使用相对于 RIP 的地址,常用于访问全局变量, 全局变量 a 常通过 a(%rip) 进行访问


Relative Instruction-Pointer


x64 规范地址(Canonical Address)
AT&T: source 在 destination 前,在较早期的 GNU 工具中普遍使用(如 gcc, gdb 等)
mov $4, %eax
mov $4, %(eax)
Intel: destination 在 source 前, “[. . .]”含义类似于解引用, MASM, NASM 等工具中使用
mov eax, 4
mov [eax], 4


典型内存寻址方式

MOVS (MOVSB / MOVSW / MOVSD): 用于实现字符串或内存的复制

SCAS: 用 AL/AX/EAX 减去[EDI],更新 EFLAGS,并对 EDI 自增/自减

STOS: 将 AL/AX/EAX 的值写入 EDI 指向的内存

x64 算术运算: 多数算术运算都提升到 64 位,即使操作数只有 32 位
MOV RAX, 1122334455667788H
XOR EAX, EAX ;也会清除RAX的高32位,执行后RAX=0
MOV RAX, 0FFFFFFFFFFFFFFFFH
INC EAX ;执行后RAX=0
CMP: 算数比较
TEST: 逻辑比较
JMP: 无条件跳转到目标指令地址(可用相对地址或绝对地址)
Jcc (cc=conditional code)
栈: 线性地址空间中连续的内存区域, 后进先出, 存在于一个栈段内,该栈段可由段寄存器 SS 检索的段描述符指向:
栈操作指令
PUSH EBP; MOV EBP, ESPMOV ESP, EBP; POP EBP“ESP:(SS 段中的)栈指针, 栈区域的栈顶地址”1
“栈帧基指针: 由 EBP 指向的被调用函数栈帧的固定参考点”2
栈帧: 是将调用函数和被调用函数联系起来的机制,栈被分割为栈帧,栈帧组成栈。栈帧的内容包含:
栈帧基指针: 由 EBP 指向的被调用函数栈帧的固定参考点
返回指令指针: 由 CALL 指令压入栈中的 EIP 寄存器中的指令地址

函数的指令框架
PUSH EBP
MOV EBP, ESP
…
MOV ESP, EBP ;opt
POP EBP
RETN
EBP “EBP:(SS 段中的)栈内数据指针, 栈帧的基地址, 用于为函数调用创建栈帧”3
ESP “ESP:(SS 段中的)栈指针, 栈区域的栈顶地址”1
EIP “x86 上 32 位的 EIP, 存放当前代码段中将被执行的下一条指令的线性地址偏移”4
esp,eip,ebp -->对应64位的 rsp,rip,rbp
CALL 指令语义(及后继指令语义)
RET 指令语义(及前序指令语义)
RETN 指令执行之前
将栈顶的内容(返回指令指针)弹出到 EIP
若 RETN 指令有参数 n, 则将 ESP 增加 n 字节, 从而释放栈上的参数
恢复对调用函数的执行
近调用/近返回
控制流转移到/出当前代码段中的被调用函数, 通常提供对本地函数的访问
远调用/远返回
控制流转移到/出其他代码段中的被调用函数, 通常提供对 OS 函数或其他进程函数的访问
是对函数调用时如何传递参数和返回值的约定
x86 主要调用惯例
System V AMD64 ABI 调用惯例

long myfunc(long a, long b, long c, long d,
long e, long f, long g, long h) {
long xx = a * b * c * d * e * f * g * h;
long yy = a + b + c + d + e + f + g + h;
long zz = utilfunc(xx, yy, xx % yy);
return zz + 20;
}

rsp 指针向下(低地址)的 128 字节栈空间可保留为不被信号或中断处理程序更改,,从而作为函数的临时数据空间, 称为 red zone
叶函数(即不调用其他函数的函数)使用 red zone 作为其栈帧,而不需要在其序言语句和收尾语句中修改栈指针
叶函数实例
long utilfunc(long a, long b, long c){
long xx = a + 2;
long yy = b + 3;
long zz = c + 4;
long sum = xx + yy + zz;
return xx * yy * zz + sum;
}

对于需要栈帧的函数, 处理栈帧基址针有两种方法
方法 1: 传统方法(GCC 无优化选项时)
方法 2: 更快的方法(GCC 优化选项时)
中断: 通常指由I/O设备触发的异步事件
异常(exception): CPU在执行指令时, 检测到一个或多个预定义条件时产生的同步事件
中断和异常的处理
中断与一个索引值相关, 该索引值是一个函数指针数组(中断向量表IVT/中断描述符表IDT)的索引, 当中断发生时,CPU执行对应索引处的函数, 然后恢复中断发生前的执行
INT n
生成一个软件中断, 其中n为中断向量编号
INT 80H
IRET
节点:由一系列汇编指令组成的基本块
有向边:连接各基本块
从一个基本块可以发出多条有向边
(略)
可执行文件格式
被装载器和链接器使用的可执行文件的规范格式
典型代表
Unix/Linux 下: Executable and Linkable Format(ELF)格式
Windows 下: Portable Executable(PE)格式
由于对象文件参与程序的链接和执行,因此 ELF 格式提供两个并发的视图
链接视图(Linking view)
执行视图(Execution view)


用于链接视图, 包含 ELF 文件的各个节的描述信息
每个节包含
名称 含义
.interp 程序解释器(动态链接器)的路径名
.text 程序代码(可执行指令序列)
.data 已初始化的全局数据
.rodata 只读的数据
.bss 未初始化的全局数据
.init 用于进程初始化的可执行指令序列
.fini 用于进程终止的可执行指令序列
.plt 保存过程链接表(procedure linkage table), 其中包含了
动态链接器调用从共享库中导入的函数所必需的相关代码
.rel. 节 的重定位信息
.dynamic 动态链接信息
用于执行视图, 告诉系统如何在内存中创建一个进程
将文件的主体看作一系列的段, 每个段包含段类型, 被请求的内存位置, 权限, (在文件中或在内存中的)大小
段类型
段是程序执行的必要组成部分, 每个段中划分为不同的节
对程序内存布局的描述由程序头表完成
每个 ELF 目标文件都有节, 但不一定有节头
节头对程序执行不是必需的
节头表信息主要用于反汇编(如 objdump)和调试
缺少节头并不意味着节不存在, 只是无法通过节头引用节

绝对代码 vs 位置独立代码(position-independent code, PIC)
程序运行前的链接: 多个目标文件(.o, .so) ⇒ 一个可执行文件或.so
以多个目标文件作为输入
将类型相同的节合并到结果目标文件中, 例如,将多个.text 节合并到一个.text 节
重定位代码/数据(通过重定位信息)
// file1
int x = 5;
extern int function();
int main() {
int r = x +function();
exit (0);
}
// file2
int v = 10;
int u = 32;
int y;
int function() {
return v+u;
}
