GNU/Linux >> Tutoriales Linux >  >> Linux

Herramientas de ingeniería inversa en Linux:cadenas, nm, ltrace, strace, LD_PRELOAD

Este artículo explica las herramientas y los comandos que se pueden usar para aplicar ingeniería inversa a un ejecutable en un entorno Linux.

La ingeniería inversa es el acto de descubrir qué hace un software, para lo cual no hay un código fuente disponible. Es posible que la ingeniería inversa no le proporcione los detalles exactos del software. Pero puede comprender bastante bien cómo se implementó un software.

La ingeniería inversa implica los siguientes tres pasos básicos:

  1. Recopilación de información
  2. Determinación del comportamiento del programa
  3. Interceptar las llamadas de la biblioteca

Yo. Recopilación de información

El primer paso es recopilar la información sobre el programa de destino y lo que hace. Para nuestro ejemplo, tomaremos el comando 'quién'. El comando 'quién' imprime la lista de usuarios conectados actualmente.

1. Comando de cadenas

Strings es un comando que imprime las cadenas de caracteres imprimibles en los archivos. Así que ahora usemos esto contra nuestro comando objetivo (quién).

# strings /usr/bin/who

Algunas de las cadenas importantes son,

users=%lu
EXIT
COMMENT
IDLE
TIME
LINE
NAME
/dev/
/var/log/wtmp
/var/run/utmp
/usr/share/locale
Michael Stone
David MacKenzie
Joseph Arceneaux

A partir de la salida about, podemos saber que "quién" está usando unos 3 archivos (/var/log/wtmp, /var/log/utmp, /usr/share/locale).

Leer más:Ejemplos de comandos de cadenas de Linux (buscar texto en archivos binarios de UNIX)

2. Comando nm

comando nm, se utiliza para enumerar los símbolos del programa de destino. Al usar nm, podemos conocer las funciones locales y de biblioteca y también las variables globales utilizadas. nm no puede funcionar en un programa que está rayado usando el comando 'strip'.

Nota:Por defecto, el comando 'quién' está eliminado. Para este ejemplo, compilé el comando "quién" una vez más.

# nm /usr/bin/who

Esto enumerará lo siguiente:

08049110 t print_line
08049320 t time_string
08049390 t print_user
08049820 t make_id_equals_comment
080498b0 t who
0804a170 T usage
0804a4e0 T main
0804a900 T set_program_name
08051ddc b need_runlevel
08051ddd b need_users
08051dde b my_line_only
08051de0 b time_format
08051de4 b time_format_width
08051de8 B program_name
08051d24 D Version
08051d28 D exit_failure

En la salida anterior:

  • t|T:el símbolo está presente en la sección de código .text
  • b|B:el símbolo está en la sección .data inicializada por la ONU
  • D|d:el símbolo está en la sección de datos inicializados.

La Mayúscula o Minúscula, determina si el símbolo es local o global.

De la salida about, podemos saber lo siguiente,

  • Tiene la función global (main,set_program_name,usage,etc..)
  • Tiene algunas funciones locales (print_user, time_string, etc.)
  • Tiene variables inicializadas globales (Versión, exit_failure)
  • Tiene las variables inicializadas por la ONU (time_format, time_format_width, etc.)

A veces, al usar los nombres de las funciones, podemos adivinar qué harán las funciones.

Leer más:10 ejemplos prácticos de comandos de Linux nm

Los otros comandos que se pueden usar para obtener información son

  • comando ldd
  • comando del fusor
  • comando lsof
  • /sistema de archivos proc

II. Determinación del comportamiento del programa

3. Comando ltrace

Rastrea las llamadas a la función de biblioteca. Ejecuta el programa en ese proceso.

# ltrace /usr/bin/who

El resultado se muestra a continuación.

utmpxname(0x8050c6c, 0xb77068f8, 0, 0xbfc5cdc0, 0xbfc5cd78)          = 0
setutxent(0x8050c6c, 0xb77068f8, 0, 0xbfc5cdc0, 0xbfc5cd78)          = 1
getutxent(0x8050c6c, 0xb77068f8, 0, 0xbfc5cdc0, 0xbfc5cd78)          = 0x9ed5860
realloc(NULL, 384)                                                   = 0x09ed59e8
getutxent(0, 384, 0, 0xbfc5cdc0, 0xbfc5cd78)                         = 0x9ed5860
realloc(0x09ed59e8, 768)                                             = 0x09ed59e8
getutxent(0x9ed59e8, 768, 0, 0xbfc5cdc0, 0xbfc5cd78)                 = 0x9ed5860
realloc(0x09ed59e8, 1152)                                            = 0x09ed59e8
getutxent(0x9ed59e8, 1152, 0, 0xbfc5cdc0, 0xbfc5cd78)                = 0x9ed5860
realloc(0x09ed59e8, 1920)                                            = 0x09ed59e8
getutxent(0x9ed59e8, 1920, 0, 0xbfc5cdc0, 0xbfc5cd78)                = 0x9ed5860
getutxent(0x9ed59e8, 1920, 0, 0xbfc5cdc0, 0xbfc5cd78)                = 0x9ed5860
realloc(0x09ed59e8, 3072)                                            = 0x09ed59e8
getutxent(0x9ed59e8, 3072, 0, 0xbfc5cdc0, 0xbfc5cd78)                = 0x9ed5860
getutxent(0x9ed59e8, 3072, 0, 0xbfc5cdc0, 0xbfc5cd78)                = 0x9ed5860
getutxent(0x9ed59e8, 3072, 0, 0xbfc5cdc0, 0xbfc5cd78)

