binary-hardening
二进制加固(Binary Hardening)
用途
引导代理启用和验证二进制安全缓解措施:checksec 分析、编译器和链接器加固标志(RELRO、PIE、栈金丝雀、FORTIFY_SOURCE、CFI)、硬件影子栈,以及用于纵深防御的 seccomp-bpf 系统调用过滤。
触发场景
- "如何加固我的二进制文件以防止漏洞利用?"
- "如何检查我的二进制文件具有哪些安全缓解措施?"
- "checksec 输出的含义是什么?"
- "如何启用 RELRO、PIE 和栈金丝雀?"
- "如何用 seccomp 限制系统调用?"
- "如何启用 CFI(控制流完整性)?"
工作流程
1. 用 checksec 分析现有二进制文件
# 安装 checksec
pip install checksec.py # 或:apt install checksec
# 检查一个二进制文件
checksec --file=./mybinary
checksec --file=/usr/bin/ssh
# 输出示例
# RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
# Full RELRO Canary found NX PIE No RPATH No RUNPATH No Symbols Yes 6 10 ./mybinary
# 检查目录中的所有二进制文件
checksec --dir=/usr/bin
| 保护措施 | 良好值 | 需关注 |
|---|---|---|
| RELRO | Full RELRO(完全 RELRO) | Partial / No RELRO |
| Stack Canary(栈金丝雀) | Canary found(发现金丝雀) | No canary |
| NX | NX enabled(NX 已启用) | NX disabled |
| PIE | PIE enabled(PIE 已启用) | No PIE |
| FORTIFY | Yes | No |
2. 加固编译器和链接器标志
# 完整加固构建(GCC 或 Clang)
CFLAGS="-O2 -pipe \
-fstack-protector-strong \
-fstack-clash-protection \
-fcf-protection \
-D_FORTIFY_SOURCE=3 \
-D_GLIBCXX_ASSERTIONS \
-fPIE \
-Wformat -Wformat-security -Werror=format-security"
LDFLAGS="-pie \
-Wl,-z,relro \
-Wl,-z,now \
-Wl,-z,noexecstack \
-Wl,-z,separate-code"
gcc ${CFLAGS} -o prog main.c ${LDFLAGS}
标志参考:
| 标志 | 保护措施 | 说明 |
|---|---|---|
-fstack-protector-strong |
栈金丝雀 | 比 -fstack-protector 更强 |
-fstack-clash-protection |
栈碰撞 | 防止大型栈分配 |
-fcf-protection |
Intel CET(IBT+SHSTK) | x86 硬件 CFI(需要内核+CPU 支持) |
-D_FORTIFY_SOURCE=2 |
缓冲区溢出检查 | 为字符串/内存函数添加边界检查 |
-D_FORTIFY_SOURCE=3 |
增强 FORTIFY | GCC ≥12,Clang ≥12 |
-fPIE + -pie |
PIE/ASLR | 位置无关可执行文件 |
-Wl,-z,relro |
部分 RELRO | main 前使 GOT 只读 |
-Wl,-z,now |
完全 RELRO | 启动时解析所有 PLT → GOT 完全只读 |
-Wl,-z,noexecstack |
NX 栈 | 将栈标记为不可执行 |
3. 控制流完整性(CFI)
Clang 的 CFI 防止通过错误类型调用虚函数(vtable CFI)以及对类型不匹配函数的间接调用:
# Clang CFI——需要 LTO 和可见性控制
clang -fsanitize=cfi -fvisibility=hidden -flto \
-O2 -fPIE -pie main.cpp -o prog
# 特定 CFI 检查
clang -fsanitize=cfi-vcall # 虚调用类型检查
clang -fsanitize=cfi-icall # 间接调用类型检查
clang -fsanitize=cfi-derived-cast # 派生到基类的转换
clang -fsanitize=cfi-unrelated-cast # 无关类型转换
# 跨 DSO 的 CFI(跨共享库——更复杂)
clang -fsanitize=cfi -fsanitize-cfi-cross-dso -flto -fPIC -shared
# Microsoft CFG(Windows 等价方案)
cl /guard:cf prog.c
link /guard:cf prog.obj
4. 栈金丝雀详解
# GCC 金丝雀选项
-fno-stack-protector # 禁用
-fstack-protector # 保护有 alloca 或缓冲区 > 8 字节的函数
-fstack-protector-strong # 保护有本地数组或取地址的函数
-fstack-protector-all # 保护所有函数(最慢,最全面)
# 验证金丝雀是否存在
objdump -d prog | grep -A5 "__stack_chk"
readelf -s prog | grep "stack_chk"
5. FORTIFY_SOURCE
FORTIFY_SOURCE 在编译时能确定缓冲区大小时,将不安全的 libc 函数(memcpy、strcpy、sprintf)替换为带边界检查的版本:
# 级别 2(加固构建的 GCC/Clang 默认值)
-D_FORTIFY_SOURCE=2
# 运行时检查:溢出时 abort()
# 级别 3(GCC ≥12,覆盖更多情况)
-D_FORTIFY_SOURCE=3
# 添加动态缓冲区大小跟踪,提高覆盖率
# 检查 FORTIFY 覆盖情况
objdump -d prog | grep "__.*_chk" # 加固变体
checksec --file=prog | grep FORTIFY
6. seccomp-bpf 系统调用过滤
#include <seccomp.h>
void apply_seccomp_filter(void) {
scmp_filter_ctx ctx;
// 默认:对任何非白名单系统调用杀死进程
ctx = seccomp_init(SCMP_ACT_KILL_PROCESS);
// 白名单所需的系统调用
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 0);
// 应用过滤器(此后不可逆)
seccomp_load(ctx);
seccomp_release(ctx);
}
// 在 main() 中尽早调用,完成所有初始化之后
int main(void) {
// ... 初始化 ...
apply_seccomp_filter();
// ... 受限操作 ...
}
# 用 strace 测试 seccomp 过滤器
strace -e trace=all ./prog 2>&1 | grep "killed by SIGSYS"
# 分析系统调用以构建白名单
strace -c ./prog # 统计所有使用的系统调用
7. 影子栈(Intel CET / 硬件 SHSTK)
# 在支持的 x86 硬件上启用(Intel Tiger Lake+,内核 ≥6.6)
# -fcf-protection=full 同时启用 IBT 和 SHSTK
clang -fcf-protection=full -O2 -o prog main.c
# 检查二进制中的 CET 支持
readelf -n prog | grep "NT_GNU_PROPERTY"
objdump -d prog | grep "endbr64" # IBT 末端分支指令
# 内核支持
cat /proc/cpuinfo | grep shstk # CPU 支持
完整的加固标志参考,参见 references/hardening-flags.md。
相关技能
- 使用
skills/runtimes/sanitizers在开发期间使用 ASan/UBSan - 使用
skills/observability/ebpf通过 libbpf 编写 seccomp-bpf 程序 - 使用
skills/rust/rust-security了解 Rust 的内存安全加固方法 - 使用
skills/binaries/elf-inspection验证 ELF 二进制文件中的缓解措施
More from killvxk/low-level-dev-skills-zh
binutils
GNU binutils 二进制操作与分析技能。适用场景:使用 ar 管理静态库、使用 strip 或 objcopy 处理二进制文件、使用 addr2line 将地址转换为源码位置、使用 strings 提取文本、或使用 c++filt 对 C++ 名称进行反混淆。触发条件:涉及 ar、strip、objcopy、addr2line、strings、c++filt、ranlib 或二进制后处理任务的查询。
1ebpf
Linux 可观测性和网络的 eBPF 技能。适用场景:使用 libbpf 或 bpftrace 编写 eBPF 程序、挂载 kprobe/tracepoint/XDP 钩子、调试验证器错误、使用 eBPF map,或实现跨内核版本的 CO-RE 可移植性。触发条件:查询 eBPF、bpftool、bpftrace、XDP 程序、libbpf、验证器错误、eBPF map 或使用 BPF 进行内核追踪相关问题。
1clang
C/C++ 项目的 Clang/LLVM 编译器技能。适用场景:使用 clang 或 clang++ 进行诊断、sanitizer 插桩、优化备注、通过 clang-tidy 进行静态分析、通过 lld 实现 LTO,或从 GCC 迁移到 Clang。触发条件:涉及 clang 标志、clang-tidy、clang-format、更好的错误信息、Apple/FreeBSD 工具链或 LLVM 特定优化的查询。涵盖标志选择、诊断调优及与 LLVM 工具的集成。
1gcc
C/C++ 项目的 GCC 编译器技能。适用场景:选择优化级别、警告标志、调试构建、LTO、sanitizer 插桩,或诊断 GCC 编译错误。涵盖调试与发布构建的标志选择、ABI 问题、预处理器宏、配置引导优化(PGO)及与构建系统的集成。触发条件:涉及 gcc 标志、编译错误、性能调优、警告抑制或跨标准编译的查询。
1cmake
C/C++ 项目的 CMake 构建系统技能。适用场景:编写或重构 CMakeLists.txt、配置源外构建、选择生成器(Ninja、Make、VS)、使用 target_link_libraries 管理目标和依赖、通过 find_package 或 FetchContent 集成外部包、启用 Sanitizer、为交叉编译配置工具链文件,或导出 CMake 包。触发条件:涉及 CMakeLists.txt、cmake 配置错误、目标属性、安装规则、CPack 或 CMake Presets 的查询。
1cpp-modules
现代 C++ 项目的 C++20 模块技能。适用场景:使用命名模块、模块分区、头文件单元、CMake MODULE_SOURCES、Clang -fmodules-ts、BMI 缓存问题,或从头文件迁移到模块。触发条件:涉及 C++20 模块、import 语句、模块接口单元、头文件单元或 BMI 文件的查询。
1