Imagínese no tener acceso al código fuente de un software pero aún poder entender cómo se implementa el software, encontrar vulnerabilidades en él y, mejor aún, corregir los errores. Todo esto en forma binaria. Suena como tener superpoderes, ¿no es así?
Usted también puede poseer tales superpoderes, y las utilidades binarias de GNU (binutils) son un buen punto de partida. Los binutils de GNU son una colección de herramientas binarias que se instalan de forma predeterminada en todas las distribuciones de Linux.
El análisis binario es la habilidad más subestimada en la industria informática. Lo utilizan principalmente los analistas de malware, los ingenieros inversos y las personas
que trabajan en software de bajo nivel.
Este artículo explora algunas de las herramientas disponibles a través de binutils. Estoy usando RHEL, pero estos ejemplos deberían ejecutarse en cualquier distribución de Linux.
[~]# cat /etc/redhat-release
Red Hat Enterprise Linux Server release 7.6 (Maipo)
[~]#
[~]# uname -r
3.10.0-957.el7.x86_64
[~]#
Tenga en cuenta que algunos comandos de empaquetado (como rpm ) podría no estar disponible en distribuciones basadas en Debian, así que use el equivalente dpkg comando donde corresponda.
Desarrollo de software 101
En el mundo del código abierto, muchos de nosotros nos enfocamos en el software en forma de fuente; cuando el código fuente del software está disponible, es fácil simplemente obtener una copia del código fuente, abrir su editor favorito, tomar una taza de café y comenzar a explorar.
Pero el código fuente no es lo que se ejecuta en la CPU; son las instrucciones en lenguaje binario o de máquina las que se ejecutan en la CPU. El archivo binario o ejecutable es lo que obtienes cuando compilas el código fuente. Las personas expertas en depuración a menudo obtienen su ventaja al comprender esta diferencia.
Compilación 101
Antes de profundizar en el paquete binutils, es bueno comprender los conceptos básicos de la compilación.
La compilación es el proceso de convertir un programa desde su fuente o forma de texto en un determinado lenguaje de programación (C/C++) a código de máquina.
El código de máquina es la secuencia de 1 y 0 que entiende una CPU (o el hardware en general) y, por lo tanto, la CPU puede ejecutar o ejecutar. Este código de máquina se guarda en un archivo en un formato específico que a menudo se denomina archivo ejecutable o archivo binario. En Linux (y BSD, cuando se usa la compatibilidad binaria de Linux), esto se llama ELF (formato ejecutable y enlazable).
El proceso de compilación pasa por una serie de pasos complicados antes de presentar un archivo ejecutable o binario para un archivo fuente determinado. Considere este programa fuente (código C) como ejemplo. Abre tu editor favorito y escribe este programa:
#include <stdio.h>
int main(void)
{
printf("Hello World\n");
return 0;
}
Paso 1:Preprocesamiento con cpp
El preprocesador C (cpp ) se utiliza para expandir todas las macros e incluir los archivos de encabezado. En este ejemplo, el archivo de encabezado stdio.h se incluirán en el código fuente. stdio.h es un archivo de encabezado que contiene información sobre un printf función que se utiliza dentro del programa. cp se ejecuta en el código fuente y las instrucciones resultantes se guardan en un archivo llamado hello.i . Abra el archivo con un editor de texto para ver su contenido. El código fuente para imprimir hola mundo está al final del archivo.
[testdir]# cat hello.c
#include <stdio.h>
int main(void)
{
printf("Hello World\n");
return 0;
}
[testdir]#
[testdir]# cpp hello.c > hello.i
[testdir]#
[testdir]# ls -lrt
total 24
-rw-r--r--. 1 root root 76 Sep 13 03:20 hello.c
-rw-r--r--. 1 root root 16877 Sep 13 03:22 hello.i
[testdir]#
Paso 2:Compilación con gcc
Esta es la etapa en la que el código fuente preprocesado del Paso 1 se convierte en instrucciones en lenguaje ensamblador sin crear un archivo de objeto. Utiliza la colección de compiladores GNU (gcc ). Después de ejecutar gcc comando con la -S opción en hello.i archivo, crea un nuevo archivo llamado hello.s . Este archivo contiene las instrucciones en lenguaje ensamblador para el programa C.
Puedes ver los contenidos usando cualquier editor o el gato comando.
[testdir]#
[testdir]# gcc -Wall -S hello.i
[testdir]#
[testdir]# ls -l
total 28
-rw-r--r--. 1 root root 76 Sep 13 03:20 hello.c
-rw-r--r--. 1 root root 16877 Sep 13 03:22 hello.i
-rw-r--r--. 1 root root 448 Sep 13 03:25 hello.s
[testdir]#
[testdir]# cat hello.s
.file "hello.c"
.section .rodata
.LC0:
.string "Hello World"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
call puts
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-36)"
.section .note.GNU-stack,"",@progbits
[testdir]#
Paso 3:Ensamblar con as
El propósito de un ensamblador es convertir instrucciones en lenguaje ensamblador en código de lenguaje de máquina y generar un archivo de objeto que tenga un .o extensión. Usar el ensamblador GNU como que está disponible por defecto en todas las plataformas Linux.
[testdir]# as hello.s -o hello.o
[testdir]#
[testdir]# ls -l
total 32
-rw-r--r--. 1 root root 76 Sep 13 03:20 hello.c
-rw-r--r--. 1 root root 16877 Sep 13 03:22 hello.i
-rw-r--r--. 1 root root 1496 Sep 13 03:39 hello.o
-rw-r--r--. 1 root root 448 Sep 13 03:25 hello.s
[testdir]#
Ahora tiene su primer archivo en formato ELF; sin embargo, no puede ejecutarlo todavía. Más tarde, verá la diferencia entre un archivo de objeto y un archivo ejecutable .
[testdir]# file hello.o
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
Paso 4:Vinculación con ld
Esta es la etapa final de compilación, cuando los archivos de objeto se vinculan para crear un ejecutable. Un ejecutable generalmente requiere funciones externas que a menudo provienen de bibliotecas del sistema (libc ).
Puede invocar directamente el enlazador con ld dominio; sin embargo, este comando es algo complicado. En su lugar, puede usar el gcc compilador con -v (detallado) bandera para entender cómo ocurre la vinculación. (Usando el ld El comando para vincular es un ejercicio que queda para que lo explore.)
[testdir]# gcc -v hello.o
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/lto-wrapper
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man [...] --build=x86_64-redhat-linux
Thread model: posix
gcc version 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC)
COMPILER_PATH=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/:/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/:[...]:/usr/lib/gcc/x86_64-redhat-linux/
LIBRARY_PATH=/usr/lib/gcc/x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/:/lib/../lib64/:/usr/lib/../lib64/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64'
/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/collect2 --build-id --no-add-needed --eh-frame-hdr --hash-style=gnu [...]/../../../../lib64/crtn.o
[testdir]#
Después de ejecutar este comando, debería ver un archivo ejecutable llamado a.out :
[testdir]# ls -l
total 44
-rwxr-xr-x. 1 root root 8440 Sep 13 03:45 a.out
-rw-r--r--. 1 root root 76 Sep 13 03:20 hello.c
-rw-r--r--. 1 root root 16877 Sep 13 03:22 hello.i
-rw-r--r--. 1 root root 1496 Sep 13 03:39 hello.o
-rw-r--r--. 1 root root 448 Sep 13 03:25 hello.s
Ejecutando el archivo comando en a.out muestra que de hecho es un ejecutable ELF:
[testdir]# file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=48e4c11901d54d4bf1b6e3826baf18215e4255e5, not stripped
Ejecute su archivo ejecutable para ver si hace lo que indica el código fuente:
[testdir]# ./a.out
Hello World
¡Lo hace! Suceden tantas cosas entre bastidores solo para imprimir Hello World en la pantalla. Imagina lo que sucede en programas más complicados.
Explore las herramientas de binutils
Este ejercicio proporcionó una buena base para utilizar las herramientas que se encuentran en el paquete binutils. Mi sistema tiene binutils versión 2.27-34; es posible que tenga una versión diferente dependiendo de su distribución de Linux.
[~]# rpm -qa | grep binutils
binutils-2.27-34.base.el7.x86_64
Las siguientes herramientas están disponibles en los paquetes binutils:
[~]# rpm -ql binutils-2.27-34.base.el7.x86_64 | grep bin/
/usr/bin/addr2line
/usr/bin/ar
/usr/bin/as
/usr/bin/c++filt
/usr/bin/dwp
/usr/bin/elfedit
/usr/bin/gprof
/usr/bin/ld
/usr/bin/ld.bfd
/usr/bin/ld.gold
/usr/bin/nm
/usr/bin/objcopy
/usr/bin/objdump
/usr/bin/ranlib
/usr/bin/readelf
/usr/bin/size
/usr/bin/strings
/usr/bin/strip
El ejercicio de compilación anterior ya exploró dos de estas herramientas:el como se usó el comando como ensamblador, y el ld El comando se usó como un enlazador. Siga leyendo para conocer las otras siete herramientas del paquete GNU binutils resaltadas en negrita arriba.
readelf:muestra información sobre archivos ELF
El ejercicio anterior mencionó los términos archivo de objeto y archivo ejecutable . Usando los archivos de ese ejercicio, ingrese readelf usando la -h (encabezado) para volcar el encabezado ELF de los archivos en su pantalla. Observe que el archivo de objeto que termina con .o la extensión se muestra como Tipo:REL (archivo reubicable) :
[testdir]# readelf -h hello.o
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 [...]
[...]
Type: REL (Relocatable file)
[...]
Si intenta ejecutar este archivo, obtendrá un error que indica que no se puede ejecutar. Esto simplemente significa que aún no tiene la información necesaria para que se ejecute en la CPU.
Recuerda, debes agregar la x o bit ejecutable en el archivo de objeto usando primero chmod comando o de lo contrario obtendrá un Permiso denegado error.
[testdir]# ./hello.o
bash: ./hello.o: Permission denied
[testdir]# chmod +x ./hello.o
[testdir]#
[testdir]# ./hello.o
bash: ./hello.o: cannot execute binary file
Si prueba el mismo comando en el a.out archivo, verá que su tipo es un EXEC (archivo ejecutable) .
[testdir]# readelf -h a.out
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
[...]
Type: EXEC (Executable file)
Como se vio anteriormente, este archivo puede ser ejecutado directamente por la CPU:
[testdir]# ./a.out
Hello World
El readelf El comando proporciona una gran cantidad de información sobre un binario. Aquí, le dice que está en formato ELF de 64 bits, lo que significa que solo se puede ejecutar en una CPU de 64 bits y no funcionará en una CPU de 32 bits. También le dice que está destinado a ejecutarse en la arquitectura X86-64 (Intel/AMD). El punto de entrada al binario está en la dirección 0x400430, que es solo la dirección del principal función dentro del programa fuente C.
Prueba el readelf comando en los otros binarios del sistema que conoce, como ls . Tenga en cuenta que su salida (especialmente Type: ) puede diferir en los sistemas RHEL 8 o Fedora 30 y superiores debido a los cambios del ejecutable independiente de la posición (PIE) realizados por razones de seguridad.
[testdir]# readelf -h /bin/ls
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Aprenda qué bibliotecas del sistema los ls el comando depende del uso de ldd comando, de la siguiente manera:
[testdir]# ldd /bin/ls
linux-vdso.so.1 => (0x00007ffd7d746000)
libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f060daca000)
libcap.so.2 => /lib64/libcap.so.2 (0x00007f060d8c5000)
libacl.so.1 => /lib64/libacl.so.1 (0x00007f060d6bc000)
libc.so.6 => /lib64/libc.so.6 (0x00007f060d2ef000)
libpcre.so.1 => /lib64/libpcre.so.1 (0x00007f060d08d000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007f060ce89000)
/lib64/ld-linux-x86-64.so.2 (0x00007f060dcf1000)
libattr.so.1 => /lib64/libattr.so.1 (0x00007f060cc84000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f060ca68000)
Ejecutar readelf en la libc archivo de biblioteca para ver qué tipo de archivo es. Como bien señala, es un DYN (Archivo de objeto compartido) , lo que significa que no se puede ejecutar directamente por sí solo; debe ser utilizado por un archivo ejecutable que utilice internamente cualquier función disponible en la biblioteca.
[testdir]# readelf -h /lib64/libc.so.6
ELF Header:
Magic: 7f 45 4c 46 02 01 01 03 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - GNU
ABI Version: 0
Type: DYN (Shared object file)
size:muestra los tamaños de las secciones y el tamaño total
El tamaño El comando solo funciona en objetos y archivos ejecutables, por lo que si intenta ejecutarlo en un archivo ASCII simple, arrojará un error que dice Formato de archivo no reconocido .
[testdir]# echo "test" > file1
[testdir]# cat file1
test
[testdir]# file file1
file1: ASCII text
[testdir]# size file1
size: file1: File format not recognized
Ahora, ejecute tamaño en el archivo de objetos y el archivo ejecutable del ejercicio anterior. Observe que el archivo ejecutable (a.out ) tiene mucha más información que el archivo objeto (hola.o ), basado en la salida del comando de tamaño:
[testdir]# size hello.o
text data bss dec hex filename
89 0 0 89 59 hello.o
[testdir]# size a.out
text data bss dec hex filename
1194 540 4 1738 6ca a.out
Pero, ¿qué significa el texto , datos y bss secciones significan?
El texto Las secciones se refieren a la sección de código del binario, que tiene todas las instrucciones ejecutables. Los datos las secciones son donde están todos los datos inicializados, y bss es donde se almacenan todos los datos no inicializados.
Comparar tamaño con algunos de los otros binarios del sistema disponibles.
Para los ls comando:
[testdir]# size /bin/ls
text data bss dec hex filename
103119 4768 3360 111247 1b28f /bin/ls
Puedes ver que gcc y gdb son programas mucho más grandes que ls con solo mirar la salida del tamaño comando:
[testdir]# size /bin/gcc
text data bss dec hex filename
755549 8464 81856 845869 ce82d /bin/gcc
[testdir]# size /bin/gdb
text data bss dec hex filename
6650433 90842 152280 6893555 692ff3 /bin/gdb
cadenas:Imprime las cadenas de caracteres imprimibles en archivos
Suele ser útil agregar la -d bandera a las cadenas Comando para mostrar solo los caracteres imprimibles de la sección de datos.
hola.o es un archivo de objeto que contiene instrucciones para imprimir el texto Hello World . Por lo tanto, la única salida de las cadenas el comando es Hola Mundo .
[testdir]# strings -d hello.o
Hello World
Ejecutar cadenas en a.out (un ejecutable), por otro lado, muestra información adicional que se incluyó en el binario durante la fase de vinculación:
[testdir]# strings -d a.out
/lib64/ld-linux-x86-64.so.2
!^BU
libc.so.6
puts
__libc_start_main
__gmon_start__
GLIBC_2.2.5
UH-0
UH-0
=(
[]A\A]A^A_
Hello World
;*3$"
Recuerde que la compilación es el proceso de convertir las instrucciones del código fuente en código de máquina. El código de máquina consta de solo 1 y 0 y es difícil de leer para los humanos. Por lo tanto, ayuda presentar el código máquina como instrucciones en lenguaje ensamblador. ¿Cómo son los lenguajes ensambladores? Recuerde que el lenguaje ensamblador es específico de la arquitectura; dado que estoy usando la arquitectura Intel o x86-64, las instrucciones serán diferentes si está usando la arquitectura ARM para compilar los mismos programas.
objdump:muestra información de los archivos de objetos
Otra herramienta de binutils que puede volcar las instrucciones en lenguaje máquina del binario se llama objdump. .
Use la -d opción, que desensambla todas las instrucciones de ensamblaje del binario.
[testdir]# objdump -d hello.o
hello.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 :
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: bf 00 00 00 00 mov $0x0,%edi
9: e8 00 00 00 00 callq e
e: b8 00 00 00 00 mov $0x0,%eax
13: 5d pop %rbp
14: c3 retq
Este resultado parece intimidante al principio, pero tómese un momento para comprenderlo antes de continuar. Recuerda que el .text La sección tiene todas las instrucciones del código máquina. Las instrucciones de montaje se pueden ver en la cuarta columna (es decir, empujar , movimiento , llamarq , pop , retq ). Estas instrucciones actúan sobre registros, que son ubicaciones de memoria integradas en la CPU. Los registros en este ejemplo son rbp , rsp , edi , eax , etc., y cada registro tiene un significado especial.
Ahora ejecuta objdump en el archivo ejecutable (a.out ) y mira lo que obtienes. La salida de objdump en el ejecutable puede ser grande, así que lo reduje al principal función usando el grep comando:
[testdir]# objdump -d a.out | grep -A 9 main\>
000000000040051d :
40051d: 55 push %rbp
40051e: 48 89 e5 mov %rsp,%rbp
400521: bf d0 05 40 00 mov $0x4005d0,%edi
400526: e8 d5 fe ff ff callq 400400
40052b: b8 00 00 00 00 mov $0x0,%eax
400530: 5d pop %rbp
400531: c3 retq
Observe que las instrucciones son similares al archivo de objeto hello.o , pero contienen información adicional:
- El archivo objeto hola.o tiene la siguiente instrucción:
callq e
- El ejecutable a.out consta de la siguiente instrucción con una dirección y una función:
callq 400400 <puts@plt>
La instrucción de ensamblaje anterior está llamando a puts función. Recuerda que usaste un printf función en el código fuente. El compilador insertó una llamada a puts función de biblioteca para generar Hello World a la pantalla.
Mire la instrucción de una línea arriba de puts :
- El archivo objeto hola.o tiene la instrucción mov :
mov $0x0,%edi
- La instrucción mov para el ejecutable a.out tiene una dirección real ($0x4005d0 ) en lugar de $0x0 :
mov $0x4005d0,%edi
Esta instrucción mueve lo que esté presente en la dirección $0x4005d0 dentro del binario al registro llamado edi .
¿Qué más podría haber en el contenido de esa ubicación de memoria? Sí, lo has adivinado bien:no es más que el texto Hola, Mundo . ¿Cómo puedes estar seguro?
El readelf El comando le permite volcar cualquier sección del archivo binario (a.out ) en la pantalla. Lo siguiente le pide que descargue el .rodata , que son datos de solo lectura, en la pantalla:
[testdir]# readelf -x .rodata a.out
Hex dump of section '.rodata':
0x004005c0 01000200 00000000 00000000 00000000 ....
0x004005d0 48656c6c 6f20576f 726c6400 Hello World.
Puedes ver el texto Hola Mundo en el lado derecho y su dirección en binario en el lado izquierdo. ¿Coincide con la dirección que viste en el mov? instrucción anterior? Sí, lo hace.
strip:descarta símbolos de archivos de objetos
Este comando se usa a menudo para reducir el tamaño del binario antes de enviarlo a los clientes.
Recuerda que dificulta el proceso de depuración ya que se elimina información vital del binario; no obstante, el binario se ejecuta sin problemas.
Ejecútelo en su a.out ejecutable y observe lo que sucede. Primero, asegúrese de que el binario no esté eliminado ejecutando el siguiente comando:
[testdir]# file a.out
a.out: ELF 64-bit LSB executable, x86-64, [......] not stripped
Además, realice un seguimiento de la cantidad de bytes originalmente en el binario antes de ejecutar la strip comando:
[testdir]# du -b a.out
8440 a.out
Ahora ejecute la strip comando en su ejecutable y asegúrese de que funcionó usando el archivo comando:
[testdir]# strip a.out
[testdir]# file a.out
a.out: ELF 64-bit LSB executable, x86-64, [......] stripped
Después de eliminar el binario, su tamaño se redujo a 6296 del anterior 8440 bytes para este pequeño programa. Con tantos ahorros para un programa pequeño, no es de extrañar que los programas grandes a menudo se eliminen.
[testdir]# du -b a.out
6296 a.out
addr2line:Convierte direcciones en nombres de archivo y números de línea
La líneaaddr2 La herramienta simplemente busca direcciones en el archivo binario y las compara con líneas en el programa de código fuente C. Genial, ¿no?
Escriba otro programa de prueba para esto; solo que esta vez asegúrate de compilarlo con -g marca para gcc , que agrega información de depuración adicional para el binario y también ayuda al incluir los números de línea (proporcionados en el código fuente aquí):
[testdir]# cat -n atest.c
1 #include <stdio.h>
2
3 int globalvar = 100;
4
5 int function1(void)
6 {
7 printf("Within function1\n");
8 return 0;
9 }
10
11 int function2(void)
12 {
13 printf("Within function2\n");
14 return 0;
15 }
16
17 int main(void)
18 {
19 function1();
20 function2();
21 printf("Within main\n");
22 return 0;
23 }
Compilar con -g marcarlo y ejecutarlo. Aquí no hay sorpresas:
[testdir]# gcc -g atest.c
[testdir]# ./a.out
Within function1
Within function2
Within main
Ahora usa objdump para identificar las direcciones de memoria donde comienzan sus funciones. Puedes usar el grep comando para filtrar las líneas específicas que desee. Las direcciones para sus funciones se destacan a continuación:
[testdir]# objdump -d a.out | grep -A 2 -E 'main>:|function1>:|function2>:'
000000000040051d :
40051d: 55 push %rbp
40051e: 48 89 e5 mov %rsp,%rbp
--
0000000000400532 :
400532: 55 push %rbp
400533: 48 89 e5 mov %rsp,%rbp
--
0000000000400547 :
400547: 55 push %rbp
400548: 48 89 e5 mov %rsp,%rbp
Ahora usa la addr2line herramienta para mapear estas direcciones del binario para que coincidan con las del código fuente C:
[testdir]# addr2line -e a.out 40051d
/tmp/testdir/atest.c:6
[testdir]#
[testdir]# addr2line -e a.out 400532
/tmp/testdir/atest.c:12
[testdir]#
[testdir]# addr2line -e a.out 400547
/tmp/testdir/atest.c:18
Dice que 40051d comienza en la línea número 6 del archivo fuente atest.c , que es la línea donde la llave inicial ({ ) para función1 empieza. Haga coincidir la salida para función2 y principal .
nm:enumera los símbolos de los archivos de objetos
Use el programa C anterior para probar el nm herramienta. Compílalo rápidamente usando gcc y ejecutarlo.
[testdir]# gcc atest.c
[testdir]# ./a.out
Within function1
Within function2
Within main
Ahora ejecuta nm y grep para obtener información sobre sus funciones y variables:
[testdir]# nm a.out | grep -Ei 'function|main|globalvar'
000000000040051d T function1
0000000000400532 T function2
000000000060102c D globalvar
U __libc_start_main@@GLIBC_2.2.5
0000000000400547 T main
Puedes ver que las funciones están marcadas T , que representa símbolos en el texto sección, mientras que las variables están marcadas como D , que representa símbolos en los datos inicializados sección.
¿Imaginas lo útil que será ejecutar este comando en binarios donde no tienes el código fuente? Esto le permite mirar dentro y comprender qué funciones y variables se utilizan. A menos, por supuesto, que se hayan eliminado los binarios, en cuyo caso no contienen símbolos y, por lo tanto, el nm El comando no sería muy útil, como puede ver aquí:
[testdir]# strip a.out
[testdir]# nm a.out | grep -Ei 'function|main|globalvar'
nm: a.out: no symbols
Conclusión
The GNU binutils tools offer many options for anyone interested in analyzing binaries, and this has only been a glimpse of what they can do for you. Read the man pages for each tool to understand more about them and how to use them.