Puede observar que hay un conjunto de llamadas a getutxent y su familia de funciones de biblioteca. También puede notar que ltrace da los resultados en el orden en que se llama a las funciones en el programa.

Ahora sabemos que el comando "quién" funciona llamando a getutxent y su familia de funciones para obtener los usuarios registrados.

4. Comando de rastreo

El comando strace se usa para rastrear las llamadas al sistema realizadas por el programa. Si un programa no usa ninguna función de biblioteca y solo usa llamadas al sistema, entonces usando ltrace simple, no podemos rastrear la ejecución del programa.

# strace /usr/bin/who
[b76e7424] brk(0x887d000)               = 0x887d000
[b76e7424] access("/var/run/utmpx", F_OK) = -1 ENOENT (No such file or directory)
[b76e7424] open("/var/run/utmp", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
.
.
.
[b76e7424] fcntl64(3, F_SETLKW, {type=F_RDLCK, whence=SEEK_SET, start=0, len=0}) = 0
[b76e7424] read(3, "\10\325"..., 384) = 384
[b76e7424] fcntl64(3, F_SETLKW, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0

Puede observar que cada vez que se llama a la función malloc, llama al sistema brk(). La función de la biblioteca getutxent en realidad llama a la llamada del sistema 'abrir' para abrir '/var/run/utmp' y coloca un bloqueo de lectura y lee el contenido y luego libera los bloqueos.

Ahora confirmamos que el comando who lee el archivo utmp para mostrar el resultado.

Tanto 'strace' como 'ltrace' tienen un conjunto de buenas opciones que se pueden usar.

  • -p pid:se adjunta al pid especificado. Útil si el programa ya se está ejecutando y desea conocer su comportamiento.
  • -n 2:aplica una sangría de 2 espacios a cada llamada anidada.
  • -f – Seguir bifurcación

Leer más:7 ejemplos de Strace para depurar la ejecución de un programa en Linux

III. Interceptando las llamadas de la biblioteca

5. LD_PRELOAD Y LD_LIBRARY_PATH

LD_PRELOAD nos permite agregar una biblioteca a una ejecución particular del programa. La función de esta biblioteca sobrescribirá la función de biblioteca real.

Nota:No podemos usar esto con programas configurados con el bit 'suid'.

Tomemos el siguiente programa.

#include <stdio.h>
int main() {
  char str1[]="TGS";
  char str2[]="tgs";
  if(strcmp(str1,str2)) {
    printf("String are not matched\n");
  }
  else {
    printf("Strings are matched\n");
  }
}

Compile y ejecute el programa.

# cc -o my_prg my_prg.c
# ./my_prg

Imprimirá "Las cadenas no coinciden".

Ahora escribiremos nuestra propia biblioteca y veremos cómo podemos interceptar la función de la biblioteca.

#include <stdio.h>
int strcmp(const char *s1, const char *s2) {
  // Always return 0.
  return 0;
}

Compile y establezca la variable LD_LIBRARY_PATH en el directorio actual.

# cc -o mylibrary.so -shared library.c -ldl
# LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH

Ahora se creará un archivo llamado 'library.so'.
Establezca la variable LD_PRELOAD en este archivo y ejecute el programa de comparación de cadenas.

# LD_PRELOAD=mylibrary.so ./my_prg

Ahora imprimirá "Las cadenas coinciden" porque usa nuestra versión de la función strcmp.

Nota:si desea interceptar cualquier función de biblioteca, entonces su propia función de biblioteca debe tener el mismo prototipo que la función de biblioteca original.

Acabamos de cubrir las cosas muy básicas necesarias para aplicar ingeniería inversa a un programa.

Para aquellos que deseen dar el siguiente paso en la ingeniería inversa, comprender el formato de archivo ELF y el programa de lenguaje ensamblador ayudará en mayor medida.


Linux
  1. 5 herramientas de Rust que vale la pena probar en la línea de comandos de Linux

  2. Comando IP de Linux

  3. Comando cd de linux

  4. Cómo usar los comandos strace y ltrace en Linux

  5. Comando de Linux para encontrar cadenas en archivos binarios o no ascii

Tutorial de comandos de cadenas de Linux para principiantes (5 ejemplos)

Tutorial del comando strace de Linux para principiantes (8 ejemplos)

Cómo rastrear la ejecución del programa usando el comando Strace de Linux

11 Comando Strace con ejemplo en Linux

Herramientas de administrador de sistemas:11 formas de usar el comando ls en Linux

Cómo usar el comando Strace de Linux