Contribuez à SecuObs en envoyant des bitcoins ou des dogecoins.
Nouveaux articles (fr): 1pwnthhW21zdnQ5WucjmnF3pk9puT5fDF
Amélioration du site: 1hckU85orcGCm8A9hk67391LCy4ECGJca

Contribute to SecuObs by sending bitcoins or dogecoins.



Nicolas BRULEZ, (Armadillo) Les participants bloqués sur les obfuscations et les layers de cryptages

Par Rédaction, secuobs.com
Le 21/05/2004


Résumé : Autodidacte, sa carrière débute pour Cartel Sécurité en tant qu'Expert Reverse Engineering conjointement à un rôle de consultant pour Sillicon Realms. En 2002, il devient responsable de la sécurité de la protection Armadillo



Autodidacte, sa carrière débute pour Cartel Sécurité en l'an 2000 en tant qu'Expert Reverse Engineering conjointement à un rôle de consultant pour Sillicon Realms. En 2002, il rejoint justement Silicon Realms en tant que responsable de la sécurité de la protection américaine Armadillo, poste qu'il occupe toujours actuellement. Nicolas Brulez est également rédacteur pour le magazine de sécurité informatique MISC. Différentes participations à des évenements comme SSTIC03-04 , Ecole d'été CEA 2004, Challenge SecuriTech 2004 conjointement à des Cours en tant que professeur à Epitech, au LACO (Limoges), et bientot à l'ESIEA.

Quelle est l'origine de votre collaboration avec le staff du Challenge SecuriTech ?

Il me semble que l'origine de ma collaboration est un message passé sur la mailing list de MISC. Après quelques échanges de mails avec l'organisateur du concours, Pierre Betouin, nous avons décidé d'ajouter un niveau de Reverse complexe pour le Challenge SecuriTech 2004. Pierre m'a parlé du niveau de la première année, et voulait un niveau plus complexe, et surtout plus resistant. Je crois que je ne m'en suis pas trop mal sorti :-)

Quelles sont les différentes protections utilisées dans le niveau 3 du Challenge SecuriTech ?

Les protections utilisées dans le niveau 3 sont principalement de type anti-debugging. Beaucoup d'obfuscations, des layers de cryptage pour protéger le code contre l'analyse statique, des techniques anti-breakpoints, et quelques détections d'outils de cracking. Toutes ses protections ont pour but de retarder la personne qui analyse la protection. La vraie protection étant la clé de cryptage à brute forcer intelligemment.

Peu de validation pour le niveau que vous avez codé, quelle est selon vous la raison principale ?

La première raison qui me vient à l'esprit est la complexité du niveau. Ensuite, le manque d'expérience des candidats dans le reverse engineering "Win32", surtout sur des binaires protégés contre l'analyse. La majorité des participants ont été bloqués sur les obfuscations et les layers de cryptages.

Votre binaire ne peut pas être désassemblé par "IDA", en quoi avez vous modifié la structure du fichier pour le bloquer et comment peut on contrer ces modifications ?

Certaines versions de "IDA" seulement étaient touchées par "cette protection".
Pour bloquer le désassemblage, il suffisait de modifier le "PE Header" pour faire pointer les relocations sur la section "DATA". Etant donné que "IDA" cherchait une structure bien définie pour la relocation, et que la section "DATA" ne contenait pas de structure valide, le désassembleur se mettait à boucler à cause d'une valeur trop grande, et finissait par générer un message d'erreur, et "IDA" se fermait.

Pour détecter cette modification il suffisait de regarder la table des sections. Il n'y aucune section ".reloc" et pourtant "IDA" essaye d'appliquer des relocations. On s'aperçoit très vite que les pointeurs dans la "Directory Table" sont incorrects. En remplaçant l'adresse "RVA" et la taille des relocations dans cette table, le programme se désassemble sans problèmes.

