-
-
[原创]PWN入门-20-GLibC堆请UAF
-
发表于: 2025-3-9 01:10 4409
-
在使用使用内存大致有两种常见且合规的方式,一是使用局部变量,操作局部变量一般是对栈进行操作,除非局部变量被static
关键字修饰(此时位于全局变量区),栈空间是由编译器控制的,在编译时栈空间就会确定下来,二是使用堆,堆内存相比栈内存有一个好处,就是堆是动态申请的,不向栈内存那样死板。
至于内存地址的增长方向是向上还是向下,对于AMD64架构来讲,栈是向下增长的,堆是向上增长的,不过这个增长方向并不重要,理论上来讲向上和向下没有什么太大的分别。
在了解堆的细节之前,我们需要先了解GLibC下堆内存的布局情况。
在GLibC中,统计堆内存信息的元素叫做arena,它由struct malloc_state
结构体描述。
一般来讲线程间的arena都是相互独立的,每个线程独占一个arena,主线程的arena被称作是main_arena
,它是GLibC定义的全局变量,子线程的arena被称作是non_main_arena
。
但在二般情况下,就可能不再是一个线程对应一个arena了,这是因为系统是有CPU核数限制的,当线程数量超过CPU核数时,就必然会有线程处于等待状态,这个时候让每个线程都独占一个arena是奢侈的。
GLibC对arena的控制是NARENAS_FROM_NCORES
宏完成的,它根据系统位数和CPU核数决定arena的上限。
通过malloc_state
的attached_threads
成员,可以确定arena被多少线程使用(没有则为0),当内存被释放后,对应的arena并不会移出链表,而是将attached_threads
递减1。
在struct malloc_state
中存在着一个名为next
的成员,它会指向其余的arena,链表中的最后一个arena的next
成员会重新指向main_arena
。
system_mem
成员记录了系统在当前区域已分配的内存,而max_system_mem
成员则记录了系统在当前区域最大分配过的内存。
mutex
成员用于避免多线程间使用同个arena冲突的情况,flags
记录了arena的属性信息,fastbinsY
保存着fast chunk
链表,bins
保存着其余类型chunk的链表,top
记录了top chunk
的地址,last_remainder
记录了chunk分割后剩余部分的地址,最后是binmap
,它记录了bins中是否包含空闲chunk。
malloc
申请到的内存区域被称作是chunk,chunk分成头信息和数据信息两个部分,chunk头信息由struct malloc_chunk
结构体描述。
mchunk_prev_size
记录了低地址临近且空闲chunk的大小,mchunk_size
代表自身的大小,fd
指向下一个空闲chunk,bk
指向上一个空闲chunk,fd_nextsize
指向上一个与自身大小不一致的chunk,bk_nextsize
指向下一个与自身大小不一致的chunk。
mchunk_size
要求是MALLOC_ALIGNMENT
的倍数,而且mchunk_size
的第三个比特位是保留的,用于指示chunk的属性信息,每个比特位各代表一类信息。
位于最高处的chunk被称作是top chunk
,top chunk
下方是其余的chunk,位于最底部地址的chunk,可以通过计算公式top + mchunk_size - system_mem
得到。
mchunk_size
需要去除低比特位的标志位后才可以得到正确的大小,如果mchunk_size
的大小为0,那么就说明该chunk没有被使用。
在GLibC的定义中,64位系统下的chunk最小值是32(因为fd_nextsize
前面有四个指针类型的成员,所以offsetof
得到的偏移值是4 * 8
)。
chunk除了有最小值的要求外,还有对齐的要求,一般来讲,GLibC要求跟16对齐。
被释放的chunk并不会马上将内存归还给内核,GLibC的堆管理器ptmalloc会继续管理着空闲chunk,当申请内存的请求再次被提交时,ptmalloc会从空闲chunk中挑选一个合适的chunk,把它交给程序使用。
ptmalloc会根据chunk大小进行分类,同类型的chunk会放入箱子bin内收纳,bin可以分成fast bin
、small bin
、large bin
、unsorted bin
四大类。
bin的信息由记录堆状态的malloc_state
结构体进行维护。
fast bin
放在malloc_state
的fastbinsY
内,其余类型的bin放在bins
内。
fast bin
用于存放比global_max_fast
小的chunk,global_max_fast
的大小一般都会是128,设计fast bin
的初衷,就是快速的对小内存进行分配和释放。
fastbinsY
在malloc_state
中被定义成了一个数组,数组的容量是NFASTBINS
,它的值一般都是10。
fastbinsY
中存储的NFASTBINS
个fast bin
链表,链表根据chunk的大小进行区分,链表对应chunk的大小越小,该链表在数组中的排名越靠前。好处就是,fastbinsY
数组中的元素对应的fast bin
大小是固定的,免去了按大小进行排序的需求。
GLibC中提供了一个名为fastbin_index
的宏,它会接收chunk的大小,然后将它右移4位后再减去2,右移四位可以消除对齐MALLOC_ALIGNMENT
(值一般为16)的影响,减2可以消除最小值MIN_CHUNK_SIZE
的影响,从而得到chunk大小在数组中的索引值。
通过fastbin_index
和fast bin
两个宏,快速定位到fastbinsY
数组中的链表。
程序申请的内存大小肯定不可能是刚好和MALLOC_ALIGNMENT
对齐的,GlibC贴心的提供了宏csize2tidx
,用于获取申请内存在fastbinsY
中对应的下标找到空闲的chunk。
fast bin
之所以孤立其他的bin,是因为它需要具备一些特殊的属性。fast chunk
的chunk信息中的mchunk_size
不止记录了chunk的大小,低比特位还记录了chunk的属性,对于fast chunk
来讲,比特位PREV_INUSE
永远为1,保证fast chunk
不会被合并。
fast chunk
在一般情况下不会被合并,但在二般情况下就不好说了(碎片化的内存数量过多),fast chunk
不容易被合并的特点,保障了它能被快速的释放和取出使用。
fast bin
还有一个特点,就是它采用LIFO后进先出的策略。尽管CPU和内存的通信速度已经很快了,但相当于CPU自身的运行速度还是太慢了,所以CPU一般都会设置CPU缓存,CPU缓存的容量有限,不会像内存那样大,但是CPU和CPU缓存间的通信速度是相当快的,假如CPU经常使用的内存数据都位于CPU缓存上,那么CPU的执行效率就会大大提高,LIFO可以帮助我们达到这一目的。
一般来讲,越晚使用的内存数据留存在CPU缓存上几率越大,使用LIFO管理fast bin
正是利用了这一点,提高CPU缓存命中率,保障fast bin
能被快速的释放和取出使用。
fast bin
只使用struct malloc_state
中的fd
成员,当chunk被释放进入链表时,会先取出fast bin
链表指针FB
,然后让空闲chunk的fd
指向FB,最后更新链表指针为当前空闲chunk,经过这番操作后,链表最后的元素就是最近释放的chunk,之前的fast bin
可以被fd
成员索引,由于fast bin
链表只使用了fd
,所以该链表是单向的。
从空闲的fast bin
链表中取出chunk时,当然就是进行反向操作,先拿出最晚入列的chunk,最后将fast bin
链表更新为fd
指向的后续链表信息。
上面的操作只是一个假想,在实际情况中p
并不是缓冲区变量对应的malloc_chunk
地址,而是缓冲区变量地址-0x10,这样做的好处就是链表中存放的直接就是缓冲区变量地址,不需要根据malloc_chunk
进行进一步索引。
如果arena中拥有空闲的fast chunk
,那么在arena对应的struct malloc_state
,不止会发现fastbinsY
存在不为NULL的元素,也会发现malloc_state->have_fastchunks
成员的数值也不是0。
在实际情况中,如果对fastbinsY
数组中链表上的地址进行观察,会发现实际地址会有一定量的偏移,这个偏移值是宏PROTECT_PTR
产生的,它只保证链表中最后一个空闲chunk的地址是原始且正确的,其余的地址都会经过PROTECT_PTR
完成地址随机化。
small bin
和large bin
的特殊之处在于,它们的大小并不固定,small bin
的最大值是1024,而large bin
的最小值则是1024。
GLibC中的in_smallbin_range
宏可以判断出程序申请的内存应该划入small bin
里面,还是划入large bin
里面,in_smallbin_range
宏的判断依据是MIN_LARGE_SIZE
宏的数值大小。
上面展示的宏中有一个名为SMALLBIN_CORRECTION
的宏,该宏出现在MIN_LARGE_SIZE
宏以及smallbin_index
宏中并影响着它们,至于SMALLBIN_CORRECTION
宏在不在其他宏中起实际作用,取决于MALLOC_ALIGNMENT
是否大于CHUNK_HDR_SZ
。
GLibC会接收程序申请的内存大小,然后根据改申请的大小判断它在bins
数组中的下标,其中small bin
通过smallbin_index
获取下标,large bin
通过largebin_index_64
获取下标。
结构体struct malloc_state
中的bins[NBINS * 2 - 2]
数组元素的数量是比较奇怪的,按照道理来讲,一个NBINS
就足够了,为什么要NBINS * 2 - 2
呢?
首先内存有最小值的限制MALLOC_ALIGNMENT
,所以sz >> 4
永远都不会是0,所以bins
数组元素的数量就变成了NBINS - 1
,又因为bins
数组中的链表都是双向双向链表,存储着fd
和bk
两个成员,所以bins
数组就扩成了2*(NBINS - 1)
。
由于奇怪的bins
数组,所以GLibC提供bin_at
接口索引数组,但不幸的是,bin_at
接口的索引方式更加奇怪。
首先bins[(i-1)*2]
中的(i-1)*2
指的是bins
数组中第i
对fd
和bk
成员。
找到第i
对元素后会取出元素在数组中的内存地址,最后再减去fd
的偏移值,这是一个很奇怪但又不奇怪的操作。
在bins
数组存放数据的角度中,数组中的每个元素放置的都是由stuct malloc_chunk
描述的空闲chunk信息。
但在bins
数组的设计角度来看,每bins[i * 2]
和bins[i * 2 - 1]
构成了第i
对空闲chunk链表,bin_at
用于检索空闲chunk链表,而不是某个空闲chunk。
空闲chunk进入链表时,会先从bins
中取出第i - 1
对空闲chunk链表,bck->fd
可以获取第i
对空闲chunk链表中(*(bck + offsetof(fd))
),然后会更新victim
的bk
和fd
,其中bk
为bck
,fd
为第i
对链表,最后将第i
对链表的fd
(bck->fd
)更新成victim
,以及fwd->bk
更新成victim
。
空闲chunk离开链表时,会先检查链表中是不是只有一个元素,如果不是就会开始移除空闲chunk,它会先更新第idx
对链表头的bk
,bk
对应victim->bk
,再更新bck
的fd
为第idx - 1
对链表,最后返回被移除链表的victim
指针。
bins
数组中的fd
和bk
它们的分工并不相同,fd
指向的永远是最晚入链的空闲chunk,而bk
则会指向最早入链的空闲chunk,出链时都是让bk
指向的空闲chunk出去,显然这是一种FIFO的管理方式,它与LIFO不同,越先进入队列的数据也会越先出列。
当arena完成初始化后,你可能会发现bins
数组中不管是哪对链表,其fd
和bk
指向的都是第i-1
对链表,这是因为arena初始化时,bins
数组会被挨个初始化。
bins
数组可以看作由两大部分组成,一是按照大小区分的链表,二是链表中fd
和bk
组成的双向链表,而通过bin_at
接口可以直接对特定链表中的fd
和bk
进行管理。
上面提到过,因为chunk需要跟MALLOC_ALIGNMENT
对齐,所以bins[0]
和bins[1]
会空出来,这个空出来的bin_at[1]
并不会闲置,而是会给unsorted bin
使用。
不过unsorted bin
又是个什么东西呢?
unsorted bin
可以看作是一个缓冲区,空闲chunk进入small bin
和large bin
之前会先进入unsorted bin
链表中,分配内存时(small_bin
范围内),如果其余类型的链表中找不到空闲chunk,那就会进入bin_at[1]
中查找,bin_at[1]
中如果存在合适的chunk就会把它提供给申请者。
这么做主要是为了增强局部性,申请者有更大的机会使用到仍位于CPU缓存上的内存地址。
chunk被释放变成空闲chunk时,其内存中存放的还是原来的数据吗?
答案当然不是,准确来讲是未必是。
之所以将未必是,是因为空闲chunk被赋新值需要在特定的场景下才会被激活。
第一种情况是看chunk会不会进入tcache bins
中(),tcache是GLibC推出的一种提高堆管理性能的机制,在GLibC的tcache机制被启用的情况下,被释放的chunk会优先进入tcache bins
中。
如果chunk进入tcache bins
,那么会通过tcache_put
接口完成进入操作,该函数会将e->next
对应的chunk地址放入tcache->entries
中,离开时,GLibC会通过tcache_get
接口取出chunk,tcache_put
存放e->next
之前,e->next
中保存的数据会变成tcache->entries[tc_idx]
中的原数值(头插法),如果GLibC的版本比较新,那么新数值还会被PROTECT_PTR
随机化。
第二种情况是看chunk属不属于fastbin
,如果属于fastbin
,那么向链表插入新chunk时,数据也会被更新成上一个链表头的地址。
对于主线程的arena来讲,不断的申请堆内存会导致arena中的top
不断向上扩展。首先当申请的内存大小nd
加上最小值仍在top chunk
的范围内时,GLibC会从top chunk
中取出内存交给主程序,如果发现申请内存大小nd
加MINSIZE
超出top chunk
的范围,就会通过sysmalloc
扩展top chunk
。
sysmalloc
会通过brk
扩充内存,并更新arena中的system_mem
,完成内存空间的扩充后,sysmalloc
会更新top
,并将返回top
中的可用内存地址给主程序使用。
上面是主线程的arena针对堆内存申请时的扩展操作,子线程是不是也一样呢?
尽管主线程的arena和子线程的arena都通过struct malloc_state
描述,但是它们之间的差别还是挺大的。
首先主线程的arena和子线程的arena有一个非常明显的差别,它就是分配出来的内存地址形式,main arena
的地址是跟主程序地址相似的,而non main arena
的地址则是跟动态库的地址相似。
其次就是non main arena
对待申请堆内存的态度并不与main arena
一致。
在分配内存的数量不够多不够大时,仍会在top chunk
中获取可用的内存区域,但是当申请的堆内存足够多时,non main arena
就会分成多个子堆,子堆之间通过heap_info
结构体进行管理。
ar_ptr
记录着子堆所属的arena地址,prev
记录着上一个子堆的heap_info
地址(单向链表,主子堆的heap_info
地址是起始位置,结束位置的prev
数值为0),size
记录当前内存的大小,mprotect_size
记录这被PROT_READ|PROT_WRITE
标志保护的内存大小,pagesize
代表当前内存页大小,至于pad
,它是为了结构体大小和0x10或0x8对齐才存在的,没有什么特别含义。
知道有heap_info
结构体这么个东西后,又要面临一个问题,heap_info
结构体的地址是怎么产生的呢?arena根据什么信息可以获取子堆呢?
首先non main arena
的heap_info
地址是根据arena的top
地址产生的,GLibC会在top
地址的下方选择heap_info
地址,并要求heap_info
地址与HEAP_MAX_SIZE
对其,由此可以得到heap_info
地址的计算公式为top & ~(HEAP_MAX_SIZE - 1)
。
GLibC提供heap_for_ptr
接口,快速找到arena对应的heap_info
地址。
至于其他的子堆,则需要通过主子堆heap_info
中的prev
成员进行遍历。
heap_info
的上方(heap_info_addr + sizeof(heap_info)
)就是数据区。
_int_malloc
分配内存时,发现top chunk
空间不够用时,会进入sysmalloc
函数的内部,该函数会根据arena地址进行判断,主线程arena是一个待遇,即通过brk
机制扩展可用内存区域,子线程arena则是另一种处理方法。
sysmalloc
对子线程的子堆主要有两种处理方法。
第一种情况是发现申请内存大小nd
超出当前top chunk
的大小,那么就会先对内存区域进行扩展,sysmalloc
会先拿到arnea对应的heap_info
地址,然后进入grow_heap
函数的内部,只要新申请的内存没有超过HEAP_MAX_SIZE
的限制,子堆的内存上限就会被扩大,程序会继续使用子堆的内存区域。
如果子堆的内存不够用了,那么就会通过new_heap
创建新的子堆,non main arena
指向的永远都是最新创建的heap_info
(创建新子堆时会更新prev
)。
在栈溢出中,我们已经知道了,由于栈上会存放调用者的栈底地址rbp
、被调用函数的局部变量以及被调用函数的返回地址三大关键信息,使得栈溢出发生时,我们有了控制数据和程序执行流程的能力。
存放在栈上的数据中,返回地址能被控制是重中之重,它诞生了各种类ROP攻击。
但是堆内存中,存放应该都只是仅供读写的数据啊,一般来讲,没有什么类似于返回地址这样可以控制程序执行流程的数据,而且不只是被用于读写,还会被取出放到当前程序指针寄存器rip
上,所以堆溢出似乎很难直接的控制程序的执行流程。
那么堆内存是如何被利用的呢?
比起堆溢出,程序对于堆的处理还存在一个逻辑上偏差,这个偏差是相对于GLibC的。
之前了解PIE时,我们就知道了一个事情,在没有mmap_min_addr
保护的情况下,空指针也是可以被正常使用,但由于空指针一般都被视为错误的,操作空指针应该要带来崩溃,显然向空指针写入数据是存在安全风险的,因此Linux添加了mmap_min_addr
保护空指针,只要操作小于mmap_min_addr
的内存地址就会发生崩溃。
上面的例子中,存在着一个事实,虚拟内存地址只要是属于程序的就可以被操纵。
我们不难得出一个结论,GLibC假释放堆内存,将空闲chunk纳入自己的管理范围时,虚拟内存也是没有真正脱离主程序的,所以主程序仍然可以对空闲chunk进行操作。
在预期情况下,占用堆的缓冲区变量完成释放后,它应该被赋值成空指针。
但是如果被释放的内存没有赋值成空指针呢?
不难知道,释放后仍保留原内存地址的缓冲区变量,还是可以继续向内存写入数据的,这种方式也被称作是Use After Free
。
但是它会如何被利用呢?
首先如果被释放的缓冲区变量还指向原内存地址,那么会发生下面几种情况。
chunk存入fastbin
和tcache bins
内,内存数据变更成旧链表头的地址,地址可能被随机化。
chunk存入其余类型的bin内,内存数据保留原值,不会被重新赋值。
当内存数据变成可用地址时,且地址没有被随机化,那么被释放的缓冲区变量就存在泄露内存地址的风险,如果地址被随机化,那么即使地址泄露也不会造成太大的风险。
现在我们抛出这样的一个问题,假如被释放的缓冲区变量A没有赋值成空指针,而且缓冲区变量A在后面的代码中,还被类似read
的函数写入了数据,那么会发生什么呢?
答案是比较简单直接的,缓冲区变量被写入了新的数据。
在非预期情况下被写入数据,难免会导致一些意外情况发生,对于PWN来讲,意外情况可以分成两种。
首先是错误的使用已释放缓冲区变量中的数据,最严重的情况,比如将数据当中函数指针进行调用。
其次,从上面的描述中,我们可以看到堆的管理远比栈的管理复杂的多,GLibC定义了种种结构体描述堆信息,通过异常方式写入的数据,有没有可能影响这些属性信息呢,如果堆属性信息被控制,那么有能不能对程序进行进一步的控制呢?
下面是一段示例程序,在示例程序中,存在两处UAF漏洞,第一处是free(buf)
后,继续打印buf
中的数据,第二处是free(tmp)
后,继续向tmp
中写入数据,并且后面还会将tmp
看作是函数指针进行调用。
由于buf
分配的内存大小是0x30,这个大小是小于0x80的,所以它属于fastbin
,释放后buf
中会存放旧fastbin
链表头的内存地址,当然由于当前GLibC的版本较高,所以该内存地址会被随机化,很难根据地址获取有用的信息。
最后就是致命的缓冲区变量tmp
,由于被释放后不仅没有置零,且程序中存在一个名为gift_give
的函数,它可以调用Shell,所以向该已释放变量写入gift_give
函数地址后,程序会调用此函数。
通过上面的分析构造出下面的exploit。
运行exploit后成功拿到Shell。
#define NARENAS_FROM_NCORES(n) ((n) * (sizeof (long) == 4 ? 2 : 8))
#define NARENAS_FROM_NCORES(n) ((n) * (sizeof (long) == 4 ? 2 : 8))
(gdb) p main_arena
$
1
=
{
mutex
=
0
, flags
=
0
, have_fastchunks
=
0
,
fastbinsY
=
{
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
},
top
=
0x0
, last_remainder
=
0x0
, bins
=
{
0x0
... },
binmap
=
{
0
,
0
,
0
,
0
},
next
=
0x7ffff7f9cc60
<main_arena>,
next_free
=
0x0
, attached_threads
=
1
, system_mem
=
0
,
max_system_mem
=
0
}
(gdb) p main_arena
$
1
=
{
mutex
=
0
, flags
=
0
, have_fastchunks
=
0
,
fastbinsY
=
{
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
},
top
=
0x0
, last_remainder
=
0x0
, bins
=
{
0x0
... },
binmap
=
{
0
,
0
,
0
,
0
},
next
=
0x7ffff7f9cc60
<main_arena>,
next_free
=
0x0
, attached_threads
=
1
, system_mem
=
0
,
max_system_mem
=
0
}
| main_arena |
-
>
next
-
> | non_main_arena1 | ...
-
>
next
-
> | main_arena |
| main_arena |
-
>
next
-
> | non_main_arena1 | ...
-
>
next
-
> | main_arena |
p
*
(struct malloc_chunk
*
)
0x4059f0
$
16
=
{
mchunk_prev_size
=
0
, mchunk_size
=
132625
, fd
=
0x0
,
bk
=
0x0
, fd_nextsize
=
0x0
, bk_nextsize
=
0x0
}
p
*
(struct malloc_chunk
*
)
0x4059f0
$
16
=
{
mchunk_prev_size
=
0
, mchunk_size
=
132625
, fd
=
0x0
,
bk
=
0x0
, fd_nextsize
=
0x0
, bk_nextsize
=
0x0
}
#define PREV_INUSE 0x1 是否被分配
#define IS_MMAPPED 0x2 是否由mmap分配
#define NON_MAIN_ARENA 0x4 是否属于主线程
#define PREV_INUSE 0x1 是否被分配
#define IS_MMAPPED 0x2 是否由mmap分配
#define NON_MAIN_ARENA 0x4 是否属于主线程
| top chunk | (struct malloc chunk
*
) top |
| ...... | |
| data
2
| addr
4
=
addr
3
+
sizeof(mallo_chunk) |
| malloc chunk
2
| addr
3
=
addr
2
+
mchunk_size |
| data
1
| addr
2
=
addr
1
+
sizeof(mallo_chunk) |
| malloc chunk
1
| addr
1
|
^ |
| top
+
top
-
>mchunk_size
-
arena
-
>system_mem |
| top chunk | (struct malloc chunk
*
) top |
| ...... | |
| data
2
| addr
4
=
addr
3
+
sizeof(mallo_chunk) |
| malloc chunk
2
| addr
3
=
addr
2
+
mchunk_size |
| data
1
| addr
2
=
addr
1
+
sizeof(mallo_chunk) |
| malloc chunk
1
| addr
1
|
^ |
| top
+
top
-
>mchunk_size
-
arena
-
>system_mem |
#define MIN_CHUNK_SIZE (offsetof(struct malloc_chunk, fd_nextsize))
#define MIN_CHUNK_SIZE (offsetof(struct malloc_chunk, fd_nextsize))
#define MALLOC_ALIGNMENT 16
#define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)
#define MALLOC_ALIGNMENT 16
#define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)
struct malloc_state
mfastbinptr fastbinsY[NFASTBINS]
mchunkptr bins[NBINS
*
2
-
2
]
unsigned
int
binmap[BINMAPSIZE]
struct malloc_state
mfastbinptr fastbinsY[NFASTBINS]
mchunkptr bins[NBINS
*
2
-
2
]
unsigned
int
binmap[BINMAPSIZE]
#define set_max_fast(s) \
global_max_fast
=
(((size_t) (s) <
=
MALLOC_ALIGN_MASK
-
SIZE_SZ) \
? MIN_CHUNK_SIZE
/
2
: ((s
+
SIZE_SZ) & ~MALLOC_ALIGN_MASK))
(gdb) p global_max_fast
$
38
=
128
#define set_max_fast(s) \
global_max_fast
=
(((size_t) (s) <
=
MALLOC_ALIGN_MASK
-
SIZE_SZ) \
? MIN_CHUNK_SIZE
/
2
: ((s
+
SIZE_SZ) & ~MALLOC_ALIGN_MASK))
(gdb) p global_max_fast
$
38
=
128
struct malloc_state
mfastbinptr fastbinsY[NFASTBINS]
struct malloc_state
mfastbinptr fastbinsY[NFASTBINS]
(gdb) p sizeof(size_t)
$
39
=
8
#define INTERNAL_SIZE_T size_t
#define SIZE_SZ (sizeof (INTERNAL_SIZE_T))
#define fastbin_index(sz) \
((((unsigned
int
) (sz)) >> (SIZE_SZ
=
=
8
?
4
:
3
))
-
2
)
(gdb) p sizeof(size_t)
$
39
=
8
#define INTERNAL_SIZE_T size_t
#define SIZE_SZ (sizeof (INTERNAL_SIZE_T))
#define fastbin_index(sz) \
((((unsigned
int
) (sz)) >> (SIZE_SZ
=
=
8
?
4
:
3
))
-
2
)
idx
=
fastbin_index (nb)
mfastbinptr
*
fb
=
&fastbin (av, idx)
#define fastbin(ar_ptr, idx) ((ar_ptr)->fastbinsY[idx])
idx
=
fastbin_index (nb)
mfastbinptr
*
fb
=
&fastbin (av, idx)
#define fastbin(ar_ptr, idx) ((ar_ptr)->fastbinsY[idx])
# define csize2tidx(x) (((x) - MINSIZE + MALLOC_ALIGNMENT - 1) / MALLOC_ALIGNMENT)
# define csize2tidx(x) (((x) - MINSIZE + MALLOC_ALIGNMENT - 1) / MALLOC_ALIGNMENT)
enter
-
> P
-
>fd
=
*
FB;
*
FB
=
P;
leave
-
> P
=
*
FB;
*
FB
=
P
-
>fd;
enter
-
> P
-
>fd
=
*
FB;
*
FB
=
P;
leave
-
> P
=
*
FB;
*
FB
=
P
-
>fd;
__libc_free
-
> mem
-
> free bufferr address
-
> example
-
> free(
0x465060
)
-
> mem
=
0x465060
-
> p
=
mem2chunk (mem)
-
> p
=
mem
-
0x10
-
> _int_free (ar_ptr, p,
0
);
-
> p
-
>fd
=
PROTECT_PTR (&p
-
>fd, old);
-
>
*
fb
=
p;
_int_malloc
-
> victim
=
*
fb;
-
>
*
fb
=
REVEAL_PTR (victim
-
>fd);
-
> void
*
p
=
chunk2mem (victim);
-
>
return
p;
__libc_free
-
> mem
-
> free bufferr address
-
> example
-
> free(
0x465060
)
-
> mem
=
0x465060
-
> p
=
mem2chunk (mem)
-
> p
=
mem
-
0x10
-
> _int_free (ar_ptr, p,
0
);
-
> p
-
>fd
=
PROTECT_PTR (&p
-
>fd, old);
-
>
*
fb
=
p;
_int_malloc
-
> victim
=
*
fb;
-
>
*
fb
=
REVEAL_PTR (victim
-
>fd);
-
> void
*
p
=
chunk2mem (victim);
-
>
return
p;
#define PROTECT_PTR(pos, ptr) \
((__typeof (ptr)) ((((size_t) pos) >>
12
) ^ ((size_t) ptr)))
commands
>p main_arena
-
>fastbinsY
>p buf_pool[i]
>end
$
112
=
{
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x4059b0
,
0x0
,
0x0
,
0x0
,
0x0
}
$
113
=
0x405a30
"main"
$
114
=
{
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x405a20
,
0x0
,
0x0
,
0x0
,
0x0
}
$
115
=
0x405a30
"\265]@"
$
120
=
{
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x405a90
,
0x0
,
0x0
,
0x0
,
0x0
}
$
121
=
0x405aa0
"%^@"
p
/
x
*
(struct malloc_chunk
*
)(
0x405a20
)
$
117
=
{mchunk_prev_size
=
0x0
, mchunk_size
=
0x71
, fd
=
0x405db5
, bk
=
0x0
, fd_nextsize
=
0x0
, bk_nextsize
=
0x0
}
p
/
x
*
(struct malloc_chunk
*
)(
0x405a90
)
$
122
=
{mchunk_prev_size
=
0x0
, mchunk_size
=
0x71
, fd
=
0x405e25
, bk
=
0x0
, fd_nextsize
=
0x0
, bk_nextsize
=
0x0
}
| correct fd | current fd | offset (
0x405xxx
>>
12
=
0x405
) |
|
0x4059b0
|
0x405db5
|
0x405
|
|
0x405a20
|
0x405e25
|
0x405
|
#define PROTECT_PTR(pos, ptr) \
((__typeof (ptr)) ((((size_t) pos) >>
12
) ^ ((size_t) ptr)))
commands
>p main_arena
-
>fastbinsY
>p buf_pool[i]
>end
$
112
=
{
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x4059b0
,
0x0
,
0x0
,
0x0
,
0x0
}
$
113
=
0x405a30
"main"
$
114
=
{
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x405a20
,
0x0
,
0x0
,
0x0
,
0x0
}
$
115
=
0x405a30
"\265]@"
$
120
=
{
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x405a90
,
0x0
,
0x0
,
0x0
,
0x0
}
$
121
=
0x405aa0
"%^@"
p
/
x
*
(struct malloc_chunk
*
)(
0x405a20
)
$
117
=
{mchunk_prev_size
=
0x0
, mchunk_size
=
0x71
, fd
=
0x405db5
, bk
=
0x0
, fd_nextsize
=
0x0
, bk_nextsize
=
0x0
}
p
/
x
*
(struct malloc_chunk
*
)(
0x405a90
)
$
122
=
{mchunk_prev_size
=
0x0
, mchunk_size
=
0x71
, fd
=
0x405e25
, bk
=
0x0
, fd_nextsize
=
0x0
, bk_nextsize
=
0x0
}
| correct fd | current fd | offset (
0x405xxx
>>
12
=
0x405
) |
|
0x4059b0
|
0x405db5
|
0x405
|
|
0x405a20
|
0x405e25
|
0x405
|
#define NSMALLBINS 64
#define SMALLBIN_WIDTH MALLOC_ALIGNMENT
#define SMALLBIN_CORRECTION (MALLOC_ALIGNMENT > CHUNK_HDR_SZ)
#define MIN_LARGE_SIZE ((NSMALLBINS - SMALLBIN_CORRECTION) * SMALLBIN_WIDTH)
#define NSMALLBINS 64
#define SMALLBIN_WIDTH MALLOC_ALIGNMENT
#define SMALLBIN_CORRECTION (MALLOC_ALIGNMENT > CHUNK_HDR_SZ)
#define MIN_LARGE_SIZE ((NSMALLBINS - SMALLBIN_CORRECTION) * SMALLBIN_WIDTH)
#define in_smallbin_range(sz) \
((unsigned
long
) (sz) < (unsigned
long
) MIN_LARGE_SIZE)
#define bin_index(sz) \
((in_smallbin_range (sz)) ? smallbin_index (sz) : largebin_index (sz))
#define in_smallbin_range(sz) \
((unsigned
long
) (sz) < (unsigned
long
) MIN_LARGE_SIZE)
#define bin_index(sz) \
((in_smallbin_range (sz)) ? smallbin_index (sz) : largebin_index (sz))
#define smallbin_index(sz) (((unsigned) (sz)) >> 4) + SMALLBIN_CORRECTION
#define largebin_index_64(sz) \
(((((unsigned
long
) (sz)) >>
6
) <
=
48
) ?
48
+
(((unsigned
long
) (sz)) >>
6
) :\
......)
#define largebin_index(sz) largebin_index_64
#define smallbin_index(sz) (((unsigned) (sz)) >> 4) + SMALLBIN_CORRECTION
#define largebin_index_64(sz) \
(((((unsigned
long
) (sz)) >>
6
) <
=
48
) ?
48
+
(((unsigned
long
) (sz)) >>
6
) :\
......)
#define largebin_index(sz) largebin_index_64
#define bin_at(m, i) \
(mbinptr) (((char
*
) &((m)
-
>bins[((i)
-
1
)
*
2
]))
-
offsetof (struct malloc_chunk, fd))
#define bin_at(m, i) \
(mbinptr) (((char
*
) &((m)
-
>bins[((i)
-
1
)
*
2
]))
-
offsetof (struct malloc_chunk, fd))
*
-
-
-
-
-
-
-
-
bins
-
-
-
-
-
-
-
-
-
-
*
*
-
-
-
-
-
-
-
-
-
-
-
-
-
-
new struct
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
*
| ... | chunk_$i | ... |
-
> | fd_$(i
-
1
) | bk_$(i
-
1
) | fd_$i | bk_$i |
*
-
-
-
-
-
-
-
-
bins
-
-
-
-
-
-
-
-
-
-
*
*
-
-
-
-
-
-
-
-
-
-
-
-
-
-
new struct
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
*
addr
=
bin_at(M, i)
addr
-
>fd
=
fd_i
addr
-
>bk
=
bk_i
*
-
-
-
-
-
-
-
-
bins
-
-
-
-
-
-
-
-
-
-
*
*
-
-
-
-
-
-
-
-
-
-
-
-
-
-
new struct
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
*
| ... | chunk_$i | ... |
-
> | fd_$(i
-
1
) | bk_$(i
-
1
) | fd_$i | bk_$i |
*
-
-
-
-
-
-
-
-
bins
-
-
-
-
-
-
-
-
-
-
*
*
-
-
-
-
-
-
-
-
-
-
-
-
-
-
new struct
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
*
addr
=
bin_at(M, i)
addr
-
>fd
=
fd_i
addr
-
>bk
=
bk_i
#define last(b) ((b)->bk)
#define chunk2mem(p) ((void*)((char*)(p) + CHUNK_HDR_SZ))
enter
-
>
bck
=
bin_at (av, victim_index);
/
/
bck
=
&(bins[(i
-
2
)
*
2
])
fwd
=
bck
-
>fd;
/
/
fwd
=
bins[(i
-
1
)
*
2
]
=
fd_i
victim
-
>bk
=
bck;
/
/
set
new chunk bk to default link
victim
-
>fd
=
fwd;
/
/
let new chunk link to old chunk
fwd
-
>bk
=
victim;
/
/
let old chunk link to new chunk
bck
-
>fd
=
victim;
/
/
update fwd to new chunk
leave
-
>
bin
=
bin_at (av, idx);
/
/
bin
=
&(bins[(i
-
2
)
*
2
])
if
((victim
=
last (
bin
)) !
=
bin
) {
/
/
victim
=
bins[(i
-
1
)
*
2
+
1
]
=
bk_i
bck
=
victim
-
>bk;
/
/
get current bk_i linked chunk_a
bin
-
>bk
=
bck;
/
/
update current bk_i to chunk_a
bck
-
>fd
=
bin
;
/
/
update current fd_i to default
void
*
p
=
chunk2mem (victim);
}
#define last(b) ((b)->bk)
#define chunk2mem(p) ((void*)((char*)(p) + CHUNK_HDR_SZ))
enter
-
>
bck
=
bin_at (av, victim_index);
/
/
bck
=
&(bins[(i
-
2
)
*
2
])
fwd
=
bck
-
>fd;
/
/
fwd
=
bins[(i
-
1
)
*
2
]
=
fd_i
victim
-
>bk
=
bck;
/
/
set
new chunk bk to default link