Esquivando nuestro antidebuggers simple a golpe de LD_PRELOAD
Hace un tiempo vimos como escribir un sencillo antidebugger y como esquivarlo, en ese momento lo evitamos eliminando la llamada a ptrace directamente del binario, ahora veremos como "cazar" la llamada y reemplazarla por la nuestra propia sin tener que tocar el archivo.
Priemero crearemos un archivo con la función que lo reemplazará
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
/* Cabeceras estándar. */ #include <stdio.h> #include <stdarg.h> #include <unistd.h> /* Definiciones relativas a ptrace. */ #include <sys/ptrace.h> /* Si no lo queremos cazar, estas cabeceras nos proporcionan funciones para dejar seguir por su "caudal normal" a la función. */ #include <sys/types.h> #define __USE_GNU #include <dlfcn.h> /* Cadenas de error, para más realismo. */ #include <errno.h> /* Tenemos en cuenta si ya nos estamos traceando, para hacerlo más creible. */ int is_self_tracing = 0; /* La función que se llamará realmente cuando se haga ptrace. */ long ptrace(enum __ptrace_request request, ... ){ /* firma real de 'man ptrace': long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data); */ /* Tomamos la lista de argumentos. */ va_list args; va_start(args, 3); pid_t pid = va_arg(args, pid_t); void *addr = va_arg(args, void*); void *data = va_arg(args, void*); va_end(args); /* Si es del tipo que queremos "cazar", un traceo a si mismo. */ if (request == PTRACE_TRACEME){ /* Si ya se ha intentado trazar antes, lo declaramos como error de permisos y devolvemos -1. */ if (is_self_tracing){ errno = EPERM; return -1; } /* Sino, anotamos que se esté trazando y devolvemos 0. */ else{ is_self_tracing = 1; return 0; } } /* Si no nos interesa. */ else{ /* Conseguimos la función "real". */ long (*real_ptrace)(enum __ptrace_request request, pid_t pid, void *addr, void *data) = dlsym(RTLD_NEXT, "ptrace"); /* Y le enviamos los parámetros. */ return real_ptrace(request, pid, addr, data); } } |
Para compilarlo hay que enviar un par de flags:
1 |
gcc -fPIC -shared faketrace.c -o libft.so |
Y ya podemos lanzarlo contra el programa de la otra vez, teniendo en cuenta que hay que hacer "PRELOAD" de la librería "falsa" (setear la variable de entorno LD_PRELOAD al path de la librería), pero dentro del ltrace, para evitar tropezar con el:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$ ltrace env LD_PRELOAD=`pwd`/libft.so ./anti_example AAAAAAAA __libc_start_main(0x4013e0, 4, 0x7fffdce7d678, 0x403680, 0x403710 <unfinished ...> strrchr("env", '/') = NULL setlocale(6, "") = "gl_ES.UTF-8" bindtextdomain("coreutils", "/usr/share/locale") = "/usr/share/locale" textdomain("coreutils") = "coreutils" __cxa_atexit(0x401f40, 0, 0, 0x736c6974756572, 3) = 0 getopt_long(4, 0x7fffdce7d678, "+iu:0", 0x00403be0, NULL) = -1 getopt_long(4, 0x7fffdce7d678, "+iu:0", 0x00403be0, NULL) = -1 strchr("LD_PRELOAD=/home/kenkeiras/faket"..., '=') = "=/home/kenkeiras/faketrace/libft"... putenv("LD_PRELOAD=/home/kenkeiras/faket"...) = 0 strchr("./anti_example", '=') = NULL execvp(0x7fffdce7f31c, 0x7fffdce7d688, 0, 1024, 65535 <unfinished ...> --- Called exec() --- __libc_start_main(0x400684, 2, 0x7fff279e6eb8, 0x4007b0, 0x400840 <unfinished ...> ptrace(0, 0, 0, 0, 0x400840) = 0 strcmp("AAAAAAAA", "02468KMOQSk") = 17 puts("Error :/"Error :/ ) |
Listo, y sin tocar el binario.
Referencias: Stack overflow: UNIX ptrace() block child's system calls Revista Occams Razor, Número 1