17 Jul 2019

Off-By-One

Off-By-One in the heap

11

  • 堆块验证机制

    • inuse()

      /* extract p's inuse bit */
      #define inuse(p)                                                               
          ((((mchunkptr)(((char *) (p)) + chunksize(p)))->mchunk_size) & PREV_INUSE)
      

      仅通过下一块的 prev_in_use 位来判定当前块是否使用

    • prev_chunk()

      /* Ptr to previous physical malloc_chunk.  Only valid if prev_inuse (P).  */
      #define prev_chunk(p) ((mchunkptr)(((char *) (p)) - prev_size(p)))
      

      仅使用本块的 prev_size 来寻找前块的头。

    • next_chunk()

      /* Ptr to next physical malloc_chunk. */
      #define next_chunk(p) ((mchunkptr)(((char *) (p)) + chunksize(p)))
      

      仅通过本块头+本块size的方式来寻找下一块的头

    • chunksize()

      /* Get size, ignoring use bits */
      #define chunksize(p) (chunksize_nomask(p) & ~(SIZE_BITS))
      

      仅通过本块的size确定本块的大小

  • 原理

    边界验证不严或字符串操作不合适导致出现单字节溢出

    1. 溢出字节为可控制任意字节

      通过修改大小造成块结构之间出现重叠,从而泄露其他块数据,或是覆盖其他块数据

      int main()
      {
          void *chunk1, *chunk2;
          chunk1 = malloc(16);
          chunk2 = malloc(16);
          char *p = chunk1;
          puts("gg");
          for(int i = 0; i <= 16; i++)
          {
              p[i] = getchar();
          }
          puts("gg");
          return 0;
      }
      

      溢出前

      pwndbg> x/64gx 0x8402250
      0x8402250:      0x0000000000000000      0x0000000000000021
      0x8402260:      0x0000000000000000      0x0000000000000000
      0x8402270:      0x0000000000000000      0x0000000000000021
      0x8402280:      0x0000000000000000      0x0000000000000000
      0x8402290:      0x0000000000000000      0x0000000000020d71
      

      溢出后

      pwndbg> x/64gx 0x8402250
      0x8402250:      0x0000000000000000      0x0000000000000021
      0x8402260:      0x6161616161616161      0x6161616161616161
      0x8402270:      0x0000000000000061      0x0000000000000021
      0x8402280:      0x0000000000000000      0x0000000000000000
      0x8402290:      0x0000000000000000      0x0000000000001011
      

      输入了17个’a’,下一个块的 prev_size 被覆盖了一个字节

    2. 溢出字节为 NULL 字节

      在 size ≥ 0x100 的时候,溢出 NULL 字节prev_in_use被清,这样前块会被认为是 free 块,可以触发unlink

      int main(void)
      {
          char buffer[400]="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";//'a'*0x108
          void *chunk1,*chunk2;
          chunk1=malloc(256);
          chunk2=malloc(256);
          puts("gg");
          strcpy(chunk1,buffer);
          puts("gg");
          return 0;
           
      }
      

      溢出前

      pwndbg> x/64gx 0x8402250
      0x8402250:      0x0000000000000000      0x0000000000000111
      0x8402260:      0x0000000000000000      0x0000000000000000
      0x8402270:      0x0000000000000000      0x0000000000000000
      0x8402280:      0x0000000000000000      0x0000000000000000
      0x8402290:      0x0000000000000000      0x0000000000000000
      0x84022a0:      0x0000000000000000      0x0000000000000000
      0x84022b0:      0x0000000000000000      0x0000000000000000
      0x84022c0:      0x0000000000000000      0x0000000000000000
      0x84022d0:      0x0000000000000000      0x0000000000000000
      0x84022e0:      0x0000000000000000      0x0000000000000000
      0x84022f0:      0x0000000000000000      0x0000000000000000
      0x8402300:      0x0000000000000000      0x0000000000000000
      0x8402310:      0x0000000000000000      0x0000000000000000
      0x8402320:      0x0000000000000000      0x0000000000000000
      0x8402330:      0x0000000000000000      0x0000000000000000
      0x8402340:      0x0000000000000000      0x0000000000000000
      0x8402350:      0x0000000000000000      0x0000000000000000
      0x8402360:      0x0000000000000000      0x0000000000000111
      0x8402370:      0x0000000000000000      0x0000000000000000
      0x8402380:      0x0000000000000000      0x0000000000000000
      0x8402390:      0x0000000000000000      0x0000000000000000
      0x84023a0:      0x0000000000000000      0x0000000000000000
      0x84023b0:      0x0000000000000000      0x0000000000000000
      0x84023c0:      0x0000000000000000      0x0000000000000000
      0x84023d0:      0x0000000000000000      0x0000000000000000
      0x84023e0:      0x0000000000000000      0x0000000000000000
      0x84023f0:      0x0000000000000000      0x0000000000000000
      0x8402400:      0x0000000000000000      0x0000000000000000
      0x8402410:      0x0000000000000000      0x0000000000000000
      0x8402420:      0x0000000000000000      0x0000000000000000
      0x8402430:      0x0000000000000000      0x0000000000000000
      0x8402440:      0x0000000000000000      0x0000000000000000
      

      溢出后

      pwndbg> x/64gx 0x8402250
      0x8402250:      0x0000000000000000      0x0000000000000111
      0x8402260:      0x6161616161616161      0x6161616161616161
      0x8402270:      0x6161616161616161      0x6161616161616161
      0x8402280:      0x6161616161616161      0x6161616161616161
      0x8402290:      0x6161616161616161      0x6161616161616161
      0x84022a0:      0x6161616161616161      0x6161616161616161
      0x84022b0:      0x6161616161616161      0x6161616161616161
      0x84022c0:      0x6161616161616161      0x6161616161616161
      0x84022d0:      0x6161616161616161      0x6161616161616161
      0x84022e0:      0x6161616161616161      0x6161616161616161
      0x84022f0:      0x6161616161616161      0x6161616161616161
      0x8402300:      0x6161616161616161      0x6161616161616161
      0x8402310:      0x6161616161616161      0x6161616161616161
      0x8402320:      0x6161616161616161      0x6161616161616161
      0x8402330:      0x6161616161616161      0x6161616161616161
      0x8402340:      0x6161616161616161      0x6161616161616161
      0x8402350:      0x6161616161616161      0x6161616161616161
      0x8402360:      0x6161616161616161      0x0000000000000100  <== off-by-one null byte
      0x8402370:      0x0000000000000000      0x0000000000000000
      0x8402380:      0x0000000000000000      0x0000000000000000
      0x8402390:      0x0000000000000000      0x0000000000000000
      0x84023a0:      0x0000000000000000      0x0000000000000000
      0x84023b0:      0x0000000000000000      0x0000000000000000
      0x84023c0:      0x0000000000000000      0x0000000000000000
      0x84023d0:      0x0000000000000000      0x0000000000000000
      0x84023e0:      0x0000000000000000      0x0000000000000000
      0x84023f0:      0x0000000000000000      0x0000000000000000
      0x8402400:      0x0000000000000000      0x0000000000000000
      0x8402410:      0x0000000000000000      0x0000000000000000
      0x8402420:      0x0000000000000000      0x0000000000000000
      0x8402430:      0x0000000000000000      0x0000000000000000
      0x8402440:      0x0000000000000000      0x0000000000000000
      

      此时 prev_size 域启用,可以伪造 prev_size ,使块之间发生重叠(新版本代码存在检查,只有2.28及以前版本可用)

            if (__glibc_unlikely (chunksize(p) != prevsize))
              malloc_printerr ("corrupted size vs. prev_size while consolidating");
            unlink_chunk (av, p);
      
  • 利用方式

    • chunk overlapping

      使目标堆块被重新分配到某个可控的新的堆块中,实现对目标堆块任意读写。

      • off-by-one overwrite allocated
      • off-by-one overwrite freed
      • off-by-one null byte
    • 触发unlink

      • off-by-one small bin

      • off-by-one large bin

  • off-by-one overwrite allocated

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         A(off-by-one)                         |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         B(allocated)                          |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         C(target,allocated)                   |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    
    1. off-by-one 改B的size,使其包含C,prev_in_use 位保持为1否则会触发unlink导致崩溃
    2. free B
    3. malloc一个大小为B+C的块
    4. 通过新块对C任意读写
  • off-by-one overwrite freed

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         A(off-by-one)                         |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         B(freed)                              |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         C(target,allocated)                   |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    
    1. off-by-one 改B的size,使其包含C,prev_in_use 位保持为1

    2. malloc一个大小为B+C的块

    3. 通过新块对C任意读写

  • off-by-one null byte

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         A(allocated)                          |
    |                                                               |
    |                                                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         B(allocated)                          |
    |                                                               |
    |                                                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         C(allocated)                          |
    |                                                               |
    |                                                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      
      
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         A(allocated)                          |
    |                                                               |
    |                                                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         B1                                    |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         B2(target)                            |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                                                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         C(allocated)                          |
    |                                                               |
    |                                                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    

    A发生off-by-one,溢出一个'\x00' 覆盖B的size域的低字节,prev_in_use 位被覆盖为0,前块被认为是free块

    此时再malloc两个较小块B1、B2(不能是fastbin,B1+B2<B),会分配在B块中,然后free C和B1,导致B和C合并,再malloc 和B大小相同的块,就可以得到B2

    1. off-by-one '\x00' 覆盖B的size域低字节

    2. free B

    3. malloc B1, malloc B2

    4. free C, free b1

    5. malloc B

  • +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         A(off-by-one)                         |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         B(allocated)                          |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    
    1. 在A块中构造fake chunk,绕过unlink的check,覆盖B的pre_size域 和 prev_in_use

    2. free B,unlink


Tags:
0 comments



本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议CC BY-NC-ND 4.0)进行许可。

This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License (CC BY-NC-ND 4.0).