首页
论坛
课程
招聘
The House of Mind (FASTBIN METHOD) + PRIME
2022-3-5 22:13 10229

The House of Mind (FASTBIN METHOD) + PRIME

2022-3-5 22:13
10229

  接着啃http://phrack.org/issues/66/10.html,前段时间在论坛发的The House of Mind,已经对其中的4.1节--THE HOUSE OF MIND做了总结,本文接着原文的后续内容,对4.1.1节--FASTBIN METHOD、4.1.2节--av->top NIGHTMARE、4.2节--THE HOUSE OF PRIME,进行总结。

  • THE HOUSE OF MIND
      每种THE HOUSE OF XX,各自代表一种利用技术,适用于不同的漏洞形式,每种利用技术,可能又包含多种具体的利用方法,FASTBIN METHOD 和 av->top NIGHTMARE,正是THE HOUSE OF MIND的另外两个子方法,其中av->top NIGHTMARE是一种不成立的方法,作者将它写出来,可能是想表达:不要只想着学习已有的方法,自己也要尝试想想别的路子。

    • FASTBIN METHOD
      漏洞代码已经在4.1节列出,为了方便阅读,这里再来一遍:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      /*
      *K-sPecial's vulnerable program
      */
      #include <stdio.h>
      #include <stdlib.h>
       
      int main (void) {
         char *ptr  = malloc(1024);        /* First allocated chunk */
         char *ptr2;                       /* Second chunk          */
         /* ptr & ~(HEAP_MAX_SIZE-1) = 0x08000000 */
         int heap = (int)ptr & 0xFFF00000;
         _Bool found = 0;
       
         printf("ptr found at %p\n", ptr);  /* Print address of first chunk */
       
         // i == 2 because this is my second chunk to allocate
         for (int i = 2; i < 1024; i++) {
             /* Allocate chunks up to 0x08100000 */
             if (!found && (((int)(ptr2 = malloc(1024)) & 0xFFF00000) == \
                                             (heap + 0x100000))) {
                 printf("good heap allignment found on malloc() %i (%p)\n", i, ptr2);
                 found = 1; /* Go out */
                 break;
             }
         }
         malloc(1024); /* Request another chunk: (ptr2 != av->top) */
         /* Incorrect input: 1048576 bytes */
         fread (ptr, 1024 * 1024, 1, stdin);
       
         free(ptr);   /* Free first chunk  */
         free(ptr2);  /* The House of Mind */
         return(0);   /* Bye */
      }

      溢出数据构造方案:

    1. chunk2->size = 0x0d,fake_heap->ar_ptr = DTORS_END-4
      这个漏洞程序分配内存的布局,已经在The House of Mind这篇帖子中做了说明(由于FASTBIN METHOD不需要使用ptr指向的空间,所以相比于之前的内存布局图,下图将蓝色区域合并到灰色区域一起,这样只是为了让图片简洁,布局并没变化):

      (1) chunk2->size = 0x0d
        仍然欺骗NON_MAIN_ARENA=1,从而诱使glibc认为0x8100000处为chunk2所属heap的管理结构,与之前不同的是,0x0d表示chunk2的大小为8字节。
      (2) fake_heap->ar_ptr = DTORS_END-4

        FASTBIN METHOD的目标是欺骗glibc按上图中的流程执行,先假设可以按期望欺骗各个判断条件,最终欺骗glibc执行以下2行代码:
      1
      2
      fb = &(av->fastbins[fastbin_index(size)]);
      *fb = p;  // av->max_fast = p
        av和p分别对应布局图中的fake_arena和chunk2,而max_fast相对fake_arena起始位置偏移4字节,这正是欺骗glibc认为fake_arena位于DTORS_END-4的原因,因为这样就可以将位于DTORS_END处的指针值,修改为chunk2的地址,而不再指向.dtors()函数,另外,这个方法欺骗glibc执行的代码,是直接修改fake_arena这块内存,而之前的方法是欺骗glibc修改fake_arena->bins[2]指向的内存,所以,从布局图上看,这个方法比之前的方法,要少绕个弯。
      (3) chunk2位置构造指令
        由于可以修改.dtors函数指针指向chunk2,所以很容易就能想到,chunk2处应该构造shell code,只不过为了满足(1)、(2)步骤中的欺骗条件,chunk2->size必须构造为0x0d,为了使"chunk_at_offset(p, size)->size <= 2*SIZE_SZ"判断条件不成立,对ptr处的值也有一定要求,除此之外,glibc执行"p->fd = *fb"这条语句时,还会修改ptr处的值,所以shell code必须往后放,chunk2位置放一条jmp指令跳转过去即可。
      (4) 判断条件欺骗
        在步骤(2)中,只是假设可以按期望欺骗各个判断条件,但是按照思路1的构造数据,这个假设是不成立的,主要因为两个原因:
      • .dtors节的内容,是有规范的,由编译器决定(0xFFFFFFFF(DTORS_LIST开始标记) - dtors1()函数地址 - .. - dtorsN()函数地址 - 0x00000000(DTORS_END)),比如漏洞程序没有设置.dtors()函数,.dtors节中就只有0xFFFFFFFF、0x00000000两个值,这样就导致fake_arena->mutex = 0xFFFFFFFF,进一步导致漏洞程序执行free()函数,调用内部的_int_free()之前,会被lock在外面,另外fake_arena->max_fast = 0,也无法欺骗判断2成立。
      • system_mem偏移malloc_state结构1848字节,一般位于数据段和堆区之间的一块空白区域(程序加载时各个区段之间有对齐空间,不是紧挨着的),这块区域都被加载器填充为0,也就是说fake_arena->system_mem = 0,从而导致"chunksize(chunk_at_offset(p, size)) >= av->system_mem"判断成立,所以,即使能执行到_int_free()函数,也会在判断3处与期望相反。(不过,.got节一般在.dtors节之后,如果漏洞程序比较大,.got节相应也比较大的时候,fake_arena->system_mem就会落于.got节,就大概率不存在这个问题,因为.got节存放的经常是些地址值,当然也就比较大)
    2. chunk2->size = 0x0d,fake_heap->ar_ptr = &.got[f2]-4
      即fake_heap->ar_ptr = &.got[f1],仍然存在与思路1同样的问题,首先,.got[f1]存储的是f1()函数的地址,也会使fake_arena->mutex != 0,另外,.got节和DTORS_LIST位置离的很近,再加上小程序的.got节很小,所以不管f1选择什么函数,&.got[f1]离DTORS_LIST位置一定也不远,这样,两个思路中fake_arena->system_mem的位置其实差不多,一般都为0。
    3. chunk2->size = 0x0d,fake_heap->ar_ptr = EBP
      由于main()栈帧上没有EIP(main()函数没有上一层函数可以返回,main()函数中的return会被编译器替换成exit()),所以为了说明后续思路,作者调整了一下漏洞程序:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      int fvuln()
      {
         // 原漏洞程序main()函数中的代码
      }
       
      int main( int argc, char *argv[] )
      {
         return fvuln();
      }

      思路3的目的是,欺骗glibc认为chunk2所属的arena在EBP处(EBP为执行fvuln()函数时的EBP值,即fvuln()的栈帧起始位置,这个地方存的是main()的栈帧起始位置,往上4个字节存的是main()函数地址),这样,利用"fake_arene->max_fast = chunk2",就可以将fvuln()的返回地址修改为chunk2,而不再是main()函数,从而在fvuln()函数返回时,执行shell code。

      但是,这样仍然存在fake_arena->mutex != 0的问题,因为main()的栈帧起始位置不可能为0。

    4. chunk2->size = 0x15,fake_heap->ar_ptr = EBP-4
      0x15表示chunk2的大小为16字节,"*fb = p"就会等效于"fake_arena->fast_bins[0] = chunk2",也就是会修改相对fake_arena偏移8字节处的内容,那么fake_arene也应该位于EIP下方8字节处,即EBP-4,这样fake_arena->mutex的位置就可以与EBP错开了,它的值就可以等于EBP-4处的0,另外,将EBP-4作为fake_arena的起始位置,fake_arena->system_mem往往都是一个比较大的随机值(这个可以认为是经验,可以在gdb中用x/8x $ebp+1848验证),所以,将ptr处构造为0x09,是很容易让判断3不成立的,最终满足所有欺骗条件。
      但是我对EBP-4处为0这一点,比较疑惑,为此做了一个实验:

      结论就是EBP-4处为0的条件其实还挺苛刻的,可以理解为对漏洞程序、运行环境,又增加了要求,原文是这样说的:

      不管怎么说,调整chunk2的大小,至少为FASTBIN METHOD打开了一条新路,如果 EBP-4处仍然不为0,大不了继续调整chunk2的伪造大小,直到fake_arena->system_mem落在一个0值的位置。
    • av->top NIGHTMARE
      av->top NIGHTMARE的目标是欺骗glibc按如下流程执行:

      但是,为了使判断9不成立,就得将chunk2->size构造为超级大的值(这样nextchunk才可以与被要求指向栈区的av->top相等),然后横跨几乎整个进程空间去构造nextchunk->size,以欺骗之前的其它判断,但是这样肯定会出现段错误,因为当中的大片空间,并没有跟系统分配,所以这个方法不成立。
  • THE HOUSE OF PRIME
    THE HOUSE OF PRIME适用于,可以利用bug控制malloc()返回值,并且漏洞程序中存在往该"分配内存"写用户输入的逻辑(比如先欺骗malloc()返回漏洞函数的EBP,后续通过用户输入,就可以改写漏洞函数的返回地址了),原文中的示范漏洞程序如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
     
    void fvuln(char *str1, char *str2, int age)
    {
       int local_age;
       char buffer[64];
       char *ptr = malloc(1024);
       char *ptr1 = malloc(1024);
       char *ptr2 = malloc(1024);
       char *ptr3;
     
       local_age = age;
       strncpy(buffer, str1, sizeof(buffer)-1);
     
       printf("\nptr found at [ %p ]", ptr);
       printf("\nptr1ovf found at [ %p ]", ptr1);
       printf("\nptr2ovf found at [ %p ]\n", ptr2);
     
       printf("Enter a description: ");
       fread(ptr, 1024 * 5, 1, stdin);
     
       free(ptr1);
       printf("\nEND free(1)\n");
       free(ptr2);
       printf("\nEND free(2)\n");
     
       ptr3 = malloc(1024);
       printf("\nEND malloc()\n");
       strncpy(ptr3, str2, 1024-1);
     
       printf("Your name is %s and you are %d", buffer, local_age);
    }
     
    int main(int argc, char *argv[])
    {
       if(argc < 4) {
           printf("Usage: ./hop name last-name age");
           exit(0);
       }
     
       fvuln(argv[1], argv[2], atoi(argv[3]));
       return 0;
    }

    根据The House of Mind的经验,这里就不再过于详细说明了,直接将内存布局、构造数据,以及漏洞程序的执行流程,汇总如下:

    漏洞程序执行过程说明:
    (1) free(ptr1)
      伪造chunk1->size,欺骗glibc将main_arena->max_fast修改为chunk1的地址,正常逻辑中,max_fast最大可以等于512字节,表示小于512字节的都是fast chunk,被改成chunk1地址(0x80XXXXX),就表示大小不超过0x80XXXXX的chunk,glibc都会将其当作fast chunk处理。
    (2) free(ptr2)
      有了步骤(1)的铺垫,chunk2会被glibc当作fast chunk释放,并且归属于main_arena->fastbins[289],这显然已经在main_arena结构之外了,实际上正好与arena_key全局变量重叠(这是将chunk2->size构造为0x919的原因,可以事先通过objdump,查看arena_key与main_arena之间的偏移,并计算得到),从而又欺骗glibc将arena_key修改为chunk2,这样,通过构造chunk2的内容,就可以控制下一次malloc()返回任意想要的地址。
    (3) ptr3 = malloc(1024)
      经过步骤(2)后,glibc就会从arena_key分配内存,而arena_key->max_fast与chunk2->size是重叠的,即arena_key->max_fast=0x919,所以1032字节大小的chunk,会被当作fast chunk,从arena_key->fastbins[127]链表中摘取,根据构造数据可知,这个值为EBP。
    (4) strncpy(ptr3, argv[2], 1024-1)
      此时ptr3=EBP,执行strncpy(),就会向上覆盖返回地址的值,并且拷贝内容来自用户输入,显然可以覆盖成shell code的地址,根据布局图可以看见,shell code的位置可以有很多选择,具体构造就不再重复叙述了。
    (5) 判断条件欺骗
      用于欺骗判断条件的构造数据,布局图中已经使用虚线标出,都不难理解,这里主要说明一下_int_malloc()中的判断2,要求chunk3->size必须与分配大小匹配(示范漏洞程序的第三个启动参数,是写入栈中的,所以正好可以被利用,满足这一点)。
     
    实际中漏洞程序,逻辑是不受攻击者控制的,比如示范漏洞程序中最后一次malloc()的参数值,使判断1不成立,_ini_free()就会走后续逻辑,从unsorted bin中分配内存:

    unsorted bin相当于一种缓存,每次尝试从unsorted bin分配chunk时,glibc总是从unsorted bin链表头依次取出其中的chunk,如果恰好满足分配内存的大小,就返回给调用者,否则转移到相应的fast bin(av->fastbins[x])或者bin(av->bins[x]),上图代码就是从unsorted bin链表头摘除chunk的过程,当中的if用于判断是否为last remainder chunk,构造数据可以很容易使其不成立,因此可以简化成以下4条语句:

    1
    2
    3
    4
    victim = unsorted_chunks(av)->bk;  // victim位置确定,内容x
    bck = victim->bk;    // bck位置x->bk,内容EIP-8
    unsorted_chunks(av)->bk = bck;        ↑
    bck->fd = unsorted_chunks(av);  // bck内容EIP-8

    通过最后一条语句可知,如果欺骗bck=EIP-8,就可以将漏洞函数的返回地址,改写为unsorted_chunks(av),即布局图中&arena_key->bins[0],再通过第一条和第二条语句可知,如果往unsorted_chunks(av)->bk即&av->bins[0]+12处放入x,并往x->bk处放入EIP-8,就可以使bck=EIP-8,所以,x值其实有很多选择,只要保证通过溢出数据可以覆盖到,并且不与其它关键的构造位置冲突即可,原文中选的是&av->bins[0]+4,那么相应地,往x->bk即&av->bins[0]+16处放入EIP-8,并在&arena_key->bins[0]位置构造shell code,即可实现利用。
     

  • 结束语
      本次学习的利用技术,都依赖于可以将chunk大小伪造成8字节,而在较高的glibc版本中,已经增加了对释放chunk大小的检查,要求释放chunk必须满足最基本的大小(32位系统中为16字节,至少具有存放prev_size、size、fd、bk的空间),从而使这些攻击技术失效,所以攻防技术之间是持续博弈的,学习和创新,也需要不断持续,最近总结的两篇文章,只是二进制漏洞利用技术中的冰山一角,但也足以领教到真正计算机大佬的"扭曲"思维,有时候真觉得这些大佬就是"神经病",自己只能平凡的做个正常人。

【看雪培训】《Adroid高级研修班》2022年夏季班招生中!

收藏
点赞1
打赏
分享
打赏 + 50.00雪花
打赏次数 1 雪花 + 50.00
 
赞赏  Editor   +50.00 2022/04/11 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (0)
游客
登录 | 注册 方可回帖
返回