当前位置: 首页 > news >正文

ret2libc+一点点保护

概述

在程序存在栈溢出,且没有直接给可利用的后台程序的情况下。利用ROP链传参“/bin/sh”调用程序system得到shell。

例题——1

例题:第六届信安大挑战 - Dino::CTF
程序内很多已经写好可以直接调用的函数会存放在libc表里面,如“printf”“gets”“puts”,当然也包括“system”。这道题目详细讲解ROP做了什么,以实现调用libc库里的“system”。
Pasted image 20251202215818
题目基本信息如上,开启了NX保护无法写入shellcode;没有开启PIE但是需要了解每一次程序运行libc库地址都不一样,而同一版本的libc库内不同函数的相对偏移一样。所以需要程序能暴露一个函数的真实地址,能以此计算出我们所需的函数和参数(在libc库内可以找到字符串参数“/bin/sh”)地址。
Pasted image 20251202220311
在vuln的函数内,1~q可以看见这道题将printf函数的地址直接给我们了,我们可以以此计算出所需函数和参数地址。同时也可以知道溢出所需字符为72(buf的64个+rbp的8个)字节。
Pasted image 20251202221550
上文利用python的pwntools可以得到所需地址,因为有libc.address,所以下文的libc.sym和libc.search可以直接得到加上基地址的函数地址和参数地址。
Pasted image 20251202221841
Pasted image 20251202223017
(64位程序函数的前六个参数分别用寄存器rdi, rsi, rdx, rcx, r8, r9传参,后续参数采用栈传参。)
利用ida找到pop_rdi_ret代码的地址,使vlun函数的返回地址为pop_rdi_ret可以实现system函数的传参和调用。具体是溢出覆盖返回程序后,vlun函数结束后跳转到我们在程序内找到的pop_rdi_ret;此时rsp为“/bin/sh”的地址,pop rdi使其传到rdi以传参;此时rsp为system地址,ret=pop rip使得程序在pop_rbi_ret后进入system函数,得到shell。
但system函数的具体实现会检查栈对齐,有时候这样无法直接得到shell。具体原理现在我也不知道。解决方法是在payload中加一条不影响程序的ret指令地址减少8字节栈长度可解决。需要注意的是因为pop_rdi_ret第一步是把栈顶内容传参,所以这条单独的ret不能紧接在pop_rdi_ret后面。
Pasted image 20251202224254
完成payload如上。

例题——2

0x401新生交流赛 - 0x401::CTF
Pasted image 20251204220102
我们看到题目打开了Canary和PIE保护。
Pasted image 20251204220606
同时开到输入过长字符,程序提示*** stack smashing detected *** : terminated,触发了Canary保护,表示题目存在溢出漏洞。可以尝试覆盖函数返回值尝试得到shell解出。
Pasted image 20251204221726
用ida查看题目字符,没有发现systerm和/bin/sh,同时看见出题者给了一个gadgets——pop rdi,提示我们要用libc的解法——让程序泄露libc库函数地址,在定位利用libc库里面的system。
这里总共需要得到三个信息,PIE、libc.address、Canary。
Pasted image 20251204222129
Pasted image 20251204230801
Pasted image 20251204230857
ida反编译后看到有三次read,buf和v3分别存储32和56个字符,而每次输入都允许舒服256个字符,存在大量溢出空间。而这里需要解决两个前提保护——Canary和PIE。
Pasted image 20251204223005
汇编层面看到Canary被放在rbp-8。read不会自动在输入函数最后添\x00,printf正常输出会在遇到字符数组剩余空间内的第一个\x00时停下,而如果字符数组被溢出,printf会输出后面的栈内容。Canary的最低位时\x00,所以需要把这个\x00溢出覆盖收受后再添上。
Pasted image 20251204225500
实现Canary泄露的payload如上,再buf内的写入溢出。还需要泄露vlun函数ret的返回地址,计算PIE保护下的基地址。
Pasted image 20251204230340
ret返回地址时vlun的下一跳指令,偏移量为0x1347。
Pasted image 20251204230256
实现base_addr泄露的payload如上,PIE保护被攻破,还有libc的基地址不知道。接下来需要在第三次read内输入ROP链使其泄露已经调用的linc库函数地址。(补充知识:C语言调用libc内函数(如:printf),需要先调用程序内plt表内指令,获取got表内相应函数地址。简单都说ret printf@plt会执行printf,打印出printf@got会泄露libc内printf函数真实地址。)
Pasted image 20251204232956
程序内给了pop_rdi_ret。
Pasted image 20251204234720
这里把printf@got传参给printf,vuln接下来返回printf打印printf@got,可以计算到libc.address。然后程序返回了vuln。
如果不加ret_addr会发现程序崩了。

gdb.attach(pwn)
pause()

在io.send前写上如上代码动调。
Pasted image 20251204234732
看到第一行汇编最后的<[0x7fff024377b8] not aligned to 16 bytes>就是栈对其伪问题。程序崩了就多调几次把,唉~
到这里就得到了所有前提信息。最后返回read写上最后的system的ROP,就可以得到shell了。但不能返回到read前。
Pasted image 20251204235242
上图可见一个read函数传参和调用的完整汇编。会发现传入read的指证参数是以rbp定位的。而刚刚我们通过vuln的ret,返回到printf暴露libc前执行了leave。

leave
**************
mov   rsp rbp
pop   rbp

leave等与上面两个代码,其中pop会把我们之前填到rbp内地址指向的空间的垃圾内容丢给rbp,使得我们再调用read时传入的buf的指针错误。所以需要返回vuln的开头。
Pasted image 20251205000218

