Cómo echar un vistazo a los archivos binarios desde la línea de comandos de Linux

Contenidos

Un terminal Linux estilizado con líneas de texto verde en una computadora portátil.

¿Tiene un archivo misterioso? El Linux file El comando le dirá rápidamente qué tipo de archivo es. A pesar de esto, si se trata de un archivo binario, puede obtener más información al respecto. file tiene toda una serie de compañeros de cuadra que te 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 posibilitan a los paquetes de software identificar qué tipo de archivo es, así como qué representan los datos que contiene. No tendría sentido intentar 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.

Estos pueden ser unos pocos bytes de firma al principio del archivo. Esto posibilita que un archivo sea explícito sobre su formato y contenido. A veces, el tipo de archivo se infiere de un aspecto distintivo de la organización interna de los datos en sí, conocido como arquitectura de archivo.

Algunos sistemas operativos, como Windows, se guían absolutamente 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 verdaderamente un archivo de procesamiento de texto DOCX. Linux no es así, como pronto verá. Quiere una prueba y busca dentro del archivo para encontrarla.

Las herramientas descritas aquí ya estaban instaladas en las distribuciones Manjaro 20, Fedora 21 y Ubuntu 20.04 que usamos para investigar este post. Comencemos nuestra investigación usando los file mando.

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, ejecutables y archivos de texto.

los ls comando nos mostrará lo que hay en el directorio, y el -hl La opción (tamaños legibles por humanos, lista larga) nos mostrará el tamaño de cada archivo:

ls -hl

Intentemos file en algunos de estos y veamos lo que obtenemos:

file build_instructions.odt
file build_instructions.pdf
file COBOL_Report_Apr60.djvu

Los tres formatos de archivo están correctamente identificados. Donde sea factible, 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.

Inclusive si cambiamos el nombre del archivo ODT para que tenga una extensión con el valor arbitrario de XYZ, el archivo aún se identifica correctamente, tanto dentro del Files explorador de archivos y en la línea de comando usando file.

Archivo de OpenDocument correctamente identificado dentro del explorador de archivos de archivos, aunque su extensión es XYZ.

Dentro de Files explorador de archivos, se le asigna el icono correcto. En la línea de comando, file ignora la extensión y busca dentro del archivo para establecer su tipo:

file build_instructions.xyz

Usando file en los medios, como archivos de imagen y música, de forma general proporciona información sobre su formato, codificación, resolución, etc.

file screenshot.png
file screenshot.jpg
file Pachelbel_Canon_In_D.mp3

Curiosamente, inclusive con archivos de texto sin formato, file no juzga el archivo por su extensión. A modo de 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 una C genuina archivo de código fuente:

file function+headers.h
file makefile
file hello.c

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.

Usando archivo 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. A pesar de esto, los archivos binarios son un desafío mayor.

A modo de ejemplo, los archivos «hello» 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 objeto. Estos contienen el código de máquina que la computadora eventualmente ejecutará cuando se ejecute el programa terminado, junto con información para el enlazador. El vinculador comprueba cada archivo de objeto en busca de llamadas a funciones a las bibliotecas. Los vincula a las bibliotecas que utiliza el programa. El resultado de este procedimiento es un archivo ejecutable.

El archivo «watch.exe» es un ejecutable binario que ha sido compilado de forma cruzada para ejecutarse en Windows:

file wd
file wd.o
file hello
file 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 están todos identificados como Formato ejecutable y enlazable (ELF) archivos. Este es un estándar para archivos ejecutables y archivos de objetos compartidos, como bibliotecas. En breve veremos el formato de encabezado ELF.

Lo que podría llamar su atención es que los dos ejecutables («wd» y «hola») se identifican como Base estándar de Linux (LSB) objetos compartidos, 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 debido a que han sido creados por el vinculador a partir de los archivos de objeto de tal manera que heredan esta capacidad.

Esto posibilita Aleatorización del diseño del espacio de direcciones (ASMR) para cargar los ejecutables en la memoria en las direcciones de su elección. Los ejecutables estándar disponen una dirección de carga codificada en sus encabezados, que dicta dónde se cargan en la memoria.

ASMR es una técnica de seguridad. La carga de 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 la ubicación de sus funciones. Ejecutables independientes de posición (PIE) colocado en una dirección aleatoria supera esta susceptibilidad.