En quoi consistent les techniques d'obfuscations du code et dans quel cas sont elles utilisées ?

Le niveau contenait deux types d'obfuscations. En premier, il s'agissait de macros placées entre les vraies instructions pour rendre le code illisible dans un désassembleur/debugger. J'ai programmé pour l'occasion des macros en assembleur que j'utilisais comme ceci :

- "obfuscation obfuscation5 code réel obfuscation3 code réel obfuscation2 code réel obfuscation4 obfuscation6 code réel".

Il faut donc tracer avec beaucoup d'attention pour voir les instructions intéressantes. Le second type d'obfuscation est au niveau du déroulement du programme. Pour les layers de cryptage par exemple, j'utilise des tableaux de pointeurs vers des routines qui sont placées dans le désordre. Je les appele à l'aide d'un index dans le tableau. L'ordre d'exécution est donc différent de l'ordre du code dans l'exécutable compilé.

J'ai généralement couplé ces deux types d'obfuscation pour rendre le code incompréhensible par une analyse statique.

Pourquoi n'avez vous pas utilisé de checksum ?

Je n'ai pas utilisé de checksum car il s'agissait d'un niveau destiné à être cassé. Les participants pouvaient donc remplacer les macros d'obfuscation par des "nop" pour rendre le code plus lisible sous un désassembleur.

Pouvez vous nous expliquer l'utilité des BPM/BPX & les différentes manières de s'en prévenir ?

Il s'agit de deux types de point d'arrêts différents.Les BPX sont des breakpoints logiciels, ils utilisent l'interruption 3 (0xCC) et modifient le code. Les debuggers les utilisent en général par défaut car il n'y a virtuellement aucune limite dans
le nombre de BPX. les BPX peuvent être utilisés dans le cas d'applications non protégées, mais dans l'étude de protection, il est recommandé d'utiliser les BPM. Les BPX modifient le code pour y insérer une int 3, donc les contrôles d'intégrités peuvent détecter la présence d'un debugger.

Pour rendre le debugging plus compliqué, il est possible de faire des checksums de parties de code sensibles et de les utiliser comme clefs de décryptage. La présence d'un seul breakpoint affectera le décryptage de l'application. Il est aussi possible de scanner le code à la recherche d'int 3, comme je l'ai fait dans le niveau 3, pour détecter les BPX sur les fonctions de l'API windows.

Les BPM sont des breakpoints matériels, c'est à dire qu'on utilise les registres de debug du processeur. Quatre registres sont utilisés pour stocker nos breakpoints: DR0, DR1, DR2 et DR3. Ces breakpoints ne modifient pas le code de l'application que l'on debug, ils sont donc extrêmement utiles pour étudier des protections. Les checksums ne sont pas modifiés, et la detection de breakpoints sur les fonctions de l'API windows sont inutiles contre les BPM. Pour se protéger des BPM, il faut donc travailler sur les registres de debug. Ces derniers ne sont pas accessibles directement en ring 3. (Sous Win9x, en Ring 3 on obtient des fausses valeurs mais aucun crash).

La première méthode, consiste à passer en ring 0 pour accéder aux registres, et donc les effacer. Il existe pourtant une astuce permettant d'y accéder indirectement en restant en ring 3. On utilise les exceptions (ou les fonctions GetThreadContext / SetThreadContext) pour accéder au context de l'application, et donc pouvoir modifier les registres protégés.

Dans le niveau 3, les registres de debug sont mis à zéro pour éviter les BPM. Je modifie aussi le Registre Debug 7. (voir le slide). Certains outils permettent de protéger les debug registers contre ce genre de code. Pour détecter ces outils, il suffit tout simplement de mettre les registres à zéro, et de les lire juste après. Si ils sont bien à zéro, tout s'est bien passé, sinon nous sommes en train de nous faire debugger.

Pouvez vous nous détailler ce que sont les exceptions et les cas dans lesquels elles sont utiles à la protection d'un binaire ?

