Question:
Quels types de mesures puis-je prendre pour rendre mon application C ++ plus difficile à rétroconcevoir?
WilliamKF
2013-03-21 16:05:11 UTC
view on stackexchange narkive permalink

Si je construis une application C ++ et que je souhaite rendre plus difficile le reverse engineering, quelles étapes puis-je suivre pour ce faire?

  • Le choix du compilateur affecte-t-il cela?

    / li>

  • Qu'en est-il des indicateurs du compilateur, sans doute qu'un niveau d'optimisation élevé aiderait, qu'en est-il des autres indicateurs?
  • Le décapage des symboles aide-t-il et ne pas construire avec des symboles de débogage?
  • Je crypte des données internes telles que des chaînes statiques?
  • Quelles autres mesures puis-je prendre?
Un répondre:
#1
+36
Peter Andersson
2013-03-21 17:24:09 UTC
view on stackexchange narkive permalink
Le choix d'un compilateur a des effets minimes sur la difficulté de rétro-ingénierie de votre code. Les éléments importants à minimiser sont tous liés aux fuites d'informations de votre code. Vous voulez au moins désactiver toutes les informations de type d'exécution (RTTI). La fuite des informations de type et la simplicité du jeu d'instructions de la machine virtuelle sont l'une des raisons pour lesquelles le code CLR et JVM est plus facile à rétro-ingérer. Ils ont également un JIT qui applique des optimisations au code qui peuvent réduire la force de l'obfuscation. L'obscurcissement est fondamentalement l'opposé de l'optimisation et de nombreux obfuscations sont résolus en appliquant d'abord une passe d'optimisation.

Informations de débogage

Je vous conseille également désactiver toutes les informations de débogage, même si elles ne fuient aucune information très importante aujourd'hui, elles pourraient le faire demain. La quantité de fuite d'informations à partir des informations de débogage varie d'un compilateur à l'autre et du format binaire au format binaire. Par exemple, Microsoft Visual C ++ conserve toutes les informations de débogage importantes dans une base de données externe, généralement sous la forme d'un PDB. Le plus que vous pourriez fuir est le chemin que vous avez utilisé lors de la construction de votre logiciel.

Chaînes

En ce qui concerne les chaînes, vous devez absolument les chiffrer si vous en avez besoin pas du tout. Je viserais à remplacer tous ceux qui sont destinés au traçage des erreurs et à la journalisation des erreurs par des énumérations numériques. Les chaînes qui révèlent toute sorte d'informations sur ce qui se passe actuellement dans votre binaire doivent être indisponibles. Si vous cryptez les chaînes, elles seront décryptées. Essayez de les éviter autant que possible.

API système

Les importations d'API système constituent une autre source importante de fuite d'informations. Vous voulez vous assurer que toute fonction importée qui a une signature connue est bien cachée et ne peut pas être trouvée à l'aide de l'analyse automatique. Donc, un tableau de pointeurs de fonction de quelque chose comme LoadLibrary / GetProcAddress est hors de question. Tous les appels aux fonctions importées doivent passer par une fonction à sens unique et doivent être intégrés dans un bloc obscurci.

Bibliothèques d'exécution standard

Quelque chose de beaucoup des personnes oublient de prendre en compte les informations divulguées par les bibliothèques standard, telles que le moteur d'exécution de votre compilateur C ++. J'éviterais complètement son utilisation. En effet, la plupart des rétro-ingénieurs expérimentés auront des signatures préparées pour de nombreuses bibliothèques standard.

Obfuscation

Vous devriez également couvrir tout code critique avec une sorte d'obscurcissement lourd. Certains des masques les plus lourds et les moins chers actuellement sont CodeVirtualizer / Themida et VMProtect. Sachez cependant que ces packages ont une abondance de défauts. Ils transformeront parfois votre code en quelque chose qui ne sera pas l'équivalent de l'original ce qui peut conduire à une instabilité. Ils ralentissent également considérablement le code obscurci. Un facteur 10000 fois plus lent n'est pas rare. Il y a aussi le problème du déclenchement de plus de faux positifs avec un logiciel antivirus. Je vous conseille de signer votre logiciel en utilisant une autorité de certification réputée.

Séparation des blocs fonctionnels

La séparation du code en fonctions est une autre chose qui facilite le reverse engineering d'un programme. Cela s'applique en particulier lorsque les fonctions sont obscurcies, car cela crée des limites autour desquelles l'ingénieur inverse peut raisonner sur votre logiciel. De cette façon, l'ingénierie inverse peut résoudre votre programme de manière divisée et conquérir. Idéalement, vous voudriez que votre logiciel se trouve dans un bloc efficace avec une obfuscation appliquée uniformément à tout le bloc en un seul. Réduisez donc le nombre de blocs, utilisez l'inlining très généreusement et enveloppez-les dans un bon algorithme d'obscurcissement. Le compilateur peut facilement effectuer de lourdes optimisations et ordonner la pile, ce qui rendra le bloc plus difficile à rétroconcevoir.

