Solution 1: C (Mac OS X x86_64), 109 octets
La source de golf_sol1.c
main[]={142510920,2336753547,3505849471,284148040,2370322315,2314740852,1351437506,1208291319,914962059,195};
Le programme ci-dessus doit être compilé avec un accès d’exécution sur le segment __DATA.
clang golf_sol1.c -o golf_sol1 -Xlinker -segprot -Xlinker __DATA -Xlinker rwx -Xlinker rwx
Ensuite, pour exécuter le programme, exécutez ce qui suit:
./golf_sol1 $(ruby -e 'puts "\xf5\xff\xff\xfe\xff\xff\x44\x82\x57\x7d\xff\x7f"')
Résultats:
Malheureusement, Valgrind ne surveille pas la mémoire allouée par les appels système. Je ne peux donc pas montrer une fuite détectée.
Cependant, nous pouvons regarder vmmap pour voir le gros bloc de mémoire allouée (métadonnées 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
Explication
Je pense donc avoir besoin de décrire ce qui se passe réellement ici avant de passer à la solution améliorée.
Cette fonction principale abuse de la déclaration de type manquante de C (elle est donc définie par défaut sur int sans que nous ayons besoin de gaspiller des caractères pour l'écrire), ainsi que sur le fonctionnement des symboles. L'éditeur de liens se soucie seulement de savoir s'il peut ou non trouver un symbole appelé main
à appeler. Nous créons donc ici un tableau d’inters que nous initialisons avec notre shellcode qui sera exécuté. De ce fait, main ne sera pas ajouté au segment __TEXT, mais plutôt au segment __DATA. Nous devons donc compiler le programme avec un segment exécutable __DATA.
Le shellcode trouvé dans main est le suivant:
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
Cela appelle la fonction syscall pour allouer une page de mémoire (le syscall mach_vm_allocate utilise en interne). RAX doit être égal à 0x100000a (indique au syscall quelle fonction nous voulons), alors que RDI conserve la cible pour l'allocation (dans notre cas, nous voulons que ce soit mach_task_self ()), RSI doit conserver l'adresse pour écrire le pointeur sur la mémoire nouvellement créée. (nous pointons simplement sur une section de la pile), RDX conserve la taille de l'allocation (nous passons juste en RAX ou 0x100000a juste pour économiser des octets), R10 détient les drapeaux (nous indiquons qu'il peut être alloué n'importe où).
À présent, il n’est pas clairement évident d’où RAX et RDI tirent leurs valeurs. Nous savons que RAX doit être 0x100000a et que RDI doit être la valeur renvoyée par mach_task_self (). Heureusement, mach_task_self () est en réalité une macro pour une variable (mach_task_self_), qui est toujours à la même adresse mémoire (devrait changer au redémarrage). Dans mon cas particulier, mach_task_self_ se trouve à 0x00007fff7d578244. Donc, pour réduire les instructions, nous allons plutôt transmettre ces données d’argv. C'est pourquoi nous lançons le programme avec cette expression$(ruby -e 'puts "\xf5\xff\xff\xfe\xff\xff\x44\x82\x57\x7d\xff\x7f"')
pour le premier argument. La chaîne est constituée des deux valeurs combinées. La valeur RAX (0x100000a) n’est que de 32 bits et l’application est complétée par un complément (il n’ya donc pas d'octets nuls; nous ne voulons PAS la valeur pour obtenir l'original), la valeur suivante est le RDI (0x00007fff7d578244) qui a été décalé vers la gauche avec 2 octets supplémentaires inutiles ajoutés à la fin (encore une fois pour exclure les octets nuls, nous le décalons simplement vers la droite pour le ramener à l'original).
Après l'appel système, nous écrivons dans notre mémoire nouvellement allouée. La raison en est que la mémoire allouée à l'aide de mach_vm_allocate (ou de cet appel système) est en fait une page de machine virtuelle et n'est pas automatiquement paginée en mémoire. Elles sont plutôt réservées jusqu'à ce que des données leur soient écrites, puis ces pages sont mappées en mémoire. Je ne savais pas si cela satisferait aux exigences s'il était seulement réservé.
Pour la prochaine solution, nous tirerons parti du fait que notre shellcode n'a pas d'octet nul, et peut donc le déplacer en dehors du code de notre programme pour réduire la taille.
Solution 2: C (Mac OS X x86_64), 44 octets
La source de golf_sol2.c
main[]={141986632,10937,1032669184,2,42227};
Le programme ci-dessus doit être compilé avec un accès d’exécution sur le segment __DATA.
clang golf_sol2.c -o golf_sol2 -Xlinker -segprot -Xlinker __DATA -Xlinker rwx -Xlinker rwx
Ensuite, pour exécuter le programme, exécutez ce qui suit:
./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"')
Le résultat devrait être le même qu'auparavant, car nous faisons une allocation de la même taille.
Explication
Suit à peu près le même concept que la solution 1, à l'exception du fait que nous avons déplacé la majeure partie de notre code qui fuit en dehors du programme.
Le shellcode trouvé dans main est maintenant le suivant:
movq 8(%rsi), %rsi
movl $42, %ecx
leaq 2(%rip), %rdi
rep movsb (%rsi), (%rdi)
Cela copie essentiellement le shellcode que nous passons dans argv après ce code (donc, une fois copié, il exécutera le shellcode inséré). Ce qui fonctionne en notre faveur, c'est que le segment __DATA aura au moins une taille de page. Par conséquent, même si notre code n'est pas si volumineux, nous pouvons toujours en écrire plus en toute sécurité. L’inconvénient est la solution idéale ici, elle n’aurait même pas besoin de la copie, elle appellerait et exécuterait directement le shellcode dans argv. Mais malheureusement, cette mémoire ne dispose pas de droits d'exécution. Nous pourrions modifier les droits de cette mémoire, mais cela nécessiterait plus de code que la simple copie. Une stratégie alternative consisterait à modifier les droits d'un programme externe (mais nous en parlerons plus tard).
Le shellcode que nous passons à argv est le suivant:
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
Cela ressemble beaucoup à notre code précédent, la seule différence étant que nous incluons directement les valeurs pour EAX et RDI.
Solution possible 1: C (Mac OS X x86_64), 11 octets
L’idée de modifier le programme en externe nous donne la solution possible de transférer le système qui fuit vers un programme externe. Où notre programme actuel (soumission) est juste un programme factice, et le programme de fuite allouera de la mémoire dans notre programme cible. Maintenant, je ne savais pas si cela entrerait dans les règles de ce défi, mais je le partageais néanmoins.
Donc, si nous utilisions mach_vm_allocate dans un programme externe avec la cible définie pour notre programme de mise au défi, cela pourrait signifier que notre programme de mise à l'épreuve n'aurait besoin que de ressembler à ce qui suit:
main=65259;
Lorsque ce shellcode est simplement un saut bref vers lui-même (saut / boucle infini), le programme reste ouvert et nous pouvons le référencer à partir d'un programme externe.
Solution possible 2: C (Mac OS X x86_64), 8 octets
Curieusement, quand je regardais la sortie de valgrind, je voyais qu'au moins selon valgrind, dyld perdait de la mémoire. Si bien que chaque programme perd de la mémoire. Ceci étant le cas, nous pourrions en fait créer un programme qui ne fait rien (se ferme tout simplement) et qui perdra effectivement de la mémoire.
La source:
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