Dans la protection de binaire, les exceptions sont souvent employées pour accéder au context de l 'application. A partir de la il est possible d'effacer les registres debug pour se protéger des BPM, de changer le registre EIP pour rediriger le programme vers un autre endroit. Une sorte de jmp détourné. En language haut niveau, les exceptions sont souvent gérées à l'aide des instructions "try" et "except".

Dans une protection, il suffit de programmer sois même les "try" et "except", et de générer une erreur. Une personne qui désire attaquer le code, doit comprendre le fonctionnement des Excepts Handlers en assembleur pour pouvoir suivre le code.

Comment bypasser les codes polymorphiques ?

Dans le cadre du niveau 3, il suffisait de remplacer les blocs d'obfuscations par des nops à l'aide d'un outil maison, ou d'un plugin pour son debugger. Dans le cas de checksums utilisés pour décrypter la suite du code, il est beaucoup plus compliqué d'analyser la code de l'application.

Pourquoi choisissez vous de ne pas faire crasher l'application instantanément ?

A l'aide d'un debugger, il est très facile de stopper sur le code qui génère une exception. On retombe donc très rapidement sur le code qui crash l'application, et donc le code de détection. Il suffit ensuite de poser un BPM sur le code de détection, relancer l'application et la modifier en fonction pour contourner la détection.

Comment faire crasher les debuggeurs en utilisant des exceptions ?

Certains debuggers ne savent pas gérer certaines exceptions, et donc crashent. Il est aussi possible d'utiliser la fonction "UnhandledExceptionFilter" pour détecter olly debugger par exemple. Un debugger comme Turbo Debugger (TASM) ne sait pas gérer les exceptions convenablement.

Quels débuggeurs crashent ? lesquels non ?

Turbo Debugger, Ollydbg (selon la configuration ou la méthode utilisée), et sûrement d'autres debuggers Ring 3. Soft ICE n'a aucun problème avec les exceptions.

La protection contre les virtuals machine est elle systématiquement nécessaire ?

Cela dépends du niveau de sécurité voulu. Certaines détections de debuggers ne sont pas compatibles avec les machines virtuelles, il est donc important de détecter ses logiciels pour éviter tout conflits en cas d'émulation. D'un autre coté,il est possible d'utiliser Soft ICE sur les dernières version de Vmware, ce qui implique une version moins protégée, et donc un environnement de choix pour attaquer une protection.

Est il possible de spoofer un IDT valide en utilisant un émulateur ? Si oui comment ?

Il est tout à fait possible de spoofer une IDT à l'aide d'un émulateur. Cependant, cela dépends de l'émulateur et de la façon dont il fonctionne. Dans le cas du niveau 3, il était tout à fait possible de retourner l'adresse de la vrai IDT en virtualisant (appel de la vraie instruction sidt). Ensuite cela dépend de ce que l'on veut faire.

A quoi servent les 500 layers initiaux de cryptage et comment les décrypter efficacement ?

Les layers servent avant tout à décourager les personnes qui essaient de tracer le programme. Après une centaine de layers (voir bien avant), une personne va changer de méthode car il peut très bien y avoir plusieurs milliers de layers. L'intérêt est aussi de protéger le code qui décrypte l'application protégée. Cela est utile pour éviter les unpackers, surtout quand les layers sont polymorphes.
(Unpackers statiques).

