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


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

    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)
    
    • 1
    • 2

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

    mov eax, 4
    mov [eax], 4
    
    • 1
    • 2

    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          
    
    • 1
    • 2
    • 3
    • 4

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    image.png

    重定位
    动态链接


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

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

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

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

  • 相关阅读:
    css 动画实现节流效果
    LwIP笔记01:LwIP入门
    elasticsearch2-es和kibana的安装
    【人工智能数学:01概率论】(2) 离散型概率空间
    Go面试题——log.fatal和panic的区别
    “could not open `CProgram FilesJavajre7libamd64jvm.cfg”问题解决办法
    RedisTemplate 使用 pipeline 时需要注意的问题
    通过删除字母匹配到字典里最长单词
    【华为OD机试真题 JAVA】数字涂色
    高防CDN之所以强大的原因
  • 原文地址:https://blog.csdn.net/weixin_47102975/article/details/126822502