开头的mov会修正rbp为可用地址。
Pasted image 20251205202407
Pasted image 20251205204741
@完整payload如上。
明显的,上面的基础解法需要重新跑一遍vuln,过于繁琐。我们尝试优化一些。

其实在第一次发送payload后,泄露Canary的同时,也会把rdp的值泄露出来,而rbp存放的的地址前两位永远是0,故可以如下获取rbp。
Pasted image 20251206195210
因此可以将上文payload_ROP改成如下即可避免修改rbp。
Pasted image 20251206195822
Pasted image 20251206204647
但这样的payload_ROP却会让程序崩掉。
Pasted image 20251206200139
我们在payload_4发送前用gdb抓住程序逐步查看问题在哪里。
Pasted image 20251206204043
发现在read后原本因该要进行pop rdi的,但是这里没有进行。
Pasted image 20251206204013
进入syatem后用于传参的rdi为0。显然这里read的返回值出现了问题。原因在于main函数没有申请变量,当vuln的leave ret后,rsp和rbp重合了。当执行到payload_3最后的vuln_addr_read后,call read后函数的返回地址会压栈到rsp保存。而传入read的参数(v3的指证)是用rbp定位的(rbp-40h),我们用read溢出时也会把rsp内的返回地址盖住,从上图的汇编部分可以看出,覆盖在rsp上面的地址刚好是system前的ret,比我们预期的ROP链条少了两步(pop_rdi_ret binsh_addr)。
Pasted image 20251206210947
解决的第一个思路是让rbp往下挪两步,这样leave后的rbp偏下,我们后面溢出的ROP链也偏下,刚好使得在read函数进行时,溢出到rsp时刚好是pop_rdi_ret,也能达到预取效果。
Pasted image 20251206211500
另一个思路是让rbp足够靠下,rsp不会被payload_4的溢出盖住。
Pasted image 20251206212336
完整payload如上。

但是!!!!!!!!!!!!!还是太麻烦了!!!

接下来介绍更为简单的解法。

我们注意到上面vuln栈帧是70h的长度,v3的起始位置是rbp-40h,而read允许输入100h!!!

这里补充一个知识,程序在执行main前会经历一下步骤(有省略)
start -> libc_start_main -> libc_start_call_main -> main ret -> libc_start_call_main ->exit
会先进入start函数,而在start函数内部会call libc_start_main(下面简称LSM),而LSM会接着call main,故而main函数的返回值是libc库内一函数的某一步骤。
Pasted image 20251206214308
上图是用gdb动调题目附件,在start后用stack查看main的返回地址,用vmmap查看这次运行下libc库的起始地址,相减就可以得到main返回地址在libc的相对偏移。用此偏移可以计算得到下次运行时libc库的基地址。
Pasted image 20251206215357
用ROPgadget --binary ./libc.so.6 --only "pop|ret" | grep "rdi"命令便可以找到libc库内的pop rdi ret。
Pasted image 20251206220026
再确认一下偏移量就可以得到而精简的exp.py
Pasted image 20251206220134
(该payload原作者fa1lsnow)

http://www.jsqmd.com/news/64443/

相关文章:

  • AlmaLinux下mysql 8安装与数据迁移
  • ICPC Region 游记
  • 12.6(2)
  • Replicate 加入 Cloudflare:构建网络即计算机的下一代 AI 基础设施
  • abc435_f
  • Ubuntu下,MySQL修改端口号
  • 记CACC 2025区域赛
  • K8S中Ingress的采用
  • 深入解析:Chrome插件:实现Axure RP HTML原型的便捷预览
  • Ubuntu下,MySQL查询报错sql_mode=only_full_group_by
  • CRNN
  • 2025年广东地区湘菜供应链江西小炒社区厨房称重自选食材配送服务商TOP5评测!全品类供应+定制化服务权威榜单发布,赋能餐饮高效运营
  • 详细介绍:【数据库】国产数据库替代实战:金仓KES如何以“智能运维 + 低资源占用”年省百万运维成本?
  • 学习心得
  • 『NAS』在群晖部署一款好看的白板工具-Excalidraw
  • 方差的迭代计算公式 - 指南
  • Ubuntu下,MySQL密码遗失时修改密码
  • 一些特性的演变过程(C++11、C++14、C++17、C++20)
  • 支离破碎发言(七)
  • MD-FPN
  • 2025最新贵州特产/伴手礼供应商TOP5推荐!贵州/贵阳/遵义/毕节/黔东南特产选购平台/渠道/供应商/采购渠道榜单发布,甄选贵州地道风物好礼
  • 进程监控:通过 SSH 远程监测嵌入式设备进程重启
  • 街头徒手健身3硬核核心训练
  • 我们的休闲娱乐区,会变成什么样子(哽咽)
  • 【ZeroRange WebRTC】对称加密 vs 非对称加密(从原理到实践) - 详解
  • Cloudflare成功抵御AISURU僵尸网络发起的破纪录29.7 Tbps DDoS攻击
  • 2025最新贵州伴手礼厂家/采购渠道/供应商/平台/卖场/超市TOP5推荐!地道风物+文化赋能权威榜单发布,甄选贵礼传递黔地心意
  • 从 Spring Boot 2.x 到 3.5.x + JDK21:一次完整的生产环境迁移实战 - Rainbow
  • 2025.12.6日21:24-incapacity无能力
  • 001.makdown快速入门