STACK-FOUR amd64

Este nivel explica lo que puede pasar si tenemos la capacidad de sobrescribir el puntero de instrucción rip. Esto es lo que se considera un Buffer Overflow estandar.

UTILIZANDO EL DEPURADOR

Como en todos los anteriores, lo primero es utilizar rabin2 para obtener más información del binario:

stack-four. rabin2

Después lo podemos abrir en nuestro depurador, analizar todo y comprobar las funciones para ver qué estamos buscando:

stack-four. afl

Vemos que el binario utiliza las funciones printf, gets, puts y exit. Todas ellas las conocemos de los ejemplos anteriores así que ya sabemos que gets es explotable.

Además de eso, vemos que hay llamadas a tres funciones interesantes:

  • sym.complete_level

stack-four. complete_level
// COMPLETE LEVEL

// PROLOGO
push rbp
mov rbp, rsp

// EJECUCION PRINCIPAL (Puts normal)
mov edi, str.Congratulations__you_ve_finished_phoenix_stack_four...
call sym.imp.puts 

// EXIT
mov edi, 0
call sym.imp.exit

Como vemos, la función complete_level no tiene más que una llamada a puts() que no encierra ningún problema a estas alturas.

  • sym.start_level

stack-four. start_level
// START LEVEL

// PROLOGO
push rbp
mov rbp, rsp

// DECLARACIÓN DE VARIABLES
sub rsp, 0x50                            // resta 0x50 (80d) al rsp 

// EJECUCION PRINCIPAL
lea rax, qword [rbp - local_50h]         // carga en rax un puntero a [rbp - 0x50]
mov rdi, rax                             // mueve el puntero a rdi
call sym.imp.gets                        // llamada a gets en [rbp - 0x50]
mov rax, qword [rbp + arg_8h]            // mueve el contenido de [rbp + 0x8] a rax
mov qword [rbp - local_8h], rax          // mueve lo anterior a [rbp - 0x8]
mov rax, qword [rbp - local_8h]          // mueve a rax el valor de [rbp - 0x8]
mov rsi, rax                             // mueve dicho valor al rsi
mov edi, str.and_will_be_returning_to... // mueve la string al edi
mov eax, 0                               // mueve a eax el valor 0x0
call sym.imp.printf                      // llamada a printf

// EPILOGO
nop
leave
ret

Esta función ya tiene un funcionamiento más complejo. Cuando analicemos main, veremos start_level en profundidad.

  • sym.main

stack-four. main
// MAIN

//PROLOGO
push rbp
mov rbp, rsp

// DECLARACION DE VARIABLES
sub rsp, 0x10
mov dword [rbp - local_4h], edi
mov qword [rbp - local_10h], rsi

// EJECUCION PRINCIPAL
mov edi, str.Welcome_to_phoenix_stack_four
call sym.imp.puts
mov eax, 0
call sym.start_level

// EPILOGO
|           0x0040068d      b800000000     mov eax, 0
|           0x00400692      c9             leave
\           0x00400693      c3             ret

La función main no encierra ninguna complejidad. Una llamada a puts para el BANNER y después llama directamente a la función start_level. Por este motivo, se debe analizar en profundidad la función start_level.

PREPARANDO EL EXPLOIT

Si nos fijamos en la ejecución principal de la función, podemos entender un poco mejor lo que hace:

// EJECUCION PRINCIPAL
lea rax, qword [rbp - local_50h]         // carga en rax un puntero a [rbp - 0x50]
mov rdi, rax                             // mueve el puntero a rdi
call sym.imp.gets                        // llamada a gets en [rbp - 0x50]
mov rax, qword [rbp + arg_8h]            // mueve el contenido de [rbp + 0x8] a rax
mov qword [rbp - local_8h], rax          // mueve lo anterior a [rbp - 0x8]
mov rax, qword [rbp - local_8h]          // mueve a rax el valor de [rbp - 0x8]
mov rsi, rax                             // mueve dicho valor al rsi
mov edi, str.and_will_be_returning_to... // mueve la string al edi
mov eax, 0                               // mueve a eax el valor 0x0
call sym.imp.printf                      // llamada a printf

Como vemos, la función hace varias cosas:

  • Llamada a gets para llenar el buffer en [rbp - 0x50].

  • Justo después coge el valor de [rbp + 0x8] y lo mete en [rbp - 0x8]

  • Por ultimo utiliza el valor de [rbp - 0x8] en un printf junto con una string que dice algo referente a la dirección de retorno.

si analizamos el stack justo después de la llamada a gets, podremos averiguar que es lo que se almacena en [rbp - 0x8]:

  • Vamos a preparar una prueba de concepto:

  • La introducimos en el debugger con rarun2:

  • Comprobamos el stack justo después de gets():

Como podemos ver, al llenar el stack con datos, hemos alcanzado la dirección con offset [rbp + 0x4]. Si analizamos la información de [rbp + 0x8] podemos ver que tiene almacenada una dirección de memoria (Little Endian) que es 0x0040068d Si vemos el desensamblado de la función main, vemos que esta dirección de memoria es la dirección siguiente a la de call start_level. Esto significa que hemos encontrado la dirección de retorno.

Ahora, lo único que nos queda es sobrescribir la dirección de retorno con una dirección que nos lleve a la función completelevel. En este caso, la primera dirección de complete_level es: 0x0040061d.

EXPLOTANDO LA VULNERABILIDAD

A continuación podemos retocar nuestro exploit para que cumpla con nuestros requisitos:

Hemos conseguido sobrescribir la dirección de retorno y modificar el flujo del binario.

CÓDIGO FUENTE

Después de comprobar que el exploit funciona podemos analizar el código fuente para comprobar qué hacía el binario exactamente.

/*
 * phoenix/stack-four, by https://exploit.education
 *
 * The aim is to execute the function complete_level by modifying the
 * saved return address, and pointing it to the complete_level() function.
 *
 * Why were the apple and orange all alone? Because the bananna split.
 */

#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define BANNER \
  "Welcome to " LEVELNAME ", brought to you by https://exploit.education"

char *gets(char *);

void complete_level() {
  printf("Congratulations, you've finished " LEVELNAME " :-) Well done!\n");
  exit(0);
}

void start_level() {
  char buffer[64];
  void *ret;

  gets(buffer);

  ret = __builtin_return_address(0);
  printf("and will be returning to %p\n", ret);
}

int main(int argc, char **argv) {
  printf("%s\n", BANNER);
  start_level();
}

Última actualización

¿Te fue útil?