Solução 1: C (Mac OS X x86_64), 109 bytes
A fonte do golf_sol1.c
main[]={142510920,2336753547,3505849471,284148040,2370322315,2314740852,1351437506,1208291319,914962059,195};
O programa acima precisa ser compilado com acesso de execução no segmento __DATA.
clang golf_sol1.c -o golf_sol1 -Xlinker -segprot -Xlinker __DATA -Xlinker rwx -Xlinker rwx
Em seguida, para executar o programa, execute o seguinte:
./golf_sol1 $(ruby -e 'puts "\xf5\xff\xff\xfe\xff\xff\x44\x82\x57\x7d\xff\x7f"')
Resultados:
Infelizmente, o Valgrind não observa a memória alocada nas chamadas do sistema, portanto, não consigo mostrar um vazamento detectado.
No entanto, podemos ver o vmmap para ver a grande parte da memória alocada (metadados MALLOC).
VIRTUAL REGION
REGION TYPE SIZE COUNT (non-coalesced)
=========== ======= =======
Kernel Alloc Once 4K 2
MALLOC guard page 16K 4
MALLOC metadata 16.2M 7
MALLOC_SMALL 8192K 2 see MALLOC ZONE table below
MALLOC_TINY 1024K 2 see MALLOC ZONE table below
STACK GUARD 56.0M 2
Stack 8192K 3
VM_ALLOCATE (reserved) 520K 3 reserved VM address space (unallocated)
__DATA 684K 42
__LINKEDIT 70.8M 4
__TEXT 5960K 44
shared memory 8K 3
=========== ======= =======
TOTAL 167.0M 106
TOTAL, minus reserved VM space 166.5M 106
Explicação
Então, acho que preciso descrever o que realmente está acontecendo aqui, antes de avançar para a solução aprimorada.
Esta função principal está abusando da declaração de tipo ausente de C (portanto, o padrão é int, sem que seja necessário desperdiçar caracteres para escrevê-la), bem como o funcionamento dos símbolos. O vinculador se preocupa apenas com encontrar um símbolo chamado main
para chamar. Então, aqui estamos fazendo principal uma matriz de int que estamos inicializando com nosso código de shell que será executado. Por isso, main não será adicionado ao segmento __TEXT, mas ao segmento __DATA, razão pela qual precisamos compilar o programa com um segmento __DATA executável.
O shellcode encontrado em main é o seguinte:
movq 8(%rsi), %rdi
movl (%rdi), %eax
movq 4(%rdi), %rdi
notl %eax
shrq $16, %rdi
movl (%rdi), %edi
leaq -0x8(%rsp), %rsi
movl %eax, %edx
leaq -9(%rax), %r10
syscall
movq (%rsi), %rsi
movl %esi, (%rsi)
ret
O que isso está fazendo é chamar a função syscall para alocar uma página de memória (o syscall mach_vm_allocate usa internamente). RAX deve ser igual a 0x100000a (informa ao syscall qual função queremos), enquanto o RDI mantém o destino da alocação (no nosso caso, queremos que seja mach_task_self ()), o RSI deve manter o endereço para gravar o ponteiro na memória recém-criada (portanto, estamos apenas apontando para uma seção da pilha), RDX mantém o tamanho da alocação (estamos passando RAX ou 0x100000a apenas para economizar em bytes), R10 mantém os sinalizadores (estamos indicando que pode seja alocado em qualquer lugar).
Agora não é óbvio de onde RAX e RDI estão obtendo seus valores. Sabemos que o RAX precisa ser 0x100000a e o RDI precisa ser o valor que mach_task_self () retorna. Felizmente, mach_task_self () é na verdade uma macro para uma variável (mach_task_self_), que está no mesmo endereço de memória todas as vezes (no entanto, deve mudar na reinicialização). Na minha instância específica, mach_task_self_ está localizado em 0x00007fff7d578244. Portanto, para reduzir as instruções, passaremos esses dados do argv. É por isso que rodamos o programa com esta expressão$(ruby -e 'puts "\xf5\xff\xff\xfe\xff\xff\x44\x82\x57\x7d\xff\x7f"')
para o primeiro argumento. A string é os dois valores combinados, em que o valor RAX (0x100000a) é de apenas 32 bits e teve um complemento aplicado a ela (portanto, não há bytes nulos; apenas NÃO é o valor para obter o original), o próximo valor é o RDI (0x00007fff7d578244) que foi deslocado para a esquerda com 2 bytes extras de lixo eletrônico adicionados ao final (novamente para excluir os bytes nulos, basta voltar à direita para voltar ao original).
Após o syscall, estamos gravando em nossa memória recém-alocada. A razão para isso é porque a memória alocada usando mach_vm_allocate (ou este syscall) é na verdade páginas da VM e não é automaticamente paginada na memória. Em vez disso, eles são reservados até que os dados sejam gravados neles e, em seguida, essas páginas são mapeadas na memória. Não tinha certeza se atenderia aos requisitos se fosse reservado apenas.
Para a próxima solução, aproveitaremos o fato de que nosso código de shell não possui bytes nulos e, portanto, podemos movê-lo para fora do código de nosso programa para reduzir o tamanho.
Solução 2: C (Mac OS X x86_64), 44 bytes
A fonte para golf_sol2.c
main[]={141986632,10937,1032669184,2,42227};
O programa acima precisa ser compilado com acesso de execução no segmento __DATA.
clang golf_sol2.c -o golf_sol2 -Xlinker -segprot -Xlinker __DATA -Xlinker rwx -Xlinker rwx
Em seguida, para executar o programa, execute o seguinte:
./golf_sol2 $(ruby -e 'puts "\xb8\xf5\xff\xff\xfe\xf7\xd0\x48\xbf\xff\xff\x44\x82\x57\x7d\xff\x7f\x48\xc1\xef\x10\x8b\x3f\x48\x8d\x74\x24\xf8\x89\xc2\x4c\x8d\x50\xf7\x0f\x05\x48\x8b\x36\x89\x36\xc3"')
O resultado deve ser o mesmo de antes, pois estamos fazendo uma alocação do mesmo tamanho.
Explicação
Segue o mesmo conceito da solução 1, com a exceção de que movemos a parte do nosso código de vazamento para fora do programa.
O shellcode encontrado em main agora é o seguinte:
movq 8(%rsi), %rsi
movl $42, %ecx
leaq 2(%rip), %rdi
rep movsb (%rsi), (%rdi)
Isso basicamente copia o código de shell que passamos no argv para estar após esse código (então, depois de copiado, ele executará o código de shell inserido). O que funciona a nosso favor é que o segmento __DATA terá pelo menos um tamanho de página; portanto, mesmo que nosso código não seja tão grande, ainda podemos escrever com mais segurança. A desvantagem é a solução ideal aqui, nem precisaria da cópia; em vez disso, basta chamar e executar o código de shell diretamente no argv. Mas, infelizmente, essa memória não tem direitos de execução. Poderíamos alterar os direitos dessa memória, no entanto, exigiria mais código do que simplesmente copiá-lo. Uma estratégia alternativa seria alterar os direitos de um programa externo (mas mais sobre isso posteriormente).
O código de shell que passamos para o argv é o seguinte:
movl $0xfefffff5, %eax
notl %eax
movq $0x7fff7d578244ffff, %rdi
shrq $16, %rdi
movl (%rdi), %edi
leaq -0x8(%rsp), %rsi
movl %eax, %edx
leaq -9(%rax), %r10
syscall
movq (%rsi), %rsi
movl %esi, (%rsi)
ret
É o mesmo que o nosso código anterior, a única diferença é que estamos incluindo os valores para EAX e RDI diretamente.
Solução possível 1: C (Mac OS X x86_64), 11 bytes
A idéia de modificar o programa externamente, nos dá a possível solução de mover o vazador para um programa externo. Onde nosso programa atual (envio) é apenas um programa fictício, e o programa vazador alocará um pouco de memória em nosso programa de destino. Agora eu não tinha certeza se isso se enquadrava nas regras para esse desafio, mas mesmo assim é compartilhado.
Portanto, se usássemos mach_vm_allocate em um programa externo com a meta definida para o nosso programa de desafios, isso poderia significar que nosso programa de desafios precisaria ser algo como:
main=65259;
Onde esse código de shell é simplesmente um pequeno salto para si mesmo (salto / loop infinito), o programa permanece aberto e podemos fazer referência a um programa externo.
Solução possível 2: C (Mac OS X x86_64), 8 bytes
Curiosamente, quando eu estava olhando para a saída do valgrind, vi que, pelo menos de acordo com o valgrind, o dyld vazava memória. Tão eficazmente que todo programa está vazando alguma memória. Sendo esse o caso, poderíamos realmente criar um programa que não faz nada (simplesmente sai) e que realmente estará vazando memória.
Fonte:
main(){}
==55263== LEAK SUMMARY:
==55263== definitely lost: 696 bytes in 17 blocks
==55263== indirectly lost: 17,722 bytes in 128 blocks
==55263== possibly lost: 0 bytes in 0 blocks
==55263== still reachable: 0 bytes in 0 blocks
==55263== suppressed: 16,316 bytes in 272 blocks