Técnicas anti-debugging sencillas para GNU/Linux

En este artículo repasaremos unas cuantas técnicas anti-debugging que se suelen utilizar para evitar que nuestros programas sean trazados. Muchas de estas ideas, bonitas por su sencillez, han sido recolectadas de varios documentos escritos por gente importante en este campo como Silvio Césare o Iñaki López. El mundo del anti-debugging es apasionante: la eterna lucha entre el cazador y la huidiza presa, que gira en espiral hasta no poder distinguir si es el depurador quién ha sido cazado y engañado por el programa, o si finalmente quien traza el programa es capaz de seguir su ejecución paso a paso. Está claro que ninguna de estas técnicas evitarán que alguien con conocimientos trace vuestros programas, pero es curioso ver como tan pocas líneas pueden complicar la vida a muchos analistas novatos 😉

 

Método 1: descriptores de ficheros.

Cuando se carga un proceso en memoria, antes de que se ejecute, se realizan diversas operaciones de lectura sobre varios ficheros implicados en la creación de la imagen del proceso en ejecución.

En principio, un proceso debería “nacer” con los descriptores 0 (entrada estándar), 1 (salida estándar) y 2 (salida de error) creados y el resto sin crear.

Debuggers coomo el GDB abren más descriptores que los necesarios y el intento de cierre de los mismos NO produce un error (como cabría esperar si estuviéramos en un entorno normal, donde esos ficheros no existen). En este ejemplo, nada más comenzar, se intenta cerrar el descriptor de fichero 3. Si esto no provoca un error (-1), es que algo está yendo mal:

#include <stdio.h>

int main( int argc, char *argv[] )
{
        if(close(3) == -1)
        {
                printf("OK\n");
        }
        else
        {
                printf("traced!\n");
                exit(-1);
        }

        return 0;
}

Probamos a ver si esto es cierto…

$ ./antidebug1
OK

$ gdb ./antidebug1
GNU gdb 6.4.90-debian
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".

(gdb) run
Starting program: /home/txipi/projects/ensamblador/antidebug/antidebug1
traced!

Program exited with code 0377.
(gdb)

Como vemos, esta técnica ha logrado detectar al GDB, pero no tiene tanta suerte con otros trazadores como ltrace o strace:

