控制劫持基础(Control Hijacking Basics)

  • 定义: 通过二进制利用(Binary Exploitation)破坏应用程序的信任边界, 典型手段为内存损坏(Memory Corruption);
    • 目标: 实现远程代码执行(Remote Code Execution, RCE)或提权(Privilege Escalation);
    • 常见攻击类型:
      • 缓冲区溢出(Buffer Overflow)
      • 格式化字符串攻击(Format String)
      • 整数溢出(Integer Overflow)

内存与汇编基础(Memory & Assembly Fundamentals)

  • 内存布局:
    • x64 架构的每一个 entry 尺寸为 64b = 8B;
    • 代码段(Text Segment): 存储可执行指令;
    • 数据段(Data Segment): 存储全局变量;
    • 堆(Heap): 动态分配内存;
    • 栈(Stack): 管理函数调用与局部变量(后进先出);
      • 从高地址向低地址增长;
      • 0xffffffff -> 0x00000000;
      • $rsp 地址小于等于 $rbp 地址;
      • 栈上数据的填充是从低地址向高地址进行填充的
  • 关键寄存器:
    • RIP: pc, 指向下一条执行指令;
    • RSP: stack top pointer, 指向栈顶;
    • RBP: stack base pointer, 标记当前栈帧起始;
  • 汇编指令:
    • mov: 数据移动;
      • mov 的功能实际上是复制, 不会影响源数据
      • mov rax, rbx 将 rbx 的值复制到 rax
      • mov rax, 0x1234ABCD 将立即数 0x1234ABCD 复制到 rax
      • mov rax, [rbx] 将 rbx 指向的内存地址的值复制到 rax
      • mov rax, [rbp-8] 将 rbp - (8 Byte) 指向的内存地址的值复制到 rax
    • jmp/call: 控制流跳转;
      • call会将返回地址压栈;
        • call = push(rip) + jmp;
      • jmp不会返回;
    • push/pop: 栈操作;
    • lea: (Load Effective Address) 加载有效地址,
      可以将计算有效地址加载到寄存器中, 而不访问/读取地址的数据;
      • lea rax, [rbp-8] 将 rbp - (8 Byte) 指向的内存地址复制到 rax
    • sub 减法, 常用来给数组预留空间

x64 函数调用汇编 pattern

  • Caller:
    • 函数参数调用:
      • 前6个参数依次存入 RDI, RSI, RDX, RCX, R8, R9;
      • 其余参数通过栈传递;
    • push(rip)
    • jmp
  • Callee:
    • push(rbp) 存入 stack base 地址
    • mov(rbp, rsp) 把当前栈顶地址存入 stack base
    • … 函数体
    • leave: mov(rsp, rbp) + pop(rbp)
    • ret: pop(rip)

x86 isa 寄存器分类

  • 4个数据寄存器(EAX, EBX, ECX和EDX)
  • 2个变址寄存器 Index Register (ESI和EDI)
    • 主要用于存放存储单元在段内的偏移量, 用它们可实现多种存储器操作数的寻址方式
    • 32位CPU有2个32位通用寄存器ESI和EDI;其低16位对应SI和DI, 对低16位数据的存取, 不影响高16位的数据;
  • 2个指针寄存器(ESP和EBP)
    • 32位CPU有2个32位通用寄存器EBP和ESP;其低16位对应BP和SP, 对低16位数据的存取, 不影响高16位的数据;
    • 主要用于存放堆栈内存储单元的偏移量
  • 6个段寄存器(ES, CS, SS, DS, FS和GS)
  • 1个指令指针寄存器(EIP) 1个标志寄存器(EFlags)
  • 9个控制寄存器(C0, C1, C2, C3, C4, C5, C6, C7, C8)其中C5~C7架构保留;
  • 3保护模式寄存器(GDTR, LDTR, IDTR)

ESI (Source Index)

  • 通常指向操作的源数据地址(如字符串, 数组的起始位置);
  • 在字符串指令(如 MOVSB, LODSB)中, 配合 DS 段寄存器使用;

EDI (Destination Index)

  • 通常指向操作的目标地址(如写入数据的内存位置);
  • 在字符串指令(如 MOVSB, STOSB)中, 配合 ES 段寄存器使用;

缓冲区溢出(Buffer Overflow)

  • 原理: 输入数据超出缓冲区边界, 覆盖返回地址或关键数据;
  • 示例代码漏洞:
1
2
3
4
void foo(char *str) {
char buffer[4];
strcpy(buffer, str); // 无边界检查
}

数据执行保护 (DEP) 防御

  • 内存分区的写与执行权限隔离(Write XOR Execute 原则)
    • 一段内存要么只能写入读取要么只能执行而不能修改
  • 可以有效防御 最基础的 buffer overflow 和 stack shellcode 攻击
  • 无法防御下面的 rop 攻击

