关于 binary-protection-bypass
面向二进制分析与漏洞利用的安全技能,讲解如何识别各类防护及其绕过方法和所需原语。内容覆盖常见编译防护与硬件防护。
主要功能
- 01识别 ASLR、PIE、NX、RELRO 和 Canary
- 02将防护与绕过方法对应
- 03列出方法所需的条件与利用原语
- 04涵盖 FORTIFY_SOURCE、CET 和 ARM MTE
适用场景
- 01检查 checksec 报告的防护
- 02根据已启用的防护规划利用路径
- 03比较绕过方法及其前提
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ yaklang/ hack-skills --skill binary-protection-bypass安装后在 Claude Code 中输入/binary-protection-bypass确认已加载。
附带文件PROTECTION_BYPASS_MATRIX.md
详细介绍
技能:二进制保护绕过——专家攻击手册
AI 加载说明:专家级二进制保护识别与绕过技术。涵盖 ASLR、PIE、NX、RELRO、canary、FORTIFY_SOURCE、栈冲突、CET shadow stack 和 ARM MTE。每种保护都配有其绕过方法和所需原语。内容摘编自 ctf-wiki 的缓解措施章节和真实世界的漏洞利用案例。基础模型经常混淆哪些保护会阻止哪些攻击,也会忽略多种保护组合产生的影响。
0. 相关路由
- stack-overflow-and-rop — 用 ROP 链绕过 NX,用 ret2libc 绕过 ASLR
- format-string-exploitation — 泄露 canary、PIE、libc 地址的主要方法
- heap-exploitation — 利用堆攻击绕过 RELRO(GOT 只读时)
- arbitrary-write-to-rce — RELRO 保护 GOT 时需要覆盖的目标
高级参考
加载 PROTECTION_BYPASS_MATRIX.md,查看全面的保护 × 绕过方法 × 原语矩阵。
1. 保护识别
$ checksec ./binary
[*] '/path/to/binary'
Arch: amd64-64-little
RELRO: Full RELRO ← GOT read-only
Stack: Canary found ← stack canary enabled
NX: NX enabled ← stack not executable
PIE: PIE enabled ← position-independent code
FORTIFY: Enabled ← fortified libc functions
快速识别表
| 保护 | 检查命令 | 二进制特征 |
|---|---|---|
| ASLR | cat /proc/sys/kernel/randomize_va_space | 操作系统级(0=关闭,1=部分,2=完全) |
| PIE | checksec 或 readelf -h(Type: DYN) | 使用 -pie 编译的二进制文件 |
| NX | checksec 或 readelf -l(没有 RWE 段) | gcc -z noexecstack(默认开启) |
| Canary | checksec 或查找 __stack_chk_fail@plt | gcc -fstack-protector-all |
| 部分 RELRO | readelf -l(GNU_RELRO 段,.got.plt 可写) | gcc -Wl,-z,relro |
| 完全 RELRO | readelf -l + .got 段只读 | gcc -Wl,-z,relro,-z,now |
| FORTIFY | 存在 __printf_chk、__memcpy_chk 等 | gcc -D_FORTIFY_SOURCE=2 |
2. 绕过 ASLR
ASLR 会在每次执行时随机化栈、堆、libc 和 mmap 区域的基地址。
| 绕过方法 | 所需原语 | 说明 |
|---|---|---|
| 信息泄露 | 任意读取原语(格式化字符串、越界读取、UAF) | 泄露 libc/栈/堆地址 → 计算基地址 |
| 部分覆盖 | 写入原语(长度有限) | 覆盖最后 1-2 个字节(页偏移固定) |
| 暴力破解(32 位) | 能够重新连接/重试 | 约 256–4096 次尝试(8-12 位熵) |
| Return-to-PLT | 栈溢出 | PLT 地址相对于二进制基地址的偏移固定(无 PIE 时) |
| ret2dlresolve | 栈溢出 + 写入原语 | 无需知道 libc 基地址即可解析任意函数 |
| 格式化字符串泄露 | 格式化字符串漏洞 | 使用 %N$p 获取栈/libc/堆地址 |
| 读取栈内容 | 逐字节读取(fork 服务端) | 通过崩溃预言机逐字节读取栈内容 |
ASLR 熵(x86-64 Linux)
| 区域 | 熵(位) | 位置数 |
|---|---|---|
| 栈 | 22 | 约 4M |
| mmap / libc | 28 | 约 256M |
| 堆(brk) | 13 | 约 8K |
| PIE 二进制文件 | 28 | 约 256M |
3. 绕过 PIE
PIE(位置无关可执行文件)会随机化二进制文件自身代码/数据的基地址。
| 绕过方法 | 所需原语 | 说明 |
|---|---|---|
| 信息泄露 | 从栈中读取返回地址 | PIE 基址 = 泄露地址 - 已知偏移 |
| 部分覆盖 | 单字节或双字节写入 | 页偏移的最后 12 位固定 |
| 格式化字符串泄露 | 格式化字符串漏洞 | 使用 %N$p,其中 N 指向 .text 返回地址 |
| 相对寻址 | 了解二进制文件布局 | 如果知道相对偏移,只需一次泄露 |
部分覆盖详情
PIE binary loaded at: 0x555555554000 (example)
Function at offset 0x1234: 0x555555555234
Overwrite return address last 2 bytes: 0x?234 → 0x?XXX
Unknown: bits 12-15 (one nibble = 4 bits = 16 possibilities)
Success rate: 1/16 per attempt
4. NX / DEP 绕过
NX(No-eXecute)/ DEP(数据执行保护)会阻止在栈/堆上执行代码。
| 绕过方法 | 详情 |
|---|---|
| ROP(面向返回的编程) | 将以 ret 结尾的现有代码片段串联起来 |
| ret2libc | 直接调用 libc 函数(system、execve) |
| ret2csu | 使用 __libc_csu_init 代码片段进行受控函数调用 |
| ret2dlresolve | 伪造动态链接器结构以解析任意函数 |
| SROP | 使用 sigreturn 从伪造的信号帧设置所有寄存器 |
| mprotect ROP | 串联 mprotect(addr, size, PROT_RWX) → 将页面设为可执行 → 跳转到 shellcode |
| JIT 喷射 | 在 JIT 环境(V8 等)中,通过 JIT 编译器创建可执行代码 |
mprotect 链
# Make stack executable, then jump to shellcode
rop = b'A' * offset
rop += p64(pop_rdi) + p64(stack_page) # page-aligned address
rop += p64(pop_rsi) + p64(0x1000) # size
rop += p64(pop_rdx) + p64(7) # PROT_READ|PROT_WRITE|PROT_EXEC
rop += p64(mprotect_addr)
rop += p64(shellcode_addr) # jump to shellcode on now-executable stack
5. RELRO 绕过
| RELRO 级别 | GOT 状态 | 绕过方法 |
|---|---|---|
| 无 RELRO | GOT 完全可写 | 直接覆盖 GOT |
| 部分 RELRO | .got.plt 可写(延迟绑定) | 仍可覆盖 GOT |
| 完全 RELRO | 加载时解析所有 GOT 条目,GOT 只读 | 无法写入 GOT → 攻击其他结构 |
完全 RELRO 的替代目标
| 目标 | 适用情况 | 方法 |
|---|---|---|
__malloc_hook | glibc < 2.34 | 使用 one_gadget 覆盖 |
__free_hook | glibc < 2.34 | 使用 system 覆盖,触发 free("/bin/sh") |
_IO_FILE vtable | 任意 glibc | FSOP / vtable 劫持 |
__exit_funcs | 任意 glibc | 覆盖退出处理函数列表 |
TLS_dtor_list | glibc ≥ 2.34 | 线程局部析构函数列表(需要指针保护) |
.fini_array | 可写时 | 覆盖析构函数指针 |
| 栈返回地址 | 直接写入栈 | 覆盖返回地址以进行 ROP |
参见 arbitrary-write-to-rce,了解完整的目标列表。
6. CANARY 绕过
| 方法 | 条件 | 详情 |
|---|---|---|
| 格式字符串泄露 | printf(user_input) | 使用 %N$p 从栈上读取 canary |
| 暴力破解 | fork() 服务(canary 在子进程中保持不变) | 逐字节尝试:256 × (canary_size-1) 次 |
| 栈读取 | 部分覆盖 / 信息泄露 | 覆盖 canary 的空字节,通过输出泄露 |
| 线程 canary 覆盖 | 溢出到达 TLS | canary 位于 fs:[0x28];缓冲区溢出到 TLS 后,将 canary 覆盖为已知值 |
| 相对于 canary 覆盖 | 溢出发生在 canary 之后、返回地址之前 | 跳过 canary,仅覆盖返回地址(少见布局) |
| 基于堆的攻击 | 漏洞位于堆上,而非栈上 | canary 只保护栈 |
| __stack_chk_fail GOT 覆盖 | 部分 RELRO | 将 __stack_chk_fail@GOT 覆盖为指向无害函数 → canary 检查通过 |
Canary 格式
x86: 0x00XXXXXX (4 bytes, leading null byte)
x86-64: 0x00XXXXXXXXXXXXXX (8 bytes, leading null byte)
开头的 \x00 可防止字符串操作意外读取 canary。
7. FORTIFY_SOURCE 绕过
_FORTIFY_SOURCE=2 会增加缓冲区大小检查,并限制格式字符串操作。
| 强化函数 | 限制 | 绕过方法 |
|---|---|---|
__printf_chk | 禁止使用带位置参数的 %n(%N$n) | 使用非位置参数 %n 或 %hn 链 |
__memcpy_chk | 检查目标缓冲区大小 | 改用堆溢出而非栈溢出 |
__strcpy_chk | 同上 | |
__read_chk | 检查读取大小是否超过缓冲区 |
使用 FORTIFY_SOURCE 的格式字符串
# %1$n is blocked by __printf_chk
# But sequential (non-positional) %n may still work:
# Print exact byte count, then %hn — must be very precise
# Or: find unfortified printf in binary/libc via ROP
8. CET(控制流强制技术)
Intel CET 增加了两种机制:
影子栈
- 由硬件维护的返回地址副本
- 执行
ret时,CPU 会检查影子栈是否与实际栈匹配 - 不匹配 →
#CP故障(控制保护异常)
| 影响 | 详情 |
|---|---|
| 阻止 ROP | 执行 ret 时会检测到返回地址覆盖 |
| 仍可使用 JOP | 影子栈不会检查 jmp [reg] |
| 仍可使用 COP | call [reg] 会将地址压入影子栈,但目标由 IBT 验证 |
间接分支跟踪(IBT)
- 间接
jmp/call必须跳转到ENDBR64指令 - 未落在
ENDBR上 →#CP故障
绕过方式:
- 仅针对数据的攻击(不改变控制流)
- 查找可链接至有用操作的有效 ENDBR 小工具
- 使用带 ENDBR 前缀小工具的 JOP
- 以超出 CFI 范围的目标结构为目标(modprobe_path、函数指针数组)
9. MTE(内存标记扩展,ARM)
ARM MTE 为内存指针和分配分配 4 位标记。标记不匹配 = 故障。
| 方面 | 详情 |
|---|---|
| 标记位 | 指针中的 4 位(第 56–59 位)= 16 种可能的标记 |
| 粒度 | 16 字节(每个 16 字节粒度有一个标记) |
| 检查 | 加载/存储:指针标记必须与内存标记匹配 |
| 概率性 | 随机标记 → 攻击者猜中的概率为 1/16 |
绕过方法
| 方法 | 成功率 |
|---|---|
| 暴力破解 | 每次尝试为 1/16(6.25%) |
| 标记预言机 | 使用侧信道确定标记(时序、错误消息) |
| 界内利用 | 保持在同一标记区域内(使用相对偏移量) |
| 标记绕过小工具 | 如果可访问,则使用 LDGM/STGM 指令 |
| 推测执行 | 以 Spectre 风格绕过标记检查 |
10. 决策树
Binary analysis: checksec output
├── NX disabled?
│ └── Shellcode on stack/heap (simplest path)
│
├── NX enabled (standard modern binary)?
│ ├── Need code execution → ROP/ret2libc
│ │
│ ├── Canary enabled?
│ │ ├── fork server? → byte-by-byte brute-force
│ │ ├── Format string? → leak canary via %p
│ │ ├── Heap vuln? → canary doesn't protect heap
│ │ └── Partial RELRO? → overwrite __stack_chk_fail@GOT
│ │
│ ├── PIE enabled?
│ │ ├── Format string? → leak .text address → PIE base
│ │ ├── Partial overwrite → last 12 bits fixed (1/16 brute-force)
│ │ └── OOB read? → leak code pointer
│ │
│ ├── ASLR enabled?
│ │ ├── Info leak available → leak libc base
│ │ ├── No leak → ret2dlresolve or SROP
│ │ ├── 32-bit? → brute-force feasible (~4096 attempts)
│ │ └── Return-to-PLT (no libc base needed for PLT calls)
│ │
│ ├── RELRO level?
│ │ ├── None/Partial → GOT overwrite
│ │ └── Full → alternative targets:
│ │ ├── glibc < 2.34 → __malloc_hook / __free_hook
│ │ ├── glibc ≥ 2.34 → _IO_FILE / exit_funcs / TLS_dtor_list
│ │ ├── .fini_array (if writable)
│ │ └── Stack return address
│ │
│ └── FORTIFY_SOURCE?
│ ├── Blocks positional %n → use sequential %n or heap exploit
│ └── Blocks buffer overflows in fortified functions → use unfortified paths
│
├── CET (shadow stack)?
│ ├── ROP blocked → data-only attack or JOP
│ └── ENDBR-gadget chaining
│
└── MTE (ARM)?
├── 1/16 brute-force
└── Stay in-bounds for relative corruption
评论
支持 Markdown