【软件与系统安全笔记】三、基础技术

【软件与系统安全】三、基础技术

Image

这是《【软件与系统安全】笔记与期末复习》系列中的一篇

2022-02-21 第三次课后部分

2022-02-28 第四次课

2022-03-21 第四次课前半部分

x86 概念与内存模型

基本概念

x86: 基于 Intel 8086/8088 CPU 的一系列向后兼容的 ISA 的总称

  • IA-32: 32 位版本的 x86 指令集体系结构

    • 三种主要操作模式:

      • 实模式 (Real Mode)
      • 保护模式 (Protected Mode)
      • 系统管理模式 (System Management Mode) (大概就是 BIOS 下,对操作系统透明)
  • CISC(Complex Instruction Set Computer)体系结构

  • x64: 又称 x86-64, 是 x86 的扩展, 是与 x86 兼容的 64 位 ISA

字节序:多字节数据在内存中存储或在网络上传输时各字节的存储/传输顺序

小端序(little endian):低位字节存储在内存中低位地址, 效率较高(Intel CPU 使用)

大端序(big endian):低位字节存储在内存中高位地址, 符合思维逻辑。RISC 架构处理器(如 MIPS, PowerPC)采用

IA-32 内存模型

分段内存模型

程序内存由一系列独立的地址空间(称为“段”)组成。代码、数据和栈在不同的段中

逻辑地址=段选择器 + 偏移量

保护模式下的内存管理:分段(必须)+ 分页(可选)

image.png

程序线性地址空间 ≤4GB, 物理地址空间 ≤64GB

每个段最大 232 字节, IA-32 程序最多使用 16383 个段

x86 Linux 系统的线性地址空间分层

image.png

image.png

  • 最大 3G ?

寄存器与数据类型

  • 通用寄存器

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

      image.png

      • EAX:(操作数和结果数据的)累加器
      • EBX:(在 DS 段中数据的指针)基址寄存器
      • ECX:(字符串和循环操作的)计数器
      • EDX:(I/O 指针)数据寄存器
      • EDI: 变址寄存器, 字符串/内存操作的目的地址
      • ESI: 变址寄存器, 字符串/内存操作的源地址
      • EBP:(SS 段中的)栈内数据指针, 栈帧的基地址, 用于为函数调用创建栈帧
      • ESP:(SS 段中的)栈指针, 栈区域的栈顶地址
    • x64 通用寄存器:16 个 64 位

  • 指令指针寄存器

    • x86 上 32 位的 EIP, 存放当前代码段中将被执行的下一条指令的线性地址偏移

    • 程序运行时, CPU 根据 CS 段寄存器和 EIP 寄存器中的地址偏移读取下一条指令, 将指令传送到指令缓冲区, 并将 EIP 寄存器值自增, 增大的大小即被读取指令的字节数

    • 不能直接修改 EIP, 修改途径:

      • 指令 JMP, Jcc, CALL, RET
      • 中断或异常
  • 程序状态与控制寄存器

  • 段寄存器

  • 控制寄存器(CR0-CR4)

  • 调试寄存器(DR0-DR7)

  • 系统表指针寄存器(GDTR, LDTR, IDTR, task register)

  • MMX(MM0-MM7, XMM0-XMM7)

  • FPU(ST0-ST7, …)

RIP 相对寻址

x64 允许指令在引用数据时使用相对于 RIP 的地址,常用于访问全局变量, 全局变量 a 常通过 a(%rip) 进行访问

image.png

image.png

Relative Instruction-Pointer

x64下PIC的新寻址方式:RIP相对寻址 - Penguin (polarxiong.com)

基本数据类型

image.png

image.png

数据类型——指针类型

image.png

x64 规范地址(Canonical Address)

  • X64 的虚拟地址宽度为 64 位,但实际 CPU 只使用 48 位地址空间
  • 虚拟地址的规范形式指 48~63 位都与第 47 位相同的地址
  • 若代码试图解引用一个非规范地址,则触发系统异常

指令集与调用惯例

汇编指令风格

AT&T: source 在 destination 前,在较早期的 GNU 工具中普遍使用(如 gcc, gdb 等)

mov $4, %eax
mov $4, %(eax)

Intel: destination 在 source 前, “[. . .]”含义类似于解引用, MASM, NASM 等工具中使用

mov eax, 4
mov [eax], 4

IA-32 指令编码

image.png

image.png

IA-32 指令的典型内存寻址

典型内存寻址方式

image.png

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

  • 在两个内存地址之间移动 1 字节/2 字节/4 字节数据
  • 使用 EDI 保存目标地址;使用 ESI 保存源地址
  • 源/目标地址自动更新, DF 标识决定自增(DF=0)/自减(DF=1)

image.png

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

image.png

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

image.png

x64 算术运算: 多数算术运算都提升到 64 位,即使操作数只有 32 位

MOV RAX, 1122334455667788H
XOR EAX, EAX                ;也会清除RAX的高32位,执行后RAX=0
MOV RAX, 0FFFFFFFFFFFFFFFFH
INC EAX                     ;执行后RAX=0          

