
Un simple PoC pour invoquer un shellcode chiffré en utilisant un appel caché
Un PoC simple pour invoquer un shellcode chiffré en utilisant un appel caché.
Ce code utilise une astuce simple pour cacher l'instruction qui effectuera le saut vers notre shellcode. Cela devrait rendre l'analyse statique ou l'émulation plus difficiles.
Comme toujours, si ce concept a déjà été expliqué dans d'autres articles, envoyez-moi un message et je l'ajouterai volontiers aux références.
La disposition mémoire pour utiliser cette technique est celle standard et décrite dans la Figure 1.
┌───────────────────────────────┐
│ │
│ │
│ │
│ │
│ encrypted shellcode │
│ │
│ │
│ │
│ │
│ │
│ │
├───────────────────────────────┤
│ │
│ │
│ decryption loop │
│ │
│ │
└───────────────────────────────┘
Figure 1. Disposition mémoire
La boucle de déchiffrement déchiffrera le shellcode et sautera dessus. Toute la « magie » se trouve à l'intérieur de la boucle de déchiffrement, car après avoir fini de déchiffrer le shellcode, la boucle de déchiffrement commencera à déchiffrer son propre code. Le déchiffrement de la première instruction résultera en un saut vers notre shellcode qui sera exécuté à l'itération suivante de la boucle :)
Ci-dessous la partie pertinente :
@decryption:
xor word ptr [eax], 06799h ; <jmp esi bytes> XOR <first two bytes of this instruction>
add eax, sizeof word
dec ecx
loop @decryption
Comme vous pouvez le voir, la boucle de déchiffrement ne contient aucune instruction qui saute vers le shellcode déchiffré. Le code assembleur est assemblé dans le format binaire suivant :
66:8130 9967 | xor word ptr ds:[eax],6799
83C0 02 | add eax,2
49 | dec ecx
E2 F5 | loop 450006
dans ce cas, la clé de déchiffrement doit être 06799h, car l'opération XOR entre 8166h (les deux premiers octets de la première instruction de la boucle de déchiffrement) et 6799h (la clé XOR) donne e6ffh qui est assemblé en jmp esi. Autrement dit :
0x8166 (xor word ptr ds:[eax],...) XOR 0x6799 (decryption key) == 0x6eff (jmp esi)
En définissant le registre ESI au début de notre shellcode, nous pouvons obtenir l'exécution :)
Ci-dessous un exemple de débogage. Initialement, le shellcode chiffré est copié, suivi du code utilisé pour déchiffrer et appeler le shellcode. On peut remarquer qu'après la troisième exécution de la boucle de déchiffrement, l'instruction xor word ptr ds:[eax],6799 se transforme en jmp esi.

Les étapes pour utiliser cette technique sont :
Afin de rendre le code de déchiffrement moins identifiable, il est possible d'utiliser des méthodes alternatives pour appeler le shellcode. Pour avoir plus de liberté, on peut envisager d'augmenter la taille de bloc qui est chiffrée à chaque itération. Dans mon PoC, j'ai utilisé 2 octets car jmp esi nécessite deux octets, mais on peut utiliser des blocs de 4 ou 8 octets, permettant à l'opérateur d'avoir plus d'alternatives qui tiennent dans un bloc de 4 ou 8 octets. Selon la méthode choisie pour appeler le shellcode, la constante de chiffrement changera également.