# Double free() attacks on ARM.

### Introduction <a href="#id-7e59" id="id-7e59"></a>

In this chapter, we will dive into **double-free** bugs. So what’s a double-free bug?

So basically it’s a vulnerability that occurs when a variable is **freed twice**. When we try to free() a resource more than once, it corrupts the data structures of the heap allocator.

For more details, you can check the link below.

{% embed url="<https://cwe.mitre.org/data/definitions/415.html>" %}

### Intro to double free() <a href="#f8d1" id="f8d1"></a>

Let’s go through an example and see this.

> \#include \<stdio.h>\
> \#include \<malloc.h>\
> int main(){
>
> char \*a = (char\*) malloc(10);\
> char \*b = (char\*) malloc(10);\
> char \*c = (char\*) malloc(10);
>
> printf(“a is at memory %p \n”,a);\
> printf(“b is at memory %p \n”,b);\
> printf(“c is at memory %p \n”,c);
>
> free(a);\
> printf(“Freeing a \n”);
>
> free(b);\
> printf(“Freeing b \n “);
>
> free(c);\
> printf(“Freeing c \n”);\
> free(c);\
> printf(“Freeing c twice \n”);
>
> return 0;
>
> }

{% embed url="<https://pastebin.com/9iDpnJHV>" %}

Compile this source code in GCC.

> pi\@raspberrypi:\~/asm/tmp $ gcc double-free.c -o db

So there are 3 pointers in the program.

> char \*a = (char\*) malloc(10);\
> char \*b = (char\*) malloc(10);\
> char \*c = (char\*) malloc(10);

So there are **3 pointers** in the program which hold three memory locations in the heap. They are freed one by one but in the last case, the pointer c is free()d twice.

Let’s run this and see what happens.

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2FMlJ8ipvN42rEoayZF3jQ%2Fimage.png?alt=media&#x26;token=ca7c4a0c-472e-4d6a-aa5f-607681e4dddf" alt=""><figcaption></figcaption></figure>

As you can see in the third case, the program was aborted due to double-free corruption. The double-free bug in the program was identified and execution was aborted.

So the question arises if the bug is identified within the program how does this bug still exist these days or how do we exploit this?

if we look at the error message, it says:

> “\*\*\* Error in \`./db’: double free or corruption (fasttop): 0x00021028 \*\*\*”

So what’s fast top?

It’s a simple check to prevent double-free vulnerabilities. it checks if the top of the bin is not the record we are going to add to the bin.

For example, when we are freeing a chunk it will get added to the corresponding bin. if the chunk that is to be freed is the same as the one on the top of the corresponding bin then that chunk will not be added. This check in the case of fastbins is fasttop. So we know that we cannot free a chunk two times consecutively.

So is there any way to bypass this check and free a chunk twice?

Yes, Because the check is done only with the top record in the bin. we can easily bypass this by freeing another chunk in between this and we can free the freed chunk again. For example, to double free the pointer c (above example) we first free c and then **free a or b**, this will bypass the fasttop check, and finally, we can free the pointer c again. This will free the memory location in the c once more causing the double-free bug.

Let’s see this in action

Firstly let’s edit the code and recompile the code.

> \#include \<stdio.h>\
> \#include \<malloc.h>\
> int main(){
>
> char \*a = (char\*) malloc(10);\
> char \*b = (char\*) malloc(10);\
> char \*c = (char\*) malloc(10);
>
> printf(“a is at memory %p \n”,a);\
> printf(“b is at memory %p \n”,b);\
> printf(“c is at memory %p \n”,c);
>
> free(a);\
> printf(“Freeing a \n”);
>
> free(c);\
> printf(“Freeing c \n”);\
> free(b);\
> printf(“Freeing b : To bypass fastbin top check \n”);\
> free(c);\
> printf(“Freeing c again \n”);

{% embed url="<https://pastebin.com/WL09Yhk2>" %}

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2FwJInAOBuPjHZS009GSDI%2Fimage.png?alt=media&#x26;token=17bc3fdb-135c-40dd-8dc3-be963548ded7" alt=""><figcaption></figcaption></figure>

As we can see now we have successfully bypassed the top check and triggered the double-free vulnerability.

### Let’s see the next example. <a href="#id-39e7" id="id-39e7"></a>

> \#include \<stdio.h>\
> \#include \<malloc.h>
>
> struct s{\
> int num;\
> };
>
> int main(){\
> struct s \*a,\*b,\*c,\*d,\*e,\*f;
>
> a = malloc(sizeof(struct s));\
> printf(“a is at %p \n”,a);
>
> b = malloc(sizeof(struct s));\
> printf(“b is at %p \n”,b);
>
> c = malloc(sizeof(struct s));\
> printf(“c is at %p \n”,c);
>
> free(a);\
> printf(“Freeing a : %p \n”,a);
>
> free(c);\
> printf(“Freeing c : %p \n”,c);
>
> free(b);\
> printf(“Freeing b : %p \n”,b); //Bypassing top check
>
> free(c);\
> printf(“Freeing c : %p again \n”,c); //Double free
>
> printf(“The freed bins will be added to the corresponding bins \n”);\
> printf(“Now we are again allocating memory using malloc into three pointers d,e and f \n”);\
> printf(“As they are of the same sizes as the previously allocated chunks, pointer d,e and f will get allocated the chunks that have been just free()d and added to the fastbin earlier \n “);\
> d = malloc(sizeof(struct s));\
> printf(“d is at %p \n”,d);
>
> e = malloc(sizeof(struct s));\
> printf(“e is at %p \n”,e);
>
> f = malloc(sizeof(struct s));\
> printf(“f is at %p \n”,f);
>
> d->num = 1111;\
> printf(“d->num is assigned to 1111 \n”);
>
> f->num = 2222;\
> printf(“f->num is assigned to 2222 \n”);
>
> printf(“Now let’s print the values of the both e->num and d->num \n”);
>
> printf(“d->num is %d \n “,d->num);\
> printf(“f->num is %d \n “,f->num);
>
> }

{% embed url="<https://pastebin.com/QZasDRxd>" %}

Compile and run the program.

Let’s inspect the output.

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2FGzHUcMxVod8ulJ2hknYF%2Fimage.png?alt=media&#x26;token=30a63f33-1bde-4794-b62e-a84f4e6e1cbd" alt=""><figcaption></figcaption></figure>

Firstly we are allocating three memory locations for the pointer a,b and c. Then we free them so that they will get added into the fastbin of the same size. Then we allocate memory for the next three-pointers (d,e,f).

As we can see in the result ‘d’ gets the location of ‘c’: 0x21028 (because c is the last chunk that was freed and it gets added to the head of the fastbin).

‘e’ will get the memory location of ‘b’ and finally ‘f’ will get the memory location of ‘c’ again (0x21028).

Hmmmm …so you all will be thinking how is this possible?

I already said this at the beginning of this article to get familiar with the fastbins and how they work. Please go through the previous articles above if you don’t have an idea about fastbins.

### Breakdown <a href="#b47b" id="b47b"></a>

Now let’s walk through this step by step.

When we free ‘a’, it will get added to the 0x10 (16) sized fastbin. let’s check this using gdb.

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2FtqGccPhU90KVwDDNnDPy%2Fimage.png?alt=media&#x26;token=7617185c-91df-40d4-a91e-356c38c42e3f" alt=""><figcaption></figcaption></figure>

As you can see now the chunk is added in the fastbin but it is added in the 0x8 fastbin but the actual size of the chunk is 0x10. Don’t get confused the minimum sized fastbin is 0x10 (16) there is no fastbin having the size of 0x8. This is just a bug in gef. This is normally added in the 0x10 fastbin as depicted in the size of the chunk.

Let’s visualize this.

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2FsfR2toLhFPZ1wwUM0Erc%2Fimage.png?alt=media&#x26;token=09ac0195-ac76-40f0-b13d-a3223c63def0" alt=""><figcaption></figcaption></figure>

The newly added chunks will be added to the head of the bin list. (LIFO manner). The fwd pointer is null here because there is no other free chunks in the bin.

Let’s free c and see what happens. The reason why we are freeing ‘c’ instead of ‘b’ is to bypass the top check. we will free ‘b’ after freeing c.

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2FPKkhywjMF2dUuxlYLIly%2Fimage.png?alt=media&#x26;token=cfc36778-3dd5-4ddb-bc00-ee57bf72c0a3" alt=""><figcaption></figcaption></figure>

Now c(address:0x21028) is added at the head of the 0x10 fastbin.

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2FVbhJNJzCYUHCQr2wJpr6%2Fimage.png?alt=media&#x26;token=588fe14c-9727-459c-b312-0e772754e3bc" alt=""><figcaption></figcaption></figure>

Chunk ‘a’ was previously at the head position, Now the head points to chunk ‘c’ and the forward pointer of chunk ‘c’ are set to point to the address of chunk ‘a’.

So what’s the forward pointer used for?

A forward pointer (fwd) is a pointer that points to the next chunk in a specific fastbin. when fwd is NULL, it indicates the end of the list. it can be used to link the chunks in the bins. This can keep track of the available free chunks in the list.

fwd is very important in double-free attacks we will see this later.

Let’s inspect chunk ‘c’ to verify this theory.

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2F36pHnUnhOpJVdttGPB51%2Fimage.png?alt=media&#x26;token=69a5ede0-81bc-4a86-9a16-68aff245c47f" alt=""><figcaption></figcaption></figure>

The purple marked area is the forward pointer and it points to the chunk ‘a’. But the address of ‘a’ is 0x21008 and the forward pointer has the address 0x21000. This is because the forward pointer points to the ‘prev size’ header section of the chunk ‘a’ which is at the location 0x21000. This is also part of the freed chunk’s metadata. But when we inspect the chunk using gef it shows that the chunk starts from 0x21008 because it doesn’t consider this ‘prev size’ metadata (it is not set in this case).

So this verifies our theory.

Let’s free ‘b’ and inspect.

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2FK0jIikjnSqf5dijVqn2O%2Fimage.png?alt=media&#x26;token=2d9405cb-1a3d-4b59-9a38-780145fa76ca" alt=""><figcaption></figcaption></figure>

Now the head points to chunk ‘b’ at 0x21018 and the forward pointer of chunk ‘b’ is adjusted to point to chunk ‘c’.

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2FVLy8hinueVvdLYJIiXGO%2Fimage.png?alt=media&#x26;token=8d92a459-32d8-4de4-b0d1-35f4e5079368" alt=""><figcaption></figcaption></figure>

Now for the important part. This is where the double free occurs. We will free the pointer c again.

Let’s inspect with gef after freeing ‘c’ again.

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2FUEIgP9HCyqf5N63DnMSm%2Fimage.png?alt=media&#x26;token=90b7eeca-cb72-4b86-9fe8-f5e661933399" alt=""><figcaption></figcaption></figure>

if you inspect the fastbin using the ‘heap bins’ command you can see something strange. if you look at the entries in the fastbin, the chunk ‘c’ that we freed to trigger double free is added to the head of the 0x10 fastbin again. gef has detected this and says ‘loop detected’.

Let’s visualize this to understand why this is a loop.

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2FvA1qdreTffDfpgfCX4Oi%2Fimage.png?alt=media&#x26;token=b2c8e843-1fd5-4d97-8cf2-2e68c37f63b8" alt=""><figcaption></figcaption></figure>

When the chunk ‘c’ is freed it is again added to the head of the fastbin and the forward pointer of the chunk ‘c’ is readjusted to point to chunk ‘b’.The link to the chunk ‘a’ is removed from chunk ‘c’. So there is no link to the chunk ‘a’ in the fastbin.

Let’s change the diagram to a more accurate representation.

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2FYc0Sm5tXdtIUDlvuMkZT%2Fimage.png?alt=media&#x26;token=541ae62e-bd35-482c-a533-76e3f61b58a4" alt=""><figcaption></figcaption></figure>

The chunk ‘b’ and chunk ‘c’ and pointing to each other like a loop.

Now let’s allocate memory for the pointers d,e, and f.

> d = malloc(sizeof(struct s));\
> printf(“d is at %p \n”,d);
>
> e = malloc(sizeof(struct s));\
> printf(“e is at %p \n”,e);
>
> f = malloc(sizeof(struct s));\
> printf(“f is at %p \n”,f);

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2FcAMJDDuoNMShCm3VrITR%2Fimage.png?alt=media&#x26;token=fde2a97b-bd15-4406-85bb-f4477d137a2d" alt=""><figcaption></figcaption></figure>

As you know fastbin works in a LIFO manner so the last record which is added in the fastbin will get removed first or the chunk that is pointed by the head in the fastbin will be allocated next for the malloc request if it requests for the same size.So the pointer ‘d’ will get the memory space of chunk ‘c ’ (0x021028 )and the head will point to the next freed chunk in the fastbin which is chunk ‘b’.This location is identified using the fwd pointer.

If we check the fastbin now using the ‘heap bins’ command, we can see that chunk ‘c’ which was at the head of the fastbin is removed and the head points to chunk ‘b’ (0x21018) now.

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2FgA4u7AfQz8yO2XdfAfp7%2Fimage.png?alt=media&#x26;token=9000b750-c306-4f3a-82b2-6eefe75dd494" alt=""><figcaption></figcaption></figure>

But even if I say it’s removed it’s not technically removed due to the loop caused by the double free. This is because the fwd of chunk ‘b’ still points to chunk ‘c’ so this tricks the heap allocator to think that chunk ‘c’ is still free and available in the fastbin even if it is being allocated and used. This is because the heap allocator uses the fwd to keep track of the available free chunks in the bins. As a result of this, chunk ‘c’ is still considered a free chunk and is available in the fastbin.This will form a circular chain in which the head points to chunk ‘b’ and chunk ‘b’ points to chunk ‘c’ which also points to chunk ‘b’.

Let’s see the diagram now.

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2FkJp8CCH6d1cyfRpt6nbc%2Fimage.png?alt=media&#x26;token=619c3790-c531-412a-a48d-7084319fc29f" alt=""><figcaption></figcaption></figure>

Let’s now allocate the space for the pointer ‘e’ now. As you have guessed by now ‘e’ will get the memory space of chunk ‘b’ (0x21018) which was in the head of the fastbin.

if you inspect using gef you can see this.

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2Fw51Qbfra6x88Bz6AfYuU%2Fimage.png?alt=media&#x26;token=2a332356-1627-4d3e-ab2c-9261326fabd8" alt=""><figcaption></figcaption></figure>

$r0 contains the address which malloc() returned for pointer ‘e’ . This contains 0x21018, which was the address of freed chunk ‘b’ in the fastbin. Also the head of the fastbin now points to chunk ‘c’ which was pointed by fwd pointer of chunk ‘b’.

Let’s see the diagram of the chunks in the fastbin and also the output of ‘heap bins’ in gef.

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2FuUR8Wf1PYUbQvRuYPbsn%2Fimage.png?alt=media&#x26;token=93997a17-2c4f-4dd5-95e2-3ccf18637467" alt=""><figcaption></figcaption></figure>

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2FHKA3IAu2YymRqEWALPnF%2Fimage.png?alt=media&#x26;token=dc573dc4-be6c-4b40-b1a8-ccf360794932" alt=""><figcaption></figcaption></figure>

if you continue the execution and the pointer ‘f’ will get allocated the memory location of chunk ‘c’ (0x21028) which is at the head of the list and the head gets updated to chunk ‘b’ (0x21018).

Let’s inspect this one last time.

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2FACQDL3ponX3v0b4Zlh46%2Fimage.png?alt=media&#x26;token=1c8ce6cd-56a7-4a31-b3d8-29efb62f99be" alt=""><figcaption></figcaption></figure>

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2FCoj8X1PTkWhjYELzGumZ%2Fimage.png?alt=media&#x26;token=de426d0b-cd56-452c-8cd7-f100f10344c9" alt=""><figcaption></figcaption></figure>

So we can conclude that as a result of the double-free bug, it returns **chunk ‘b’ and chunk ‘c’ interchangeably**. This is because it forms a **circular link.** if we keep on allocating new memory, we will only the **0x21028 and 0x21018 alternatively**.

we can verify this by modifying this same program.

> int num;
>
> };
>
> int main(){
>
> struct s \*a,\*b,\*c,\*d,\*e,\*f,\*g,\*h;
>
> a = malloc(sizeof(struct s));\
> printf(“a is at %p \n”,a);
>
> b = malloc(sizeof(struct s));\
> printf(“b is at %p \n”,b);
>
> c = malloc(sizeof(struct s));\
> printf(“c is at %p \n”,c);
>
> free(a);\
> printf(“Freeing a : %p \n”,a);
>
> free(c);\
> printf(“Freeing c : %p \n”,c);
>
> free(b);\
> printf(“Freeing b : %p \n”,b); //Bypassing top check
>
> free(c);\
> printf(“Freeing c : %p again \n”,c); //Double free
>
> printf(“The freed bins will be added to the corresponding bins \n”);\
> printf(“Now we are again allocating memory using malloc into three pointers d,e and f \n”);\
> printf(“As they are of the same sizes as the previously allocated chunks, pointer d,e and f will get allocated the chunks that have been just free()d and added to the fastbin earlier \n “);\
> d = malloc(sizeof(struct s));\
> printf(“d is at %p \n”,d);
>
> e = malloc(sizeof(struct s));\
> printf(“e is at %p \n”,e);
>
> f = malloc(sizeof(struct s));\
> printf(“f is at %p \n”,f);
>
> g = malloc(sizeof(struct s));\
> printf(“g is at %p \n”,g);
>
> h = malloc(sizeof(struct s));\
> printf(“h is at %p \n”,h);
>
> d->num = 1111;\
> printf(“d->num is assigned to 1111 \n”);
>
> f->num = 2222;\
> printf(“f->num is assigned to 2222 \n”);
>
> printf(“Now let’s print the values of the both e->num and d->num \n”);
>
> printf(“d->num is %d \n “,d->num);\
> printf(“f->num is %d \n “,f->num);
>
> }

{% embed url="<https://pastebin.com/DkPse2iT>" %}

In this program, there are extra pointers allocating for more memory. let’s check the output.

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2FWXiEAuTc0vsMUHHFmjCp%2Fimage.png?alt=media&#x26;token=a7ba646b-7529-407f-8c75-0eaa97665519" alt=""><figcaption></figcaption></figure>

As you can see now it only returns 0x21018 and 0x21028 alternatively. it doesn’t matter how many times we try to malloc(), the heap allocator will only return these two memory locations.

Now let’s explore the final section of the program.

> d->num = 1111;\
> printf(“d->num is assigned to 1111 \n”);
>
> f->num = 2222;\
> printf(“f->num is assigned to 2222 \n”);
>
> printf(“Now let’s print the values of the both e->num and d->num \n”);\
> printf(“d->num is %d \n “,d->num);\
> printf(“f->num is %d \n “,f->num);
>
> }

if we look at the output of this program it prints the output of d->num and f->num as 2222 even if both are assigned different values. d->num was assigned 1111 and f->num as 2222. So what’s the reason for this?

Some of you may have already gotten it by now.

The reason is that both d and f point to the **same memory location**.

Both ‘e’ and ‘f’ points to 0x021028 due to double free().

When d->num is assigned the value ‘1111’ it is actually writing the data in the memory location ‘0x021028’. Later when f->num is assigned the value ‘2222’ it also writes to location 0x021028 which is the same chunk used by ‘d’. So it overwrites the value to 2222. That’s why the output of d->num becomes ‘2222’.

<figure><img src="https://3643148735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuoH6DVWgxRANBNbHnsUM%2Fuploads%2FNg4ckbLtq6F3etCIWBdE%2Fimage.png?alt=media&#x26;token=f61842e3-a4b0-491c-a4cc-b18340c4129b" alt=""><figcaption><p>Don't mind the program name it's the same as above.</p></figcaption></figure>

That pretty much sums up the demo of **double-free vulnerability**.
