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

Escribiendo un salvapantallas para Gnu/Linux [C] » « Recuperando un archivo eliminado pero abierto por otro proceso