double free
对于fastbins而言,一个chunk如果free两次,那么该单向链表中就存在两个相同的chunk,由于之前的两次free此时malloc的chunk的状态是free状态的。有什么坏处呢?处于free状态的chunk有fd和bk指针,通过篡改这两个指针就可以欺骗堆管理器返回任意地址的chunk,也就有了任意地址写的漏洞。
demo
|
|
这段代码依赖老版本的libc,使用docker构建ubuntu镜像
|
|
|
|
|
|
samsara
main
|
|
menu
|
|
思路
double free可以对任意地址写,可以考虑把chunk申请到v8然后修改数据进行getshell;如果没有后门可以malloc到got表进行函数劫持
-
结合case4 case5和上面的变量可知v8 = v9 - 8,先调用case5再调用case4就可以知道v8的地址
1 2 3 4 5 6 7 8
int offset_v3; // ebx int choice; // [rsp+Ch] [rbp-44h] BYREF int offset_v5; // [rsp+10h] [rbp-40h] BYREF __gid_t rgid; // [rsp+14h] [rbp-3Ch] __int64 addr; // [rsp+18h] [rbp-38h] BYREF __int64 v8; // [rsp+20h] [rbp-30h] __int64 v9; // [rsp+28h] [rbp-28h] BYREF __int64 v10[4]; // [rsp+30h] [rbp-20h] BYREF
1 2 3 4 5 6 7 8 9
case 4: printf("Your lair is at: %p\n", &addr); // 查看地址 continue; case 5: puts("Which kingdom?"); scanf("%llu", &v9); addr = v9; // 修改地址 puts("Moved."); continue;
-
chunk_0的fd篡改为v8地址
-
fastbin链表变成如下结构,只需再malloc两次就可以可以在栈上申请chunk
-
栈区构造chunk,虽然pre_szie和size是没有意义的,但是v8的地址在chunk的数据区,后面直接getshell
v8的地址在0x7fffffffe1a0是没有想到的,翻看伪代码都感觉不太可能,要不是动态调试还不一定能发现,还是要多动静结合
exp
|
|
ACTF_2019_message
main
|
|
|
|
delete
|
|
hook
堆的hook函数主要用于在动态内存管理中自定义分配和释放内存,hook相当于给函数套了一个外壳,以malloc
为例,若__malloc_hook
这个函数指针不为NULL时,调用malloc
时glibc会转移到__malloc_hook
,这时调用malloc
就相当于调用__malloc_hook
,__malloc_hook
里面可以补充在malloc
之前或者之后的代码逻辑
|
|
|
|
|
|
|
|
当然一直没有试验成功……
思路
-
正常double free后篡改fd伪造chunk,还需要申请三个chunk才会到伪造的chunk
原本的sla是直接强转字符串加编码的,这里传送数据会有问题,所以单独拆出来发送数据
-
泄露
puts
地址开始ret2libc -
__free_hook
地址写入fake_chunk,system
地址写入__free_hook
,这时__free_hook
不为空,后续调用free(ptr)
就是在__free_hook(ptr)
即system(ptr)
-
最后在任意chunk写入/bin/sh再free即可调用
syetem("/bin/sh")
getshell
exp
|
|
issue
为什么第一个chunk要设置为0x30而不是0x20 ?
-
对于fastbin的malloc会检查chunk的size字段是否相等,是为了满足这一条件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
// glibc 2.23 void * __libc_malloc (size_t bytes) { mstate ar_ptr; void *victim; void *(*hook) (size_t, const void *) = atomic_forced_read (__malloc_hook); if (__builtin_expect (hook != NULL, 0)) // 检查__malloc_hook是否为UNLL return (*hook)(bytes, RETURN_ADDRESS (0)); // 调用__malloc_hook arena_get (ar_ptr, bytes); // 内存分配 victim = _int_malloc (ar_ptr, bytes); // 处理内存各类分配策略,包括fastbin smallbin... /* Retry with another arena only if we were able to find a usable arena before. */ if (!victim && ar_ptr != NULL) // 多线程处理逻辑 { LIBC_PROBE (memory_malloc_retry, 1, bytes); ar_ptr = arena_get_retry (ar_ptr, bytes); victim = _int_malloc (ar_ptr, bytes); } if (ar_ptr != NULL) (void) mutex_unlock (&ar_ptr->mutex); assert (!victim || chunk_is_mmapped (mem2chunk (victim)) || ar_ptr == arena_for_chunk (mem2chunk (victim))); return victim; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
// malloc.c --> _int_malloc /* If the size qualifies as a fastbin, first check corresponding bin. This code is safe to execute even if av is not yet initialized, so we can try it without checking, which saves some time on this fast path. */ if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ())) // 判断是否是fastbin { idx = fastbin_index (nb); mfastbinptr *fb = &fastbin (av, idx); mchunkptr pp = *fb; do // 获取空闲块 { victim = pp; if (victim == NULL) break; } while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim)) != victim); if (victim != 0) { if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0)) // 检查chunk的size { errstr = "malloc(): memory corruption (fast)"; errout: malloc_printerr (check_action, errstr, chunk2mem (victim), av); return NULL; } check_remalloced_chunk (av, victim, nb); // fastbin重新分配为chunk void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } }
为什么选择__free_hook
而不是__malloc_hook
?
__malloc_hook
的利用需要在__malloc_hook
附近伪造chunk来进行覆盖,而__free_hook
却不用这么麻烦- 由于
malloc
的参数一般都是size,参数填/bin/sh不起作用,对于__malloc_hook
的利用一般是gadget;free
的参数是ptr正好可用使用/bin/sh的ptr调用system("/bin/sh")
总结
double free的利用思路
- 后门
- 攻击
__malloc_hook
&&__free_hook
- chunk分配到got表劫持函数