jueves, 13 de diciembre de 2012

Amosando a traza dun programa cando da `Segmentation fault' (2ª parte) [C][Gnu/Linux]

Antes, no post de sacar as trazas cando hai faios de acceso na memoria mencionarades a posibilidade de inxectar a xestión dos faios dende fora do programa, isto sería moi útil cando non é posible recompila-lo código, é unha idea que compre explorar.

Vexamos entón como cazar as funcións que crexamos susceptibles... digamos `strcpy` para proba-lo usaremos este programa:
#include <string.h>
#include <stdlib.h>

// Produce segfault
void fail(){
    char buff[40];
    strcpy(NULL, buff);
}


int main(int argc, char **argv){
    fail();

    return 0;
}


Ben, o seguinte é cazar a chamada a `strcpy`, para isto temos que preparar unha libraría compartida coa función, este sería o código:

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

// As funcións para busca-la función real de strcpy están aquí
#define __USE_GNU
#include <dlfcn.h>

// A función a cazar
char *strcpy (char *dest, const char *src){
    // Facémos o que teñamos que facer
    fprintf(stderr, "strcpy(\"%s\", \"%s\")\n", dest, src);

    // Buscámos a función "orixinal"
    char *(*real_call)(char *, const char *) = dlsym(RTLD_NEXT, "strcpy");

    // Exécutamos a dita función e recuperamos o resultado
    char *resultado = real_call(dest, src);

    // E devolvémo-lo resultado
    return resultado;
}

Este programa compílase nun *.so cos flags `-fPIC -shared -ldl`
gcc strcpy.c -o strcpy.so -fPIC -shared -ldl

Para rematar, só temos que executalo facendo `preload` da libraría que acabamos de xerar:
LD_PRELOAD=`pwd`/strcpy.so ./fail

Nota: Tede en conta que hai que indicar a ruta completa no LD_PRELOAD (por iso o do `pwd`)

Só queda cazar o sinal, o código quedaría así:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

// As funcións para busca-la función real de strcpy están aquí
#define __USE_GNU
#include <dlfcn.h>

#define BUFFER_SIZE 128

void show_backtrace(){
    void *buffer[128];
    char **symbols;

    int ptr_num = backtrace(buffer, BUFFER_SIZE);
    int i;

    symbols = backtrace_symbols(buffer, ptr_num);
    if (symbols == NULL){
        perror("backtrace_symbols");
        return;
    }

    for(i = 0; i < ptr_num; i++){
        printf("%i:\t %s\n", i, symbols[i]);
    }

    free(symbols);
}

void segfault_handler(int signal){
    printf("Faio de segmentación, amosando traza:\n\n");
    show_backtrace();

    abort();
}

// A función a cazar
char *strcpy (char *dest, const char *src){

    // Cazamos a sinal e gardamos o manexador anterior
    void *previous_handler = signal(SIGSEGV, segfault_handler);

    // Buscámos a función "orixinal"
    char *(*real_call)(char *, const char *) = dlsym(RTLD_NEXT, "strcpy");

    // Exécutamos a dita función e recuperamos o resultado
    char *resultado = real_call(dest, src);

    // Restablecemos o manexador
    signal(SIGSEGV, previous_handler);

    // E devolvémo-lo resultado
    return resultado;
}


Se repetimos os pasos anteriores, acabamos con isto:

E xa está! por suposto o nome dos arquivos é completamente arbitrario e podemos meter todas as funcións que queiramos dentro.

 ps: Sobre o de preparar as sinais e despois facer execve para carga-lo programa a memoria... nanai, pérdense os manexadores.

Saúdos

No hay comentarios:

Publicar un comentario