本页目录

ASLR 与安全防护

覆盖: ASLR (stack/mmap/exec) → PIE → RELRO → stack canary → NX → CET (IBT/SHSTK) → CFI → sanitizers as defense 适用: Linux 用户态, GCC/Clang + glibc + kernel

概述

ASLR 是 Linux 用户态安全的第一道防线——通过随机化地址空间布局,让攻击者无法预测代码、数据和栈的位置。但 ASLR 单独不够——PIE、RELRO、stack canary、NX 各自解决一类攻击面,组合使用形成纵深防御。

ASLR 三层

# /proc/sys/kernel/randomize_va_space
#   0: 关闭
#   1: 随机化 mmap base + stack + vDSO (partial)
#   2: 全部随机化 + brk (full, 默认)

# 随机化哪些:
#   - stack:      每次 exec 随机偏移 (可达 ~8MB 随机范围)
#   - mmap base:  共享库 .so 和匿名 mmap 的起始地址
#   - executable: 仅 PIE (Position-Independent Executable)
#   - brk (heap): full mode 下随机

# 不受 ASLR 影响:
#   - 非 PIE 可执行文件: 固定地址 (0x400000)
#   - vsyscall: 固定地址 (0xffffffffff600000)

PIE: Position-Independent Executable

# 非 PIE (传统, GCC 默认 -no-pie, 已过时):
#   .text 加载到固定地址 0x400000
#   → 攻击者知道所有 gadget 的地址 → ROP 简单
file old_binary  # "ELF 64-bit LSB executable"

# PIE (现代默认, GCC -pie):
#   整个 binary 像 .so 一样可以随机加载 → ASLR 生效
#   加载基址在每次 exec 时随机
file /bin/ls     # "ELF 64-bit LSB pie executable"

# 查看加载基址:
cat /proc/self/maps | head -1  # 每次不同

# PIE 的性能代价: ~2-3% (GOT 的额外间接)
#   通过 -fno-plt 可以消除部分 (直接 GOT 访问, 跳过 PLT)

RELRO: 重定位只读

# 链接器选项:
# -Wl,-z,norelro: 无保护
# -Wl,-z,relro:   部分保护 (默认)
# -Wl,-z,now:     完全保护 (BIND_NOW + RELRO)

# 效果:
#   Partial RELRO:
#     .got 和 .dynamic 在 startup 后变为 read-only
#     .got.plt 仍然可写 (lazy binding 需要)
#
#   Full RELRO (BIND_NOW):
#     所有 GOT 在 startup 后变为 read-only
#     代价: 所有符号在 startup 时立即解析 → 启动稍慢
#     好处: GOT 不可写 → 无法覆盖 GOT pointer → 防止 GOT overwrite attack

# 检查:
readelf -l /bin/ls | grep GNU_RELRO  # 有 → RELRO enabled
readelf -d /bin/ls | grep BIND_NOW   # 有 → Full RELRO

Stack Canary

// GCC -fstack-protector (自动启用)
// 在每个函数的栈帧底部放一个 8-byte canary (随机值)
// 函数返回前检查 canary → 如果被改 → 栈溢出 → abort

// Canary 来源:
//   /proc/sys/kernel/randomize_va_space → AT_RANDOM (16 bytes)
//   或: __stack_chk_guard (glibc TLS, 启动时从 AT_RANDOM 初始化)

// 检查:
//   __stack_chk_fail() → __fortify_fail() → abort + 日志

// 编译器级别:
//   -fstack-protector:      保护有 char buf[8+] 的函数
//   -fstack-protector-strong: 保护有数组/地址取操作的函数 (推荐)
//   -fstack-protector-all:   保护所有函数 (性能代价 ~5%)

NX (No-Execute)

硬件 (x86: XD bit / NX bit, ARM: XN):
  ELF segment PF_X → 映射为 executable
  栈和堆: 无 PF_X → 不可执行

  → 攻击者不能注入 shellcode 到堆/栈并跳转执行
  → 但 ROP 仍然有效 (复用已有的 .text 片段)

检查:
  cat /proc/cpuinfo | grep nx   # x86: 'nx' in flags
  readelf -l /bin/ls | grep STACK  # PT_GNU_STACK: RW (no X)

CET: Control-flow Enforcement (Intel 11th gen+)

IBT (Indirect Branch Tracking):
  硬件: 间接跳转(jmp/call [reg])只能到 endbr32/endbr64 指令
    → ROP/JOP 跳到 gadget 中间 → CPU 检测 → #CP fault

SHSTK (Shadow Stack):
  硬件: 维护独立的 shadow stack (不能通过常规 store 修改)
    call: 硬件 push 返回地址到 shadow stack
    ret:  硬件检查 shadow stack vs 普通栈 → 不匹配 → #CP fault
    → ROP 修改栈 → ret 时检测到 → 拒绝

编译器: GCC 8+ / Clang 7+ -fcf-protection=full
内核: CET 支持 (5.18+)

组合防御

攻击向量             缓解措施
────────────────────────────────────
栈溢出 →            canary + NX + SHSTK
堆溢出 →            FORTIFY_SOURCE, ASAN (调试)
GOT overwrite →     Full RELRO
ROP/JOP →           ASLR + PIE + IBT
信息泄露 →          kptr_restrict, dmesg_restrict
UAF →               ASAN (调试), SLAB_FREELIST_RANDOM

检查工具

# 检查 binary 的安全加固
checksec --file=/bin/ls
# 或: hardening-check /bin/ls  (Debian/Ubuntu devscripts)

# 输出:
#   Position Independent Executable: yes
#   Stack protected: yes
#   Fortify Source functions: yes
#   Read-only relocations: yes
#   Immediate binding: yes

参考

  • 内核文档⁠: Documentation/admin-guide/sysctl/kernel.rst (randomize_va_space)
  • LWN: "CET on x86", "Full RELRO and BIND_NOW"
  • 工具⁠: checksec, hardening-check, pwntools

关键词: ASLR, PIE, RELRO, stack canary, NX, CET, IBT, SHSTK, FORTIFY_SOURCE, BIND_NOW