【pwn学习】堆溢出(四)- off-by-one 漏洞

off-by-one 漏洞

前置学习

  • 【pwn学习】堆溢出(一)
  • 【pwn学习】堆溢出(二)- First Fit
  • 【pwn学习】堆溢出(三)- Unlink和UAF

off-by-one是一种特殊的溢出漏洞,指只溢出一个字节的情况。

造成原因

造成off-by-one的原因常常是因为边界验证不充分。

  • 循环写入时,循环次数设置错误导致多写入了一个字节;

  • 字符串操作有误;

    strlen 是我们很熟悉的计算 ascii 字符串长度的函数,这个函数在计算字符串长度时是不把结束符 '\x00' 计算在内的,但是 strcpy 在复制字符串时会拷贝结束符 '\x00'

利用

  • 泄露的字节为可控制任意字节:通过修改大小造成块大小的重叠。

  • 泄露的字节为null字节:可以清楚标志位。下面的检查在2.28版本后添加来针对这种情况。

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

示例

栅栏错误

栅栏错误通常是由于循环的边界没有控制好导致写入多执行了一次。

// example_2.c
int my_gets(char *ptr,int size)
{
    int i;
    for(i=0;i<=size;i++)
    {
        ptr[i]=getchar();
    }
    return i;
}
int main()
{
    void *chunk1,*chunk2;
    chunk1=malloc(16);
    chunk2=malloc(16);
    puts("Get Input:");
    my_gets(chunk1,16);
    return 0;
}

示例程序中for循环多循环了一次,因此实际上会读入17个字节导致off-by-one漏洞

用gdb调试,读入前

pwndbg> x/30gx 0x405290
0x405290:       0x0000000000000000      0x0000000000000021   <== chunk1
0x4052a0:       0x0000000000000000      0x0000000000000000
0x4052b0:       0x0000000000000000      0x0000000000000021	 <== chunk2
0x4052c0:       0x0000000000000000      0x0000000000000000
0x4052d0:       0x0000000000000000      0x0000000000000411

读入17个字符’a’后

pwndbg> x/30gx 0x405290
0x405290:       0x0000000000000000      0x0000000000000021
0x4052a0:       0x6161616161616161      0x6161616161616161
0x4052b0:       0x0000000000000061      0x0000000000000021
0x4052c0:       0x0000000000000000      0x0000000000000000
0x4052d0:       0x0000000000000000      0x0000000000000411
0x4052e0:       0x75706e4920746547      0x00000000000a3a74

off-by-null

第二种常见的导致 off-by-one 的场景是字符串操作,原因是字符串的结束符计算有误

// example_3.c
int main(void)
{
    char buffer[40]="";
    void *chunk1;
    chunk1=malloc(24);
    puts("Get Input");
    gets(buffer);
    if(strlen(buffer)==24)
    {
        strcpy(chunk1,buffer);
    }
    return 0;

}

上述程序中,strlen 是我们很熟悉的计算 ascii 字符串长度的函数,这个函数在计算字符串长度时是不把结束符 '\x00' 计算在内的,但是strcpy 在复制字符串时会拷贝结束符 '\x00' 。这就导致了我们向 chunk1 中多写入了一个结束符\x00

strcpy写入前

pwndbg> x/30gx 0x405290
0x405290:       0x0000000000000000      0x0000000000000021
0x4052a0:       0x0000000000000000      0x0000000000000000
0x4052b0:       0x0000000000000000      0x0000000000000411
0x4052c0:       0x75706e4920746547      0x0000000000000a74
0x4052d0:       0x0000000000000000      0x0000000000000000

写入后

pwndbg> x/30gx 0x405290
0x405290:       0x0000000000000000      0x0000000000000021
0x4052a0:       0x6161616161616161      0x6161616161616161
0x4052b0:       0x6161616161616161      0x0000000000000400
0x4052c0:       0x75706e4920746547      0x0000000000000a74

可以发现下一个chunk的size区域的低字节被\x00覆盖。

其中mchunk_size记录了当前chunk的大小,chunk的大小都是8字节对齐,所以mchunk_size的低3位并不用来表示地址。为了充分利用内存空间,这三位被当作了标志位。

---------------------------------------------------
|...........................................|A|M|P|
---------------------------------------------------
  • A (NON_MAIN_ARENA) : 记录当前chunk是否不属于主线程。1 = 不属于,0 = 属于。
  • M (IS_MMAPED): 记录当前chunk是否是由mmap分配的。1 = 是, 0 = 否。
  • P (PREV_INUSE): 记录前一个chunk块是否被分配。1 = 是,0 = 否。这里的前一个chunk指的是物理地址上相邻的前一个chunk。

溢出 NULL 字节可以使得 prev_in_use位被清,这样前块会被认为是 free 块。

例题

例题-1 Asis CTF 2016 b00ks

你可能感兴趣的:(pwn学习,#,堆,学习)