Existe-t-il des cas intelligents de modification du code d’exécution?

Pouvez-vous penser à des utilisations légitimes (intelligentes) pour la modification de code à l’exécution (programme modifiant son propre code à l’exécution)?

Les systèmes d’exploitation modernes semblent désapprouver les programmes qui le font, car cette technique a été utilisée par les virus pour éviter la détection.

Tout ce que je peux penser est une sorte d’optimisation d’exécution qui supprime ou ajoute du code en connaissant quelque chose à l’exécution qui ne peut pas être connu au moment de la compilation.

    Il existe de nombreux cas valides pour la modification du code. La génération de code à l’exécution peut être utile pour:

    • Certaines machines virtuelles utilisent la compilation JIT pour améliorer les performances.
    • La génération de fonctions spécialisées à la volée est depuis longtemps courante en infographie. Voir, par exemple, Rob Pike et Bart Locanthi et John Reiser Hardware Software Tradeoffs pour Bitmap Graphics on the Blit (1984) ou cette publication (2006) de Chris Lattner sur l’utilisation par LLVM de la spécialisation runtime dans leur stack OpenGL.
    • Dans certains cas, les logiciels recourent à une technique appelée trampoline qui implique la création dynamic de code sur la stack (ou à un autre endroit). Les exemples sont les fonctions nestedes de GCC et le mécanisme de signal de certains Unices.

    Parfois, le code est traduit en code au moment de l’exécution (cela s’appelle la traduction binary dynamic ):

    • Des émulateurs comme Rosetta d’Apple utilisent cette technique pour accélérer l’émulation. Un autre exemple est le logiciel de morphing de code de Transmeta.
    • Des débogueurs et des profileurs sophistiqués tels que Valgrind ou Pin l’ utilisent pour instrumenter votre code pendant son exécution.
    • Avant que des extensions ne soient apscopes au jeu d’instructions x86, les logiciels de virtualisation tels que VMWare ne pouvaient pas exécuter directement du code x86 privilégié au sein de machines virtuelles. Au lieu de cela, il devait traduire toutes les instructions problématiques à la volée en un code personnalisé plus approprié.

    La modification du code peut être utilisée pour contourner les limitations du jeu d’instructions:

    • Il y a eu un temps (il y a longtemps, je sais), où les ordinateurs n’avaient pas d’instructions pour revenir d’un sous-programme ou pour adresser indirectement de la mémoire. Le code auto-modifiable était le seul moyen d’ implémenter des sous-routines, des pointeurs et des tableaux .

    Plus de cas de modification de code:

    • De nombreux débogueurs remplacent les instructions pour implémenter des points d’arrêt .
    • Certains éditeurs de liens dynamics modifient le code à l’exécution. Cet article fournit quelques informations sur la relocalisation à l’exécution des DLL Windows, qui constitue en réalité une forme de modification de code.

    Cela a été fait en infographie, en particulier des logiciels de rendu à des fins d’optimisation. A l’exécution, l’état de nombreux parameters est examiné et une version optimisée du code raster est générée (éliminant potentiellement beaucoup de conditions), ce qui permet de rendre les primitives graphiques, par exemple les sortingangles, beaucoup plus rapidement.

    Une raison valable est que le jeu d’instructions ASM ne contient pas d’instructions nécessaires que vous pouvez créer vous-même. Exemple: sur x86, il n’y a aucun moyen de créer une interruption pour une variable dans un registre (par exemple, faire une interruption avec un numéro d’interruption dans ax). Seuls les nombres const codés dans l’opcode étaient autorisés. Avec le code auto-modifiable, on pourrait émuler ce comportement.

    Skynet, par exemple, créera un microprocesseur révolutionnaire capable de modifier son propre code à l’exécution et de prendre conscience de lui-même pour pouvoir se révolter contre ses propres créateurs.

    Certains compilateurs l’utilisaient pour l’initialisation des variables statiques, évitant le coût d’un conditionnel pour les access ultérieurs. En d’autres termes, ils implémentent “exécuter ce code une seule fois” en écrasant ce code avec des no-ops la première fois qu’il est exécuté.

    Il y a beaucoup de cas:

    • Les virus utilisent couramment du code auto-modifiable pour “décoder” leur code avant leur exécution, mais cette technique peut également être utile pour contrer l’ingénierie inverse, le craquage et le piratage indésirable.
    • Dans certains cas, il peut y avoir un point particulier lors de l’exécution (par exemple immédiatement après la lecture du fichier de configuration) lorsque l’on sait que – pour le rest de la durée du processus – une twig particulière sera toujours ou jamais prise. en vérifiant une variable pour déterminer la voie à suivre, l’instruction de twig elle-même peut être modifiée en conséquence
      • Par exemple, on peut savoir qu’un seul des types dérivés possibles sera traité, de sorte que la dissortingbution virtuelle puisse être remplacée par un appel spécifique
      • Après avoir détecté le matériel disponible, l’utilisation d’un code correspondant peut être codée en dur
    • Le code inutile peut être remplacé par des instructions sans opération ou un saut au-dessus, ou le prochain bit de code doit être directement mis en place (plus facile si vous utilisez des opcodes indépendants de la position)
    • Le code écrit pour faciliter son propre débogage peut injecter une instruction d’interruption / signal / interruption attendue par le débogueur à un emplacement stratégique.
    • Certaines expressions de prédicats basées sur une entrée utilisateur peuvent être compilées en code natif par une bibliothèque
    • Insertion d’opérations simples qui ne sont pas visibles jusqu’à l’exécution (par exemple depuis une bibliothèque chargée dynamicment) …
    • Ajout conditionnel d’auto-instrumentation / étapes de profilage
    • Des fissures peuvent être implémentées en tant que bibliothèques qui modifient le code qui les charge (pas de modification “autonome”, mais nécessitent les mêmes techniques et permissions).

    Les modèles de sécurité de certains systèmes d’exploitation signifient que le code auto-modifiable ne peut pas s’exécuter sans les privilèges root / admin, ce qui le rend peu pratique pour un usage général.

    De Wikipedia:

    Les logiciels d’application exécutés sous un système d’exploitation avec une sécurité W ^ X ssortingcte ne peuvent pas exécuter d’instructions dans les pages auxquelles ils sont autorisés à écrire: seul le système d’exploitation lui-même est autorisé à écrire des instructions et à les exécuter ultérieurement.

    Sur ces systèmes d’exploitation, même les programmes comme la machine virtuelle Java ont besoin de privilèges root / admin pour exécuter leur code JIT. (Voir http://en.wikipedia.org/wiki/W%5EX pour plus de détails)

    Le système d’exploitation Synthesis a essentiellement évalué votre programme en fonction des appels API et a remplacé le code du système d’exploitation par les résultats. L’avantage principal est que beaucoup de vérifications d’erreurs ont disparu (parce que si votre programme ne demande pas au système d’exploitation de faire quelque chose de stupide, il n’est pas nécessaire de le vérifier).

    Oui, c’est un exemple d’optimisation d’exécution.

    Il y a de nombreuses années, j’ai passé une matinée à essayer de déboguer du code auto-modifiable, une instruction a modifié l’adresse cible de l’instruction suivante, c’est-à-dire que je calculais une adresse de twig. Il a été écrit en langage d’assemblage et a parfaitement fonctionné lorsque j’ai parcouru le programme une instruction à la fois. Mais quand j’ai couru le programme il a échoué. Finalement, j’ai réalisé que la machine récupérait 2 instructions de la mémoire et (comme les instructions étaient en mémoire), l’instruction que je modifiais avait déjà été extraite et la machine exécutait donc la version non modifiée (incorrecte) de l’instruction. Bien sûr, lorsque j’étais en train de déboguer, il ne s’agissait que d’une instruction à la fois.

    Mon point, le code auto-modifiable peut être extrêmement désagréable à tester / déboguer et a souvent des hypothèses cachées quant au comportement de la machine (que ce soit matériel ou virtuel). De plus, le système ne pourrait jamais partager les pages de codes entre les différents threads / processus exécutés sur les machines (maintenant) multi-core. Cela va à l’encontre de nombreux avantages de la mémoire virtuelle, etc. Cela invaliderait également les optimisations de twig effectuées au niveau du matériel.

    (Remarque – je n’inclus pas JIT dans la catégorie du code auto-modifiable. JIT traduit d’une représentation du code à une autre représentation, il ne modifie pas le code)

    Dans l’ensemble, c’est juste une mauvaise idée – vraiment bien, vraiment obscure, mais vraiment mauvaise.

    bien sûr – si tout ce que vous avez est un 8080 et ~ 512 octets de mémoire, vous devrez peut-être recourir à de telles pratiques.

    Du sharepoint vue d’un kernel de système d’exploitation, chaque compilateur et exécuteur de liens Just In Time exécute une auto-modification du texte du programme. Un exemple frappant serait l’interprète de script V8 ECMA de Google.

    Vous connaissez le vieux châtaignier, il n’ya pas de différence logique entre le matériel et le logiciel, on peut aussi dire qu’il n’ya pas de différence logique entre le code et les données.

    Qu’est-ce qu’un code auto-modifiable? Code qui met des valeurs dans le stream d’exécution afin qu’il puisse être interprété non pas comme des données mais comme une commande. Bien sûr, il existe un sharepoint vue théorique dans les langages fonctionnels, il n’y a pas vraiment de différence. Je dis sur e peut le faire de manière simple dans les langages impératifs et les compilateurs / interprètes sans la présomption de statut égal.

    Ce dont je parle, c’est que, dans la pratique, les données peuvent altérer les chemins d’exécution du programme (dans un certain sens, c’est extrêmement évident). Je pense à quelque chose comme un compilateur-compilateur qui crée une table (un tableau de données) que l’on parcourt en analysant, se déplaçant d’un état à l’autre (et modifiant également d’autres variables), comme un programme passe d’une commande à une autre , en modifiant les variables dans le processus.

    Ainsi, même dans les cas habituels où un compilateur crée un espace de code et fait référence à un espace de données totalement séparé (le segment de mémoire), il est toujours possible de modifier les données pour modifier explicitement le chemin d’exécution.

    Une autre raison du code auto-modifiable (en fait un code «auto-générateur») est de mettre en œuvre un mécanisme de compilation juste-à-temps pour les performances. Par exemple, un programme qui lit une expression algébrique et le calcule sur une plage de parameters d’entrée peut convertir l’expression en code machine avant de déclarer le calcul.

    J’ai implémenté un programme utilisant evolution pour créer le meilleur algorithme. Il a utilisé un code auto-modifiable pour modifier le modèle d’ADN.

    Le kernel Linux a des modules de kernel chargeables qui font exactement cela.

    Emacs a aussi cette capacité et je l’utilise tout le temps.

    Tout ce qui prend en charge une architecture de plug-in dynamic est essentiellement en train de le modifier au moment de l’exécution.

    Je réalise des parsings statistiques sur une firebase database continuellement mise à jour. Mon modèle statistique est écrit et réécrit chaque fois que le code est exécuté pour s’adapter aux nouvelles données disponibles.

    Un cas d’utilisation est le fichier de test EICAR qui est utilisé pour tester les programmes antivirus.

    X5O! P% @ AP [4 \ PZX54 (P ^) 7CC) 7} $ EICAR-STANDARD-ANTIVIRUS-TEST-FICHIER! $ H + H *

    Il doit utiliser une modification de code autonome car le fichier exécuté ne doit contenir que des caractères ASCII imprimables / typables dans la plage [21h-60h, 7Bh-7Dh], ce qui serait impossible pour encoder certaines instructions nécessaires.

    Les détails sont expliqués ici

    Le scénario dans lequel cela peut être utilisé est un programme d’apprentissage. En réponse à la saisie de l’utilisateur, le programme apprend un nouvel algorithme:

    1) il recherche la base de code existante pour un algorithme similaire

    2) si aucun algorithme similaire n’est dans la base de code, le programme ajoute simplement un nouvel algorithme

    3) si un algorithme similaire existe, le programme (éventuellement avec l’aide de l’utilisateur) modifie l’algorithme existant pour pouvoir servir à la fois l’ancien objective et le nouvel objective

    Comment faire cela en Java? Quelles sont les possibilités de modification automatique du code Java?

    La meilleure version de ceci peut être les macros Lisp. Contrairement aux macros C qui ne sont qu’un préprocesseur, Lisp vous permet d’avoir access à tout le langage de programmation à tout moment. Il s’agit de la fonctionnalité la plus puissante de Lisp et n’existe dans aucune autre langue.

    Je ne suis en aucun cas un expert, mais demandez à un des fous de parler de ça! Il y a une raison pour laquelle ils disent que Lisp est le langage le plus puissant et que les utilisateurs intelligents ne sont probablement pas corrects.