dimanche 21 juillet 2019

Technique Anti-Desassembly




I.    Introduction


La technique consiste à mettre en déroute l'analyse statique du code faite au travers des outils comme IDA qui est sûrement le plus utilisé pour l'analyse statique.
l'analyse statique ce base sur la transcription des Opcodes en assembleur.
Exemples 33 C0 correspond à l'instruction XOR EAX,EAX.

Mais cette analyse statiques des opcodes est fait linéairement, tous simplement à la suite
Prenons un exemple  55 8B EC , on va regarder le premier octet 55h qui correspond à l'instruction PUSH EBP et passer au octet suivant et on va trouver 8Bh ECh qui est l'instruction  MOV EBP,ESP et ainsi de suite.

A contrario un processeur traite les instructions et en fonction de celle-ci, va faire évoluer son pointeur d'instruction qui est contenu dans le registre EIP pour définir le prochaine emplacement ou il va continuer le traitement.

Le registre EIP ( Instruction Pointer ) contient l’offset de la prochaine instruction à exécuter. Il est modifié automatique à chaque exécution et peut être manipulé par des instructions de type jmp, call, ret, ..etc.

C'est là que tous ce joue, le cœur du mécanisme de Anti-désassemblage et permet de mettre en déroute les désassembleur pour fausser l'analyse sur un bloc d'octet dans les grandes lignes.

La méthode principale est de faire un saut quelque soit l'instruction utilisé et d'atterrir dans  l'un des opcodes d'une instruction assembleur . C'est un peut comme le mécanisme ROP
Vous trouverez aussi cela sous le nom de certains hackeurs comme l'éclatement d'instruction.

Modélisation du principe avec un cas d'école basé sur le pivot 0x58. Le terme pivot vient du fait que pour construire leurs codes ils se base sur un octet référentielle que l'on souhaite obtenir pour une instruction non existante ou une séquence d'octet. Mais démarrant par le pivot.

Dans le cas d'exemple, c'est pour crée un nombre magique servant ensuite de filtre pour
crée des hashs . Mais cela sort de notre article ici.

Voici la séquence extraite pour notre exemple "\x39\x68\x58\xEB\xFC\x03\x58\xC3"

en désassemblant se bloc au travers de notre outil maison. Nous obtenons cette vu.


 












On a l'impression d'avoir le code. Mais c'est pas ce que le microprocesseur interprète
Nous avons mis les deux visions sur un schéma conceptuel.




 
Comme vous pouvez le voir, le jmp renvoi en plein milieu d'une instruction. Ici "CMP DWORD PTR[EAX+0x58],EBP et donc il éclate l'instruction.
Le processeur continuant à partir du 0x68, il va interpréter une instruction PUSH IMM
(pushes immediate static value onto the stack)  0x68 0xXX 0xXX 0xXX 0xXX.

En conséquence le processeur va mettre sur la pile 0x03FCEB58. Et continuer son exécution en dépliant la valeur sur la pile pour la positionner dans le registre EAX .

Ce nombre magique peut être utilisé pour cacher d'autre nombre au travers d'instruction ADD , SUB, XOR.
Comme des hashs d'Api

II.    Quelque technique d'éclatement classique


Nous allons présenté plusieurs technique d'éclatement d'instruction classique, vous permettant de bien visualiser la mécanique décrit au dessus avec des noms les décrivant.

Méthode 1 :  Instructions de saut  inconditionnel à un emplacement constant

Elle est baser sur tous instructions de type JUMP

Exemple 1  - "\x33\xD2\xE9\xFF\xFF\xFF\xFF\xC2\x8B\xC2\xC3"

Si on passe cela dans un désassembleur, il verra cela de la manière suivante:
















Il faut bien voir qu l'instruction E9 FF FF FF FF fait que le EIP va être renvoyé dans la séquence d'octet de l'instruction JMP et arriver sur le dernier FF composant l'instruction.

Nous avons mis les deux visions sur un schéma conceptuel pour distingué les deux vues.



Il est intéressant de noté qu'un compilateur ne peut pas crée les séquences suivant naturellement étant des saut casant l'instruction elle même du JUMP

\xE9\xFF\xFF\xFF\xFF
\xE9\xFD\xFF\xFF\xFF
\xE9\xFC\xFF\xFF\xFF
\xE9\xFB\xFF\xFF\xFF

Cette méthode est aussi décrit par des développeurs "Auto-mutilation de l'instruction JUMP"

Méthode 2 :  Mettez deux saut conditionnelles consécutives mais opposées

Cela revient au point d'avant à la différence que l'on teste une condition opposées effectuant le saut vers la même adresses . Voici un petit exemple avec jz suivi d'un jnz.

l'exemple est une fonction qui renvoi un char 'e'

Exemple 2  - "\x74\x04\x75\x02\xE9\x48\xB0\x65\xC3"

Ce que va traduire un désassembleur comme ceci














 
Mais au niveau du microprocesseur sera vu comme la séquence suivant:

"\x74\x04\x75\x02  => \xB0\x65\xC3"

Nous avons mis les deux visions sur le schéma conceptuel





























 
Méthode 3 :  Forcer un saut conditionnelles pour un saut constant

Cela revient également à faire un saut dans une instruction  à la différence que comme on force la condition ont fixe le saut.

Exemple 3 -  "\x8B\xC3\x56\x8B\x34\xB0\x33\xC3\x5E\x74\xFA\x5E\x40\xC3"

Ce que va traduire un désassembleur comme ceci

 


Mais au niveau du microprocesseur sera vu comme la séquence suivant

"\x8B\xC3\x56\x8B\x34\xB0\x33\xC3\x5E\x74\xFA => \xB0\x33\xC3"

Ce qui fait que l'appel de ce bloque va fournir le caractère '3' ou  vu en  hexa 0x33

Nous avons mis les deux visions sur un schéma conceptuel.




 
Méthode 4 :  le débrayage d'un octet ( Cas particulier de la  méthode 1 ) 

Cette Technique est fortement lié à la création d'instruction commencent par l'optcode FF
Elle ce caractérise par l'utilisation du Jump short  (  \xEB\xFF  )  

De la vous pouvez disposer de tous les instructions commencent par l'octet  0xFF

Quelque combinaison possible sans être exhaustive  :

            Jmp eax                       FF E0
            Call eax                       FF,D0
            Inc eax                        FF,C0


Exemple 4 -  "\x33\xC0\xEB\xFF\xC0\x40\xC3\x50"

Ce que va traduire via un désassembleur comme ceci






 
Mais au niveau du microprocesseur sera vu comme la séquence

"\x33\xC0\xEB\xFF  => \xFF\xC0\x40\xC3\x50"

Ce qui fait que l'appel de ce bloque va fournir la valeur 0x02 

Nous avons mis les deux visions sur un schéma conceptuel.





























 
Méthode 5 :  Saute mouton ou passe le suivant  ( Cas particulier de la méthode 1 ) 

Son nom, caractérise bien son action, il est associer ( xEB\x01 )

Exemple 5 : "\xEB\x01\x8B\x68\x6B\x68\x79\x00\x58\xC3"

Cette fonction renvoi un DWORD contenant les données d'une chaîne "khy" en ANSI

Ce que va traduire via un désassembleur comme ceci















 
Mais au niveau du microprocesseur sera vu comme la séquence suivant:

"\xEB\x01 => \x68\x6B\x68\x79\x00\x58\xC3"

Nous avons mis les deux visions sur un schéma conceptuel.



Le terme saute mouton est uniquement le fait qu'il y a que un octet de sauté. Mais au finale est qu'une sous famille de la famille de la Méthode 1.

Méthode 6 :  Auto-mutilation de l'instruction CALL

Cela est beaucoup utilisé pour crée un GetEIP  permettant de connaître l'adresse ou on ce trouve un shellcode

Au travers de  \xE8\xFF\xFF\xFF\xFF\xC0\x58 , il existe autant de variante que de registre possible

\xE8\xFF\xFF\xFF\xFF\xC1\x59
\xE8\xFF\xFF\xFF\xFF\xC2\x5A

Et vous pouvez aussi faire varier le registre de réception du GetEIP

tout est basé sur une instruction qui commence par FF XX YY KK ... ce qui vous laisse un panel de combinaison assez importante.

Voici un exemple permettant une fonction  qui renvoi une chaîne "Hah!"

Exemple 6: "\xE8\xFF\xFF\xFF\xFF\xC2\x58\x83\xC0\x06\xC3\x48\x61\x68\x21\x00\xB7\x86"

Ce que va traduire vu du désassembleur comme ceci

















Mais au niveau du microprocesseur sera vu comme la séquence suivant:

