Obfuscation, la bête noir des reversers - Big

Transcription

Obfuscation, la bête noir des reversers - Big
Obfuscation, la bête noir des
reversers
Squallsurf
Decembre 2008
Sous licence Creative Commons CC-BY-SA
Contents
1 Introduction
2
2 Le polymorphisme, ou le chiffrement
2.1 Introduction et historique . . . . . .
2.2 Exemple de polymorphisme . . . . .
2.3 Améliorations . . . . . . . . . . . . .
par l’aléatoire
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
3 Le métmorphisme, ou la modification par
3.1 Introduction et explications sommaires . .
3.2 Analyse des faits et théorie avancée . . . .
3.3 Limites d’une telle technique . . . . . . .
équivalence
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
3
3
3
5
6
6
6
9
4 La permutation de code, ou la furtivité par le mélange
10
4.1 Introduction et problèmatique . . . . . . . . . . . . . . . . . . . . 10
4.2 Théorie et pratique . . . . . . . . . . . . . . . . . . . . . . . . . . 10
4.3 Points forts, faiblesses et améliorations . . . . . . . . . . . . . . . 12
5 Outroduction : vers l’infini et au dela !!
13
6 Liens et remerciements
14
1
1
Introduction
Premièrement sachez que ce document n’as aucunement la prétention de
répondre à toutes les questions imaginables sur la modification dynamique de
code, il faudrait une bible de la taille des manuels intel pour y répondre un tant
soit peu correctement. :-)
Ce document essaieras donc simplement de vous expliquer les différentes formes
de modification dynamique, leurs méthodes ainsi que les différentes ressources
disponible à ce sujet.
Avant d’entrée dans le vif du sujet, il me semble judicieu de définir les termes qui seront ci-dessous employés afin d’éviter toute confusion. Les définitions
nous permettrons de donner un sens à un simple mot ainsi les termes employés
ne sont peut-être pas les bons, cependant l’idée derrière ceux-ci seras expliquer
clairement et vous pourrez nommer comme bon vous semble une certaine technique.
Un moteur polymorphique est un programme permettant de modifier aléatoirement la clef qui serviras à chiffrer ET déchiffrer chaque nouvelle génération d’un
programme.
Un moteur métamorphique ne ressemble aucunement aux moteurs polymorphiques vus précédemments. Leurs but est de généré des instructions ayant la
même sémantique que les précédentes.
Un moteur de (per)mutations est un programme permettant de modifier la
structure d’une "cible" tout en gardant les résultats initiallement atteinds. C’est
à dire que la modification de l’ordre du code n’affecteras pas le bon fonctionnement du programme cible.
Une signature est une suite de bytes permettant d’identifier un programme ou
une de ses fonctions. Dans le cas de l’identification d’un programme entier on
prendras généralement un endroit clés et spécifique à ce programme, tel que le
coeur du virus dans le cas d’un malware(non protégé) ou un layer d’un protector
(protégé).
2
2
Le polymorphisme, ou le chiffrement par l’aléatoire
2.1
Introduction et historique
Les premiers articles traitant du polymorphisme date du début des années
90, certains plus techniques que d’autres, ils restaient toute fois relativement
vague, à l’image de cette technique.
Bien que très basique et maintenant obsolète, le polymorphisme reste utilisée
par les virus afin de retarder l’inévitable : leur détection. En théorie, un moteur
polymorphique n’est rien de plus qu’une fonction créant un ou plusieurs nombres
pseudo-aléatoire qui vont servir de clefs de chiffrement lors de la génération d’un
ou plusieurs layers de (dé)chiffrements (Souvent, un seul layer est utilisé et est
prédéfinis par le coder du virus).
En terme plus clair, voici ce qu’un moteur polymorphique doit accomplir :
• Générer un nombre pseudo-aléatoire et le stocker dans X
• Générer la boucle (layer) de chiffrement de la nouvelle génération du virus
• Inclure dans la boucle de chiffrement la clef X à utiliser
• Générer la boucle de déchiffrement correspondante
• Chiffrer la nouvelle génération du virus à l’aide de X
• Y ajouter la boucle de déchiffrement
2.2
Exemple de polymorphisme
Voici un exemple simple de moteur polymorphique en asm :
c a l l GetPseudoRandomNumber
; Retourne l e nombre pseudo a l é a t o i r e dans eax
CryptLoopBegin :
mov e s i , V i r u s C o r e S t a r t A d d r
; Début du c o e u r du v i r u s , l a p a r t i e à c h i f f r e r
mov ecx , V i r u s C o r e S i z e
; R é c u p é r a t i o n de l a t a i l l e de code à c h i f f r e r
CryptLoop :
add b y t e p t r [ e s i+e c x ] , eax
dec b y t e p t r [ e s i+e c x ]
s h r b y t e p t r [ e s i+e c x ] , eax
x o r b y t e p t r [ e s i+e c x ] , eax
dec e c x
l o o p CryptLoop
; Tant que e c x e s t d i f f é r e n t de 0 , s a u t e v e r s CryptLoop
CryptLoopEnd :
c a l l SetDecryptionLayerInNewVirusGen
Ici, nous avons un layer de chiffrement basé sur un nombre pseudo-aléatoire
encore présent dans eax au moment du call final. Ce layer étant très basique
et le choix des opérations arithmétiques utilisées restant le votre, je ne vais pas
détailler plus son fonctionnement. (Attention toutefois à choisir des opérations
possédants un inverse : add/sub;shr/shl;dec/inc;etc...).
Regardons maintenant le rôle de la fonction SetDecryptionLayerInNewVirusGen :
3
SetDecryptionLayerInNewVirusGen :
push eax
mov e s i , o f f s e t DecryptLoopBegin
mov ecx , o f f s e t DecryptLoopEnd
sub ecx , e s i
add ecx , 12
c a l l CreateStubForDecryptionLoop
rep
mov
mov
mov
movsb
dword
dword
dword
; Récupère l a t a i l l e du l a y e r dde d é c r y p t i o n
; A jo u t e à l a t a i l l e du l a y e r , 3 dwords
; i n p u t : e c x==t a i l l e du l a y e r
; o u tp u t : e d i=addr du s t u b
; r e m p l i s l e s t u b a v e c l e l a y e r de d é c r y p t i o n
p t r [ e d i ] , eax
; mets l a c l e f à l a f i n du s t u b
p t r [ e d i + 4] , VirusCoreAddr ; p u i s l ’ a d r e s s e du c o e u r du v i r u s
p t r [ e d i + 8] , V i r u s C o r e S i z e
Rien de bien compliqué, passons plutôt à la fonction de déchiffrement :
DeryptLoopBegin :
mov e s i , o f f s e t DecryptLoopEnd
mov eax , dword p t r [ e s i ]
mov ecx , dword p t r [ e s i +8]
mov e s i , dword p t r [ e s i +4]
DecryptLoop :
x o r b y t e p t r [ e s i+e c x ] , eax
s h l b y t e p t r [ e s i+e c x ] , eax
i n c b y t e p t r [ e s i+e c x ]
sub b y t e p t r [ e s i+e c x ] , eax
dec e c x
l o o p DecryptLoop
jmp e s i
DecryptLoopEnd :
dd 0
dd 0
dd 0
; Récupèr l ’ a d r e s s e où s o n t s o t c k é s l e s d o n n ée s
; Récupère l a c l e f
; La t a i l l e du c o e u r du v i r u s
; l ’ addr du début du v i r u s
; O p é r a t i o n s a r i t h m é t h i q u e s i n v e r s e s de c e l l e s
; u t i l i s é e s d ur an t l ’ e n c r y p t i o n
; Saute v e r s l e début du c o e u r du v i r u s
; 3 DWORDs v i d e s , un pour l a c l e f
; un a u t r e pour l ’ a d r e s s e du début du c o e u r du v i r u s
; e t un d e r n i e r pour l a t a i l l e à d é c h i f f r e r
Ce moteur étant plus que sommaire, je me permet de le diffuser sans me
soucier des difficultés théoriques (maintenant largement dépassées) qu’il pourrait apporter en matière de recherche anti-virales.Essayons donc de voir ce que
l’on pourrait améliorer.
4
2.3
Améliorations
Pour renforcer l’éfficacitée de ce genre de moteur on pourrais :
• Générer dynamiquement le layer de déchiffrement
Bien que cette méthode entraine une augmentation de la taille du moteur polymorphique, elle rends le déchiffrement du coeur du virus par un logiciel anti-virus
plus lente.
• Ajouter des anti-* et du junkcode
Tout ce qui peu retarder l’analyse d’un virus est bon à prendre si l’on veut que
son code survive quelques temps dans la jungle d’un ordinateur où les soldats
vous traquerons jusqu’à ce que votre espèce disparaisse en s’aidant de tankAV
ou toute autre arme qu’ils jugeront utiles.
• Générer plusieurs layers pour (dé)crypter le coeur du virus
Toujours le même but, retarder l’inévitable.
• Générer un layer différents pour chaque nouvelle génération de virus
Générer un layer différent pour chaque nouvelle génération de virus rends sa
reconaissance par signature beaucoup plus difficile.
Evoluons ...
5
3
3.1
Le métmorphisme, ou la modification par équivalence
Introduction et explications sommaires
Les créateurs de virus ont longtemps cherchés à contrer les méthodes de détection par signature, il a souvent été constaté qu’une nouvelle variante d’un
virus bien connu avait été modifié de quelques instructions et pouvait outre
passer ainsi cette technique de détection par signature. Or modifier un programme d’une centaine d’instructions par leurs équivalence demande beaucoup
de temps, ce qui est en sois très rébarbatif, d’où la naissance de moteurs métamorphiques qui se chargent de se travail comme des grands.
D’après les travaux que j’ai pu trouvé à ce sujet, la majorité des moteurs métamorphique utilisent des algorithmes relativement poussés concernant la forme
binaire des instructions, ici nous utiliserons une table de correspondance pour
faciliter la tâche, cependant cette technique, reste limitée par le nombres de
correspondances entrées et est donc bien inférieur à celle généralement utilisé.
Les moteurs métamorphiques sont donc en réalitée des programmes permettant
de modifier les instructions d’un programme afin de changer sa suite d’opcodes
tout en gardant les résultats initiallement escomptés.Donc, cela revient à lire
une instruction, à l’analyser, puis à la remplacer par une (ou plusiers) instruction(s) ayant le même effets.
3.2
Analyse des faits et théorie avancée
Le polymorphisme nous permet d’empêcher, ou du moins de retarder, la
possibilitée de création d’une signature concernant le coeur du virus. Cependant
le layer de chiffrement resteras le même, et il est ainsi possible d’en créer une
signature, et donc d’identifier le virus. L’évolution du polymorphisme vers le
métamorphisme va nous permettre de palier à cette faiblesse non-négligeable.
Récapitulons, nous avons besoin d’empêcher la création d’une signature pour
le layer de chiffrement. Pour cela nous disposons du métamorphisme qui va
nous permettre de changer des opcodes par leurs équivalent à chaque nouvelle
génération du virus et ainsi de rendre théoriquement impossible la génération
d’une signature.
6
Pour créer un moteur métamorphique nous aurons besoin :
• De connaitre la taille de l’opcode à modifier et d’avoir des informations
sur l’instructions (désassemblage)
Nom : BeaEngine .
T a i l l e : 294 336 o c t e t s .
Supporte : t o u t e s l e s i n s t r u c t i o n s i n t e l r é f é r e n c é e s dans
l e s manuels i n t e l de novembre 2006 e t notamment l e s FPU,
MMX, SSE , SSE2 , SSE3 , SSSE3 , SSE4 . 1 , SSE4 . 2 e t VMX.
Auteur : BeatriX .
Equipe : French R e v e r s e E n g i n e e r i n g Team .
• D’un tableau contenant certaines instructions et leurs équivalents utilisable ainsi que leurs tailles.
Je limiterais le nombre d’opcodes référencés dans ce tableau au strict minium
pour notre exemple. Vous pouvez en trouver déjà conçus, ou le créer vous même,
c’est à votre guise.
Voici un schéma grossier résument ce qu’un moteur basique de polymorphisme
avancée devrais réaliser :
7
1. Récupération de la taille de l’instruction à analyser et d’informations à
son sujet
2. Récupération de l’instruction
3. Recherche d’instructions équivalentes dans le tableau d’équivalences
4. Vérification de la taille de l’instruction équivalente
5. Si la taille est supérieur à celle de l’instruction première, on cherche une
autre instructions
6. Si la taille est inférieur ou égal à celle de l’instruction première, on modifie
le layer et on pad de nop si nécessaire
Voici le résultat attendu sur une fonction de type DeltaOffset, servant à
récupérer l’adresse de Kernel32.dll en mémoire sans utiliser l’API LoadLibrary.
DeltaOffset original
start :
mov e s i , dword p t r [ e s p ]
and e s i , 0FFFFF0000h
DeltaOffset :
sub e s i , 1000 h
cmp word p t r [ e s i ] , 5A4Dh
jne DeltaOffset
mov hKrn32 , e s i
DeltaOffset métamorphisé : Generation 1
start :
mov eax , dword p t r [ e s p ]
push eax
and dword p t r [ e s p ] , 0FFFFF0000h
DeltaOffset :
sub dword p t r [ e s p ] , 1000 h
mov ecx , dword p t r [ e s p ]
cmp word p t r [ e c x ] , 5A4Dh
jne DeltaOffset
l e a eax , hKrn32
pop dword p t r [ eax ]
Etc ... Un énorme point faible du métamorphisme est le fait que le code
augmente de taille de façon flagrante entre chaque génération (dans le cas d’un
virus). La premiere solution envisageable est de remplacer opcode par un autre
opcode équivalent et de taille inférieur. Qui-t-à padder l’opcode à l’aide de
NOP (No OPeration) jusqu’à ce que la taille du second opcode+nop soit ségal
à celle du premier. Un peu brute comme méthode n’est-ce pas ? Les pioners
8
en matière de métamorphisme font partit de 29A un fameux groupe de créateur
de virus, on peut citer Z0mBiE, Vecna et The Mental Driller, utilisaient une
toute autre technique pour pallier à ce problème, le "shrinking". Cette méthode
consiste à dé-métamorphiser le code de la génération précédente du virus afin
de repartir d’un code apparemment sain pour éviter la grossissement du code.
Chaque génération avait une taille approximativement égal et était majestuesement différente.
DeltaOffset métamorphisé : Generation 2 utilisation d’un shrinker
start :
pop e s i
push e s i
mov eax , 0FFFFF0000h
and e s i , eax
DeltaOffset :
add e s i , −1000h
push e s i
cmp word p t r [ e s p ] , 5A4Dh
pop e s i
jne DeltaOffset
push e s i
pop hKrn32
Le moteur métamorphise non pas les instructions du Delta Offset de
la génération 1 mais un code ressemblant au code original mais "compressé"
(plusieurs instructions réduites à 1 par exemple,nommé généralement de l’optimisation).
De ce fait le grossissement du code que peut entrainer notre moteur est très réduit du fait que le métamorphisme ne se base pas sur le déjà code obfusqué du
Delta Offset de la génération 1.
Au départ de l’écriture de ce document je pensais fournir un exemple
concret de moteurmétamorphique, cependant j’ai jugé plus raisonnable de ne
faire part que de la théorie et de vous laisser le soin de chercher par vous même
un exemple. De plus mon moteur étant loin d’être un bon exemple, ca va aider
tout le monde :-)
3.3
Limites d’une telle technique
Bien que la modification du code soit évoluée, elle reste facilement mise en
défaite après l’étude du moteur polymorphique et le recoupement des données
utilisé durant le métamorphisme.
Evoluons ...
9
4
La permutation de code, ou la furtivité par le
mélange
4.1
Introduction et problèmatique
Essayons de voir les choses différemment, si les signatures se basent sur une
suite d’opcodes, à la place de changer les opcode, il serrait peut-être judicieux
de changer leurs ordres tout en gardant le même résultat afin, encore une fois,
d’empêcher la création d’une signature de manière aisé.
En se basant sur le fait qu’on souhaite changer l’ordre des opcodes, on peut
penser à plusieurs manières de le faire :
• Changer l’ordre de chaque opcode (si possible)
Modifier l’ordre tout en gardant le même résultat, et donc obéir à certaines règles
prédéfinies et stricts, laissant donc peu de place aux fantaisies mais n’ayant pas
moins d’efficacitée face à beaucoup d’émulateur.
• Changer l’ordre de groupes d’opcodes
Et donc avoir différentes redirection de l’execution du programme pour qu’elle
diffère de l’original, ainsi les opcodes ne seront plus écrit dans le même ordre
à l’intérieur du fichier mais l’execution seras exactement la même. Les règles
sont moins stricts mais un travail de retouche des adresses inscrites dans le programme et du PE seras à executer pour ne pas provoquer d’erreurs malencontreuses. De plus, de pâr le fait d’insérer des redirections, la taille du programme
augmenteras (plus ou moins celon le nombres de groupes d’opcodes).
4.2
Théorie et pratique
Je me dois de vous rapeller que ce document ne présente aucunement de
manière suffisamment satisfaisante toutes les différentes manières de modifié
dynamiquement du code. J’ai fait certains choix sur les options présentées et
sur le degré de précision que j’estimais nécesaire de donner. Il en va que finalement, je préfère restait relativement vague et ne fournir aucun code utile : au
final, les personnes n’ayant pas les compétences nécessaire de coder se genre de
moteur n’ont pas envie que je leur donne des codes tout fait (du moins c’est ce
que j’ose espérer).
Ici, nous choisirons premièrement d’étudier la première des deux options suscitées et ensuite de nous pencher brièvement sur la deuxième. Changer l’ordre
de chaque opcode est, comme je vous l’expliquais, très contraint. Il n’y a que
des règles plus ou moins poussées qui le permettent et quasiment aucune place
pour de l’aléatoir. Premièrement, attardons nous sur l’analyse des opérandes
Source et Destination d’instructions.
mov eax , ebx
10
soit
Destination = Source
Ainsi, pour l’instruction MOV, la première opérande seras la destination et la
seconde la source. Il en va de même pour quasiment toutes les instructions de
déplacement de données.
Il en va de même pour les instructions arithmétiques simples (ne pas y inclure
(I)MUL et (I)DIV), ici on prendras pour exemple ADD, la Destination est encore la première opérande, seulement la Source comprends en réalité les deux
opérandes :
add eax , ebx
soit
Destination = Destination + Source
Il n’est pas difficile, avec un peu de recherche et d’expérience, de faire
l’analyse de la majorité des instructions générales.
Ainsi, à l’aide de règles plus ou moins complexe (dépendant directement du
niveau de complexité de l’obfuscation voulu) d’obfuscation, la permutation d’opcodes
s’effectue relativement aisément (pour un premier jet tout du moins). Par exemple, étudions ce bout de code et essayons de le permuter simplement
mov eax , 42
mov ecx , 3
add eax , e c x
sub eax , 2
xchg ebx , eax
dec e c x
movzx eax , cx
En nommant l’instruction actuelle comme Instr1 comportant une opérande
source Source1 et une opérande destination Dest1 et l’instruction suivant Instr2
(avec les opérandes Source2 et Dest2), on peut permuter des groupes de 2 instructions aisément. Regardons à quoi ressemblerait une règle de permutation
permettant d’avoir un résultat convenable.
[title=Tiré de "Permutation conditions" de Z0mbie]
S i Dest1 != Dest2 OU
Dest1 != S o u r c e 2 OU
Dest2 != S o u r c e 1
Permutation ( I n s t r 1 , I n s t r 2 )
FinSi
ProchaineInstr ()
11
Ce qui donne comme résultat, après plusieurs passage :
mov ecx , 3
mov eax , 42
add eax , e c x
dec e c x
sub eax , 2
xchg ebx , eax
movzx eax , cx
4.3
Points forts, faiblesses et améliorations
Le gros points fort de la permutation de code, à l’inverse du métmorphisme,
est que une fois modifié, le code garde la même taille qu’à l’origine. Hélas,
bien que cette technique soit puissante elle dépends beaucoup des instructions
et de l’ordre de celles-ci. Par exemple, pour une fonction de cryptographie
relativement avancée, la mutation ne seras que minime et effectué, au mieux, aux
deux extrémités de la fonction : la création d’une signature sur cette fonction
seras donc relativement aisé.
Evoluons ...
12
5
Outroduction : vers l’infini et au dela !!
Ces techniques sont souvent utilisées séparemment, ce qui est extrêment
dommage. Cependant on voit aujourd’hui apparaitre des malwares posédant
une très bonne défense qui se base sur l’obfuscation et d’autres astuces antireversing. D’ici peut on seras très certainement ammené à étudier des malwares
comportant un moteur métamorphique et un moteur de permutations (C’est le
cas de MetaPHOR, seulement je le considére plus comme un PoC, un très beau
travail technique, que comme un malware), le tout couplé à des centaines de
layers, du junkcode et autres techniques folkloriques du reverse-engineering ;)
Devoir analyser un worm comportant cette défense est chose hardue, et le détecter de façon automatique l’est encore plus, cela représenterais une menace
très importante si le tout est bien ficellé.
L’évolution vers d’autres techniques d’obfuscations ne se feras qu’avec de nouvelles idées et technologies, on pourrait par exemple se baser sur les hypervisor
pour imaginer un déchiffrement furtif (à l’heure actuelle). Ou encore généré
la clef de chiffrement des layers en fonctions du contenu de zones mémoires
choisient aléatoirement, ou du contenu de la mémoire vidéo, qui sait... Les possibilitées sont quasi infinis, la simple restriction est l’imagination et le courage
du codeur.
13
6
Liens et remerciements
Merci à Kaine(God certified ?), Neitsa, BeatriX, Baboon, Virtualabs, sh4ka,
Guett@, 0vercl0Ck, Natsouillette ainsi que la majorité des zigotos que j’ai pu
rencontré depuis mes débuts dans ce monde qui me bouffe tout mon temps ;]
Liens :
Je vous conseille de jeter un oeil aux liens suivant si le domaine vous intéresse
et que vous souhaitez le découvrir plus en profondeur.
- http://www.29a.net/
- http://vx.netlux.org/lib/
-
http://vx.netlux.org/lib/vmd03.html
http://vx.netlux.org/lib/agw00.html
http://vx.netlux.org/lib/ayt01.html
http://vx.netlux.org/lib/ajm02.html
http://vx.netlux.org/lib/vzo10.html
http://vx.netlux.org/lib/vzo18.html
http://vx.netlux.org/lib/vzo20.html
http://vx.netlux.org/lib/vmd01.html
http://vx.netlux.org/lib/vzo06.html
http://vx.netlux.org/lib/vzo43.html
http://vx.netlux.org/lib/vzo15.html
http://vx.netlux.org/lib/vmn03.html
Analyse de virus :
- http://www.openrce.org/articles/full_view/29
- http://www.openrce.org/articles/full_view/27
Contrer l’obfuscation :
- http://vx.netlux.org/lib/adb00.html
- http://vx.netlux.org/lib/aww00.html
14