¿Cuál es el ejecutable Mach-O ejecutable más pequeño posible en x86_64? El programa no puede hacer nada (ni siquiera devolver un código de retorno), pero debe ser un ejecutable válido (debe ejecutarse sin errores).
Mi intento:
Ensamblador GNU ( null.s):
.text
.globl _main
_main:
retq
Recopilación y vinculación:
as -o null.o null.s
ld -e _main -macosx_version_min 10.12 -o null null.o -lSystem
Tamaño: 4248 bytes
Mirando los valores hexadecimales parece que hay mucho relleno cero que tal vez se pueda eliminar, pero no sé cómo. Además, no sé si es posible ejecutar el exectubale sin vincular libSystem ...
code-golf
tips
assembly
machine-code
Martin M.
fuente
fuente

Respuestas:
El Mach-O ejecutable más pequeño debe tener al menos
0x1000bytes. Debido a la limitación de XNU, el archivo debe ser al menos dePAGE_SIZE. Verxnu-4570.1.46/bsd/kern/mach_loader.c, alrededor de la línea 1600.Sin embargo, si no contamos ese relleno, y solo contamos una carga útil significativa, entonces el tamaño mínimo de archivo ejecutable en macOS es
0xA4bytes.Tiene que comenzar con mach_header (o
fat_header/mach_header_64, pero esos son más grandes).Su tamaño es
0x1Cbytes.magictiene que serMH_MAGIC.Lo usaré
CPU_TYPE_X86ya que es unx86_32ejecutable.filtetypetiene que serMH_EXECUTEejecutablencmdsysizeofcmdsdepender de comandos, y debe ser válido.flagsno son tan importantes y son demasiado pequeños para proporcionar cualquier otro valor.Luego están los comandos de carga. El encabezado debe estar exactamente en un mapeo, con derechos de RX; nuevamente, limitaciones de XNU.
También necesitaríamos colocar nuestro código en alguna asignación de RX, por lo que está bien.
Para eso necesitamos a
segment_command.Veamos la definición.
cmdtiene que serLC_SEGMENTycmdsizetiene que sersizeof(struct segment_command) => 0x38.segnameel contenido no importa, y lo usaremos más tarde.vmaddrtiene que ser una dirección válida (usaré0x1000),vmsizetiene que ser válida y múltiplo dePAGE_SIZE,fileofftiene que ser0,filesizetiene que ser menor que el tamaño del archivo, pero mayor quemach_headeral menos (sizeof(header) + header.sizeofcmdses lo que he usado).maxprotyinitprottiene que serVM_PROT_READ | VM_PROT_EXECUTE.maxportpor lo general también tieneVM_PROT_WRITE.nsectsson 0, ya que realmente no necesitamos ninguna sección y se sumarán al tamaño. Me puseflagsa 0.Ahora, necesitamos ejecutar algo de código. Hay dos comandos de carga para eso:
entry_point_commandythread_command.entry_point_commandno nos conviene: verxnu-4570.1.46/bsd/kern/mach_loader.c, alrededor de la línea 1977:Por lo tanto, usarlo requeriría que DYLD funcione, y eso significa que necesitaremos
__LINKEDIT, vaciarsymtab_commandydysymtab_command,dylinker_commandydyld_info_command. Exageración para el archivo "más pequeño".Entonces, usaremos
thread_command, específicamenteLC_UNIXTHREADya que también configura la pila que necesitaremos.cmdva a serLC_UNIXTHREAD,cmdsizesería0x50(ver más abajo).flavouresx86_THREAD_STATE32, y cuenta esx86_THREAD_STATE32_COUNT(0x10).Ahora el
thread_state. Necesitamosx86_thread_state32_taka_STRUCT_X86_THREAD_STATE32:Por lo tanto, son los 16
uint32_tlos que se cargarán en los registros correspondientes antes de que se inicie el subproceso.Agregar encabezado, comando de segmento y comando de subproceso nos da
0xA4bytes.Ahora, es hora de elaborar la carga útil.
Digamos que queremos que se imprima
Hi Frandyexit(0).Convención de syscall para macOS x86_32:
Vea más sobre syscalls en macOS aquí .
Entonces, sabiendo eso, aquí está nuestra carga útil en el ensamblaje:
Observe la línea antes del primero
int 0x80.segnamepuede ser cualquier cosa, ¿recuerdas? Para que podamos poner nuestra carga útil en él. Sin embargo, son solo 16 bytes, y necesitamos un poco más.Entonces, en
14bytes colocaremos unjmp.Otro espacio "libre" son los registros de estado de hilo.
Podemos configurar cualquier cosa en la mayoría de ellos, y pondremos el resto de nuestra carga allí.
Además, colocamos nuestra cadena
__eaxy__ebx, dado que es más corta que moverlos.Por lo tanto, podemos utilizar
__ecx,__edx,__edipara encajar el resto de nuestra carga útil. Al observar la diferencia entre la dirección dethread_cmd.state.__ecxy el final desegment_cmd.segname, calculamos que necesitamos ponerjmp 0x3a(oEB38) en los últimos dos bytes desegname.Entonces, nuestra carga útil ensamblada es
53 50 31C0 89E7 6A08 57 6A01 50 B004para la primera parte,EB38para jmp yCD80 6A00 B001 50 CD80para la segunda parte.Y último paso: configurar el
__eip. Nuestro archivo se carga en0x1000(recordarvmaddr), y la carga comienza en el desplazamiento0x24.Aquí está el
xxdarchivo de resultados:Rellenar con cualquier cosa hasta
0x1000bytes, chmod + x y ejecutar :)PS About x86_64: se requieren binarios de 64 bits
__PAGEZERO(cualquier mapeo conVM_PROT_NONEpágina de cobertura de protección en 0x0). IIRC ellos [Apple] no lo hicieron necesario en el modo de 32 bits solo porque algunos software heredados no lo tenían y tienen miedo de romperlo.fuente
truncate -s 4096 foo(siendo foo el archivo ejecutable) para que se ajuste a0x1000bytes y funcione perfectamente :)28 bytes, precompilados.
A continuación se muestra un volcado hexadecimal formateado del binario Mach-O.
Consiste completamente en el encabezado y no necesita los datos ni los cmds. Este es, por naturaleza, el binario Mach-O más pequeño posible. Es posible que no se ejecute correctamente en cualquier hardware concebible, pero coincide con la especificación.
Proporcionaría el archivo real, pero consta completamente de caracteres no imprimibles.
fuente
(uint) 0x00000007 es "I386" y "X86" (el nombre depende de en qué parte de la especificación XNU está buscando, pero es el arco correcto) (uint) 0x0x01000007 es X86_64
Teóricamente, puede O cualquier valor de CPU con 0x1000000 para convertirlo en una versión de 64 bits. XNU parece no siempre considerarlos valores discretos; por ejemplo, ARM 32 y 64 son 0x0000000C y 0x0100000C, respectivamente.
Ah, diablos, aquí está la lista que terminé teniendo que descubrir hace unos años, tenga en cuenta que la mayoría de estos son anteriores a OS / X:
fuente