No se como llegó este tema aquí, pero la propuesta es esa, veremos:

  • Como hacer ingeniería inversa a un programa que nos pide una contraseña ( muy rápidamente ).
  • Como añadir un anti-debugger sencillo a ese programa para evitar que se recupere la contraseña.
  • Como eliminar nuestra propia protección.

Veamos, entonces el programa, nos enfrentamos a esto:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char **argv){
    if(argc != 2){
        printf("%s <contraseña>\n", argv[0]);
        exit(0);
    }

    char passwd[12];
    int i;

    memset(passwd, '\0', 12);
    for( i = 0; i < 11; i++ ){
        passwd[i] = i<5?i*2+0x30:i<10?i*2+0x41:i+0x61;
    }

    printf("%s\n",strcmp(argv[1],passwd)?"Error :/":"OK");
}

Así que lo compilamos e intentaremos extraer la contraseña:

1
gcc example.c -o example

La contraseña se comprueba contra nuestro intento con strcmp, así que podemos obtener la original simplemente traceando las llamadas a librerías ( ltrace ):

1
2
3
4
5
6
$ ltrace ./example aaa
__libc_start_main(0x80484d4, 2, 0xbfeba454, 0x80485e0, 0x80485d0
memset(0xbfeba390, '\000', 12) = 0xbfeba390
strcmp("aaa", "02468KMOQSk")   = 1
puts("Error :/")               = 9
+++ exited (status 9) +++

En el strcmp() se puede observar la contraseña, si se comprueba funciona :), ahora a lo interesante...

Anti-debugging

Por muy complicado que pueda sonar es bastante simple, el truco está en que tanto el gdb ( el debugger de GNU ) como las herramientas strace y ltrace se basan en el syscall ptrace, que permite controlar un proceso, pero con un detalle, un proceso solo puede ser controlado por otro ( o por si mismo a la vez ), nunca por más de uno, así que si nos traceamos a nosotros mismos, solucionado, si llamamos a ptrace con los argumentos adecuados y no nos deja tracearnos ya sabemos que pasa:

 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
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <sys/ptrace.h>

int main(int argc, char **argv){
    if(argc != 2){
        printf("%s <contraseña>\n", argv[0]);
        exit(0);
    }

    if (ptrace(PTRACE_TRACEME, 0, 0, 0) == -1){
        printf( "Debuggers no, gracias\n" );
        exit(1);
    }

    char passwd[12];
    int i;

    memset(passwd, '\0', 12);
    for( i = 0; i < 11; i++ ){
        passwd[i] = i<5?i*2+0x30:i<10?i*2+0x41:i+0x61;
    }

    printf("%s\n",strcmp(argv[1],passwd)?"Error :/":"OK");
}

 

1
gcc anti_example.c -o anti_example

 

1
2
3
4
5
6
$ ltrace ./anti_example aaa
__libc_start_main(0x8048504, 2, 0xbfd9c914, 0x8048650, 0x8048640
ptrace(0, 0, 0, 0, 0x49fb60)    = -1
puts("Debuggers no, gracias")   = 22
exit(1
+++ exited (status 1) +++

Y con eso, evitamos a los cotillas, por ahora...

Anti-Anti-debugging

En este apartado mejor no esperar una solución tan sencilla como la anterior, editaremos el binario para evitar la protección, es un reversing trivial, pero es un momento tan bueno como otro cualquiera para desperezarse y sacar el desensamblador. Primero desensamblamos el ejecutable y localizamos el trozo de código a ejecutar, para el ejemplo usaré objdump para el desensamblado y gHex2 para editar los binarios, simples pero cumplen su cometido:

1
objdump anti_example -d > anti_example.asm

Lo resaltado es el código que hace la comprobación, el detalle es que el call devolverá 0 si el resultado es satisfactorio y -1 si falla, si el traceado no se utiliza más podemos simplemente sobreescribirlo con un XOR EAX,EAX y NOPs. Buscamos la cadena de caracteres del string y la reemplazamos por:

  • 31 C0 : XOR eax, eax
  • 90 : NOP
  • 90 : NOP
  • 90 : NOP

Y asunto resuelto:

1
2
3
4
5
6
$ ltrace ./anti_example_cleaned 02468KMOQSk
__libc_start_main(0x8048504, 2, 0xbfae5fc4, 0x8048650, 0x8048640
memset(0xbfae5f00, '\000', 12)       = 0xbfae5f00
strcmp("02468KMOQSk", "02468KMOQSk") = 0
puts("OK")                           = 3
+++ exited (status 3) +++

Saludos