关于堆

Author Avatar
Aryb1n 7月 25, 2017

前面

网上一个博主说感觉Linux下堆内存分配不如windows下安全
这篇主要是参考自Sploitfun的blog,然后才发现网络上好多堆相关的文章配图都来自这里

前面

Glibc使用的是ptmalloc2,是forkdlmalloc
malloc底层其实是调用了brk或者mmap

emmmm

作者写太好了,翻译不出来,看英文好了

allocated chunk

没有fd,bk…本来应该放这些指针的空间现在用来存储数据
previous size这个字段,如果previous的块是空闲的,那么这个字段确实存储的是previous size,否则,这一块也丢给上一个块存数据…这样子听起来好像把这个字段送给别人的感觉,ahhhhhh~

+---------------+ ==
previous size   + |
+---------------+ |
size | N | M |P + |  chunk[xxx]
+---------------+ |
User data       + |
+---------------+ ==
previous size   + ----> 由于chunk[xxx]以及分配,这个也存放`chuck[xxx]`的User data
----------------+ |
......

我有一个问题,就是…我们在malloc的时候给的一个size,到这里指的是,上图的User data,还是User Data加上下一块的这个previous size
感觉应该是不算???

Free chunk

+---------------+ ====
previous size --+------> 因为空闲块都会合并,前面的块一定是allocated的,这里存的就是User data
+---------------+ |
size | N | M |P + |  chunk[xxx]
+---------------+ |
fd ---------------+---> to next chunk [Free]
bk ---------------+---> to previous chunk [Free]
User data       + |
+---------------+ ====
previous size   + -----> 因为chunk[xxx]是Free的,所以这里存的就是previous size
----------------+ |
......

所有的free的chunk会串联成一串就是叫做bins list

所这样子看起来,正常情况,只有一种previous size 的用途不确定(可能不适用于Fast bin???)

当前块 当前块previous 下一块previous
allocated 未知 存上一块data
free 存上一块Data 存previous size

bins

根据chunk大小分为

Fast bin

大小: 16 - 80字节
数量: 10101010101010101010
特点: 每个中存储的是单向链表, 并且链上的chunk是LIFO的

其他 bin

这其他的都是双向链表,所以每一表项有fd,bk两项

struct {
void * fd;
void * bk
}bins[]

Unsorted bin

大小: 任意
数量: 1 (Bin1)
特点: Small chunck, Large chunck被Free掉的时候,就来到了这里,这样子就可以用两次再回到原表里

Small bin

大小: 小于512字节
数量: 62 (Bin2~Bin63)
特点: 8字节递增

Bin2: 16字节

Bin63: 63 * 8 = 504字节

Large bin

大小: 大于等于512字节
数量: 63
特点: 比较迷乱,看下面

  • 32 bins contain binlist of chunks of size which are 64 bytes apart. ie) First large bin (Bin 65) contains binlist of chunks of size 512 bytes to 568 bytes, second large bin (Bin 66) contains binlist of chunks of size 576 bytes to 632 bytes and so on…
  • 16 bins contain binlist of chunks of size which are 512 bytes apart.
  • 8 bins contain binlist of chunks of size which are 4096 bytes apart.
  • 4 bins contain binlist of chunks of size which are 32768 bytes apart.
  • 2 bins contain binlist of chunks of size which are 262144 bytes apart.
  • 1 bin contains a chunk of remaining size.

Top chunck

Top chunk 不属于任何bin,找不到任何合适的bin的时候才会使用Top chunk

存储bin的数据结构

fastbinsY: This array hold fast bins.
bins: This array hold unsorted, small and large bins. Totally there are 126 bins and its divided as follows:

  • Bin 1 –> Unsorted bin
  • Bin 2 to Bin 63 –> Small bin
  • Bin 64 to Bin 126 –> Large bin

Chunk 和 bin list

bin list相当于只是存着指针(或者双向指针)的一个数组,数组中每一项都通过指针把可用的chunk串起来,所以只是表,表,表,那我如何得到bin list的地址呢

chunk是实实际际上可用的内存块,heap区的, 所以地址可能是0x080xxxxx,malloc的返回值就是某一块chunk的user data的首地址,不包括chunck头部的哦

实例

来自原作者blog

/* 
 Heap overflow vulnerable program. 
 */
#include <stdlib.h>
#include <string.h>

int main( int argc, char * argv[] )
{
        char * first, * second;

/*[1]*/ first = malloc( 666 );
/*[2]*/ second = malloc( 12 );
        if(argc!=1)
/*[3]*/         strcpy( first, argv[1] );
/*[4]*/ free( first );
/*[5]*/ free( second );
/*[6]*/ return( 0 );
}

我们执行过[1], [2]两句,看一下

gdb-peda$ p first
$2 = 0x804b008 ""
gdb-peda$ p second
$3 = 0x804b2a8 ""

gdb-peda$ x/20x 0x804b2a8 - 0x08
0x804b2a0:    0x00000000    0x00000011    0x00000000    0x00000000
0x804b2b0:    0x00000000    0x00020d51    0x00000000    0x00000000
0x804b2c0:    0x00000000    0x00000000    0x00000000    0x00000000
0x804b2d0:    0x00000000    0x00000000    0x00000000    0x00000000
0x804b2e0:    0x00000000    0x00000000    0x00000000    0x00000000
gdb-peda$ x/20x 0x804b008 - 0x08
0x804b000:    0x00000000    0x000002a1    0x00000000    0x00000000
0x804b010:    0x00000000    0x00000000    0x00000000    0x00000000
0x804b020:    0x00000000    0x00000000    0x00000000    0x00000000
0x804b030:    0x00000000    0x00000000    0x00000000    0x00000000
0x804b040:    0x00000000    0x00000000    0x00000000    0x00000000

我们-0x08是为了看到他chunk的头部

+-------------+
Top chunk
+-------------+
size=0x20d51
+-------------+ 
previous size
+-------------+ 
second chunk
+-------------+ 0x804b2a8 ------+
size=0x11                       |
+-------------+ 0x804b2a4       |
previous size                   |=> 0x2a0个字节
+-------------+ 0x804b2a0       |
First chunck                    |
+-------------+ 0x804b008 ------+
size=0x2a1
+-------------+ 0x804b004
previous size
+-------------+ 0x804b000

first chunk在malloc的时候给的参数是666,这里first chunk的size是
0x2a1 = 673 为什么是这样子???
智障了吧,最后三位是0 or 1不代表大小
后三位是没有意义的,他会8字节对齐,最后三位用于标志位
0x2a1 = 0000 0010 1010 0001
所以这里代表的大小是 666 对于 8 向上对齐,就是 672 / 8 = 84
所以First Chunk是从 0x804b200 ~ 0x804b2a8 这672字节

这样子的话是不是说Heap最低地址一般都是 0x804x000
然后size + previous size 是 8字节
这样子正好chunk的User data开始的地址也是对齐的
是这个意思吗

同理,second chunk大小是12也不能对齐8,所以对齐到了16(0x10),又由于second的前一块,即first chunk也被allocate出去了,所以标志位最后一位是1,所以,内存中second chunk的大小是0x11 = 0000 0000 0001 0001

不过有点不太对劲啊,难道…这0x2a0个字节只是说了这一块User data到下一块User data之间的距离,已经把人家下一块的size + previous也算进去了???

也就是说计算的时候,对应的项差size个字节是这个意思吗???
就是
chunk[1].size == chunk[2].data_offset - chunk[1].data_offset
chunk[1].size == chunk[2].size_offset - chunk[1].size_offset

那我们验证一下,second chunk的size在0x804b2a4,大小为0x10,那0x804b2b4 = 0x804b2a4 + 0x10存的应该是op chunk的大小

喔,,,果然是这样子

gdb-peda$ x 0x804b004
0x804b004:    0x000002a1   // first chunk'size
gdb-peda$ x 0x804b004 + 0x000002a0
0x804b2a4:    0x00000011  // second chunk'size
gdb-peda$ x 0x804b2a4 + 0x00000010
0x804b2b4:    0x00020d51  // Top chunk'size

所以在[4], free掉Fisrt chunk的时候
检查前一块是否使用
通过自己的size = 0x2a1, 最后一位是1, 所以前一块不空闲(这里first chunk是第一块,他其实是没有前一块,但应该为了统一,假设第一块前面那一块不存在的块是在使用的块,就不能合并了)

检查后一块是否使用
检查后一块就是检查second块,可以通过second的下一块即Top chunk的size字段最后一位,即0x00020d51的最后一位标志的top的前一块也就是second正在使用,所以不能合并

这里都不是空闲的,如果是空闲的话,就会触发binlist的unlink操作,就是把被合并的块从binlist里卸下来

而如果我们在[3]里的strcpy的时候,进行了奇怪的操作,就是不光写了first chunk的data部分,还写了second chunk的头部

prev_size = even number and hence PREV_INUSE bit is unset.
size = -4
fd = free address – 12
bk = shellcode address

那么在[4], free掉First chunk的时候,
检查前一块是否使用
通过自己的size = 0x2a1, 最后一位是1, 所以前一块不空闲

检查后一块是否使用
检查后一块就是检查second块,可以通过second的下一块的size字段来看,上面刚说到这个下一块的size字段,其实就是用second的size字段的位置加上second的size字段,放在这里就是,往回数4个字节,正好到了sedond chunk的previous size这里,而这里被我们改写成了偶数,这个时候malloc就会认为我们的second是free的,就会把他从bin list上取下来,准备合并

+-------------+ 高地址
Top chunk
+-------------+
size=0x20d51
+-------------+ 
previous size
+-------------+ 
second chunk
bk -------------> addr_free - 12
fd -------------> addr_shellcode
+-------------+ 
size= -4
+-------------+ <--- size_of_second
previous size =>>>> 覆盖成偶数
+-------------+ <--addr_size_of_second + size_of_second

在unlink掉second时候,会将second的fd和bk字段赋值给FD和BK变量,还有,还有看代码吧

#define unlink(P, BK, FD) {                                            \
    FD = P->fd;                                      \
    BK = P->bk;                                      \
    FD->bk = BK;                                  \
    BK->fd = FD;                                  \
    ...
}

在我们这里是

FD = addr_free - 12;
BK = addr_shellcode; 
FD -> bk = addr_shellcode; //GOT[free] = addr_shellcode, 到此就完成了GOT overwrite
BK -> fd = FD;  // (shellcode + 8) = addr_free - 12

这里的addr_free就是GOT[free],我们的FD是addr_GOT[free]-12,对吧,这里的12怎么来的就是看第三句,我们要FD -> bk, 我们看下,对于一个块,他的bk指针是偏移12字节的,前面有prev_size, sizefd

struct {
dword prev_size;
dword size;
bin * fd;
bin * bk;
}

结论:这链表链着的是块头部,从prev_size开始的
炒鸡注意:这里第四句会把shellcode的第8-11这四个字节给破坏掉

这里就是整个比较老的glibc的攻击思路

前面的我们搞清楚了

但.新的glibc加了一些防护功能
见wooyun这一篇 http://www.tuicool.com/articles/E3Ezu2u