"UNIX es simple. Sólo necesita un genio para entender su simplicidad" (Dennis Ritchie)

Con esa frase del gran Dennis abro este post, donde voy a intentar explicar como wrappear llamas al sistema.

Linux nos da control de absolutamente todo, y es tan flexible, que uno nunca termina de aprender la totalidad de funcionalidades y aplicaciones que se le puede dar.

No me caracterizo por ser una persona de manual, más bien, todo lo que aprendí lo hice de forma autodidacta y con más práctica que libros. Así que voy directo a la parte divertida.

Veamos un ejemplo:

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

int main() {  
  char *superSecret = "p455w0rd!";
  char *inp;

  printf("Ingrese password: ");
  scanf("%ms", &inp);

  if (strcmp(superSecret, inp) == 0) {
    printf("Correcto");
  } else {
    printf("Incorrecto");
  }
}

Es un código muy sencillo donde se pregunta por un password, que estará guardado en un string dentro del mismo binario. Si bien sería mucho más fácil pasarle "strings" para ver que hay adentro, lo que vamos a hacer es aprovechar el preload del dynamic linker para ver la comparación que realiza.

Compilamos el binario:

gcc -o app app.c  

En otro archivo distinto, escribimos el siguiente código:

#define _GNU_SOURCE                                                                                                                                                
#include <dlfcn.h>
#include <stdio.h>

typedef int (*orig_strcmp)(const char *str1, const char *str2);

int strcmp(const char *s1, const char *s2) {  
  int retval=0;

  // Referenciamos a la función strcmp original para pasar desapercibidos
  orig_strcmp ostrcmp;
  ostrcmp = (orig_strcmp)dlsym(RTLD_NEXT, "strcmp");

  // Hacemos algo con lo que nos llega
  // En este caso imprimimos en pantalla, pero se puede loguear a un archivo, mandar un mail, sms, etc etc
  printf("Comparando %s y %s\n", s1, s2);

  // Llamamos a la función original y devolvemos el valor ;)
  retval = ostrcmp(s1, s2);
  return retval;
}

Compilamos la librería:

gcc -shared -fPIC spy.c -o spy.so -ldl  

Ahora viene la parte más divertida... ejecutamos el binario seteando la variable de entorno LD_PRELOAD para que precargue nuestra librería maligna buena onda:

~# LD_PRELOAD=./spy.so ./app

Ingrese password: laClaveQueIngreso  
Comparando p455w0rd! y laClaveQueIngreso  
Incorrecto 

Vemos como la librería nuestra se interpuso entre la aplicación y la llamada a strcmp, por lo que obtuvimos la clave que deberíamos ingresar:

~# LD_PRELOAD=./spy.so ./app

Ingrese password: p455w0rd!  
Comparando p455w0rd! y p455w0rd!  
Correcto  

Ahora, para los que quieran un poco de la parte aburrida del manual: http://man7.org/linux/man-pages/man8/ld.so.8.html

Como pueden ver, es bastante sencillo, y la aplicación que se le puede dar a esto realmente no tiene límites. Con un poco de imaginación pueden salir cosas super interesantes.

En un sistema comprometido, se podría agregar en /etc/ld.so.preload para que la cargue antes de ejecutar cualquier binario, SI, CUALQUIER binario ;). En la man page de ld.so pueden encontrar esa y mucha más información.

:wq!