堆利用之unlink

一 small bin,large bin等的内存结构
未分配的chunk
|  pre chunk size  |   size  |F|C|P|
|  fd       |         bk         |
presize: 前一个块的大小(如果前一个块是空闲的)
size:当前块的大小
F标志位:目前还没有用
C标志位:如果当前块已被分配,置1;否则,置0
P标志位:如果前一个块已被分配,置1,;否则,置0


二、unlink
久经辛苦终于看明白了。还是很精妙的。
unlink是free非fastbin时,会先检查该chunk前一个chunk是否为空闲,如果是空,则合并。
利用思路是溢出修改被free的chunk头部的pre chunk size和size中的p位为0,导致合并空闲chunk。并在相邻处伪造一个chunk头部。最终得到一个任意地址写。


实例说明
以how2heap的unsafe_unlink为例子说明,程序的注释已经去掉


uint64_t *g_fake_chunk; //a
int main()
{
int malloc_size = 0x80; //we want to be big enough not to use fastbins
int header_size = 2;
g_fake_chunk = (uint64_t*) malloc(malloc_size); //chunk0
uint64_t *chunk1_ptr  = (uint64_t*) malloc(malloc_size); //chunk1  b
g_fake_chunk[2] = (uint64_t) &g_fake_chunk-(sizeof(uint64_t)*3); //c
g_fake_chunk[3] = (uint64_t) &g_fake_chunk-(sizeof(uint64_t)*2); //d
g_fake_chunk[1] = sizeof(size_t); //e
uint64_t *chunk1_hdr = chunk1_ptr - header_size;
chunk1_hdr[0] = malloc_size; //f
chunk1_hdr[1] &= ~1; //g
free(chunk1_ptr); //h
char victim_string[8];
strcpy(victim_string,"Hello!~");
g_fake_chunk[3] = (uint64_t) victim_string; //i
fprintf(stderr, "g_fake_chunk is now pointing where we want, we use it to overwrite our victim string.\n");
fprintf(stderr, "Original value: %s\n",victim_string);
g_fake_chunk[0] = 0x4141414142424242LL;
fprintf(stderr, "New Value: %s\n",victim_string);
}


a unlink时检查(F -> bk == p && B -> fd == p),需要一个保存着堆地址的空间,一般为一个地址可知的变量
b 此时,内存分布如下:
g_fake_chunk------->    chunk0      |    0  |   size(0x90)  |        (addr)low
| 0(fd) |       0(bk)      |   
| 内容0x80byte       |
      chunk1      |     0         |    size        |       
|    0(fd)   |   0(bk)      |   
| 内容0x80byte         | (addr)high
chunk0和chunk1是相邻的。此外,g_fake_chunk指向chunk0


c d e三步骤后, chunk0的内存分布为
chunk0 | 0 |
|   0x90 |
chunk0内容 | 0 |   |---------------> g_fake_chunk-0x18| |
| 8 |         |   |---> g_fake_chunk-0x10| |
|     g_fake_chunk-0x18   |------|   | g_fake_chunk-0x8  | |
|     g_fake_chunk-0x10   |-----------------| g_fake_chunk | |
此步作用是为了能通过F -> bk == p && B -> fd == p检查,具体在free中描述

f g步骤后,chunk1的头部被修改为:
chunk1  |   0x80   |   size |p=0 |       
                | 0(fd)    |       0(bk)    |   
      | 0*0x80             |
这个修改表示,前一块chunk没有被使用,长度为0x80


h 重点来了,free非fastbin时,检查前后chunk是否为空闲,如果是,会合并相邻chunk。并做unlink。
为了清楚,再细画一次此时的内存状态。


chunk0 |   0    |
|     0x90    |
chunk0内容fake_chunk0         |   0    |    |------------> g_fake_chunk-0x18 | |
|       8     |    |   |---> g_fake_chunk-0x10 | |
| g_fake_chunk-0x18  |-----|   | g_fake_chunk-0x8   | |
| g_fake_chunk-0x10  |---------------| g_fake_chunk  | |
| 0x60字节    | 0x60字节是因为去掉了伪造的chunk头部0x20剩下的
chunk1 | 0x80           | 
| size     |p=0 |       
|    0(fd)                 | 
| 0(bk)           |   
|    0*0x80             |

由于f g步骤后,chunk1的p位被窜改为0,认为前一块为空,大小为0x80。
计算到前一块的头为chunk1-0x80,即chunk0内容位置,此块空间是漏洞利用时可控的,且c d e三步骤时在在这里伪造了一个chunk头。这里称呼它为fake_chunk0
此时执行unlink
unlink(p) //p即g_fake_chunk0
F = p -> fd;  //F = g_fake_chunk - 12
B = p -> bk;  //B = g_fake_chunk - 8
if (F -> bk == p && B -> fd == p){ //
  F -> bk = B;  // g_fake_chunk[0] = B = g_fake_chunk - 8
  B -> fd = F;  // g_fake_chunk[0] = F = g_fake_chunk -12
}
此时,内存结构为
g_fake_chunk-0x18 |     |<---|
g_fake_chunk-0x10 |     |       |
g_fake_chunk-0x8   |     |       |
    g_fake_chunk      |g_fake_chunk-12|-----|

i 因为这个地址并不是我们想要的任意地址,要实现任意地址写,需要把g_fake_chunk覆盖为任意地址






 

你可能感兴趣的:(ctf,pwn)