100days_0ctf_babyheap
100days_0ctf_babyheap
前言
我把这道pwn题归到我的个人感悟这个类别中,且标题前面是100days
没错可能已经有读者能猜到,这道题是我100天前入门堆利用的第一道题,在8月的最后一天,在公司实习无聊时,再次做了一下100天前让自己无从下手,望而生畏的堆题,第一次做的时候真的是打击信心,险些想放弃学习pwn,但是现在再看,轻舟已过万重山,这题做起来已经非常得心应手了,属于一眼出思路,而且我又把当时参考的wp拿出来看了一下,甚至觉得自己写的这份exp更简单粗暴,从一开始对着wp都一知半解,到自己流畅地独立完成getshell,中间相隔100来天,挺多感慨,坚持不断的pwn学习不仅让我有技术层面的进步,还让我找到了一份不错的实习机会,伙食补贴,交通补贴,每周还有两次下午的盒装水果,且薪资甚至高出不少大厂的日薪,算是第一次真的意义上感受到学习带给自己的好处,如果现在的你迷茫不知所措,请停止焦虑,静下心来,你只管坚持,终会有一天你会和我一样看到坚持下来的意义,可能不是100天,只要50天,也可能200天,但是不管多久,总有这么一天
再探0ctf_babyheap
先放两个当初我参考的wp,当时参考了也还是懵懵懂懂的
https://cloud.tencent.com/developer/article/1764339
https://blog.csdn.net/mcmuyanga/article/details/108360375
看这道题的wp的师傅们大多数应该都是处于入门水平,所以这里我自己的wp我会解释的详细一点,每一步带着进行调试
检查保护
第一步还是先看一下保护开了哪些
全绿,如果没记错,这也是我当初第一次做的保护全开的pwn,
第一次看到这个全绿的场景多多少少是有点发怵的,但是不用怕,因为对于堆利用来说,其实影响是不太大的,魔高一尺道高一丈,我们会有许多的手段来应对
本地调试
本地来看的话是四个有用的功能,功能还是很齐全的
IDA静态分析
add():
add创建chunk功能对应choice:1,没什么特别的,只不过这里用的calloc去申请chunk,其特性为:在申请时会清空数据,还有关于tcache的申请特性这里先不介绍了,因为这里的ubuntu环境是16.04没有引用tcache,感兴趣的师傅可自行百度,不过这里calloc无太大影响
自动化交互函数:
1 | def add(size): |
fill():
再看fill功能对应choice :2,这里问题就很大了,也是本题漏洞点,首先会让我们输入index,也就是选择相应chunk,然后让我们输入size,并且后续我们可以输入的content大小就是根据我们这里输入的size决定的,那么就造成了堆溢出,比如我们先使用1创建了0x10size的chunk,这里我们重新输入size0x20,那么就可以溢出到下个chunk,修改其presize,size,fd,bk指针等重要字段,这里注意我们输入size是多少,我们就要填充对应的大小的内容,否则不会break
自动化交互函数:
1 | def fill(index,size,content): |
free():
free功能对应choice :3,正常的free且指针置0了不存在UAF
自动化交互函数:
1 | def free(index): |
show():
show打印功能对应choice:4,打印我们chunk的内容
自动化交互函数:
1 | def show(index): |
思路整理
首先IDA静态分析下来我们可以发现漏洞位于fill()模块,能造成堆溢出,那么我们可以用这个堆溢出做些什么呢,
我们先想一下我们最终要达到怎样的利用效果,这里保护是全部开启的,我们无法攻击got表,一般我们可以选择攻击hook函数,也就是钩子函数,这个东西是什么,我们gdb调试看一下
我们看到glibc2.23-64位下位于&main_arena-0x10的位置,如果malloc_hook里有值,我们调用malloc时会拦截该调用并执行我们自定义的代码,如果我们这个地方放上one_gadget,或者我们构造的shellcode的地址当然这里NX开启不能执行,我们选择one_gadget,再去申请堆块时就实现了getshell,这里用calloc调用同样会触发
我们可以用工具one_gadget去查找libc中合适的地址
我们得到的只是静态地址,无法直接使用,我们还需要得到libc的加载基地址,那么如何去获取这个基址,在堆利用中我们通常借助unsortedbin去泄露libc因为unsortedbin 有一个特性,就是如果 unsortedbin 只有一个 bin ,它的 fd 和 bk 指针会指向同一个地址(unsorted bin 链表的头部),这个地址为 main_arena + 0x58
还记得上面malloc_hook在哪吗?,&main_arena-0x10,那么如果我们得到了这个main_arena+88的地址,我们的libc基址就可以表示为
1 | libc_addr = addr - 88 -0x10 - libc.sym['__malloc_hook'] |
如果有UAF就很简单了,直接释放一个chunk进unsortedbin中再打印出来即可,但是这里我们是没有UAF可以利用的,我们只有一个堆溢出,释放了的chunk就不能使用show了,那有没有什么办法可以让一个没有释放的chunk也能存在这个地址呢,也就是我们没有释放这个chunk,但是它却在unsortedbin中
假设我们现在创建了3个chunk,如果我们chunk0,chunk1,chunk2,如果释放chunk0,可以连带着chunk1进入unsortedbin的话,相当于chunk1的指针还是存在的,我们还可以使用,那么该怎么做,我们可以利用堆溢出修改chunk的size位,把它的size改大,直到包含住下一个chunk不就可以了吗
我们先创建四个chunk
1 | add(0x60) #0 |
第四个chunk用来防止free时和topchunk合并
那么我们现在去填充0,溢出到1的size位
1 | fill(0,0x70,b'a'*(0x60)+p64(0)+p64(0x70+0x71)) |
可以看到,chunk1的size成功被我们修改为了0xe1,此时再去释放chunk1就会连带chunk2也给放进unsortedbin
1 | free(1) |
如果我们再把chunk1申请出来,那么chunk2的fd和bk指针就指向unsortedbin了,且chunk2的malloc指针也是存在的,我们可以对其进行打印
1 | add(0x60) #1 |
那么第一步泄露地址就完成了,接下来要攻击malloc_hook,这里选择fastbin_attack,伪造malloc_hook附近的地址为一个fastbin申请过来就可以修改malloc_hook了,可以看到我上面申请的chunk的都是0x60,free后就进了0x70的fastbin,这是一个小技巧,因为像0x7f这样的值我们比较容易找到,方便我们去伪造
通常64位malloc_hook-0x23的位置就是适合我们去伪造fastbin的位置
这里可以被当成一个0x70的chunk
我们先把chunk2申请回来此时再申请的相当于chunk4,再放进fastbin,注意此时有两个指针指向了同一块地址,chunk2和chunk4指向的同一块地址修改2,对应chunk4的内容也是跟着一起修改的
1 | add(0x60) #4 *2,4 -> 2 |
我们直接修改chunk2的fd,对应被放进fastbin的chunk4的fd也会跟着修改
我们再申请两次就可以申请到我们想要的地址了
1 | add(0x60) #4 |
这里的chunk5就是我们的目标chunk,malloc_hook-0x23的位置,但是由于会用0x10来提供给presize和size位,其实我们是从malloc_hook-0x13的位置开始编辑的
1 | payload = b'a'*(0x13)+p64(one) |
这个时候malloc_hook就已经被我们填入了one_gadget再add一次就getshell了
全部exp:
1 | # encoding=utf-8 |

