Solución 1: C (Mac OS X x86_64), 109 bytes
La fuente de golf_sol1.c
main[]={142510920,2336753547,3505849471,284148040,2370322315,2314740852,1351437506,1208291319,914962059,195};
El programa anterior debe compilarse con acceso de ejecución en el segmento __DATA.
clang golf_sol1.c -o golf_sol1 -Xlinker -segprot -Xlinker __DATA -Xlinker rwx -Xlinker rwx
Luego, para ejecutar el programa, ejecute lo siguiente:
./golf_sol1 $(ruby -e 'puts "\xf5\xff\xff\xfe\xff\xff\x44\x82\x57\x7d\xff\x7f"')
Resultados:
Desafortunadamente, Valgrind no busca la memoria asignada de las llamadas al sistema, por lo que no puedo mostrar una buena fuga detectada.
Sin embargo, podemos mirar vmmap para ver la gran parte de la memoria asignada (metadatos 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
Explicación
Así que creo que necesito describir lo que realmente está sucediendo aquí, antes de pasar a la solución mejorada.
Esta función principal es abusar de la declaración de tipo faltante de C (por lo que se usa de manera predeterminada int sin que tengamos que desperdiciar caracteres al escribirla), así como también cómo funcionan los símbolos. El enlazador solo se preocupa por si puede o no encontrar un símbolo llamado main
a llamar. Así que aquí estamos haciendo main una matriz de int que estamos inicializando con nuestro shellcode que se ejecutará. Debido a esto, main no se agregará al segmento __TEXT sino más bien al segmento __DATA, razón por la cual necesitamos compilar el programa con un segmento ejecutable __DATA.
El shellcode encontrado en main es el siguiente:
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
Lo que está haciendo es llamar a la función syscall para asignar una página de memoria (la syscall mach_vm_allocate usa internamente). RAX debe ser igual a 0x100000a (le dice a syscall qué función queremos), mientras que RDI mantiene el objetivo para la asignación (en nuestro caso queremos que sea mach_task_self ()), RSI debe mantener la dirección para escribir el puntero en la memoria recién creada (así que solo lo estamos apuntando a una sección en la pila), RDX tiene el tamaño de la asignación (solo estamos pasando RAX o 0x100000a solo para guardar en bytes), R10 tiene las banderas (estamos indicando que puede ser asignado en cualquier lugar).
Ahora no es claramente obvio de dónde obtienen sus valores RAX y RDI. Sabemos que RAX debe ser 0x100000a, y RDI debe ser el valor que devuelve mach_task_self (). Afortunadamente, mach_task_self () es en realidad una macro para una variable (mach_task_self_), que está en la misma dirección de memoria cada vez (sin embargo, debe cambiar al reiniciar). En mi caso particular, mach_task_self_ está ubicado en 0x00007fff7d578244. Entonces, para reducir las instrucciones, pasaremos estos datos de argv. Por eso ejecutamos el programa con esta expresión$(ruby -e 'puts "\xf5\xff\xff\xfe\xff\xff\x44\x82\x57\x7d\xff\x7f"')
para el primer argumento La cadena son los dos valores combinados, donde el valor RAX (0x100000a) es de solo 32 bits y se le ha aplicado un complemento (por lo que no hay bytes nulos; simplemente NO somos el valor para obtener el original), el siguiente valor es el RDI (0x00007fff7d578244) que se ha desplazado hacia la izquierda con 2 bytes basura adicionales agregados al final (nuevamente para excluir los bytes nulos, simplemente lo volvemos a la derecha para volver al original).
Después de la llamada al sistema, estamos escribiendo en nuestra memoria recién asignada. La razón de esto es porque la memoria asignada usando mach_vm_allocate (o esta syscall) son en realidad páginas VM y no se paginan automáticamente en la memoria. Más bien se reservan hasta que se escriben datos en ellos, y luego esas páginas se asignan a la memoria. No estaba seguro de si cumpliría con los requisitos si solo estuviera reservado.
Para la próxima solución, aprovecharemos el hecho de que nuestro shellcode no tiene bytes nulos, por lo que podemos moverlo fuera del código de nuestro programa para reducir el tamaño.
Solución 2: C (Mac OS X x86_64), 44 bytes
La fuente para golf_sol2.c
main[]={141986632,10937,1032669184,2,42227};
El programa anterior debe compilarse con acceso de ejecución en el segmento __DATA.
clang golf_sol2.c -o golf_sol2 -Xlinker -segprot -Xlinker __DATA -Xlinker rwx -Xlinker rwx
Luego, para ejecutar el programa, ejecute lo siguiente:
./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"')
El resultado debería ser el mismo que antes, ya que estamos haciendo una asignación del mismo tamaño.
Explicación
Sigue el mismo concepto que la solución 1, con la excepción de que hemos movido la mayor parte de nuestro código de fuga fuera del programa.
El shellcode encontrado en main ahora es el siguiente:
movq 8(%rsi), %rsi
movl $42, %ecx
leaq 2(%rip), %rdi
rep movsb (%rsi), (%rdi)
Básicamente, esto copia el código de shell que pasamos en argv para que esté después de este código (así que después de que lo haya copiado, ejecutará el código de shell insertado). Lo que funciona a nuestro favor es que el segmento __DATA tendrá al menos un tamaño de página, por lo que incluso si nuestro código no es tan grande, aún podemos escribir más "con seguridad". La desventaja es la solución ideal aquí, ni siquiera necesitaría la copia, sino que simplemente llamaría y ejecutaría el shellcode en argv directamente. Pero desafortunadamente, esta memoria no tiene derechos de ejecución. Podríamos cambiar los derechos de esta memoria, sin embargo, requeriría más código que simplemente copiarlo. Una estrategia alternativa sería cambiar los derechos de un programa externo (pero más sobre eso más adelante).
El shellcode que pasamos a argv es el siguiente:
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
Esto es muy similar a nuestro código anterior, la única diferencia es que estamos incluyendo los valores para EAX y RDI directamente.
Posible solución 1: C (Mac OS X x86_64), 11 bytes
La idea de modificar el programa externamente, nos da la posible solución de mover el filtrador a un programa externo. Donde nuestro programa real (envío) es solo un programa ficticio, y el programa de fuga asignará algo de memoria en nuestro programa objetivo. Ahora no estaba seguro de si esto estaría dentro de las reglas para este desafío, pero aun así compartirlo.
Entonces, si tuviéramos que usar mach_vm_allocate en un programa externo con el objetivo establecido para nuestro programa de desafío, eso podría significar que nuestro programa de desafío solo necesitaría ser algo similar a:
main=65259;
Donde ese shellcode es simplemente un salto corto a sí mismo (salto / bucle infinito), el programa permanece abierto y podemos hacer referencia a él desde un programa externo.
Posible solución 2: C (Mac OS X x86_64), 8 bytes
Curiosamente, cuando estaba mirando la salida de valgrind, vi que al menos según valgrind, dyld pierde memoria. Así que, efectivamente, cada programa pierde algo de memoria. Siendo este el caso, en realidad podríamos hacer un programa que no haga nada (simplemente salga), y que realmente perderá memoria.
Fuente:
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