64位系统以下各字段均为8字节,32位系统为4字节。
glibc2.26及以上版本有tcache机制。咱目前也不太懂,快刀斩乱麻,glibc2.25的实验在Ubuntu16.04上做,glibc版本信息如下:
5. unsafe_unlink.c
运行结果:
分析:
题外话,观察到,对于同一个可执行文件unsafe_unlink,每次运行打印的地址不同,而每次调试则相同。希望以后理解。
伪造的堆块为chunk0,就是下图红框中的内容。可以看到,它舍弃了分配的堆块的首部两个单元。
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
chunk0_ptr是uint64_t类型的指针,&chunk0_ptr是指针自身的地址,chunk0_ptr是指针指向的地址(也就是指针地址那个内存单元存的内容)。可以看到,伪造的堆块chunk0的fd和bk分别指向指针chunk0_ptr自身地址减去0x18和0x10处。
我们继续看看&chunk0_ptr周围的内存情况.chunk0_ptr指针自身的地址位于0x602070,其指向伪造的堆0x603010。伪造了两个堆块,Fd的块首(水绿)位于0x602058,Bk的块首(深蓝)位于0x602060。这样,Fd->bk == chunk0,Bk->fd == chunk0。结合前面chunk0的修改,就绕过了(P->fd->bk != P || P->bk->fd != P) == False的检测。这时chunk0被伪造成位于某链表。
接着是对chunk1的pre_size和标志位进行修改,来使chunk0被认为是空闲的。
chunk1_hdr[0] = malloc_size; //pre size=0x80,而不包含原来的首部
chunk1_hdr[1] &= ~1; //pre chunk allocated标志位置为0
抛弃首部是怕继续合并么?希望以后理解。
free(chunk1_ptr)时,glibc发现chunk0是空闲的,就会向后合并。这时需要把chunk0从链表中摘除,就会调用unlink。可以推测,它先做Fd->bk=Bk,再做Bk->fd=Fd,也就是chunk0_ptr指针的内容先变成0x602060,再变成0x602058。自此,伪造的堆块chunk0,不再被认为位于0x603010,而是位于0x602058。
chunk0_ptr[3] = (uint64_t) victim_string;
也就是:
&chunk0_ptr == 0x602070 ;
chunk0_ptr == 0x602058 ;
chunk0_ptr[3] == 0x602058+0x18 == 0x602070 ;
chunk0_ptr = victim_string;
chunk0_ptr指针指向0x00007fffffffde60,堆块chunk0被认为位于victim_string指向的地址。假如这里存在恶意代码,或者错误的数据,被当成正常的内容利用,就会有问题。当然,本例是改写了这块地址的内容。
回想:出发点是free(chunk1_ptr),目的是修改chunk0_ptr。经过是chunk0_ptr从指向payload首地址,改为指向一个自定义地址。涉及到unlink中链表的设计和调整,pre_size和标志位的修改。妙啊!但是咋用呢?希望以后理解。