CMP: 算数比较

  • 比较两个操作数(通过相减), 并设置 EFLAGS 中的适当标识位
  • 常与条件跳转 Jcc 指令配合使用, 跳转依据即 CMP 运算结果

TEST: 逻辑比较

  • 比较两个操作数(通过逻辑 AND 运算), 并设置 EFLAGS 中的适当标识位

JMP: 无条件跳转到目标指令地址(可用相对地址或绝对地址)

Jcc (cc=conditional code)

栈操作与函数调用指令

: 线性地址空间中连续的内存区域, 后进先出, 存在于一个栈段内,该栈段可由段寄存器 SS 检索的段描述符指向:

  • 任意时刻, ESP 寄存器所包含的栈指针均指向栈顶位置
  • 所有针对栈的指令操作, 均基于 SS 对当前栈的引用
  • 通常由高地址向低地址扩展(PUSH 时 ESP 自减,POP 时 ESP 自增)

栈操作指令

  • PUSH: 将字(或双字)压栈 (ESP 先自减)
  • POP: 将字(或双字)弹出栈 (ESP 后自增)
  • PUSHA/POPA/PUSHAD/POPAD
  • ENTER:进入栈帧, 等价于 PUSH EBP; MOV EBP, ESP
  • LEAVE:退出栈帧, 等价于 MOV ESP, EBP; POP EBP

“ESP:(SS 段中的)栈指针, 栈区域的栈顶地址”1

“栈帧基指针: 由 EBP 指向的被调用函数栈帧的固定参考点”2

栈帧: 是将调用函数和被调用函数联系起来的机制,栈被分割为栈帧,栈帧组成栈。栈帧的内容包含:

  • 函数的局部变量
  • 向被调用函数传递的参数
  • 函数调用的联系信息(栈帧相关的指针: 栈帧基址针,返回指令指针)

栈帧基指针: 由 EBP 指向的被调用函数栈帧的固定参考点

返回指令指针: 由 CALL 指令压入栈中的 EIP 寄存器中的指令地址

  • 该地址是被调用函数返回后首先执行的调用函数指令的地址,即调用函数中 CALL 指令的下一条指令的地址

image.png

函数的指令框架

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 指令语义(及后继指令语义)

  1. 将 EIP 的当前值(返回指令指针)压栈
  2. 将 CALL 的目标指令(被调用函数的第一条指令)的地址偏移载入 EIP 寄存器
  3. CALL 指令结束后开始执行被调用函数
    1. PUSH EBP: 将调用函数的栈帧的 EBP 压栈
    2. ESP -> EBP,确立新栈帧(被调用函数栈帧)的基址针
    3. 执行被调用函数的具体功能

RET 指令语义(及前序指令语义)

  1. RETN 指令执行之前

    1. EBP -> ESP: 清空当前被调用函数的栈帧(此时 ESP 指向的栈顶内容恰好为调用者函数的 EBP)(可选)
    2. POP EBP: 将 EBP 恢复为调用者函数的原始 EBP
  2. 将栈顶的内容(返回指令指针)弹出到 EIP

  3. 若 RETN 指令有参数 n, 则将 ESP 增加 n 字节, 从而释放栈上的参数

  4. 恢复对调用函数的执行

近调用/近返回

控制流转移到/出当前代码段中的被调用函数, 通常提供对本地函数的访问

远调用/远返回

控制流转移到/出其他代码段中的被调用函数, 通常提供对 OS 函数或其他进程函数的访问

  • 前述指令语义针对近调用. 对于远调用(调用函数和被调用函数不在同一代码段中), 除需要保存 EIP 之外还要保存 CS
  • CPU 不跟踪返回指令指针的位置, 由程序员确保在执行 RET 指令时,栈顶内容为返回指令指针
  • CPU 不要求返回指令指针必须指回调用者函数, 在执行 RET 指令前,返回指令指针可以被修改并指向当前代码段中任一地址(脆弱性)

调用惯例 (Calling Convention)

是对函数调用时如何传递参数和返回值的约定

  • 参数传递用寄存器?用栈?两者都用?
  • 参数从左到右/从右到左压栈?
  • 返回值存储在栈?寄存器?两者都存?

x86 主要调用惯例

  • cdecl: C 语言中使用(GCC, GNU libraries); 参数从右到左压栈; EAX, ECX, EDX 不保存(调用者应自己保存); 返回值由 EAX 返回;调用者清理栈 (ESP 自增)
  • stdcall: 常用于 Win32 API, 被调用者清理栈(RETN n)
  • fastcall: 类似于 stdcall, 但使用寄存器 ECX、EDX 传递函数的前 2 个参数

System V AMD64 ABI 调用惯例

image.png

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;
}

image.png

Red-Zone 优化

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;
}

image.png

栈帧基址针优化

