作者:hongxiaochen8847_106 | 来源:互联网 | 2023-10-10 23:51
作者:rac_cp
稿费:300RMB(不服你也来投稿啊!)
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
前言
最近的上海大学生网络安全赛就只出了一题pwn450,对于还不是很会的我,瞬间懵逼,不过大佬还是大佬,最后还是挺多大佬做出来了,不过反正我没做出来,最后看题解,用到了两个点,一个是堆溢出,另一个就是利用这个FILE结构体做文章;菜鸡的我只有一个一个学,堆溢出的这点后面再学,先学习了如何利用FILE结构体,也就写出了这篇文章。学习的过程主要也是看大牛的博客文章写出来的,在最后也会附上相应的文章。
一、结构体介绍
首先介绍下FILE结构体,下面图就是FILE结构体:
平常我们正常使用FILE结构体的情景是:
此时,系统会给应用程序申请一段空间将相应数据赋值好后然后将地址返回给fp。事实上,系统并不是直接分配的FILE(_IO_FILE)结构体,而是名字为_IO_FILE_plus结构体,这个结构体包含了_IO_FILE结构体,还包含了一个虚函数表指针,其定义为:
这个结构体里面的_IO_jump_t指针类似于C++中的虚函数表,而其结构中具体虚函数名称定义如下:
二者之间的整个关系结构图如下:
图片来源:https://outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/
二、利用原理
我们平常在应用程序中调用fclose、fputs这些函数的时候系统最终都会通过_IO_jump_t这个函数表指针对函数进行调用(如fclose会调用close函数等)。
在知道了这一点后,试想如果我们想办法利用其他各种溢出方式覆了应用程序的文件指针,使其指向我们可控区域,在该区域伪造相应的_IO_FILE_plus头(主要是_IO_jump_t表或者是表中函数的指针),最终在程序调用fclose函数或其它函数的时候,就可以控制程序去执行我们想要它执行的地址,control the eip, control the world。
比较常见的是利用strcpy,strcat等覆盖了文件指针然后进一步利用,下面会举两个例子来进一步理解如何实现,一个是利用UAF来覆盖修改指针,一个是利用strcpy栈溢出。
三、实例
1)UAF利用
直接贴出源代码(来源:https://outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/):
可以看到,程序首先申请_IO_FILE_plus (sizeof(_IO_FILE+sizeof(void)) 结构体大小的内存块,接下了free掉,紧接着打开一个文件,此时系统会将刚刚释放掉的内存空间分配给文件指针(不懂可以先去看下UAF的相关资料)即此时str与fp指向同一块内存空间,这时利用str将fp结构体的_IO_jump_t指针指向我们伪造的funcs数组,同时再系统最后调用fclose的时候,即调用_IO_jump_t里面的close函数的时候最终变成了调用我们定义的pwn函数,最终程序执行截图如下:
2)栈溢出利用
先贴出源码(看了别人的文章以后改编的)
程序的意思是将”abc…..”这个字符串写进到aa.txt中,编译。
首先是漏洞的发现,可以输入最多1046个字符,最后strcpy拷贝到stage缓冲区中,会造成缓冲区溢出,拖进IDA中,如下图
可以看到stage为bp-0x41A而fp指针则为bp-0x8,也就是说在输入长度大于0x41a-0x8=0x412(1042)的时候就可以覆盖fp指针,最大允许输入为0x416,多四字节,刚好可以覆盖fp指针,为了降低难度只体现利用FILE结构体的思想,程序中没有要求我们自己泄露栈地址什么的,而是直接给出了buff地址,所以可以直接覆盖fp指针,指向buff中的地址。最后在调用fputs(源代码是fprintf,编译的时候优化成了fputs)的时候,我们把它调用_IO_jump_t结构中对应的函数地址改写成get_flag函数的地址即可。
在写exp的时候还遇到了不少的困难,下面说我是怎样一步一步解决的。大牛的文章是将stderr指针中的数据拷贝到我们输入的内存中(详细情况可以参考http://www.evil0x.com/posts/13764.html),可是我想文件指针里面的数据要不都是地址要不都不怎么用,如果我们事先使用gdb将stderr里面数据拷出来,由于地址随机化的影响,数据很大肯能性已经变得不可访问了,这样容易出错,在看了一遍_IO_FILE结构体后我决定直接将fp里面的数据全都覆盖成stage的地址。前面我又已知了系统的_IO_FILE结构体大小为0x94(148),所以在0x94后面的数据我把_IO_jump_t函数表指针的值设置成了stage+180的地址,在stage+180的地方伪造_IO_jump_t的函数表,并将函数表里面的地址都指向get_flag函数。这样最初写出来的exp代码如下:
执行脚本…….
没有任何反应,gdb附载程序,看下哪里出错,断点下在fputs函数调用前。重新执行脚本
可以看到执行fputs时,此时的fp指针是0xbf9e3c76,而打印出来的stage地址为:
二者相同,说明第一 步我们已经成功,即将fp指针覆盖成我们输入的地址,可以看下此时stage地址中的数据内容
全都是stage的地址,到这一步还没错,接下来按c继续执行看哪出错。程序崩了,重新再来一次,这次不下断点,直接看哪里出错
程序错在fupts+141这里,也就是call[eax+0x1c]这里,查看eax寄存器里面的内容
发现和stage的地址很像,只是错位了两个字节
往上追溯,看eax是在哪赋值的,由于之前的调试,现在我直接去看fputs+120的地方。
看到在fputs+123的地方将eax赋值,查看esi寄存器
发现是stage的地址,其实也就是文件指针,fputs+123代码将文件指针+0x94(_IO_FILE大小就是0x94),刚好是_IO_jump_t函数表指针的地址,不过这里还多加了个eax*1,继续往前翻,看eax来自于哪里
可以看到eax寄存器中的内容来自于esi+0x46,突然想起之前的_IO_FILE结构图,
里面有个offset字段,也就是说通过_IO_FILE指针寻找_IO_jump_t函数表指针的时候不是直接+0x94得到的,而是通过+0x94+offset得到的,之前我们输入的offset可能会很大,导致访存错误,所以下一步修改我们输入的offset字段,也就是偏移0x46处的内容,由于strcpy会截断,故该字段值不能为’x00’,为了方便,我把它直接改成了0x4,同时此时需要将伪造的_IO_jump_t函数表指针地址往后移四个字节。修改后的exp如下:
同时在执行的时候,在fputs+109处下断点,观察eax寄存器中的值
可以看到此时esi+0x46的值正是我们所控制的0x4,继续跟,直到跟到取出_IO_jump_t函数表指针地址的地方,在我这里也就是fputs+123的地方
可以看到此时计算esi+0x94+eax后所指向的地址,正是我们写的指向get_flag函数的地址,进一步查看它偏移0x1c的地址
仍然为get_flag函数的地址(因为我在脚本里将整个_IO_jump_t表函数地址都写成get_flag的地址)。最后在call的时候,就会跳到get_flag函数那里去执行。从而得到shell
到这里就算完成了利用,最后结果如下
最后再提一下,在这里fputs首先调用的是_IO_jump_t+0x1c的地址,
(JUMP_INIT_DUMMY 8个字节),也就是先调用xsputn函数(我也不知道干什么用,之前我猜的是会调用write函数,不过可能是初始化调用这个函数,后面可能还是会调用write,有兴趣的可以继续跟下去),在这里提一下是后面如果遇到,在不覆盖全部指针的情况下,仍然知道如何处理。
到这里,就算写完了。还有就是脚本跑个五六次可能会有一俩次出错,重新跑一次就行了,猜测是stage地址刚好包含’x00’,导致strcpy直接截断,而不复制,最后无果而终,地址随机化开了,多试个一两次就好。
四、 小结
利用FILE结构体,最主要的就是那个函数表,控制了它就可以控制函数的流程,linux系统也没有进行检查,可能后面会进行修补吧,不过修了也没关系,大牛们总会有办法的,不过这就和我没什么关系了。现在想想,这个其实也不是很难,只是一个知识点还可以接受,上海的比赛把这个和堆结合起来有有点蒙圈了。
学了这个之后,下一步可能会针对上海的那题再学下堆的利用,再把二者结合起来再总结一下。
第一次写文章,里面应该有很多不对的地方,有什么吐槽的尽管说,我会学习了以后再修改。最后再感谢下大牛们的博客,学到了很多东西,也再一次看到了差距,还要继续努力。
五、 参考资料
1、http://www.evil0x.com/posts/13764.html
2、https://securimag.org/wp/news/buffer-overflow-exploitation/
3、https://outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/
4、http://repo.thehackademy.net/depot_ouah/fsp-overflows.txt