"\xE8\xFF\xFF\xFF\xFF  => \xFF\xC2\x58\x83\xC0\x06\xC3"

Ce qui fait que l'appel de ce bloque va fournir dans EAX l'adresse situé à prêt  l'instruction RET à la ligne 0x0000000B qui contient un texte "Hah!"

Nous avons mis les deux visions sur un schéma conceptuel.



 

III.    Test des exemples  


Nous allons crée un petit programme reprenant ses divers exemples et pour tester les résultats
Vous pouvez voir sur la capture suivant l'appel de l'exemple 6 sur notre testeur.


























Ou nous avons bien après appel du bloc un pointeur sur la chaîne "Hah!". Si vous souhaiter tester également ses petit exemple. Il vous suffit d'écrire un côte proche de celui en dessous correspondant au bouton "6" dans un projet MFC Dialog


void CExAsmAntiDisassemblyDlg::OnBnClickedBtSample6()
{
                char Table[]="\xE8\xFF\xFF\xFF\xFF\xC2\x58\x83\xC0\x06\xC3\x48\x61\x68\x21\x00\xB7\x86";

                char* (*FnRun)() = (char*(*)())(&Table[0]);
                char* pText = FnRun();

                SetDlgItemText(IDC_ED_RES,pText);
}

IV.    Exemple dans le code d'un malware


Nous Prendrons notre exemple de Gandcrab 4.3 qui est un ransomware 

Nous mettons le bloc extrait qui nous intéresse d'un des échantillons et son format shellcode

0000   55 8B EC E8 00 00 00 00 3E 83 04 24 11 75 05 74    U.......>..$.u.t
0010   03 E9 28 14 58 FF E0 00 E9 E8 8C FF FF FF 6A 00    ..(.X.........j.
0020   FF 15 50 00 41 00 5D C3                                       ..P.A.].

\x55\x8B\xEC\xE8\x00\x00\x00\x00\x3E\x83\x04\x24\x11\x75\x05\x74
\x03\xE9\x28\x14\x58\xFF\xE0\x00\xE9\xE8\x8C\xFF\xFF\xFF\x6A\x00
\xFF\x15\x50\x00\x41\x00\x5D\xC3

Nous avons passé les octets dans notre outils





















  
Vous pouvez voir que la séquence d'instruction JNZ suivi de JZ  à l'adresse 0x0000000D
correspond à la méthode 2 




















 

Donc les octets de l'adresse 0x00000011 à 0x00000013 ne seront jamais appeler. On peut les remplacer par Nop  ( 0x90 ) , cela nous donne maintenant 

























 
Est pour finir , si on regarde bien la partie ce trouvant à l'adresse 0x00000003. Cette séquence est tres standard pour récupérer le EIP ( cela permet de savoir ou on ce trouve dans le shellcode) en effectuant E8 00 00 00 00 , on obtient sur la pile l'adresse suivant de cette instruction donc l'addresse ESP point sur 0x00000008. Hors comme par hasard juste à prés l'instruction suivant ADD DWORD PTR DS:[ESP] ,0x00000011 va ajouter à notre valeur pointé 0x11. Ce qui fait la nouvelle valeur pointé par ESP est 0x00000019.

Comme nous avons neutraliser les octets non appelé par des NOP , la prochaine instruction sera un POP EAX ( oh cela est bien ) .car cela vient  juste avant le JMP EAX ( Asbolue )
On tombe sur la méthode 1 ( saut inconditionnel ) 

Donc comme le Jmp fixé ici est absolue on le microprocesseur va directement fixer sa prochaine séquence d'octet à traiter à l'adresse 0x00000019. Et donc il devient assez claire que les deux octet "00 E9" ne seront jamais appelé. 

En conséquence faut voir cela comme un jauge avec comme point terminal à atteindre est fixer à l'emplacement pointé par ESP et la mécanique JNZ/JZ comme le curseur pointant le saut vers la borne terminale entre deux ont met ce que l'on veut comme octet pour perturber l'analyste ou le désassembleur.

V.    Conclusions


Dans cette article, vous avez pu apprendre davantage sur les stratégies anti-disassembly mis en place en Assembleur ou dans des shellcodes pour dupé l'analyse. Vous permettant de comprendre qu'un code peut en cacher un autre et qu'il ne faut pas toujours se reposer sur les outils pour l'analyse des éléments.


Aucun commentaire:

Enregistrer un commentaire