对于需要栈帧的函数, 处理栈帧基址针有两种方法

  • 方法 1: 传统方法(GCC 无优化选项时)

    • 函数序言: 保存 caller 的栈帧基址针; 创建新的栈帧基址针
    • 函数体: 对栈单元的引用采用相对于栈帧基址针的方式
    • 函数结尾: 恢复 caller 的栈帧基址针
  • 方法 2: 更快的方法(GCC 优化选项时)

    • 不保存/恢复栈帧基址针; rbp 作为通用寄存器使用
    • 对栈单元的访问通过相对于 rsp 来完成
    • 初始进行栈帧分配; rsp 在函数执行过程中保持在一个固定位置

中断指令

中断: 通常指由I/O设备触发的异步事件

异常(exception): CPU在执行指令时, 检测到一个或多个预定义条件时产生的同步事件

  • 故障 (fault): 可修正的异常。故障处理后执行产生故障的指令
  • 陷入 (trap): 调用特定指令(如SYSENTER)时产生的异常。陷入处理后执行产生陷入的指令的下一条指令

中断和异常的处理

中断与一个索引值相关, 该索引值是一个函数指针数组(中断向量表IVT/中断描述符表IDT)的索引, 当中断发生时,CPU执行对应索引处的函数, 然后恢复中断发生前的执行

INT n

  • 生成一个软件中断, 其中n为中断向量编号

    • INT3 / INT 3H
    • 显式地调用断点异常处理程序
    • 指令的十六进制值: 0xCC或0xCD 0x03
  • INT 80H

    • IA-32的Unix系统调用 (对于x64, 系统调用为SYSCALL指令)

IRET

  • 从中断处理程序返回被中断函数
  • 与RET的区别: IRET还会从栈上恢复EFLAGS

控制流图

  • 节点:由一系列汇编指令组成的基本块

    • 一个基本块由一系列顺序执行的汇编代码组成, 其间没有跳转指令,也没有其他外部跳转指令以其间为跳转目标
  • 有向边:连接各基本块

    • 从基本块b1到基本块b2的有向边的含义是:在执行完b1后,有可能开始执行b2
  • 从一个基本块可以发出多条有向边

    • 例如, 当该基本块的最后一条指令为条件跳转指令时

可执行文件格式

(略)

可执行文件格式
被装载器和链接器使用的可执行文件的规范格式

典型代表
Unix/Linux 下: Executable and Linkable Format(ELF)格式
Windows 下: Portable Executable(PE)格式

可执行程序中的元信息(meta information)

ELF 格式

对象文件的分类与生成

对象文件的视图

由于对象文件参与程序的链接和执行,因此 ELF 格式提供两个并发的视图
链接视图(Linking view)
执行视图(Execution view)

image.png

image.png

节头表(Section header table)

用于链接视图, 包含 ELF 文件的各个节的描述信息

每个节包含

  • 名称和类型
  • 在运行时请求的内存位置
  • 权限

名称 含义
.interp 程序解释器(动态链接器)的路径名
.text 程序代码(可执行指令序列)
.data 已初始化的全局数据
.rodata 只读的数据
.bss 未初始化的全局数据
.init 用于进程初始化的可执行指令序列
.fini 用于进程终止的可执行指令序列
.plt 保存过程链接表(procedure linkage table), 其中包含了
动态链接器调用从共享库中导入的函数所必需的相关代码
.rel. 节 的重定位信息
.dynamic 动态链接信息

程序头表(Program header table)

用于执行视图, 告诉系统如何在内存中创建一个进程

将文件的主体看作一系列的段, 每个段包含段类型, 被请求的内存位置, 权限, (在文件中或在内存中的)大小

段类型

  • PHDR 该段保存了程序头表本身的位置和大小
  • LOAD 可装载的段, 此类型的段将被装载到内存中,LOAD 类型的段例如
    • 代码段: 可读且可执行
    • 数据段: 可读且可写, 或只可读
  • INTERP 指向当前可执行文件的动态链接器的路径(.interp section)
  • DYNAMIC 指向动态链接信息(.dynamic section)
  • NOTE 指向操作系统相关的规范信息, 运行时不需要, 因而易被感染

段 vs 节

段是程序执行的必要组成部分, 每个段中划分为不同的节
对程序内存布局的描述由程序头表完成
每个 ELF 目标文件都有节, 但不一定有节头
节头对程序执行不是必需的
节头表信息主要用于反汇编(如 objdump)和调试
缺少节头并不意味着节不存在, 只是无法通过节头引用节

ELF 可执行文件的装载过程

对象文件的装载

image.png

绝对代码 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;
}

image.png

重定位
动态链接


  1. ESP:(SS 段中的)栈指针, 栈区域的栈顶地址 ↩︎ ↩︎

  2. 栈帧基指针: 由 EBP 指向的被调用函数栈帧的固定参考点 ↩︎

  3. EBP:(SS 段中的)栈内数据指针, 栈帧的基地址, 用于为函数调用创建栈帧 ↩︎

  4. x86 上 32 位的 EIP, 存放当前代码段中将被执行的下一条指令的线性地址偏移 ↩︎

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

框架主义者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值