发表关于《来pwn我一下好吗》的write up已经过去一段时间了,这篇write up对于我这样的Linux漏洞攻防新手来说很多地方都是很难理解的,后来结合这篇write up,我又调试了一下这个pwn me的题目,发现无论是从error出的题目,还是z神写的write up,都特别精彩,有很多值得学习的地方。
这个漏洞writeup从各个角度看,涵盖了很多Linux下的攻防技巧,于是我把我分析思考的过程,进行了一个总结,拿出来跟大家一起分享,或者说,我把这个漏洞拨茧抽丝,提取一些最有意思的部分,来和大家一起享受这个精彩的pwn me之旅。
在开头的最后,必须再次感谢一下出题人 Error 以及 writeup的作者 Explorer ,真的学习到了很多东西。
这篇文章需要结合z神的writeup来看,传送门在此 http://drops.wooyun.org/binary/16257
文中若有不到位或者描述有误之处,请大牛们批评指正,多多交流,也希望能对大家有所帮助!(抱拳!)
在阅读这篇writeup的时候产生了很多疑问,其实主要还是对于Linux下堆管理机制,以及一些Linux攻防技巧不熟的原因导致的,其实漏洞并不难处理,关键在于如何大开脑洞来利用,下面我抛出我在做这个pwn me调试时产生的问题,并且通过动态调试的过程来解决这些疑问。
这三个问题其实都只是引子,在动态调试的过程中,发现了很多有趣的地方。在开始调试前,我想再啰嗦一下为什么会产生这三个问题。
首先第一个问题,fastbin这种malloc机制比较好理解,单向链表,也就是说只有一个指向下一个chunk的指针,但是如何利用这个指针达到任意地址写的呢?
第二个问题,从z神的writeup利用中提到了两种leak方法,那么它们的工作机制是怎样的呢?
第三个问题,writeup中提到的通过改写free hook达到执行/bin/sh的目的,那么这个过程又是什么样的呢?
下面带着这三个问题,开始最精彩的旅行~
在writeup中z神提到了几个坑,其中一个是check函数的,作者error后来也在文末提到了,两种方法,一种是利用任意地址写来修改全局变量a000的大小,另一种是直接无视掉,因为确实有时候会出现check函数检测没有通过的情况。
另外一个坑是关于FULL RELRO保护,其实通过gdb-peda可以直接查看保护开启情况。
这个保护主要是针对got表,这个got表存放着很多函数指针,针对got表攻防的概念有点像Windows下虚函数的攻击,关于got表的攻击有一篇文章讲的挺好的,传送门在此 http://drops.wooyun.org/binary/6521
这里不再赘述,主要是z神在这个pwn题目中利用方法也很有意思。
说到leak addr就要从fastbin chunk说起,fastbin chunk是Linux下堆的一种分配方式,比较简单,采用的是单向链表的方式,只有一个fd指针指向下一个未分配的chunk,利用这种方法,可以泄露出堆的地址,这和后面的任意地址写有很重要的关系。
对于fastbin chunk,网上的说明有很多了,其实简化版的图是这样的。
在这个pwn题目中,对Vul结构的构造,rank恰好处于fd指针的位置,那么接下来在delete函数中调用free的时候,这个rank的位置会泄露堆地址,这是fastbin chunk的机制。
在这个漏洞中,要分别malloc申请两个fastbin chunk,然后对这两个chunk进行free操作,首先来看一下申请过程。
在6040a0地址位置是rank存放的位置,也就是fd的位置,下面要关注这个地址,其他chunk的申请过程我不再展示了,直接来看看free chunk之后构成fastbin chunk链表的过程。
下面对这个chunk执行free操作。
这里要提的一点是两个free,在pwn题目中,一个free是对detail指针的free,另一处是free整个memory,观察地址00604110位置,其实这里就是之前vul结构中detail指针malloc地址的位置,这里作为006040a0之前的chunk释放,而detail指针位置恰好是fd的位置,因此这里指向了00604070,也就是下一个未使用chunk的地址。
接下来观察eip处于memory free的函数调用处,接下来单步步过,观察006040a0地址。
可以看到,这时006040a0位置已经泄露了堆地址,而这个位置正好之前提到的rank的地址,而在writeup中已经提到了,在rank赋值时有一处if语句判断,如果rank大于0则赋值,因此,只要控制rank小于等于0,则不会赋值,通过这种方法,再通过程序的show函数,就可以通过rank打印出堆地址。
那么其实这个过程,就是在二进制漏洞攻防中,常用于bypass ASLR的利用方式,而上述的这个调试情景,就是一个最简单的过程
那么接下来,又是如何达到任意地址写的过程的呢?这就要提到pwn题目中edit函数存在的use after free释放后重利用,这个其实是uaf中一个非常简单的情况,在chunk释放后,没有对其进行标记,从而导致在释放后仍然能对该chunk进行操作,通过这种方式,可以修改fd的值,这就是一个典型的fake chunk的利用场景了,这样如果再次malloc,可以讲地址跳到任意地址,从而达到任意地址写的目的。
关于fake chunk,freebuf上有一篇不错的文章,传送门在此 http://www.freebuf.com/news/88660.html
接下来我们来看一下这个uaf漏洞是如何通过uaf漏洞造成fake chunk的,这里我要说一下,在调试的过程中,虚拟机很不给力的挂了两次,导致调试时堆地址发生了改变,但整个过程和描述是不变的。(忘了用快照的悲剧。。。)
首先是free之后在edit中可以对rank进行赋值,而此时实际上rank所处的chunk已经被释放了。
可以看到,尽管该chunk已经被释放,但是title标记仍然存在,也就是说,在memcmp的过程中仍然可以匹配到title的值,从而可以继续接下来的赋值,就是这样的过程,令我门可以控制rank的位置,而之前也提到这里可以创造fake chunk,接下来重新申请。
这里地址改变的原因之前已经提到,在这里的fake chunk地址是我随便输入的,证明fake chunk在fastbin chunk申请过程中对chunk地址的影响。
在开头的三个问题中,我提到了一个泄露libc基址的问题,其实在上面一个小节中,已经提到了泄露堆基址的方法,以及任意内存读写的方法,那么其实在z神的writeup中,提到了leak libc的两种方法,第一种是他在文中提到的泄露libc基址的方法,在我调试的过程中,确实发现了在chunk内某个偏移位置存在一个libc的地址,那么利用任意地址读的方法就可以找到这个libc的地址,以此计算出libc的基址。
其实这里我想说的是z神提到的另一种方法normal bin,normal bin和fast bin差别比较大,首先,normal bin chunk采用的是双向链表,在后面动态调试中可以看到,而在normal bin双向链表表头地址是在libc中,当时我的疑问也是在于, 释放chunk后会产生libc的指针到底是怎么一回事 。
带着这个疑问来调试这个原理,位置仍然是在add函数中,首先对vul结构第一个malloc位置没法控制,但是对于detail指针却是可以控制的,通过控制detail长度,我们就可以让fastbin chunk变成normalbin chunk,我们申请一个大小为4000的chunk,这时候,linux就会采用normal bin的机制。
我们需要申请两个chunk,在释放时构成双向链表,在第一个chunk释放的时候,chunk空间并没有什么变化。
接下来对第二个chunk进行释放,首先到达free的位置前,来观察一下chunk空间的情况。
单步步过,在此观察chunk中的情况。
可以看到,此时位于libc中的链表头部指针已经暴露出来了,可以再通过任意地址读取的方法获得到这libc中的地址,之后可以计算出libc的基址地址。
在got表被保护的情况下,通过任意地址写的方法,可以修改关键地址,从而达到执行代码的目的,刚开始一直对z神 write up中关于最后执行的地方百思不得其解,后来通过调试终于发现这到底是怎么回事了。
其实在前面的小节我已经提到过,在free的时候,会先free detail指针,而detail指针在创建的时候是可控的,于是按照z神的说法,在detail指针写入/bin/sh,这样在free detail的时候,如果通过任意地址写覆盖掉free,将system函数的地址覆盖上,这样free detail就会变成system detail,于是最后delete函数关键位置变成执行: system(/bin/sh)
这样就可以在目标端口拿到shell了(好像比shellcode还方便,但真实攻防中很难)
到这里,所有疑问都基本上解决了,感觉学到了很多东西,我接触二进制的时间比较短,还在努力摸索,这里想分享一些小心得,希望大家能多多指点。
最后感谢三个白帽,ca叔邀请我到三个白帽做这个pwn题的时候还是看到我离大牛们还是有不少的差距,今后还是希望自己能够多受点挫折,多进步一点!