Inspector de binarios Mach-O
Lee un binario Mach-O como lo hacen otool y lipo, directamente de los bytes y por completo en tu navegador. Un archivo de macOS o iOS es o bien fino —una sola arquitectura— o bien universal: una tabla big-endian de arquitecturas en la que cada entrada apunta a una imagen completa. Se manejan los dos casos, y si el archivo es universal verás primero la tabla de arquitecturas con el desplazamiento, el tamaño y la alineación de cada slice, y luego eliges uno para leer el resto. Dentro de un slice, la cabecera da el tipo y el subtipo de CPU (arm64, arm64e, x86_64, i386, ppc), el tipo de archivo —ejecutable, biblioteca dinámica, bundle, objeto reubicable, dSYM— y los flags que importan: MH_PIE, MH_TWOLEVEL, MH_DYLDLINK, MH_ALLOW_STACK_EXECUTION y MH_NO_HEAP_EXECUTION. Los load commands aportan lo demás: cada segmento con su dirección virtual, su tamaño en memoria, su desplazamiento y su tamaño en el archivo, las secciones que contiene (__TEXT,__text o __DATA,__const), cada LC_LOAD_DYLIB y LC_LOAD_WEAK_DYLIB con sus versiones actual y compatible, las entradas LC_RPATH que el cargador sustituye por @rpath, el install name que una biblioteca publica en LC_ID_DYLIB, el UUID que empareja un binario con su dSYM, y la plataforma, el sistema mínimo y el SDK de LC_BUILD_VERSION o del antiguo LC_VERSION_MIN_MACOSX. Los tamaños de los segmentos son la respuesta a “por qué ocupa 40 MB este binario”, pero léelos con cuidado: el tamaño en memoria puede ser mucho mayor que el del archivo, porque las secciones de relleno de ceros y los cuatro gigabytes de __PAGEZERO no ocupan disco alguno. Dos cosas que no hace. No desensambla. Y no verifica la firma: un LC_CODE_SIGNATURE significa que el archivo lleva un bloque de firma, mientras que si esa firma es válida, de quién es el certificado y si el binario está notarizado solo lo responden codesign y spctl en un Mac. El orden de bytes y el tamaño de palabra salen del magic en lugar de darse por supuestos, así que un binario PowerPC de 32 bits big-endian de hace veinte años se abre igual de bien que uno arm64e, y un archivo cuya tabla de load commands promete más bytes de los que tiene se rechaza con un error propio en vez de leerse a medias.
Cómo usar
- Suelta el binario en el recuadro: una herramienta de línea de comandos sin extensión, un .dylib, un .bundle, un .o, el DWARF de un dSYM o el ejecutable de dentro de un .app.
- Si el archivo es universal, mira primero la tabla de arquitecturas —esa es la respuesta a “¿trae arm64?”— y luego pulsa una arquitectura para ver su cabecera, sus segmentos y sus bibliotecas.
- Revisa las protecciones: PIE debería ser Sí en cualquier binario moderno, y una pila ejecutable merece una explicación antes de distribuir el archivo.
- Consulta Bibliotecas enlazadas para saber qué necesitará al arrancar, Rutas de búsqueda para ver a qué directorios se expande @rpath, e Install name para la ruta que una biblioteca reclama para sí.
- Abre la lista de secciones cuando persigas tamaño: __TEXT es código, __DATA son datos escribibles, __LINKEDIT guarda las tablas de símbolos y la firma, y las secciones __DWARF de un dSYM suelen ser lo más grande del disco.
Preguntas frecuentes
- ¿Cómo sé si una app trae un slice arm64 (Apple silicon)?
- Abre el binario que está dentro del .app —Contents/MacOS/<nombre>— y mira la tabla de arquitecturas. Un archivo universal empieza con una cabecera fat big-endian que enumera cada arquitectura, su desplazamiento dentro del archivo y la alineación sobre la que se colocó su slice; lo que no aparece ahí no está en el archivo, así que una app con solo x86_64 se ejecutará bajo Rosetta y no de forma nativa. Cada slice es una imagen Mach-O completa con su propia cabecera y sus load commands, por eso un binario universal pesa aproximadamente la suma de sus partes y lipo puede adelgazarlo copiando un único slice.
- ¿Que haya firma de código significa que el binario es de fiar?
- No, y esta herramienta cuida la diferencia. LC_CODE_SIGNATURE solo dice que hay un bloque de firma en algún desplazamiento dentro de __LINKEDIT; no dice que los hashes sigan cuadrando con las páginas que cubren, que el certificado encadene hasta Apple, que no esté revocado ni que el binario esté notarizado. Comprobar eso exige la cadena de certificados y, para la notarización, los servidores de Apple: es decir, codesign --verify --deep --strict y spctl --assess en un Mac. El hardened runtime tampoco está en la cabecera, sino en los entitlements y flags de la firma, así que la cabecera por sí sola no puede decirte si está activado.
- ¿Por qué el binario pesa mucho más que el código que contiene?
- Lee la tabla de segmentos con las dos columnas de tamaño juntas. __TEXT contiene el código y los datos de solo lectura y suele mapearse como lectura-ejecución; __DATA y __DATA_CONST contienen datos escribibles; __LINKEDIT guarda la tabla de símbolos, los function starts, los fixups de dyld y la firma de código, y a menudo es lo más grande de un binario distribuido. El tamaño en memoria y el del archivo son cifras distintas a propósito: las secciones de ceros como __bss ocupan espacio de direcciones pero ningún byte en disco, y __PAGEZERO reserva cuatro gigabytes que nunca se mapearán desde el archivo. Si es universal, recuerda además que llevas todas las arquitecturas a la vez.
- ¿Qué es un install name y por qué veo @rpath por todas partes?
- Una biblioteca dinámica anota en LC_ID_DYLIB la ruta en la que espera ser encontrada, y todo programa que enlaza con ella copia esa cadena en su propio LC_LOAD_DYLIB. Si es una ruta absoluta, la biblioteca tiene que estar exactamente ahí en cada máquina. Los frameworks modernos usan en su lugar @rpath/Algo.framework/Algo y dejan la resolución al programa: las entradas LC_RPATH que aparecen aquí son los directorios que dyld prueba, en orden, en lugar de @rpath, y suelen escribirse relativas al binario con @executable_path o @loader_path para que el bundle pueda moverse. Un rpath ausente es la causa habitual del “image not found” al arrancar.
- ¿Qué significa la marca Cifrado en un binario de iOS?
- Los binarios de iOS distribuidos por la App Store llevan un LC_ENCRYPTION_INFO (o su forma de 64 bits) con cryptid igual a 1, que marca un rango de bytes —normalmente casi todo __TEXT— cifrado con una clave que aplica la tienda. El kernel descifra ese rango al cargarlo, así que en disco esa región es ilegible: strings no encuentra nada útil y un desensamblador solo ve ruido. Un binario que has compilado tú, o sacado de un IPA antes de que la tienda lo volviera a firmar, tiene cryptid 0 y está en claro. El comando existe con valor cero en muchísimos archivos, y por eso aquí se informa del cryptid y no solo de la presencia del comando.
- ¿Se sube el binario a algún sitio?
- No. El archivo se lee con la File API del navegador y lo analiza JavaScript dentro de la página. No se envía nada a ningún servidor y no existe un componente de servidor al que enviarlo, algo que aquí importa más que en la mayoría de herramientas: una compilación sin publicar o la app de un cliente no es algo que quieras entregar a una web. Puedes desconectarte de la red una vez cargada la página y todo sigue funcionando.
Herramientas relacionadas
Inspector de binarios ELF
Abre un ejecutable de Linux, un .so o un .o en tu navegador: arquitectura, bibliotecas que necesita, Build ID y si trae PIE, NX y RELRO.
Inspector PE: visor de EXE y DLL de Windows
Abre un .exe, .dll, .sys o .efi de Windows en tu navegador: arquitectura, subsistema, importaciones, exportaciones, secciones y si tiene ASLR, DEP y CFG.
Inspector de archivos .class de Java
Analiza un .class compilado en el navegador: versión del archivo y de Java, indicadores de acceso, grupo de constantes, campos, métodos y dependencias.
Inspector de archivos de fuente
Abre un .ttf, .otf o .woff y lee su nombre de familia real, el número de glifos y caracteres, la versión, el texto de licencia y los permisos de incrustación.
Inspector de archivos gzip (.gz)
Lee la cabecera de un .gz campo por campo, recorre todos sus miembros y verifica el tráiler recalculando el CRC32 y el tamaño real en tu navegador.
Inspector de flujos xz (.xz)
Lee un contenedor .xz: cabecera del flujo, cada cabecera de bloque y su cadena de filtros, el índice y el pie, con el tamaño real sin descomprimir nada.