一、前言
时隔一个月某有更新博客了,上一次搞那个patchelf调了快半个星期没弄好之后心态大崩,一下子动力失去,直接摆烂了一整个五一。五一过后没多久,被uuu师傅狠狠的打击到了orz,又捡起来了继续pwn。然后紧接着就是省赛,看了下去年的省赛两题都是2.31的堆,都是没有输出函数要打stdout去输出,最后劫持tcache去getshell,当时一看差点直接晕厥,后面一个多星期速成完了tcache和stdout输出,对这两个技巧有了大致了解后就是五月的das了,又是stdout输出(
直接赶趟了,一篇博客一道题搞完劫持stdout还有最后利用tcache劫持到exit hook完成getshell
二、做题前置知识
少年,我想你看完前置的知识,打通例题毫无🍐
1、关于IO FILE
初识IO FILE
关于IO,一开始是听说2.34版本ban掉了两个hook之后,基本都是打IO去getshell,直到现在我还没有完全看懂和理解IO。
关于IO FILE,结合众多师傅的博客和ctfwiki,我们对其有了大致的了解。
IO FILE有若干个相关的结构体,例如_IO_FILE_plus等,用于完成IO的相关功能(具体的我目前还没有仔细去了解,大致理解为控制IO函数完成输出输入等功能)。stdout就是其中一个IO结构体。_
_当我们调用puts函数时, _IO_puts
函数内部会调用 _IO_sputn
,结果会执行 _IO_new_file_xsputn
,最终会执行 _IO_overflow
(摘自A1lx师傅的博客),当我们能控制stdout这个结构体里面的内容,经过特殊布局绕过检测后,就能够输出一个libc函数的地址,从而计算出libc的基地址
stdout其中布局为:payload=p64(0xfbad1800)+p64(0)*3+’\x00’时即可,至于想细究为何,上面A1lx师傅的连接里就有,如果要利用只需要申请到stdout布局payload即可
如何找到stdout的位置
首先我们是并不知道stdout结构体的位置的,因此我们需要利用到unsorted bin的残留指针,在前面我们知道堆块在进入unsorted bin时会有指针指向main_arena+96的位置
然后我们再来康康我们的stdout结构体
前面的0x7ff那一串是不是惊人的相似,与此同时同一libc的低三位是不变的,所以即使在地址随机化的情况下我们也可以知道libc的低三位是多少,例如本图上就是0x6a0。那么我们这个存在unsorted bin里指向main_arena+96的fd指针,这里面只有第四位我们是不清楚是多少,但是我们可以爆破💣这个第四位,就有1/16的概率让这个指针指向我们的stdout结构体。此时利用这个残留指针就能申请堆块到stdout结构体。
what’s 残留的fd指针
接下来就是如何把我们的堆块🐏进stdout结构体里
这里我要特别mark一下什么叫做残留的指针
众所周知,当我们在tcache里面找不到符合用户申请大小的堆块时,会去unsorted bin里找,如果有size>malloc_size的堆块,就会切割该堆块用以满足用户需求。切割过后,fd指针就会移动到剩余的空闲堆块里。就是这个移动,移动下去后,原本的地方不会变成0,而是会残留原本的fd指针。
giao!和uuu师傅一番交♂流后,我领悟了为什么会有这个残留的指针。原因是我们malloc的时候,当我们去割unsorted之前,系统会遍历整理一遍unsorted bin里面的chunk,找有没有直接符合malloc_size的堆块,并且按照大小让unsorted里面的chunk进入small或者largebin,如果遍历了unsorted后依旧满足不了,就会去largebin里找符合size>malloc_size的堆块,如果有,就切割该堆块,并且将剩余的部分丢回去unsorted bin。因为一开始是从unsorted bin过来largebin的,所以切出来的部分fd区域存有一个指针,剩余的部分因为丢回去了unsorted bin,所以会有一个指向main_arena的指针,所以我们看起来才会像fd被挤下去了。uuu师傅tql呜呜呜
附上uuu师傅调demo的图:
接着如图,就能看到有两个指向main_arena的指针:
在割了0x21大小的堆块出来后,该0x21大小的堆块里残留着一个fd指针,如何利用
因此我愿称之为时间刺客-艾克(有个影子
2、关于tcache
tcache结构体
在glibc2.26版本之后,就加入了tcache bin用于管理空闲的堆块
关于tcache bins,glibc定义了 tcache_entry和tcache_perthread_struct两个结构体用于管理tcache bins,并且tcache struct是分配到了堆上,在堆最初产生时会分配出一定内存来保存tcache struct,如图:
我们可以看到,在2.31版本下这个0x291就是分配来管理tcache bins的(之前的版本大小好像不是这个),我们也可以劫持整个tcache struct去攻击。
tcache的攻击手段
tcache和fast bin类似,都是用FILO的单链表来维护,范围在0x20~0x420,默认每一条链表的大小为7,但是tcache并没有fastbin类似的size_check,这意味着通过我们利用tcahe可以更加方便的把堆块申请到任意地址上。而且tcache和fastbin一样,可以构造double free。
但是在glibc版本更新后,tcache加入了一些检测,其中加入了key字段指向tcache struct,以此来防止double free。与此同时,加入了counts位去记录这一条tcache bins链表里存储了多少个tcache(这也是为什么在fastbin attack那篇博客里,我们取不出fake_chunk的原因),这意味着如果我们释放了一个chunk进入tcache,伪造了这个chunk的next指针后,是申请不出来这个next指针指向的区域的,因为此时我们的counts位为0,系统认为这里面没有堆块了,转而从top chunk切割。所以我们如果想要顺利取出fake_chunk就得控制下counts位。
3、关于exit hook
实际上并没有exit hook,他不是一个钩子函数,而是在exit函数进入时会调用的一个函数指针(exit-> __run_exit_handlers -> _dl_fini 这么一条调用链)
关键就在这个fini函数,它会调用rtld_lock_default_lock_recursive 和 rtld_lock_default_unlock_recursive 函数,并且有一个 GL(dl_load_lock) 的传参。 因此我们不难想到两种利用exit hook来getshell的方法,一种是直接改成onegadget,另一种是改成system并且控制参数。
此外,exit函数属于libc函数,偏移可以计算出来,因此我们能有办法得到fini函数调用的两个函数位置,再通过申请chunk到这个地址上,就能修改函数指针使其指向我们最♥的onegadget或者system。
如果要通过改成system来getshell,就得控制GL(dl_load_lock)这一传参。gdb 跟进会发现,它其实是结构体_rtld_global的一部分,_rtld_global._dl_load_lock.mutex.__size的地址。
三、例题 DASCTF x MAY 山重水复
一些bbll
又是一月一度的DAS,只能说看着uuu师傅ak,只能膜拜orz。
这个题赛中十二点就有思路了,但是这个offbyone审计代码的时候觉得有,但是上手调一把之后发现没有chunk overlap的现象,就以为是我自己审错了,然后折磨了自己好久,最后随手申请了size为6的堆块,输入了7个a,gdb进去一看是7个,才发现真有off by one,是我申请的size不对(要8结尾的size),没有复用到presize,导致没有chunk overlap(还是太菜了
代码分析
经典菜单列表,给了一个手动调用exit的机会,但是没有输出堆块内容的函数,所以就想到应该是打stdout泄露地址,打exit hook去getshell。
但是为啥我们不去选择我们的老相好malloc_hook或者free_hook呢,原因在于他每一次选择完后都会调用一次check函数
在edit函数里,他写入数据的函数是自己定义的一个my_read(这一看就有问题
存在off by one
利用逻辑
首先因为tcache的存在,我们想要利用unsorted bin的话,要么填满tcache,要么申请一个大于0x420的堆块。这里我们采用第二种方法,于此同时,因为off by one写不进三位,所以这里我们采用的方法是用chunk0去改写chunk1的size,让chunk1包含chunk2,利用chunk1去修改chunk2的size达到目的
1 | add(0,0xb8)#1 |
然后我们释放掉chunk2,让2带着3、4、5,一起滚进去unsorted bin
此时释放掉4,5,让其进入tcache,为unsorted bin的fd能变成tcache的next指针做准备
1 | dele(2)#3 getin unsorted |
这里有一个小细节,我们先dele(4)再dele(3),因为我们说过,tcache会有counts位记录当前链表有几个chunk,所以我们先放多一个进去,让它的counts为2,然后用3这个堆块去接unsorted bin的fd,这样就能申请出来fake_chunk
最后我们再申请一个0x2的小堆块,再次切割unsorted bin,此时我们编辑这个小堆块就能修改两个字节,并且这个堆块也在tcache里(因为刚刚我们是通过free掉本来存在的3,让其进入tcache,而3本来就在伪造的大chunk里面,所以我们申请0x20,就会去切割unsorted ,分配给我们这块进入了tcache的内存,达到类似UAF的效果),因此就能够修改这个残留的fd去撞stdout结构体。
1 | add(3,0x68) |
然后再利用一次tcache poisoning去申请到exit hook,填上one gadget
完整exp:
1 | from pwn import* |
四、又是一些碎碎念
打完das之后的第二天省赛,又是off by one…我只能把exp写到劫持free_hook,因为开了沙箱,而我又对orw一窍不通,只能丢给师兄,自己在一旁坐牢。
现在逐渐又有了pwn的动力了,f师傅和g师傅都那么猛,我不能落下,丢了二进制冲分小队的脸orz。此外特别鸣谢我的偶像uuu师傅,能认识uuu师傅真的很开心捏
接下来就是准备下期末考,争取不挂科的同时摸摸ctf,搞搞作品赛
hustle every day