Exécution

Lorsque vous masquez des informations, c'est important que les informations sont également bien cachées lors de l'exécution. Un ingénieur inverse compétent examinera l'état de votre programme pendant son exécution. Donc, utiliser des variables statiques qui déchiffrent lors du chargement ou en utilisant un emballage qui est complètement décompressé lors du chargement conduira à une recherche rapide. Faites attention à ce que vous allouez sur le tas. Toutes les opérations de tas passent par des appels API et peuvent être facilement enregistrées dans un fichier et raisonnées. Les opérations de pile sont généralement plus difficiles à suivre en raison de leur fréquence. L'analyse dynamique est tout aussi importante que statique. Vous devez être conscient de l'état de votre programme à tout moment et de quelles informations se trouvent où.

Anti-débogage

L'anti-débogage est sans valeur. Ne passez pas de temps dessus. Passez du temps à vous assurer que vos secrets sont bien cachés, que votre logiciel soit au repos ou non.

Emballage et chiffrement du segment de code

Je vais regrouper le chiffrement et le conditionnement dans la même catégorie. Ils servent tous les deux le même objectif et ils ont tous les deux les mêmes problèmes. Pour exécuter le code, la CPU a besoin de voir le texte brut. Vous devez donc fournir la clé dans le binaire. Le seul moyen efficace à distance de crypter et de compresser des segments de code est de les crypter et de les décrypter à des limites fonctionnelles et uniquement si le décryptage se produit lors de l'entrée de la fonction, puis le rechiffrement se produit lorsque vous quittez la fonction. Cela fournira une petite barrière contre le dumping de votre binaire pendant son exécution, mais il doit être associé à une forte obfuscation.

Enfin

Étudiez votre logiciel dans quelque chose comme la version gratuite d'IDA. Votre objectif est de vous assurer qu'il devient pratiquement impossible pour l'ingénieur inverse de trouver une base mentale stable. Moins vous divulguez d'informations et plus l'environnement change, plus il sera difficile d'étudier. Si vous n'êtes pas un ingénieur inverse expérimenté, il est presque impossible de concevoir quelque chose de difficile à inverser.

Si vous concevez un système de protection contre la copie, préparez-vous à le casser mentalement. Assurez-vous d'avoir un plan sur la façon dont vous allez gérer la pause et comment vous assurer que la prochaine version de votre logiciel ajoute suffisamment de valeur pour conduire les mises à niveau. Construisez votre système sur une base solide qui ne peut pas être cassée, ne recourez pas à la génération de vos propres clés de licence en utilisant un algorithme personnalisé caché de la manière que j'ai décrite ci-dessus. Le système doit être construit sur une base cryptographique solide pour l'imprévisibilité des messages.

"utilisez l'inlining très généreusement et enveloppez-les dans un bon algorithme d'obfuscation" <- ajoutez simplement Boost. Boost + inlining + LTCG = enfer sur roues. Des dizaines de copies des mêmes fonctions avec différents registres utilisés pour passer des arguments et des sous-fonctions différemment incorporées, vingt types de pointeurs intelligents, argh!
Haha, ouais, vous pouvez certainement choisir un code complexe fortement basé sur des modèles afin d'obtenir une obfuscation gratuite. N'y avait-il pas un article sur / r / re montrant l'utilisation de modèles uniquement afin de créer un cadre d'obscurcissement C ++ pur avec des prédicats opaques et en utilisant la métaprogrammation de modèles pour générer des nombres aléatoires à l'aide d'un générateur congruentiel linéaire? Il me semble que je m'en souviens au moins.
"Donc, utiliser des variables statiques qui déchiffrent une fois chargées [..] mènera à une recherche rapide". Quelle est l'alternative?
@Sosukodo Je mettrais les variables dans un cache local, probablement sur la pile, je les déchiffrerais là-bas, les utiliserais, puis mettrais à zéro la mémoire. Je voudrais également m'assurer que l'algorithme de décryptage, l'utilisation et la clé sont complètement intégrés dans l'obscurcissement basé sur VM. Il est important qu'il n'y ait pas de moyen facile de trouver et de décrypter toutes les variables par des moyens automatisés. L'intégration de la clé dans le code virtualisé et non dans les données rend cela beaucoup plus difficile. Il est bien sûr très difficile de rendre cela impossible, mais nous essayons de rendre le travail de l'ingénierie inverse aussi fastidieux et ennuyeux que possible.
Je suis d'accord - nous essayons simplement de construire une meilleure serrure. Pouvez-vous donner des liens vers des articles qui traitent de ces choses que vous mentionnez?


Ce Q&R a été automatiquement traduit de la langue anglaise.Le contenu original est disponible sur stackexchange, que nous remercions pour la licence cc by-sa 3.0 sous laquelle il est distribué.
Loading...