曾听过某大佬讲,pwn的等级大致分为三种,栈,堆,内核。
这篇文章总结了我目前对堆的理解,肯定不够深入,不过我将更注重于偏抽象和本质的东西,希望各位看完能有些不同的收获。
堆题小结
就我目前的理解来说,堆和栈有个很不同的地方。
栈的漏洞
经常是可以栈溢出直接改变函数返回地址,通过pop|ret
做到连续的劫持程序流以及修改寄存器的值。
堆的漏洞
经常会导致任意已知地址写或泄露
(这一点具体原因等会说) ,对于程序流的劫持经常是通过改got
表实现(如果relro
保护开了那则经常是改写malloc
或realloc
函数中的hook来做到劫持程序流,通常只能劫持程序流一次。onegadget
在这种情况就有用武之地了。
总体来说,堆题劫持程序流比起栈题更加不自由,难度更大,且劫持之后收益也更小(堆的已知地址任意写一般都是用来改got
或hook
,,因为很少能泄漏到栈地址。)
当然,这些都是我目前的理解,实际上我做过的堆题也不多,只是感觉堆题似乎是这样的。
堆概述
首先,堆只分两种,bin
和chunk
。chunk
是使用中的堆,bin
是释放后的堆。
chunk管理
这里有一个特殊机制说明一下,任何的申请的堆size
都会被调整成整SIZE_SZ*2
的整数(SIZE_SZ是一个宏定义,在32位中是4,在64位中是8)
,这就导致size
的最后三位bit
必定为0
,这三位都用来做一个标记位,前两个标记位我不记得是用来干嘛的了。但是最后一位很重要,它标记着物理地址的前一个chunk
是否被使用)
chunk
的管理方式很简单,基本只根据物理地址及size和prev_size和标记位
管理。
图中两个chunk
,黄色标记部分是表示这个chunk
的大小size
.蓝色部分标记的是前一个chunk
的size
,也就是prev_size
。但是图中两个chunk
都在使用(堆管理器
认为这个chunk
在使用的根据是前面说的最后一bit
的标记位。这时候每一个chunk
都会借用下一个chunk
的prev_size
,原因很简单,chunk
只有在需要合并的时候才需要知道前一个chunk
有多大,这个prev_size
才能让堆管理器
最快的找到前一个chunk
判断是否能合并。而在使用时就没必要了。 (这个界面是vis
指令调出来的,今天才发现我哥们不知道这个指令,所以特意说一嘴)
还是放出最经典的图吧(转自CSDN的Morphy_Amo)
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Size of previous chunk | Size of chunk, in bytes| |A|M|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| User data starts here... .
. .
. (malloc_usable_size() bytes) .
. |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
|Size of previous chunk | Size of chunk, in bytes| |A|M|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
bin
bin
那就可有的说了,不过我也不是很懂
首先,bin
和chunk
最大的差别就是bin
通过链表来管理。而chunk
不是。
所有的bin
都是链表。通过fd和bk指针
堆管理器可以快速索引到任何bin
fastbin
范围大致为0x20-0x80
字节(64位)0x10-0x40
字节(32位).这个大小包含size及prev_size
之所以说是大致,是因为不同libc
版本似乎范围不一样。(不过这个影响倒不大,大概知道就行了)
fastbin
采用单链表结构,这也导致了fastbin
必然是先进后出
。
图中这个例子就能看出来,除了最后一个fastbin
,其他fastbin
都有指向下一个fastbin
的指针.
这样操作就是为了方便管理。
举个很怪的例子。比如你班主任(堆管理器)
为了能尽快联系到班上(fastbin)
所有人(每一个bin)
,把班上所有人根据身高,分为fastbin[0]
到fastbin[9]
,比如身高160全部进入fastbin[0]
。(因为前面说堆的大小分配只支持整的,对应到这个例子就是假设所有人身高都是整数,所以这里说的身高160其实是严格160)
而其中每一个进入fastbin[0]
的人拥有下一个进入的人的联系方式(fd指针)
,直到最后一个人什么都没有,如果班主任(堆管理器)
需要用到身高160(特定size的bin)
的人做某件事(分配给用户内存)
,那么他就找第一个人,第一个人去做事之前,会把第二个人的联系方式给班主任(堆管理器)。
此外班主任(堆管理器)
还拥有一个花名册(数组)
,上面记录着fastbin[0]到fastbin[9]
的每个bin
的大小,记录着每个bin
的第一个人的名字。
这样,堆管理器就能再需要用到特定大小的fastbin
时很轻松找到了。
small&largebin
之所以放在一起讲,是因为这两个玩意基本就尺寸的差异。
其实只要懂了fastbin
基本上就能理解这两个了。
1.fastbin
是单链表模式管理,采取先进后出。
而small&largebin
采取双链表管理,比fastbin
多了一个bk指针
,采取先进先出原则。
2.其次就是largebin
再多了一个fdnextsize和bknextsize
这样对管理器就不需要像fastbin
那样,专门为其维护保存一个储存大小的数组。而是直接通过该指针就能找到不同大小的largebin
。
unsortedbin
各种乱七八糟的bin
都有可能进入这个bin
。
具体待会在堆管理流程再说
tcachebin
这个是在比较新的libc版本才有的,有点像fastbin
。并且当同样大小(符合fastbin
的大小)的tcachebin
有了七个之后,接下来的释放操作就会放进fastbin
。
不过讲真,tcachebin
我真不懂,留个坑,以后再填吧?
堆管理机制
我也不太懂,留个坑,下次填。可以看看博客园的一篇文章。那里有张图很不错,但是我复制上来就变成压缩阉割版了。如果有兴趣建议去看看他的。