基础知识:
- fastbin: Chunk Size <= get_max_fast()的chunk(其实就是64bits的是128bytes,32位的是64bytes) ,它们会被放在被称为fastbin的bin里面
上面的fastbinsY 里面存放fastbin , 最大是128bytes,最小是32bytes,然后依次加16bytes,32位的类似,不过最大是64bytes
- 需要注意的是get_max_fast()事实上返回的是 global_max_fast,但是这个值得初始值为0,只有当第一次malloc之后它才会被赋值为相应的值
- 同时fastbin是Single linked list,所以它只使用fd,以NULL结尾
- free的时候呢,不取消下一个chunk 的prev_inuse bit,因为fastbin chunk不会和其他chunk合并
- malloc 和 free的时候glibc会有一些检查,确认heap metadata是否正确,避免一些可能的攻击方式
- fastbin执行效率高,它的里面的检查比其他类型的bin少很多
- fastbin的linklist和其他的相反,跟Stack很像free和malloc的时候先进后出
- fastbin的prev_inuse标志位在free的时候不会改变还是1,这防止了它和其他的free_chunk进行合并操作
Fastbin Corruption:
1..让fastbin linked list 指向任意位置,之后的malloc时就会把改地址当做chunk拿出来
2.free(not in_use)的chunk会被存在bin里面,修改它的fd才会造成corruption
1.double free
2.Over flow
fastbin的检查方式:
- malloc从bin里面取出chunk,要拿到合适大小的chunk(检查你这个chunk的chunk_size
- free的时候,next_chunk的size必须正确(overflow的时候才会用到)
- free时看看bin里面的第一个chunk和现在要free的是不是同一个(fasttop),这个的缺点就是它只和第一个检查,那么你可以在double free之前先free一个其他的大小合适的chunk
我们可以看源码了解一下(源码我会的不多,只说点关键的)
对应1:
//这个nb是chunk的bytes 它来源于上面的一个checked_request2size(bytes,nb) 这的作用就是malloc的bytes+8然后16对齐
if ((unsigned long)(nb) <= (unsigned long)(get_max_fast())) //chunk的size(nb)小于128 or 64的话就会执行下面
{
idx = fastbin_index(nb); //这是在fastbin里面找,把chunk的size转换成fastbin里面对应的哪一个(size/16-2)注意:0是fastbin的第一个
mfastbinptr *fb = &fastbin(av, idx);
mchunkptr pp;
victim = *fb;
if (victim != NULL)
{
if (SINGLE_THREAD_P)
*fb = victim->fd;
else
REMOVE_FB(fb, pp, victim);
if (__glibc_likely(victim != NULL))
{
size_t victim_idx = fastbin_index(chunksize(victim)); //victim是要等一下要return的chunk
if (__builtin_expect(victim_idx != idx, 0)) //这边就是在检查你拿出来的chunk的idx跟bin的idx是不是一样,不对的话error
malloc_printerr("malloc(): memory corruption (fast)");
对应2:
if (SINGLE_THREAD_P)
{
/* Check that the top of the bin is not the record we are going to
add (i.e., double free). */
if (__builtin_expect(old == p, 0)) //这个检查就是看看你free的chunk跟bin里面的chunk是不是同一个,同一个的话就error
malloc_printerr("double free or corruption (fasttop)");
p->fd = old;
*fb = p;
}
double free也会形成类似UAF的效果,可以改掉bin里面的chunk的fd,可以看一下调试截图和源码
int main()
{
void *p,*q,*r,*s;
p = malloc(30);
q = malloc(30);
free(q);
free(p);
free(q); 第一次断点下在这里
}
更重要的是,现在bin里面有两个q(其实是同一块)都指向了P,
利用姿势注意点:
取出的chunk 的size要正确,所以也不是任意地址,需要可以制作假的size
- stack上的变量做size ,可以malloc出一个stack上的位置 (可以制作stack上的overflow)
- got上,用64bits地址常见的0x40(这个got没被call过的话)做size
取得chunk后,有机会对改地址任意写
fastbin double free:
- fasttop只检查bin里面的第一个chunk,只要不是连续free同一个chunk就没关系
- double free有类似use after free的效果,可以改掉bin里面的chunk的fd
例子:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void sh(char *cmd)
{
system(cmd);
}
int main()
{
setvbuf(stdout,0,_IONBF,0);
int cmd,idx,sz;
char *ptr[10];
memset(ptr,0,sizeof(ptr));
puts("1. malloc + gets\n2. free\n3. puts");
while(1)
{
printf("> ");
scanf("%d %d",&cmd, &idx);
idx %= 10;
if(cmd==1)
{
scanf("%d%*c",&sz);
ptr[idx] = malloc(sz);
gets(ptr[idx]);
}
else if(cmd==2)
{
free(ptr[idx]);
}
else if(cmd==3)
{
puts(ptr[idx]);
}
else
{
exit(0);
}
}
return 0;
}
在64位机器上这个题目有个经典的手法来必过fastbin的检查,因为malloc必须要拿到合适的chunk,所以你malloc的chunk的size位必须要跟fastbin里面的chunk的size大小相同,我们可以在构造fastbin的时候用到了double free,这个操作就是先随便malloc2个大小一样的chunk p和q(必须是fastbin的大小),我们一次free p q p,那么就会绕过一个fastbin检测,同时这个double free也会形成一个类似环状的fastbin
那么在malloc的时候,如果从这个bin里面获取chunk的话,我们就可以通过三次malloc,两次使用chunk p,这样就可以利用gets实现有限制的地址写入,这个限制就来源于chunk大小检查,同样我们可以利用64位机器好多都是0x40,来将malloc的chunk p利用gets修改到got表的相应位置,那么第三次malloc就会把这个地址的内存当做一个chunk来取出来,进行gets,我们写入shell的地址,那么下次调用got表相应函数的时候就直接getshell