¿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
0x1000
bytes. 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
0xA4
bytes.Tiene que comenzar con mach_header (o
fat_header
/mach_header_64
, pero esos son más grandes).Su tamaño es
0x1C
bytes.magic
tiene que serMH_MAGIC
.Lo usaré
CPU_TYPE_X86
ya que es unx86_32
ejecutable.filtetype
tiene que serMH_EXECUTE
ejecutablencmds
ysizeofcmds
depender de comandos, y debe ser válido.flags
no 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.
cmd
tiene que serLC_SEGMENT
ycmdsize
tiene que sersizeof(struct segment_command) => 0x38
.segname
el contenido no importa, y lo usaremos más tarde.vmaddr
tiene que ser una dirección válida (usaré0x1000
),vmsize
tiene que ser válida y múltiplo dePAGE_SIZE
,fileoff
tiene que ser0
,filesize
tiene que ser menor que el tamaño del archivo, pero mayor quemach_header
al menos (sizeof(header) + header.sizeofcmds
es lo que he usado).maxprot
yinitprot
tiene que serVM_PROT_READ | VM_PROT_EXECUTE
.maxport
por lo general también tieneVM_PROT_WRITE
.nsects
son 0, ya que realmente no necesitamos ninguna sección y se sumarán al tamaño. Me puseflags
a 0.Ahora, necesitamos ejecutar algo de código. Hay dos comandos de carga para eso:
entry_point_command
ythread_command
.entry_point_command
no 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_command
ydysymtab_command
,dylinker_command
ydyld_info_command
. Exageración para el archivo "más pequeño".Entonces, usaremos
thread_command
, específicamenteLC_UNIXTHREAD
ya que también configura la pila que necesitaremos.cmd
va a serLC_UNIXTHREAD
,cmdsize
sería0x50
(ver más abajo).flavour
esx86_THREAD_STATE32
, y cuenta esx86_THREAD_STATE32_COUNT
(0x10
).Ahora el
thread_state
. Necesitamosx86_thread_state32_t
aka_STRUCT_X86_THREAD_STATE32
:Por lo tanto, son los 16
uint32_t
los 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
0xA4
bytes.Ahora, es hora de elaborar la carga útil.
Digamos que queremos que se imprima
Hi Frand
yexit(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
.segname
puede 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
14
bytes 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
__eax
y__ebx
, dado que es más corta que moverlos.Por lo tanto, podemos utilizar
__ecx
,__edx
,__edi
para encajar el resto de nuestra carga útil. Al observar la diferencia entre la dirección dethread_cmd.state.__ecx
y 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 B004
para la primera parte,EB38
para jmp yCD80 6A00 B001 50 CD80
para 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
xxd
archivo de resultados:Rellenar con cualquier cosa hasta
0x1000
bytes, chmod + x y ejecutar :)PS About x86_64: se requieren binarios de 64 bits
__PAGEZERO
(cualquier mapeo conVM_PROT_NONE
pá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 a0x1000
bytes 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