Cómo echar un vistazo al interior de los archivos binarios desde la línea de comandos de Linux
Publicado: 2022-01-29 ¿Tienes un archivo misterioso? El comando de file
de Linux le dirá rápidamente qué tipo de archivo es. Sin embargo, si se trata de un archivo binario, puede obtener más información al respecto. file
tiene una gran cantidad de compañeros estables que lo ayudarán a analizarlo. Le mostraremos cómo usar algunas de estas herramientas.
Identificación de tipos de archivos
Los archivos suelen tener características que permiten que los paquetes de software identifiquen qué tipo de archivo es, así como qué datos representan. No tendría sentido tratar de abrir un archivo PNG en un reproductor de música MP3, por lo que es útil y pragmático que un archivo lleve consigo algún tipo de identificación.
Esto podría ser unos pocos bytes de firma al principio del archivo. Esto permite que un archivo sea explícito sobre su formato y contenido. A veces, el tipo de archivo se deduce de un aspecto distintivo de la organización interna de los datos en sí, conocida como arquitectura de archivo.
Algunos sistemas operativos, como Windows, se guían completamente por la extensión de un archivo. Puede llamarlo crédulo o confiado, pero Windows asume que cualquier archivo con la extensión DOCX es realmente un archivo de procesamiento de texto DOCX. Linux no es así, como pronto verás. Quiere pruebas y mira dentro del archivo para encontrarlas.
Las herramientas descritas aquí ya estaban instaladas en las distribuciones Manjaro 20, Fedora 21 y Ubuntu 20.04 que usamos para investigar este artículo. Comencemos nuestra investigación usando el comando de file
.
Usando el comando de archivo
Tenemos una colección de diferentes tipos de archivos en nuestro directorio actual. Son una mezcla de documentos, código fuente, archivos ejecutables y de texto.
El comando ls
nos mostrará qué hay en el directorio, y la -hl
(tamaños legibles por humanos, lista larga) nos mostrará el tamaño de cada archivo:
ls-hl
Probemos el file
en algunos de estos y veamos qué obtenemos:
archivo build_instructions.odt
archivo build_instructions.pdf
archivo COBOL_Informe_Apr60.djvu
Los tres formatos de archivo están correctamente identificados. Siempre que sea posible, file
nos da un poco más de información. Se informa que el archivo PDF está en el formato de la versión 1.5.
Incluso si cambiamos el nombre del archivo ODT para que tenga una extensión con el valor arbitrario de XYZ, el archivo sigue identificándose correctamente, tanto en el explorador de archivos de Files
como en la línea de comandos usando file
.
Dentro del explorador de archivos de Files
, se le asigna el ícono correcto. En la línea de comando, file
ignora la extensión y busca dentro del archivo para determinar su tipo:
archivo build_instructions.xyz
El uso de file
en medios, como archivos de imágenes y música, generalmente brinda información sobre su formato, codificación, resolución, etc.:
archivo captura de pantalla.png
captura de pantalla del archivo.jpg
archivo Pachelbel_Canon_In_D.mp3
Curiosamente, incluso con archivos de texto sin formato, file
no juzga el archivo por su extensión. Por ejemplo, si tiene un archivo con la extensión ".c", que contiene texto sin formato estándar pero no código fuente, file
no lo confunde con un archivo de código fuente C genuino:
función de archivo + encabezados.h
archivo makefile
archivo hola.c
El file
identifica correctamente el archivo de encabezado (".h") como parte de una colección de archivos de código fuente C y sabe que el archivo MAKE es un script.
Uso de archivos con archivos binarios
Los archivos binarios son más una "caja negra" que otros. Los archivos de imagen se pueden ver, los archivos de sonido se pueden reproducir y los archivos de documentos se pueden abrir con el paquete de software apropiado. Los archivos binarios, sin embargo, son más un desafío.
Por ejemplo, los archivos "hola" y "wd" son ejecutables binarios. son programas El archivo llamado "wd.o" es un archivo de objeto. Cuando un compilador compila el código fuente, se crean uno o más archivos de objetos. Estos contienen el código de máquina que la computadora finalmente ejecutará cuando se ejecute el programa terminado, junto con información para el enlazador. El enlazador comprueba cada archivo de objeto en busca de llamadas de función a las bibliotecas. Los vincula a cualquier biblioteca que utilice el programa. El resultado de este proceso es un archivo ejecutable.
El archivo “watch.exe” es un ejecutable binario que ha sido compilado de forma cruzada para ejecutarse en Windows:
archivo wd
archivo wd.o
archivo hola
archivo watch.exe
Tomando el último primero, file
nos dice que el archivo "watch.exe" es un programa de consola ejecutable PE32 + para la familia de procesadores x86 en Microsoft Windows. PE significa formato ejecutable portátil, que tiene versiones de 32 y 64 bits. El PE32 es la versión de 32 bits y el PE32+ es la versión de 64 bits.
Los otros tres archivos se identifican como archivos de formato ejecutable y vinculable (ELF). Este es un estándar para archivos ejecutables y archivos de objetos compartidos, como bibliotecas. Echaremos un vistazo al formato de encabezado ELF en breve.
Lo que podría llamar su atención es que los dos ejecutables ("wd" y "hello") se identifican como objetos compartidos de Linux Standard Base (LSB), y el archivo de objeto "wd.o" se identifica como un LSB reubicable. La palabra ejecutable es obvia en su ausencia.
Los archivos de objetos son reubicables, lo que significa que el código que contienen se puede cargar en la memoria en cualquier ubicación. Los ejecutables se enumeran como objetos compartidos porque el enlazador los creó a partir de los archivos de objeto de tal manera que heredan esta capacidad.
Esto permite que el sistema de aleatorización del diseño del espacio de direcciones (ASMR) cargue los ejecutables en la memoria en las direcciones de su elección. Los ejecutables estándar tienen una dirección de carga codificada en sus encabezados, que dictan dónde se cargan en la memoria.
ASMR es una técnica de seguridad. Cargar ejecutables en la memoria en direcciones predecibles los hace susceptibles a ataques. Esto se debe a que los atacantes siempre conocerán sus puntos de entrada y las ubicaciones de sus funciones. Los ejecutables independientes de posición (PIE) colocados en una dirección aleatoria superan esta susceptibilidad.
Si compilamos nuestro programa con el compilador gcc
y proporcionamos la opción -no-pie
, generaremos un ejecutable convencional.
La opción -o
(archivo de salida) nos permite proporcionar un nombre para nuestro ejecutable:
gcc -o hola -no-pie hola.c
Usaremos file
en el nuevo ejecutable y veremos qué ha cambiado:
archivo hola
El tamaño del ejecutable es el mismo que antes (17 KB):
ls -hl hola
El binario ahora se identifica como un ejecutable estándar. Estamos haciendo esto solo con fines de demostración. Si compila aplicaciones de esta manera, perderá todas las ventajas del ASMR.
¿Por qué un ejecutable es tan grande?
Nuestro programa hello
de ejemplo tiene 17 KB, por lo que difícilmente podría llamarse grande, pero todo es relativo. El código fuente es de 120 bytes:
gato hola.c
¿Qué está aumentando el binario si todo lo que hace es imprimir una cadena en la ventana de la terminal? Sabemos que hay un encabezado ELF, pero solo tiene 64 bytes para un binario de 64 bits. Claramente, debe ser otra cosa:
ls -hl hola
Escaneemos el binario con el comando strings
como un simple primer paso para descubrir qué hay dentro. Lo canalizaremos en less
:
cuerdas hola | menos
Hay muchas cadenas dentro del binario, además de "¡Hola, mundo geek!" de nuestro código fuente. La mayoría de ellos son etiquetas para regiones dentro del binario y los nombres y la información de enlace de los objetos compartidos. Estos incluyen las bibliotecas y las funciones dentro de esas bibliotecas, de las que depende el binario.
El comando ldd
nos muestra las dependencias de objetos compartidos de un binario:
hola
Hay tres entradas en la salida, y dos de ellas incluyen una ruta de directorio (la primera no):
- linux-vdso.so: Virtual Dynamic Shared Object (VDSO) es un mecanismo del núcleo que permite acceder a un conjunto de rutinas del espacio del núcleo mediante un binario del espacio del usuario. Esto evita la sobrecarga de un cambio de contexto desde el modo kernel de usuario. Los objetos compartidos de VDSO se adhieren al formato ejecutable y vinculable (ELF), lo que les permite vincularse dinámicamente al binario en tiempo de ejecución. El VDSO se asigna dinámicamente y aprovecha ASMR. La capacidad VDSO la proporciona la biblioteca GNU C estándar si el kernel es compatible con el esquema ASMR.
- libc.so.6: El objeto compartido de la biblioteca GNU C.
- /lib64/ld-linux-x86-64.so.2: Este es el enlazador dinámico que el binario quiere usar. El enlazador dinámico interroga al binario para descubrir qué dependencias tiene. Lanza esos objetos compartidos a la memoria. Prepara el binario para ejecutarse y poder encontrar y acceder a las dependencias en la memoria. Luego, lanza el programa.
El encabezado ELF
Podemos examinar y decodificar el encabezado ELF usando la utilidad readelf
y la opción -h
(encabezado de archivo):
readelf -h hola
El encabezado se interpreta para nosotros.
El primer byte de todos los binarios ELF se establece en el valor hexadecimal 0x7F. Los siguientes tres bytes se establecen en 0x45, 0x4C y 0x46. El primer byte es una bandera que identifica el archivo como un binario ELF. Para dejar esto muy claro, los siguientes tres bytes deletrean "ELF" en ASCII:
- Clase: indica si el binario es un ejecutable de 32 o 64 bits (1=32, 2=64).
- Datos: Indica el endianness en uso. La codificación Endian define la forma en que se almacenan los números multibyte. En la codificación big-endian, un número se almacena con sus bits más significativos primero. En la codificación little-endian, el número se almacena primero con sus bits menos significativos.
- Versión: La versión de ELF (actualmente, es 1).
- OS/ABI: Representa el tipo de interfaz binaria de aplicación en uso. Esto define la interfaz entre dos módulos binarios, como un programa y una biblioteca compartida.
- Versión ABI: La versión de la ABI.
- Tipo: El tipo de binario ELF. Los valores comunes son
ET_REL
para un recurso reubicable (como un archivo de objeto),ET_EXEC
para un ejecutable compilado con el indicador-no-pie
yET_DYN
para un ejecutable compatible con ASMR. - Máquina: La arquitectura del conjunto de instrucciones. Esto indica la plataforma de destino para la que se creó el binario.
- Versión: siempre establecido en 1, para esta versión de ELF.
- Dirección del punto de entrada: la dirección de memoria dentro del binario en el que comienza la ejecución.
Las otras entradas son tamaños y números de regiones y secciones dentro del binario para que se puedan calcular sus ubicaciones.
Un vistazo rápido a los primeros ocho bytes del binario con hexdump
mostrará el byte de firma y la cadena "ELF" en los primeros cuatro bytes del archivo. La opción -C
(canónica) nos da la representación ASCII de los bytes junto con sus valores hexadecimales, y la opción -n
(número) nos permite especificar cuántos bytes queremos ver:
hexdump -C -n 8 hola
objdump y la vista granular
Si desea ver los detalles esenciales, puede usar el comando objdump
con la opción -d
(desensamblar):
objdump -d hola | menos
Esto desensambla el código de máquina ejecutable y lo muestra en bytes hexadecimales junto con el equivalente en lenguaje ensamblador. La ubicación de la dirección del primer bye en cada línea se muestra en el extremo izquierdo.
Esto solo es útil si puede leer lenguaje ensamblador o si tiene curiosidad por lo que sucede detrás de la cortina. Hay una gran cantidad de salida, por lo que la canalizamos en less
.
Compilar y enlazar
Hay muchas formas de compilar un binario. Por ejemplo, el desarrollador elige si incluir información de depuración. La forma en que se vincula el binario también influye en su contenido y tamaño. Si las referencias binarias comparten objetos como dependencias externas, será más pequeño que uno al que las dependencias se vinculan estáticamente.
La mayoría de los desarrolladores ya conocen los comandos que hemos cubierto aquí. Para otros, sin embargo, ofrecen algunas formas fáciles de hurgar y ver qué hay dentro de la caja negra binaria.