一 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覆盖为任意地址
|