Return Oriented Programming (ROP)

  • 核心思想
    • 利用现有代码片段(Gadgets)构建攻击链, 不会注入执行代码, 只会覆盖返回地址
    • Gadget特征: 以ret结尾的短指令序列
  • ROP链构建
    • 示例Gadgets: mov rax, 0x10; ret, add rax, rbp; ret
    • 通过栈控制指令流(链式调用Gadgets)
  • 分类:
    • ret2libc: 寻找合适的 ret 附近的汇编代码, 找到 reg
      填充数值合适的位置进行返回地址覆盖, 返回地址指向 libc 中的函数
    • ret2syscall: 同理, 返回地址指向系统调用函数
    • ret2shellcode: 返回地址指向 shellcode 的位置, 执行 shellcode
  • 防御: Extraneous Function Removal:
    • 在编译/链接的时候删除没有额比引用的函数, 变量或者库代码来减少 (不是消除所有) 攻击面
    • 由于仍然会剩下使用的函数, 还是会遭受 rop 攻击
  • x86的指令弱点
    • 在 x86
      架构下指令长度是不确定的, 因此攻击者可以从意料之外的字节开始解码来构造额外的
      gadget , 即任意包含 c3 的字节都可以被当做 ret 指令来使用, 增大了攻击面
  • ROP Chain
    • 对于多输入函数如 execve("/bin/sh",NULL,NULL) 需要同时填充 rdi, rsi, rdx 3 个寄存器, 可以通过连续覆盖不同地址的返回地址来实现连续覆盖并且确保多个 reg 数值合理
  • ASLR 防御
    • 地址空间随机化 (Address Space Layout Randomization)
      指程序在运行时在运行的时候随机加入一个随机的偏移量, 使得攻击者无法预测函数的地址
    • 随机化代码段, 堆, 栈的基地址
    • 局限性:
      • 部分代码段未完全随机化
      • 通过内存泄露 (如 Heartbleed 漏洞) 获取地址
      • Fine-grained ASLR 细粒度随机化, 随机化函数内的指令地址
  • Stack Canaries 栈金丝雀
    • 在函数返回地址的前面添加一个保护位
    • 每次返回之前对 canary xor initial_value 进行校验, 如果不一致 (xor = 1) 则说明栈被破坏

Buffer Over-read

  • 原理: 读取超出缓冲区边界的数据, 可能导致敏感信息泄露;
    • 攻击者可以首先读取 canary 然后合理覆盖
  • Heartbleed 漏洞: 读取超长数据导致内存泄露;

整数溢出与类型混淆

  • 数值范围: 对于 unsigned int 类型的变量, 最大值为 2^32-1, 那么如果要执行
    (2^32 - 1) * 4 计算溢出得到 0xFFFFFFFC, 这会导致分配的内存小于预期;
    • 对于 signed int 类型的变量, 最大值为 2^31-1, 如果执行 (2^31 - 1) * 4 计算溢出得到 0xFFFFFFFC 为负数, 这会导致分配的内存小于预期;
    • 常见的场景是 malloc(len * sizeof(int))len 过大 (不超过 uint 上限) 导致 len * 4 超过上限而被覆盖
    • 常用数值: 0x40000001 如果 * 4 就会得到 0x00000004
    • 符号类型混淆(有符号数误用为无符号数)
  • 防御: 使用安全类型(如size_t)和边界检查

四, 其他攻击技术

  • 格式化字符串漏洞
    • 利用printf(user_input)泄露内存或篡改数据
  • Use-After-Free(UAF)
    • 释放后重用内存对象(常见于多线程环境)
  • 缓冲区越界读取(Buffer Over-read)
    • 示例: Heartbleed漏洞(读取超长数据泄露敏感信息)

五, 防御与自动化工具

1. 自动化漏洞检测

  • 模糊测试(Fuzzing)
    • 生成随机/畸形输入触发程序崩溃
  • 静态分析工具
    • 检测不安全函数(如strcpy), 类型混淆
  • 动态分析工具
    • 内存检查(如Valgrind)捕获越界访问, UAF

2. 安全编码实践

  • 替换危险函数: strncpy替代strcpy, snprintf替代sprintf
  • 启用编译选项: -fstack-protector(栈金丝雀), -pie(ASLR支持)

六, 攻击链与综合利用

  • 多漏洞组合利用(Bug Chains)
    • 示例:
      1. 缓冲区溢出泄露ASLR偏移
      2. ROP链禁用DEP
      3. 注入并执行Shellcode
  • 现实案例
    • Heartbleed(OpenSSL漏洞): 越界读取服务器内存
    • 返回libc攻击: 调用execv("/bin/sh")获取Shell

GDB 指令简介

break point 断点设置

1
2
break *0xffffffff # 设置断点在指定地址, 没有执行该行
break vulnerable # 设置断点在函数 vulnerable 处, 执行到该行

显示地址内存

以下指令需要在断点中才能显示

1
2
3
4
x/8bx 0xffffffff  # 显示指定地址的8个字节, 以十六进制格式, 如果是小端存储则输出顺序是与实际数值相反顺序的
x/1gx 0xffffffff # 显示指定地址的1个字 (8bit), 以十六进制格式, 64位系统下使用
x/8bx $rbp # 显示 rbp 指向的地址以及地址对应的 8 个字节, 以十六进制格式
x/8bx $rbp-0x30 # 显示 rbp-0x30 指向的地址以及地址对应的 8 个字节, 以十六进制格式

如果是 x86 的 32b 系统, 则reg 名称为 eax, ebp, esp 等

反汇编函数

1
2
disassemble vulnerable  # 反汇编函数 vulnerable
disassemble *0x0000000000400a00 # 反汇编指定地址的函数

查看寄存器

1
info reg  # 查看所有寄存器的值

漏洞

指针赋值漏洞

如果在源代码中存在如 *p = a 之类的指针赋值操作, 那么可以用栈覆盖内存, 使得把 p
的数值 (指针直接存储的是一个地址而不是地址指向的数值)
覆盖为需要被攻击赋值的变量地址, 然后覆盖数值 a 来实现攻击

ASLR 攻击: NOP 雪橇

如果已知 aslr 会让栈基指针会有一定偏移量阈值, 可以用栈溢出来将 buffer
阈值长度的部分设置为 NOP 指令 (0x90) 再在后面覆盖需要执行的 shellcode, 然后覆盖 $rip 返回地址为 base +
threshold 地址, 这样就一定会指到 NOP 雪橇的中间部分, 使得攻击者可以在不需要知道具体的地址的情况下执行 shellcode