"Hay 10 tipos de personas en este mundo:los que entienden binario y los que no".
Más recursos de Linux
- Hoja de trucos de los comandos de Linux
- Hoja de trucos de comandos avanzados de Linux
- Curso en línea gratuito:Descripción general técnica de RHEL
- Hoja de trucos de red de Linux
- Hoja de trucos de SELinux
- Hoja de trucos de los comandos comunes de Linux
- ¿Qué son los contenedores de Linux?
- Nuestros últimos artículos sobre Linux
Trabajamos con binarios a diario, pero entendemos muy poco sobre ellos. Por binarios, me refiero a los archivos ejecutables que ejecuta a diario, directamente desde sus herramientas de línea de comandos hasta aplicaciones completas.
¡Linux proporciona un rico conjunto de herramientas que hace que el análisis de binarios sea muy sencillo! Cualquiera que sea su función laboral, si está trabajando en Linux, conocer los conceptos básicos de estas herramientas lo ayudará a comprender mejor su sistema.
En este artículo, cubriremos algunas de las herramientas y comandos de Linux más populares, la mayoría de los cuales estarán disponibles de forma nativa como parte de su distribución de Linux. De lo contrario, siempre puede usar su administrador de paquetes para instalarlos y explorarlos. Recuerde:aprender a utilizar la herramienta adecuada en la ocasión adecuada requiere mucha paciencia y práctica.
archivo
Qué hace:Ayuda a determinar el tipo de archivo.
Este será su punto de partida para el análisis binario. Trabajamos con archivos diariamente. No todo es de tipo ejecutable; existe una amplia gama de tipos de archivos. Antes de comenzar, debe comprender el tipo de archivo que se está analizando. ¿Es un archivo binario, un archivo de biblioteca, un archivo de texto ASCII, un archivo de video, un archivo de imagen, un PDF, un archivo de datos, etc.?
El archivo El comando lo ayudará a identificar el tipo de archivo exacto con el que está tratando.
$ file /bin/ls
/bin/ls: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=94943a89d17e9d373b2794dcb1f7e38c95b66c86, stripped
$
$ file /etc/passwd
/etc/passwd: ASCII text
$
ldd
Qué hace:Imprime dependencias de objetos compartidos.
Si ya ha utilizado el archivo comando anterior en un binario ejecutable, no puede perderse el mensaje "vinculado dinámicamente" en la salida. ¿Qué significa?
Cuando se desarrolla software, tratamos de no reinventar la rueda. Hay un conjunto de tareas comunes que la mayoría de los programas de software requieren, como imprimir resultados o leer desde la entrada estándar, abrir archivos, etc. Todas estas tareas comunes se resumen en un conjunto de funciones comunes que todos pueden usar en lugar de escribir. sus propias variantes. Estas funciones comunes se colocan en una biblioteca llamada libc o glibc .
¿Cómo se encuentra de qué bibliotecas depende el ejecutable? Ahí es donde ldd el comando entra en escena. Ejecutarlo contra un binario enlazado dinámicamente muestra todas sus bibliotecas dependientes y sus rutas.
$ ldd /bin/ls
linux-vdso.so.1 => (0x00007ffef5ba1000)
libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fea9f854000)
libcap.so.2 => /lib64/libcap.so.2 (0x00007fea9f64f000)
libacl.so.1 => /lib64/libacl.so.1 (0x00007fea9f446000)
libc.so.6 => /lib64/libc.so.6 (0x00007fea9f079000)
libpcre.so.1 => /lib64/libpcre.so.1 (0x00007fea9ee17000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007fea9ec13000)
/lib64/ld-linux-x86-64.so.2 (0x00007fea9fa7b000)
libattr.so.1 => /lib64/libattr.so.1 (0x00007fea9ea0e000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fea9e7f2000)
$
ltrazar
Qué hace:un rastreador de llamadas de biblioteca.
Ahora sabemos cómo encontrar las bibliotecas de las que depende un programa ejecutable usando el ldd dominio. Sin embargo, una biblioteca puede contener cientos de funciones. De esos cientos, ¿cuáles son las funciones reales que utiliza nuestro binario?
El ltrace El comando muestra todas las funciones que se llaman en tiempo de ejecución desde la biblioteca. En el siguiente ejemplo, puede ver los nombres de las funciones que se llaman, junto con los argumentos que se pasan a esa función. También puede ver lo que devolvieron esas funciones en el extremo derecho de la salida.
$ ltrace ls
__libc_start_main(0x4028c0, 1, 0x7ffd94023b88, 0x412950 <unfinished ...>
strrchr("ls", '/') = nil
setlocale(LC_ALL, "") = "en_US.UTF-8"
bindtextdomain("coreutils", "/usr/share/locale") = "/usr/share/locale"
textdomain("coreutils") = "coreutils"
__cxa_atexit(0x40a930, 0, 0, 0x736c6974756572) = 0
isatty(1) = 1
getenv("QUOTING_STYLE") = nil
getenv("COLUMNS") = nil
ioctl(1, 21523, 0x7ffd94023a50) = 0
<< snip >>
fflush(0x7ff7baae61c0) = 0
fclose(0x7ff7baae61c0) = 0
+++ exited (status 0) +++
$
Hexdump
Qué hace:muestra el contenido del archivo en ASCII, decimal, hexadecimal u octal.
A menudo, sucede que abre un archivo con una aplicación que no sabe qué hacer con ese archivo. Intente abrir un archivo ejecutable o un archivo de video usando vim; todo lo que verá es un galimatías arrojado en la pantalla.
Abrir archivos desconocidos en Hexdump lo ayuda a ver qué contiene exactamente el archivo. También puede optar por ver la representación ASCII de los datos presentes en el archivo mediante algunas opciones de la línea de comandos. Esto podría ayudar a darle algunas pistas sobre qué tipo de archivo es.
$ hexdump -C /bin/ls | head
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 3e 00 01 00 00 00 d4 42 40 00 00 00 00 00 |..>......B@.....|
00000020 40 00 00 00 00 00 00 00 f0 c3 01 00 00 00 00 00 |@...............|
00000030 00 00 00 00 40 00 38 00 09 00 40 00 1f 00 1e 00 |[email protected]...@.....|
00000040 06 00 00 00 05 00 00 00 40 00 00 00 00 00 00 00 |........@.......|
00000050 40 00 40 00 00 00 00 00 40 00 40 00 00 00 00 00 |@.@.....@.@.....|
00000060 f8 01 00 00 00 00 00 00 f8 01 00 00 00 00 00 00 |................|
00000070 08 00 00 00 00 00 00 00 03 00 00 00 04 00 00 00 |................|
00000080 38 02 00 00 00 00 00 00 38 02 40 00 00 00 00 00 |8.......8.@.....|
00000090 38 02 40 00 00 00 00 00 1c 00 00 00 00 00 00 00 |8.@.............|
$
cadenas
Qué hace:Imprime las cadenas de caracteres imprimibles en los archivos.
Si Hexdump parece un poco exagerado para su caso de uso y simplemente está buscando caracteres imprimibles dentro de un binario, puede usar las cadenas comando.
Cuando se desarrolla el software, se le agrega una variedad de mensajes de texto/ASCII, como mensajes de información de impresión, información de depuración, mensajes de ayuda, errores, etc. Siempre que toda esta información esté presente en el binario, se volcará a la pantalla usando cadenas. .
$ strings /bin/ls
leer
Qué hace:muestra información sobre archivos ELF.
ELF (Formato de archivo ejecutable y vinculable) es el formato de archivo dominante para archivos ejecutables o binarios, no solo en Linux, sino también en una variedad de sistemas UNIX. Si ha utilizado herramientas como el comando de archivo, que le dice que el archivo está en formato ELF, el siguiente paso lógico será usar el readelf comando y sus diversas opciones para analizar el archivo más a fondo.
Tener una referencia de la especificación ELF real a mano cuando se usa readelf puede ser muy útil. Puede encontrar la especificación aquí.
$ 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)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x4042d4
Start of program headers: 64 (bytes into file)
Start of section headers: 115696 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 31
Section header string table index: 30
$
objeto de volcado
Qué hace:muestra información de un archivo de objeto.
Los archivos binarios se crean cuando escribe código fuente que se compila con una herramienta llamada, como era de esperar, compilador. Este compilador genera instrucciones en lenguaje de máquina equivalentes al código fuente, que luego puede ejecutar la CPU para realizar una tarea determinada. Este código de lenguaje de máquina se puede interpretar a través de mnemónicos llamados lenguaje ensamblador. Un lenguaje ensamblador es un conjunto de instrucciones que lo ayudan a comprender las operaciones que realiza el programa y, en última instancia, se ejecutan en la CPU.
objdump La utilidad lee el archivo binario o ejecutable y vuelca las instrucciones en lenguaje ensamblador en la pantalla. El conocimiento del ensamblaje es fundamental para comprender el resultado del objdump comando.
Recuerde:el lenguaje ensamblador es específico de la arquitectura.
$ objdump -d /bin/ls | head
/bin/ls: file format elf64-x86-64
Disassembly of section .init:
0000000000402150 <_init@@Base>:
402150: 48 83 ec 08 sub $0x8,%rsp
402154: 48 8b 05 6d 8e 21 00 mov 0x218e6d(%rip),%rax # 61afc8 <__gmon_start__>
40215b: 48 85 c0 test %rax,%rax
$
straza
Qué hace:Rastrear llamadas y señales del sistema.
Si ha utilizado ltrace , mencionado anteriormente, piensa en strace siendo similar. La única diferencia es que, en lugar de llamar a una biblioteca, strace la utilidad rastrea las llamadas al sistema. Las llamadas al sistema son la forma de interactuar con el núcleo para realizar el trabajo.
Por poner un ejemplo, si quieres imprimir algo en la pantalla, usarás el printf o pone función de la biblioteca estándar libc; sin embargo, bajo el capó, en última instancia, una llamada al sistema llamada escribir se hará para imprimir algo en la pantalla.
$ strace -f /bin/ls
execve("/bin/ls", ["/bin/ls"], [/* 17 vars */]) = 0
brk(NULL) = 0x686000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f967956a000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=40661, ...}) = 0
mmap(NULL, 40661, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f9679560000
close(3) = 0
<< snip >>
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9679569000
write(1, "R2 RH\n", 7R2 RH
) = 7
close(1) = 0
munmap(0x7f9679569000, 4096) = 0
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++
$
nm
Lo que hace:enumera los símbolos de los archivos de objetos.
Si está trabajando con un binario que no se elimina, el nm El comando le proporcionará la valiosa información que se incrustó en el binario durante la compilación. nm puede ayudarlo a identificar variables y funciones del binario. Puede imaginar lo útil que sería esto si no tiene acceso al código fuente del binario que se está analizando.
Para mostrar nm , rápidamente escribiremos un pequeño programa y lo compilaremos con -g opción, y también veremos que el binario no se elimina usando el comando de archivo.
$ cat hello.c
#include <stdio.h>
int main() {
printf("Hello world!");
return 0;
}
$
$ gcc -g hello.c -o hello
$
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=3de46c8efb98bce4ad525d3328121568ba3d8a5d, not stripped
$
$ ./hello
Hello world!$
$
$ nm hello | tail
0000000000600e20 d __JCR_END__
0000000000600e20 d __JCR_LIST__
00000000004005b0 T __libc_csu_fini
0000000000400540 T __libc_csu_init
U __libc_start_main@@GLIBC_2.2.5
000000000040051d T main
U printf@@GLIBC_2.2.5
0000000000400490 t register_tm_clones
0000000000400430 T _start
0000000000601030 D __TMC_END__
$
gdb
Qué hace:El depurador GNU.
Bueno, no todo en el binario puede analizarse estáticamente. Ejecutamos algunos comandos que ejecutaban el binario, como ltrace y straza; sin embargo, el software consta de una variedad de condiciones que podrían conducir a la ejecución de varias rutas alternativas.
La única forma de analizar estas rutas es en tiempo de ejecución, al tener la capacidad de detener o pausar el programa en cualquier ubicación determinada y poder analizar la información y luego avanzar.
Ahí es donde entran los depuradores. en la imagen, y en Linux, gdb es el depurador de facto. Le ayuda a cargar un programa, establecer puntos de interrupción en lugares específicos, analizar la memoria y el registro de la CPU, y hacer mucho más. Complementa las otras herramientas mencionadas anteriormente y le permite realizar muchos más análisis de tiempo de ejecución.
Una cosa a tener en cuenta es que, una vez que carga un programa usando gdb , se le presentará su propio (gdb) oportuno. Todos los demás comandos se ejecutarán en este gdb símbolo del sistema hasta que salga.
Usaremos el programa "hola" que compilamos anteriormente y usaremos gdb para ver cómo funciona.
$ gdb -q ./hello
Reading symbols from /home/flash/hello...done.
(gdb) break main
Breakpoint 1 at 0x400521: file hello.c, line 4.
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400521 in main at hello.c:4
(gdb) run
Starting program: /home/flash/./hello
Breakpoint 1, main () at hello.c:4
4 printf("Hello world!");
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7_6.6.x86_64
(gdb) bt
#0 main () at hello.c:4
(gdb) c
Continuing.
Hello world![Inferior 1 (process 29620) exited normally]
(gdb) q
$
Conclusión
Una vez que se sienta cómodo con el uso de estas herramientas nativas de análisis binario de Linux y comprenda el resultado que proporcionan, puede pasar a herramientas de análisis binario de código abierto más avanzadas y profesionales como radare2.