Soluzione 1: C (Mac OS X x86_64), 109 byte
La fonte per golf_sol1.c
main[]={142510920,2336753547,3505849471,284148040,2370322315,2314740852,1351437506,1208291319,914962059,195};
Il programma sopra deve essere compilato con accesso all'esecuzione sul segmento __DATA.
clang golf_sol1.c -o golf_sol1 -Xlinker -segprot -Xlinker __DATA -Xlinker rwx -Xlinker rwx
Quindi per eseguire il programma eseguire quanto segue:
./golf_sol1 $(ruby -e 'puts "\xf5\xff\xff\xfe\xff\xff\x44\x82\x57\x7d\xff\x7f"')
risultati:
Sfortunatamente Valgrind non controlla la memoria allocata dalle chiamate di sistema, quindi non posso mostrare una bella perdita rilevata.
Tuttavia, possiamo guardare vmmap per vedere il grosso pezzo di memoria allocata (metadati 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
Spiegazione
Quindi penso di dover descrivere cosa sta realmente succedendo qui, prima di passare alla soluzione migliorata.
Questa funzione principale sta abusando della dichiarazione di tipo mancante di C (quindi per impostazione predefinita è int senza che dobbiamo sprecare i caratteri che la scrivono), nonché il modo in cui i simboli funzionano. Il linker si preoccupa solo di trovare un simbolo chiamato mainper chiamare. Quindi qui stiamo creando un array di int che stiamo inizializzando con il nostro shellcode che verrà eseguito. Per questo motivo, main non verrà aggiunto al segmento __TEXT ma piuttosto al segmento __DATA, motivo per cui è necessario compilare il programma con un segmento eseguibile __DATA.
Lo shellcode trovato in main è il seguente:
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
Ciò che sta facendo è chiamare la funzione syscall per allocare una pagina di memoria (la syscall mach_vm_allocate utilizza internamente). RAX dovrebbe essere uguale a 0x100000a (indica alla funzione syscall quale funzione vogliamo), mentre RDI mantiene l'obiettivo per l'allocazione (nel nostro caso vogliamo che questo sia mach_task_self ()), RSI dovrebbe contenere l'indirizzo per scrivere il puntatore nella memoria appena creata (quindi lo stiamo semplicemente puntando a una sezione dello stack), RDX contiene le dimensioni dell'allocazione (stiamo solo passando in RAX o 0x100000a solo per risparmiare sui byte), R10 contiene i flag (stiamo indicando che può essere assegnato ovunque).
Ora non è chiaramente ovvio da dove RAX e RDI ottengano i loro valori. Sappiamo che RAX deve essere 0x100000a e RDI deve essere il valore restituito da mach_task_self (). Fortunatamente mach_task_self () è in realtà una macro per una variabile (mach_task_self_), che è sempre allo stesso indirizzo di memoria (dovrebbe cambiare comunque al riavvio). Nel mio caso particolare mach_task_self_ sembra trovarsi in 0x00007fff7d578244. Quindi, per ridurre le istruzioni, passeremo invece a trasferire questi dati da argv. Questo è il motivo per cui eseguiamo il programma con questa espressione$(ruby -e 'puts "\xf5\xff\xff\xfe\xff\xff\x44\x82\x57\x7d\xff\x7f"')per il primo argomento. La stringa sono i due valori combinati, in cui il valore RAX (0x100000a) è solo 32 bit e ha un proprio complemento applicato su di esso (quindi non ci sono byte nulli; non abbiamo semplicemente il valore per ottenere l'originale), il valore successivo è l'RDI (0x00007fff7d578244) che è stato spostato a sinistra con 2 byte spazzatura aggiuntivi aggiunti alla fine (di nuovo per escludere i byte null, lo spostiamo di nuovo a destra per riportarlo all'originale).
Dopo il syscall stiamo scrivendo nella nostra memoria appena allocata. Il motivo è dovuto al fatto che la memoria allocata utilizzando mach_vm_allocate (o questo syscall) sono in realtà pagine di macchine virtuali e non vengono automaticamente paginate nella memoria. Piuttosto, sono riservati fino a quando i dati non vengono scritti su di loro, quindi quelle pagine vengono mappate in memoria. Non ero sicuro che avrebbe soddisfatto i requisiti se fosse stato riservato.
Per la prossima soluzione trarremo vantaggio dal fatto che il nostro shellcode non ha byte nulli, quindi possiamo spostarlo al di fuori del codice del nostro programma per ridurne le dimensioni.
Soluzione 2: C (Mac OS X x86_64), 44 byte
La fonte per golf_sol2.c
main[]={141986632,10937,1032669184,2,42227};
Il programma sopra deve essere compilato con accesso all'esecuzione sul segmento __DATA.
clang golf_sol2.c -o golf_sol2 -Xlinker -segprot -Xlinker __DATA -Xlinker rwx -Xlinker rwx
Quindi per eseguire il programma eseguire quanto segue:
./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"')
Il risultato dovrebbe essere lo stesso di prima, poiché stiamo effettuando un'allocazione delle stesse dimensioni.
Spiegazione
Segue più o meno lo stesso concetto della soluzione 1, con l'eccezione che abbiamo spostato la parte del nostro codice che perde al di fuori del programma.
Lo shellcode trovato in main ora è il seguente:
movq 8(%rsi), %rsi
movl $42, %ecx
leaq 2(%rip), %rdi
rep movsb (%rsi), (%rdi)
Questo sostanzialmente copia il codice shell che passiamo in argv per essere dopo questo codice (quindi dopo averlo copiato, eseguirà il codice shell inserito). Ciò che funziona a nostro favore è che il segmento __DATA avrà almeno una dimensione di pagina, quindi anche se il nostro codice non è così grande possiamo ancora "tranquillamente" scrivere di più. Il rovescio della medaglia è la soluzione ideale qui, non avrebbe nemmeno bisogno della copia, invece chiamerebbe ed eseguirà direttamente il shellcode in argv. Ma sfortunatamente, questa memoria non ha diritti di esecuzione. Potremmo cambiare i diritti di questa memoria, tuttavia richiederebbe più codice che semplicemente copiarlo. Una strategia alternativa sarebbe quella di cambiare i diritti da un programma esterno (ma ne parleremo più avanti).
Il codice shell che passiamo ad argv è il seguente:
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
Questo è molto simile al nostro codice precedente, con l'unica differenza che stiamo includendo direttamente i valori di EAX e RDI.
Possibile soluzione 1: C (Mac OS X x86_64), 11 byte
L'idea di modificare il programma esternamente, ci offre la possibile soluzione di spostare il leaker in un programma esterno. Laddove il nostro programma attuale (invio) è solo un programma fittizio, e il programma leaker assegnerà parte della memoria nel nostro programma target. Ora non ero sicuro che ciò rientrasse nelle regole di questa sfida, ma condividendola comunque.
Quindi se dovessimo usare mach_vm_allocate in un programma esterno con l'obiettivo impostato sul nostro programma di sfida, ciò significherebbe che il nostro programma di sfida dovrebbe solo essere qualcosa sulla falsariga di:
main=65259;
Dove quel codice shell è semplicemente un salto corto verso se stesso (salto / loop infinito), quindi il programma rimane aperto e possiamo fare riferimento a esso da un programma esterno.
Possibile soluzione 2: C (Mac OS X x86_64), 8 byte
Stranamente quando stavo guardando l'output di valgrind, ho visto che almeno secondo valgrind, il diavolo perde memoria. Così efficacemente ogni programma perde memoria. Stando così le cose, potremmo effettivamente creare un programma che non fa nulla (esce semplicemente) e che in realtà perderà memoria.
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