Si nosotros compila nuestro programa con el gcc compilador y proporcionar el -no-pie opción, generaremos un ejecutable convencional.

los -o La opción (archivo de salida) nos posibilita proporcionar un nombre para nuestro ejecutable:

gcc -o hello -no-pie hello.c

Usaremos file en el nuevo ejecutable y vea qué ha cambiado:

file hello

El tamaño del ejecutable es el mismo que antes (17 KB):

ls -hl hello

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 ejemplo hello El programa tiene 17 KB, por lo que difícilmente podría llamarse grande, pero todo es relativo. El código fuente es de 120 bytes:

cat hello.c

¿Qué es lo que aumenta el volumen del 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 de longitud para un binario de 64 bits. Claramente, debe ser otra cosa:

ls -hl hello

Vamos escanear el binario con el strings comando como un simple primer paso para descubrir lo que contiene. Lo canalizaremos en less:

strings hello | less

Hay muchas cadenas dentro del binario, al mismo tiempo del «¡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 link de objetos compartidos. Estos incluyen las bibliotecas y funciones dentro de esas bibliotecas, de las que depende el binario.

los ldd mando nos muestra las dependencias de objetos compartidos de un binario:

ldd hello

Hay tres entradas en la salida y dos de ellas incluyen una ruta de directorio (la primera no):

  • linux-vdso.so: Objeto compartido dinámico virtual (VDSO) es un mecanismo de kernel que posibilita que un binario de espacio de usuario acceda a un conjunto de rutinas de espacio de kernel. Esta evita la sobrecarga de un cambio de contexto desde el modo de kernel de usuario. Los objetos compartidos de VDSO se adhieren al formato de formato ejecutable y enlazable (ELF), lo que les posibilita vincularse dinámicamente al binario en tiempo de ejecución. El VDSO se asigna dinámicamente y aprovecha ASMR. La capacidad VDSO es proporcionada por el estándar Biblioteca GNU C si el kernel admite el esquema ASMR.
  • libc.so.6: los Biblioteca GNU C objeto compartido.
  • /lib64/ld-linux-x86-64.so.2: Este es el enlazador dinámico que el binario quiere utilizar. 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 hallar y entrar a las dependencias en la memoria. Después, lanza el programa.

El encabezado ELF

Podemos examinar y decodificar el encabezado ELF usando el readelf utilidad y la -h (encabezado de archivo) opción:

readelf -h hello

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 que esto sea 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 endianidad en uso. La codificación endian establece 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 con los bits menos significativos primero.
  • Versión: La versión de ELF (en este momento, es 1).
  • OS / ABI: Representa el tipo de interfaz binaria de la aplicación en uso. Esto establece la interfaz entre dos módulos binarios, como un programa y una biblioteca compartida.
  • Versión ABI: La versión del ABI.
  • Escribe: 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 -no-pie bandera, y ET_DYN para un ejecutable compatible con ASMR.
  • Máquina: los set de instrucciones arquitectura. Esto indica la plataforma de destino para la que se creó el binario.
  • Versión: Siempre configurado en 1, para esta versión de ELF.
  • Dirección del punto de entrada: La dirección de memoria dentro del binario en la que comienza la ejecución.

Las otros posts 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. los -C (canónica) nos da la representación ASCII de los bytes junto con sus valores hexadecimales, y la -n La opción (número) nos posibilita especificar cuántos bytes queremos ver:

hexdump -C -n 8 hello

objdump y la vista granular

Si desea ver el meollo de la cuestión, puede usar el objdumpcomando con el -d (desmontar) opción:

objdump -d hello | less

Esto desensambla el código de máquina ejecutable y lo muestra en bytes hexadecimales junto con el semejante 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 el lenguaje ensamblador o si tiene curiosidad sobre lo que sucede detrás de la cortina. Hay una gran cantidad de resultados, por lo tanto lo canalizamos a less.

Compilar y vincular

Hay muchas alternativas para compilar un binario. A modo de ejemplo, el desarrollador elige si incluir información de depuración. La forma en que se vincula el binario además 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, a pesar de esto, ofrecen algunas formas fáciles de hurgar y ver qué hay dentro de la caja negra binaria.

Suscribite a nuestro Newsletter

No te enviaremos correo SPAM. Lo odiamos tanto como tú.