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
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"
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