$ strace ./antidebug1
execve("./antidebug1", ["./antidebug1"], [/* 29 vars */]) = 0
uname({sys="Linux", node="elektron", ...}) = 0
brk(0)                                  = 0x804a000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xa7fb5000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xa7fb4000
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=61851, ...}) = 0
mmap2(NULL, 61851, PROT_READ, MAP_PRIVATE, 3, 0) = 0xa7fa4000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/tls/i686/cmov/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\3\3\1\240O\1"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1241580, ...}) = 0
mmap2(NULL, 1247388, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xa7e73000
mmap2(0xa7f9a000, 28672, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x127) = 0xa7f9a000
mmap2(0xa7fa1000, 10396, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xa7fa1000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xa7e72000
mprotect(0xa7f9a000, 20480, PROT_READ)  = 0
set_thread_area({entry_number:-1 -> 6, base_addr:0xa7e726c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
munmap(0xa7fa4000, 61851)               = 0
close(3)                                = -1 EBADF (Bad file descriptor)
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xa7fb3000
write(1, "OK\n", 3OK
)                     = 3
munmap(0xa7fb3000, 4096)                = 0
exit_group(0)                           = ?
Process 4947 detached

Método 2: argumentos y variables de entorno.

La documentación de la shell bash explica que el valor de la variable de entorno “_” contiene el nombre completo de cada aplicación que se haya ejecutado. En un entorno “sano”, esta variable debería ser igual que el valor de argv[0]. Los diferentes debuggers modifican esta variable de sitintas formas:

  ­
                argv[0]                    getenv("_")
shell           ./test                     ./test
strace          ./test                     /usr/bin/strace
ltrace          ./test                     /usr/bin/ltrace
fenris          ./test                     /usr/bin/fenris
gdb              /home/user/test           (NULL)

Veamos cómo utilizaríamos esta técnica con un ejemplo:

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

int main( int argc, char *argv[] )
{
        printf("getenv(_): %s\n", getenv("_"));
        printf("argv[0]: %s\n", argv[0]);

        if(strcmp(argv[0], (char *)getenv("_")))
        {
                printf("traced!\n");
                exit(-1);
        }

        printf("OK\n");

        return 0;
}

Lo compilamos, y lo probamos:

$ ./antidebug2
getenv(_): ./antidebug2
argv[0]: ./antidebug2
OK

Todo bien, veamos con ltrace:

$ ltrace ./antidebug2
__libc_start_main(0x8048404, 1, 0xaf8de1f4, 0x80484a0, 0x8048500 <unfinished ...>
getenv("_")                                                                                      = "/usr/bin/ltrace"
printf("getenv(_): %s\n", "/usr/bin/ltrace"getenv(_): /usr/bin/ltrace
)                                                     = 27
printf("argv0: %s\n", "./antidebug2"argv0: ./antidebug2
)                                                          = 22
getenv("_")                                                                                      = "/usr/bin/ltrace"
strcmp("./antidebug2", "/usr/bin/ltrace")                                                        = -1
printf("traced!\n"traced!
)                                                                              = 8
exit(-1 <unfinished ...>
+++ exited (status 255) +++

¡Pillados! A ver con strace:

$ strace ./antidebug2
execve("./antidebug2", ["./antidebug2"], [/* 29 vars */]) = 0
uname({sys="Linux", node="elektron", ...}) = 0
brk(0)                                  = 0x804a000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xa7fa0000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xa7f9f000
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=61851, ...}) = 0
mmap2(NULL, 61851, PROT_READ, MAP_PRIVATE, 3, 0) = 0xa7f8f000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/tls/i686/cmov/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\3\3\1\240O\1"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1241580, ...}) = 0
mmap2(NULL, 1247388, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xa7e5e000
mmap2(0xa7f85000, 28672, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x127) = 0xa7f85000
mmap2(0xa7f8c000, 10396, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xa7f8c000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xa7e5d000
mprotect(0xa7f85000, 20480, PROT_READ)  = 0
set_thread_area({entry_number:-1 -> 6, base_addr:0xa7e5d6c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0,  limit_in_pages:1, seg_not_present:0, useable:1}) = 0
munmap(0xa7f8f000, 61851)               = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xa7f9e000
write(1, "getenv(_): /usr/bin/strace\n", 27getenv(_): /usr/bin/strace
) = 27
write(1, "argv0: ./antidebug2\n", 22argv0: ./antidebug2
) = 22
write(1, "traced!\n", 8traced!
)                = 8
munmap(0xa7f9e000, 4096)                = 0
exit_group(-1)                          = ?
Process 4961 detached

Pillados también 😀 Última prueba con GDB:

$ gdb ./antidebug2
GNU gdb 6.4.90-debian
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".

(gdb) run
Starting program: /home/txipi/projects/ensamblador/antidebug/antidebug2
getenv(_): /usr/bin/gdb
argv0: /home/txipi/projects/ensamblador/antidebug/antidebug2
traced!

Program exited with code 0377.
(gdb)

Tercera pillada. Parece que este método es simple pero efectivo 🙂

Método 3: Identificación de procesos.

Existe un identificador para el proceso interactivo que se llama proceso “líder” de la sesión, el SID. La diferencia entre el proceso padre (PPID) y el proceso líder (SID) determinará si existe un programa intermedio desde el que se está ejecutando la aplicación.

Siguiendo de cerca estos valores mediante getsid(), getppid(), getpgid() y getpgrp() podremos detectar si algo raro pasa en e entorno:

#include <stdio.h>
#include <unistd.h>

int main( int argc, char *argv[] )
{
        printf("getsid: %d\n", getsid(getpid()));
        printf("getppid: %d\n", getppid());

        if(getsid(getpid()) != getppid())
        {
                printf("traced!\n");
                exit(-1);
        }

        printf("OK\n");

        return 0;
}

Lo probamos:

$ echo $$
4922
$ ./antidebug3
getsid: 4922
getppid: 4922
OK

Como vemos, lanzo el programa desde una shell con PID 4922, que es lo que debería dar la salida de getsid() y de getppid(). Veamos qué pasa cuando se ponen ltrace o strace por medio:

$ ltrace ./antidebug3
__libc_start_main(0x8048434, 1, 0xafb27c34, 0x80484c0, 0x8048520 <unfinished ...>
getpid()                                                                                         = 4980
getsid(4980, 0xa7f89ff4, 0x8048520, 0xa7f89ff4, 0)                                               = 4922
printf("getsid: %d\n", 4922getsid: 4922
)                                                                     = 13
getppid()                                                                                        = 4979
printf("getppid: %d\n", 4979getppid: 4979
)                                                                    = 14
getpid()                                                                                         = 4980
getsid(4980, 4979, 0x8048520, 0xa7f89ff4, 0)                                                     = 4922
getppid()                                                                                        = 4979
printf("traced!\n"traced!
)                                                                              = 8
exit(-1 <unfinished ...>
+++ exited (status 255) +++

Pillado, y…

$ strace ./antidebug3
execve("./antidebug3", ["./antidebug3"], [/* 29 vars */]) = 0
uname({sys="Linux", node="elektron", ...}) = 0
brk(0)                                  = 0x804a000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xa7f3e000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xa7f3d000
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=61851, ...}) = 0
mmap2(NULL, 61851, PROT_READ, MAP_PRIVATE, 3, 0) = 0xa7f2d000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/tls/i686/cmov/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\3\3\1\240O\1"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1241580, ...}) = 0
mmap2(NULL, 1247388, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xa7dfc000
mmap2(0xa7f23000, 28672, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x127) = 0xa7f23000
mmap2(0xa7f2a000, 10396, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xa7f2a000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xa7dfb000
mprotect(0xa7f23000, 20480, PROT_READ)  = 0
set_thread_area({entry_number:-1 -> 6, base_addr:0xa7dfb6c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0,  limit_in_pages:1, seg_not_present:0, useable:1}) = 0
munmap(0xa7f2d000, 61851)               = 0
getpid()                                = 4982
getsid(4982)                            = 4922
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xa7f3c000
write(1, "getsid: 4922\n", 13getsid: 4922
)          = 13
getppid()                               = 4981
write(1, "getppid: 4981\n", 14getppid: 4981
)         = 14
getsid(4982)                            = 4922
getppid()                               = 4981
write(1, "traced!\n", 8traced!
)                = 8
munmap(0xa7f3c000, 4096)                = 0
exit_group(-1)                          = ?
Process 4982 detached

… pillado :-D.

Método 4: falso desensamblado

Esta no es una técnica anti-debugging en sí, sino un método para dificultar el análisis “en frío” o lo que se conoce como “código muerto”, es decir, sin ejecutar el programa. Consiste en saltar en mitad de una instrucción, rompiendo todo el alineamiento hasta ese punto y engañando a los desensambladores más básicos. El código se ejecutará a partir de ese punto, y el desensamblador seguirá equivocándose desde ahí. Es probable que si se llega a un conjunto de NOPs, nuevamente el desensamblado vuelva a ser correcto, perdiéndose el efecto.

Veámoslo con un ejemplo del propio Silvio Cesare:

        jmp antidebug1 + 2
antidebug1:
.short 0xc606
        call reloc
reloc:
        popl %esi
        jmp antidebug2
 antidebug2:
        addl $(data - reloc),%esi
        movl 0(%esi),%edi
        pushl %esi
        jmp *%edi
data:
        .long 0

Como vemos, se salta a antidebug1 + 2 bytes, es decir, se obvian los bytes 0xc6 y 0x06 que están previamente en el código ensamblador. El desensamblador (objdump, por ejemplo), seguirá buscando la siguiente instrucción nada más terminar el jmp, y desesamblará el 0xc606:

$ objdump -d a.out

.
.
.

 8048340:       55              pushl  %ebp
 8048341:       89 e5           movl   %esp,%ebp
 8048343:       eb 02           jmp    0x8048347
 8048345:       06              pushl  %es
 8048346:       c6 e8 00        movb   $0x0,%al
 8048349:       00 00           addb   %al,(%eax)
 804834b:       00 5e eb        addb   %bl,0xffffffeb(%esi)
 804834e:       00 81 c6 0f 00  addb   %al,0xfc6(%ecx)
 8048353:       00
 8048354:       00 8b 7e 00 56  addb   %cl,0xff56007e(%ebx)
 8048359:       ff
 804835a:       e7 00           outl   %eax,$0x0
 804835c:       00 00           addb   %al,(%eax)
 804835e:       00 89 ec 5d c3  addb   %cl,0x90c35dec(%ecx)
 8048363:       90
 8048364:       90              nop

.
.
.

Como se puede observar, a partir del salto (en 0x8048343), el desesamblado es incorrecto 🙂

Método 5: Detectando Int 3.

Cuando un debugger quiere parar en un punto determinado, es decir, poner un breakpoint o punto de ruptura, introduce un “int 3” (0xCC). En versiones antiguas de GDB, este 0xCC se podía detectar:

void foo(){ printf("Hello\n");}

int main(){
  if ((*(volatile unsigned *)((unsigned)foo + 3) &
  0xff) == 0xcc) {
    printf("BREAKPOINT\n");
    exit(1);
  }
  foo();
}

Veámoslo en acción:

$ ./antidebug5
Hello

La idea es que cuando alguien ponga un breakpoint en la función foo, GDB pondrá un 0xCC en su dirección de memoria y en el if previo antes de llamar a esa función, lo detectemos:

$ gdb ./antidebug
GDB is free software and you are welcome to distribute copies of it
under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.16 (i586-debian-linux), Copyright 1996 Free Software Foundation, Inc.

(gdb) break foo
Breakpoint 1 at 0x8048373: file antidebug5.c, line 3.

(gdb) run
Starting program: /home/txipi/projects/ensamblador/antidebug/antidebug5
BREAKPOINT

Program exited with code 01.
(gdb)

Pero en las versiones actuales de GDB, esto no pasa:

$ gdb ./antidebug5
GNU gdb 6.4.90-debian
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".

(gdb) break foo
Breakpoint 1 at 0x804839a
(gdb) x/10 0x804839a
0x804839a <foo+6>:      0x042404c7      0xe8080485      0xffffff06      0x8955c3c9
0x80483aa <main+2>:     0x08ec83e5      0xb8f0e483      0x00000000      0x97a1c429
0x80483ba <main+18>:    0x25080483      0x000000ff
(gdb) x/10 0x8048394
0x8048394 <foo>:        0x83e58955      0x04c708ec      0x04850424      0xff06e808
0x80483a4 <foo+16>:     0xc3c9ffff      0x83e58955      0xe48308ec      0x0000b8f0
0x80483b4 <main+12>:    0xc4290000      0x048397a1
(gdb) disas 0x8048394
Dump of assembler code for function foo:
0x08048394 <foo+0>:     push   %ebp
0x08048395 <foo+1>:     mov    %esp,%ebp
0x08048397 <foo+3>:     sub    $0x8,%esp
0x0804839a <foo+6>:     movl   $0x8048504,(%esp)
0x080483a1 <foo+13>:    call   0x80482ac <printf@plt>
0x080483a6 <foo+18>:    leave
0x080483a7 <foo+19>:    ret
End of assembler dump.
(gdb) run
Starting program: /home/txipi/projects/ensamblador/antidebug/antidebug5

Breakpoint 1, 0x0804839a in foo ()
(gdb) cont
Continuing.
Hello

Program exited with code 06.
(gdb)

No hay rastro de 0xCC por ningún lado, por lo que el método no funciona ;-(.

Método 6: introducir int 3

Podemos hacer lo contrario que en el método anterior e introducir 0xCCs en el código para “molestar” a quien lo esté depurando con falsos breakpoints.

En este ejemplo, capturamos la señal SIGTRAP para que el programa no casque en ejecución normal, y lanzamos una “int 3” que dispara una SIGTRAP dentro del depurador, de forma parecida a un breakpoint:

#include <signal.h>

void handler(int signo){}

int main(){
   signal(handler, SIGTRAP);
   asm("int3");
   printf("Hello\n");
}

Veamos si funciona:

$ ./antidebug6
OK

El programa se ejecuta normalmente. Pero si lo ejecutamos a través del GDB…

$ gdb ./antidebug6
GNU gdb 6.4.90-debian
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".

(gdb) run
Starting program: /home/txipi/projects/ensamblador/antidebug/antidebug6

Program received signal SIGTRAP, Trace/breakpoint trap.
0x080483be in main ()
(gdb)

Vemos como salta la señal SIGTRAP y el GDB piensa que es un breakpoint introducido por el usuario 🙂

Método 7: llamando a ptrace()

Cuando estamos siendo debuggeados por un debugger en modo usuario (RING-3 en arquitectura IA-32), como GDB o strace/ltrace, sólo es posible llamar a ptrace una vez por proceso. Por lo tanto, si intentamos trazarnos a nosotros mismos y esto provoca un error, es que ya hay otro proceso trazándonos:

#include <sys/ptrace.h>

int main( int argc, char *argv[] )
{
        if( ptrace(PTRACE_TRACEME, 0, 1, 0) < 0 )
        {
                printf("traced!\n");
                return 1;
        }
        printf("OK\n");

        return 0;
}

Lo probamos:

$ ./antidebug7
OK

Probamos ahora con cualquier debugger que use ptrace():

$ ltrace ./antidebug7
__libc_start_main(0x8048394, 1, 0xafa71384, 0x8048400, 0x8048460 <unfinished ...>
ptrace(0, 0, 1, 0, 0)                                                                            = -1
printf("traced!\n"traced!
)                                                                              = 8
+++ exited (status 1) +++

Y vemos cómo ha sido pillado fácilmente 🙂

Esta técnica es la que usaba el protector de binarios Shiva para evitar ser trazado: creaba un proceso hijo con fork() y cada proceso trazaba al otro, de tal forma que no era posible usar un debugger basado en ptrace() para ninguno de los dos procesos, porque ya estaban siendo trazados por un proceso. Hablo en pasado porque el protector Shiva hace bastante tiempo que dejó de funcionar en los núcleos actuales de GNU/Linux. Una pena ;-(

10 pensamientos en “Técnicas anti-debugging sencillas para GNU/Linux

  1. meneame.net

    Técnicas anti-debugging sencillas para GNU/Linux

    En este post se enumeran unas cuantas técnicas anti-debugging que se suelen utilizar para evitar que los programas sean trazados. "..El mundo del anti-debugging es apasionante: la eterna lucha entre el cazador y la huidiza presa, que gira en…

    Responder
  2. moskis

    No entiendo, en el mundo GNU nadie necesita tracear un programa, tiene los fuentes a su disposición.
    Este artículo me remonta a los tiempos del crack y del serialnumbergenerator de windows, menos mal que ya pasaron a la historia.

    Responder
  3. Pak

    Moskis, el cierre del autor lo deja entrever:

    Hablo en pasado porque [el protector Shiva] hace bastante tiempo que dejó de funcionar en [los núcleos actuales de] GNU/Linux. Una pena ;-(

    Responder
  4. txipi

    @moskis: en el mundo GNU no, pero el mundo de Linux no solamente es el mundo GNU, también hay drivers privativos de linuxant, vmware para GNU/Linux y demás software que históricamente se ha querido crackear. Además de binarios con burneye, shiva, y demás, utilizados en intrusiones a sistemas

    Responder
  5. David Salgado

    Con respecto a lo de depurar, muchas veces es necesario depurar aunque tengas los fuentes, cuando una aplicación falla, en un crash por ejemplo, por mucho que tengas los fuentes, vete a saber donde casca =D

    Responder
  6. Pingback: A Geek’s Page » Blog Archive » Linux下的反调试技术

  7. wangcong

    Hello txipi! Thanks for your response on my blog. ;=)
    In fact, what I can understand here is only your C code. I have a question about one piece of the code. Why gdb/strace modifies the environment variable $_?
    Thank you!

    Responder

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *