Maybe_fun_game
程序分析
这道题目挺考验逆向的能力的,需要将程序比较看的清楚。
程序有四个功能new、del、edit和show功能。我们先这几个函数放在一边,这个程序的输入输出的结构是比较特殊的,我们接下来对这两个函数进行分析。首先需要先介绍一下这些程序进行输入输出的结构,进行输入输出之前都会将需要输入输出的内容转化为如下的结构,其中data中存放的就是要进行输入输出的内容。
1 | u_int64t flag1;//0x1234567812345678LL |
输入输出函数
input
input函数首先会对输入的数据进行解密(要求你输入的是加密后的数据),然后按上面的结构将数据中的内容解密出来。逆向这个解密的过程就不赘述了,这里直接上加密的代码。(函数名是decrpt不重要,就是input对数据操作的逆过程)
1 | def decrpt(con): |
对于解密完成的数据程序会做一些检查,然后将数据内容提取出来,返回一个指针ptr:
- 检查flag1==0x1234567812345678LL,否则跳到6
- 利用malloc为flag2分配flag2_size大小的空间,将flag2的内容复制上去
- 利用calloc为data分配data_size大小的空间ptr,将data的内容复制上去
- 检查sum==flag2_size+data_sisze+0x20,否则跳到6
- 检查flag2==0x4141414141414141LL,否则跳到6
- 释放掉calloc分配的指针,返回ptr指针
- 释放掉malloc和calloc申请的指针,返回error
print函数就比较简单了,会按照之前的结构存储需要输出的内容(flag2_size永远是8),这个函数会在中间利用calloc申请两次,分别用来中间存储flag2和data。
程序主逻辑
程序每次循环都利用input获得选项然后跳转到相应的函数执行,程序维护两个全局变量src和size,src存储内容的存储空间的地址,size存储内容长度。
alloc
程序输入Size,然后按照size的大小用malloc申请空间,然后将空间地址存到src中,用input输入数据,将解析后的内容复制到src中。(if ( *p )
这里是通过\x00
截断的)
1 | Print("Size >>"); |
del
释放掉src的空间,将地址src和大小size全部清空
1 | free(src); |
edit
edit函数就是重新修改src的内容和alloc输出内容的部分非常相似,唯一不同的是这里是\n
截断的,这意味着可以通过edit输入更多的数据。
1 | if ( *p_1 != '\n' ) |
show
show函数就是通过print打印出src的内容。
思路
程序运行在glibc-2.23的环境下
构造double free
这里我们观察alloc函数中的如下部分,在input完成后程序会执行free(ptr)
1 | Print("Size >>"); |
而在input函数中,如果某一步的检查没有过,就会执行如下操作
1 | Print(v18); |
这样我们故意输入错误的数据造成input进行上述操作,就能获得这样的效果free(ptr)->free(qword_2030E0)->free(ptr)
,此时就能在fastbin中构造double free。
1 | pwndbg> heapinfo |
此时我们再通过alloc申请到0x55a96a082220堆块,利用输出函数就能得到一个heap地址。
获得libc
在input中会通过我们输入的数据进行内存的申请,因此我们可以控制其申请一个很大的ptr,造成fastbin中的数据进行合并,然后进入unsortbin我们就能够拿到相应的地址。但是在每个函数(alloc、edit、del和show)之间都会有大量的print,会申请内存而且不会释放,因此需要特别构造。
当前我们申请到了0x55a96a082220的chunk,利用uaf我们修改fd到0x55a96a0821a0+0x40的chunk。
1 | (0x80) fastbin[6]: 0x55a96a0821a0 --> 0x55a96a082220 --> 0x55a96a0821e0 (size error (0x0)) |
由于与0x55a96a0821e0在0x55a96a0821a0中,我们在申请0x55a96a0821a0后对0x55a96a0821e0的size设置为0x81以绕过fastbin申请时对size大小的检查,然后设置其fd为0x55a96a0821a0。
1 | pwndbg> x/16xg 0x55a96a0821a0 |
再次申请到0x55a96a082220时将fd设置为0,为了让绕过堆合并时的检查(堆合并时不能有double free)。然后就能申请到0x55a96a0821e0,此时我们fastbin中的连就如下所示(此时能正常合并了)
1 | (0x80) fastbin[6]: 0x55a96a0821a0 --> 0x55a96a082220 --> 0x0 |
此时我们进入show,我们构造数据将ptr_size设置很大(0x400),这样这样这样就造成堆的合并。为什么在0x55a96a0821a0+0x40申请一个chunk呢?观察print,在数据拷贝输出之前要进行两次calloc,为了使libc地址刚好落在我们的chunk里面我们我们申请的空间的地址就需要在0x55a96a0821a0加上0x40。
1 | v2 = calloc(8uLL, 1uLL);//calloc 0x20的空间 |
获得shell
这个就比较简单了,用之前的方法构造double_free,然后利用uaf修改fd到__malloc_hook-0x23
的位置,然后申请到__malloc_hook-0x23
的堆块,修改__malloc_hook
为gadget就能获得shell了。
EXP
1 | from pwn import * |