Pour les décrypter facilement, il suffisait de poser des BPM "intelligents", et d'utiliser par exemple, les macros de Soft ICE pour s'aider. En effet les layers étaient de taille fixe, et sans code anti BPM. En mettant des BPM après chaque layer (grace à l'adresse actuelle + taille layer) il était possible de les passer un par un, sans avoir à les tracer.

Comment pouvait on reconnaître que le cryptage utilisé était un RC4 ?

Le RC4 pouvait être identifié grace à l'initialisation des sboxs par exemple.
Il n'était toutefois pas nécessaire de reconnaître l'algorythme pour réussir le niveau. Il est encore une fois question d'expérience et pratique.

Comment reconnaître que le mot de passe était ensuite hashé par un crc32 puis xoré aux 4 premieres lettres ?

En traçant le code, on reconnaît très facilement un CRC. La valeur retournée était un double mot placé dans un registre, donc on en déduit qu'il s'agit d'un CRC32.

Pour ce qui est du Xor et des 4 premières lettres du password, juste après le CRC32, on peut voir des operations sur le résultat du CRC32. Un Xor travaillant sur un pointeur, sur le password en l'occurrence, et un double mot. Un double mot fait 4 lettres, donc il s'agit des quatre premières lettres du password, xoré par le CRC32.

Tout est facilement déterminable en lisant le code ou en le debuggant.

Quelle est la méthodologie pour casser ce genre de code ?

Lire le code et bien le comprendre. Dumper les parties de code qui ont l'air importantes, et les commenter pour avoir un meilleur aperçu du problème. Dans le cas du niveau 3, on s'apercevait rapidement qu'un décryptage était effectué sur la section code du binaire protégé. Aucune vérification n'était faite sur le mot de passe.

Pour réussir, il fallait tout d'abord brute-forcer la clé RC4, en utilisant le padding entre les sections comme texte clair connu. On pouvait améliorer le brute-force, en recherchant des instructions potentiellement présentes dans le code décrypter, sur les 500 premiers octets par exemple.

En cas de réussite, on pouvait tout décrypter et tester le padding en fin de section. Ensuite pour trouver le vrai password à partir de la clé RC4, il suffisait d'inverser l'algorythme et de faire un mini brute force. Voir le slide pour plus d'informations.

Pourquoi avoir inséré deux layers de cryptage avant le RC4 ?

Pour forcer l'analyse complète du code. Une personne qui n'aurait pas tracé entièrement la protection, aurait essayé de brute-forcer sur la section code AVANT décryptage des deux premiers layers, ce qui implique un brute-force inutile et voué à l'échec, même si la méthodologie est bonne. Juste une petite barrière en plus.

Comment pouvez t'on identifier qu'un rsa 256 était ensuite utilisé conjointement à un hash de type RipMD 128 ?

Pour identifier le RSA 256, il fallait regarder les paramètres passés aux fonctions présentes dans la section code du programmé protégé. Il s'agit du programme originel, celui qui a été protégé. Il n'y avait aucune obfuscation à cet endroit la.

Une fonction prenant en paramètre un grand nombre (N), ainsi qu'un petit nombre (e), et un message (M) a de forte chance d'être un RSA. En traçant le code de la fonction, on pouvait aussi identifier les operations du RSA. Le grand nombre n'étant pas un nombre premier, il ne pouvait donc pas s'agir de Elgamal. (Assez similaire dans l'idée).

Pour identifier le hash, un coup d'oeil rapide permettait d'identifier une initialisation, courante pour les hash tel que MD5, SHA-1 etc. Une simple recherche des constantes utilisées dans le code sur google permettait d'identifier le hash utilisé. Il existe aussi des outils permettant de calculer différents hash, il était donc possible de comparer les résultats pour identifier l'algorithme.

Comment récupérer la clé privée RSA ?

A l'aide de Mathematica, RSAtool, ou même un outil fort puissant: PPSIQS Ver 1.1 par Satoshi Tomabechi. Il ne faut que 15 minutes pour factoriser le RSA sur une machine récente. RSAtool de part la méthode employée aura besoin d'au moins une heure.

La seconde personne à avoir réussi mon niveau a mis 5 heures pour factoriser N via RSAtool. Le plus simple est de trouver P et Q à l'aide de PPSIQS, puis ensuite de calculer la clé privée à l'aide de RSA tool. (moins d'une seconde)

Comment trouver le bon mot de passe à partir de cette clé puis finalement s'enregistrer ?

En analysant le code on en déduisait ceci : - SI RSAdecrypt(contenu rstack.key) = hash du password ALORS enregistré. Il suffit donc de hasher le bon password avec le RipMD 128 puis de faire un RSADecrypt dessus en utilisant la clé privée, puis stocker le résultat dans le fichier rstack.key pour s'enregistrer.

Est-il possible et utile de tracer toute l'exécution du code et de réaliser une analyse statique (en comparant notamment différentes executions) ?

Dans le cas de cette protection, il était possible de dumper le process lorsque la messagebox "Try Again" apparaissait, pour ensuite retirer les obfuscations et analyser le code statiquement. A partir de la, on découvre le test sur la ligne de commande, le CRC32 et le XOR.

Ensuite en entrant un mot de passe incorrect le programme boucle sur lui-même, il est alors possible de dumper le process, pour l'analyser statiquement. Il est certainement possible de faire tout, ou presque tout statiquement de cette manière, mais il y a des chances de louper certaines parties du code.

Ce type d'attaque n'est généralement pas très efficace. J'aurais très bien pu recrypter les parties de code déjà utilisées pour empêcher les analyses par dump.

Pouvez vous nous présenter les fonctionnalités & caractéristiques de The Armadillo Software Protection System ?

Armadillo est une protection d'exécutable windows PE. Elle permet de protéger les exécutables contre la modification (en mémoire ou sur le disque), le désassemblage, le debugging, l'unpacking (dump + reconstruction), et permet d'ajouter à un exécutable compilé des capacités de gestion de licenses poussées.

Par exemple, il est tout à fait possible de limiter "Regedit.exe" à 30 lancements, d'ajouter le logo de votre société au lancement de l'application, et de bloquer cette application au profile hardware de votre machine. Il est possible d'ajouter des messages de rappels d'enregistrement au démarrage, lors de la sortie du programme, et d'ajouter une gestion de licence complète au binaire.

La cryptographie à clé publique est utilisée pour assurer l'enregistrement des applications protégées. L'application originale est modifiée à plusieurs niveaux pour que celle si soit dépendante de la protection. La table d'import est complètement retirée, et le code qui appel les fonctions Windows est modifiée en dur. Il faut donc reconstruire les appels aux fonctions, en plus de la table d'import. Les outils comme ImpRec (Reconstruction de Table d'imports) sont inutiles. Certaines partie du code (plusieurs ko) sont retirés de l'application puis placées dans la protection. Ses parties ne seront jamais remises à leur place, mais plutôt exécutées dans le "context" de la protection. Certaines instructions dans l'application protégée sont aussi émulées par la protection.

Il est virtuellement impossible de reconstruire un binaire identique à celui d'origine. (Toutes les protections sont crackables, je parle de reconstruction identique du binaire)

Quels sont les principales différences fonctionnelles entre une protection basée sur ce logiciel & la protection que vous appliquée au binaire du challenge ?

La protection utilisée pour le niveau 3 du challenge de SecuriTech est une version bridée du "packer" employé une fois que Armadillo a protégé une application. (une surcouche). Autrement dit, cela ne représente que 2% des protections qu'offre Armadillo.

La protection utilisée dans le niveau 3 ne contient pas de code anti dump. Il est tout à fait possible de dumper le process lors du saut vers le point d'entrée original. La section code, une fois décryptée est complète, et identique à la section d'origine, il est donc très simple de reconstruire le binaire.

Armadillo modifie les applications pour les rendre dépendantes de la protection, ce qui implique beaucoup plus de travaille pour reconstruire un binaire. Il est obligatoire de programmer des outils maisons pour pouvoir reconstruire une application protégée, et les connaissances nécessaires sont suffisamment dures à assimiler, pour se protéger contre la majorité des crackers.


Nicolas Brulez, merçi de votre collaboration.