Qual é o menor executável Mach-O executável possível no x86_64? O programa não pode fazer nada (nem mesmo retornar um código de retorno), mas deve ser um executável válido (deve ser executado sem erros).
Minha tentativa:
Assembler GNU ( null.s
):
.text
.globl _main
_main:
retq
Compilação e vinculação:
as -o null.o null.s
ld -e _main -macosx_version_min 10.12 -o null null.o -lSystem
Tamanho: 4248 bytes
Olhando para os valores hexadecimais, parece que há muito preenchimento zero que talvez possa ser removido, mas não sei como. Também não sei se é possível fazer o exectubale rodar sem ligar o libSystem ...
code-golf
tips
assembly
machine-code
Martin M.
fonte
fonte
Respostas:
O menor Mach-O executável deve ter pelo menos
0x1000
bytes. Por causa da limitação do XNU, o arquivo deve ter pelo menosPAGE_SIZE
. Vejaxnu-4570.1.46/bsd/kern/mach_loader.c
, por volta da linha 1600.No entanto, se não contamos esse preenchimento e contamos apenas uma carga útil significativa, o tamanho mínimo do arquivo executável no macOS é de
0xA4
bytes.Tem que começar com mach_header (ou
fat_header
/mach_header_64
, mas esses são maiores).Seu tamanho é
0x1C
bytes.magic
tem que serMH_MAGIC
.Eu vou estar usando,
CPU_TYPE_X86
pois é umx86_32
executável.filtetype
deve serMH_EXECUTE
executávelncmds
esizeofcmds
depender de comandos e deve ser válido.flags
não são tão importantes e são muito pequenas para fornecer qualquer outro valor.A seguir estão os comandos de carregamento. O cabeçalho deve estar exatamente em um mapeamento, com direitos RX - novamente, limitações do XNU.
Também precisaríamos colocar nosso código em alguns mapeamentos RX, então isso é bom.
Para isso, precisamos de um
segment_command
.Vamos olhar para a definição.
cmd
tem que serLC_SEGMENT
ecmdsize
tem que sersizeof(struct segment_command) => 0x38
.segname
o conteúdo não importa, e usaremos isso mais tarde.vmaddr
tem que ser um endereço válido (eu vou usar0x1000
),vmsize
tem que ser válido e múltiplo dePAGE_SIZE
,fileoff
tem que ser0
,filesize
tem que ser menor que o tamanho do arquivo, mas maior quemach_header
pelo menos (sizeof(header) + header.sizeofcmds
é o que eu usei).maxprot
einitprot
tem que serVM_PROT_READ | VM_PROT_EXECUTE
.maxport
geralmente também temVM_PROT_WRITE
.nsects
são 0, pois não precisamos de nenhuma seção e elas aumentam de tamanho. Eu configureiflags
para 0.Agora, precisamos executar algum código. Existem dois comandos de carregamento para isso:
entry_point_command
ethread_command
.entry_point_command
não nos convém: vejaxnu-4570.1.46/bsd/kern/mach_loader.c
, por volta da linha 1977:Portanto, usá-lo exigiria que o DYLD funcionasse, e isso significa que precisaremos de
__LINKEDIT
vaziosymtab_command
edysymtab_command
,dylinker_command
edyld_info_command
. Excesso para o arquivo "menor".Então, vamos usar
thread_command
, especificamente, umaLC_UNIXTHREAD
vez que também configura a pilha que precisaremos.cmd
vai serLC_UNIXTHREAD
,cmdsize
seria0x50
(veja abaixo).flavour
éx86_THREAD_STATE32
, e contagem éx86_THREAD_STATE32_COUNT
(0x10
).Agora o
thread_state
. Precisamos dox86_thread_state32_t
aka_STRUCT_X86_THREAD_STATE32
:Portanto, são realmente 16
uint32_t
anos que seriam carregados nos registros correspondentes antes do início do encadeamento.A adição de cabeçalho, comando de segmento e comando de thread nos fornece
0xA4
bytes.Agora, é hora de criar a carga útil.
Digamos que queremos imprimir
Hi Frand
eexit(0)
.Convenção Syscall para macOS x86_32:
Veja mais sobre syscalls no macOS aqui .
Então, sabendo disso, aqui está nossa carga útil na montagem:
Observe a linha antes da primeira
int 0x80
.segname
pode ser qualquer coisa, lembra? Para que possamos colocar nossa carga útil nela. No entanto, são apenas 16 bytes e precisamos de um pouco mais.Então, em
14
bytes, colocaremos ajmp
.Outro espaço "livre" são os registros de estado do encadeamento.
Podemos definir qualquer coisa na maioria deles e colocaremos o restante de nossa carga útil lá.
Além disso, colocamos nossa string
__eax
e__ebx
, uma vez que é mais curta do que movê-las.Assim, podemos usar
__ecx
,__edx
,__edi
para ajustar o resto da nossa carga útil. Observando a diferença entre o endereço dethread_cmd.state.__ecx
e o final desegment_cmd.segname
, calculamos que precisamos colocarjmp 0x3a
(ouEB38
) nos últimos dois bytes desegname
.Portanto, nossa carga útil montada é
53 50 31C0 89E7 6A08 57 6A01 50 B004
para a primeira parte,EB38
para jmp eCD80 6A00 B001 50 CD80
para a segunda parte.E último passo - definindo o
__eip
. Nosso arquivo é carregado em0x1000
(lembre-sevmaddr
) e a carga começa no deslocamento0x24
.Aqui está o
xxd
arquivo de resultado:Pad-lo com qualquer coisa até
0x1000
bytes, chmod + xe execute :)PS Sobre os binários x86_64 - 64 bits, é necessário ter
__PAGEZERO
(qualquer mapeamento comVM_PROT_NONE
proteção cobrindo a página em 0x0). IIRC eles [Apple] não exigiram isso no modo de 32 bits apenas porque alguns softwares herdados não o possuíam e têm medo de quebrá-lo.fonte
truncate -s 4096 foo
(com foo sendo o arquivo exectuable) para torná-lo apto0x1000
bytes e ele funciona perfeitamente :)28 bytes, pré-compilados.
Abaixo está um despejo hexadecimal formatado do binário Mach-O.
Consiste inteiramente no cabeçalho e não precisa dos dados nem dos cmds. Este é, por natureza, o menor binário Mach-O possível. Pode não funcionar corretamente em qualquer hardware concebível, mas corresponde à especificação.
Eu forneceria o arquivo real, mas ele consiste inteiramente de caracteres não imprimíveis.
fonte
(uint) 0x00000007 é "I386" e "X86" (nome dependendo de onde você está procurando na especificação XNU, mas é o arco correto) (uint) 0x0x01000007 é X86_64
Teoricamente, você pode OU qualquer valor de CPU com 0x1000000 para torná-lo uma versão de 64 bits. XNU nem sempre os considera valores discretos; por exemplo, ARM 32 e 64 são 0x0000000C e 0x0100000C, respectivamente.
Ah, caramba, aqui está a lista que acabei tendo que descobrir alguns anos atrás, observe que a maioria deles é anterior ao OS / X:
fonte