Objectif de l'instruction NOP et instruction align dans l'assembly x86

15

Cela fait environ un an que j'ai suivi un cours d'assemblage pour la dernière fois. Dans cette classe, nous utilisions MASM avec les bibliothèques Irvine pour faciliter la programmation.

Après avoir parcouru la plupart des instructions, il a dit que l'instruction NOP ne faisait essentiellement rien et ne se souciait pas de l'utiliser. Quoi qu'il en soit, c'était à mi-parcours et il a un exemple de code qui ne fonctionnerait pas correctement, alors il nous a dit d'ajouter une instruction NOP et cela a bien fonctionné. J'ai demandé après le cours pourquoi et ce que cela avait réellement fait, et il a répondu qu'il ne savait pas.

Quelqu'un le sait?

alvonellos
la source
NOP ne fait rien, mais il consomme des cycles. Je ne pense pas qu'on puisse répondre à votre question, sans le code que nous pouvons seulement deviner. Eh bien, je suppose que ce serait une diapositive NOP ...
yannis
11
NOP fait réellement quelque chose. Il incrémente le pointeur d'instruction.
EricSchaefer

Réponses:

37

Souvent, le temps NOPest utilisé pour aligner les adresses des instructions. Cela se produit généralement par exemple lors de l'écriture de Shellcode pour exploiter le débordement de tampon ou la vulnérabilité de chaîne de format .

Supposons que vous ayez un saut relatif de 100 octets vers l'avant et apportez quelques modifications au code. Il est probable que vos modifications gâchent l'adresse de la cible de saut et en tant que tel, vous devrez également modifier le saut relatif susmentionné. Ici, vous pouvez ajouter des NOPs pour pousser l'adresse cible vers l'avant. Si vous avez plusieurs NOPs entre l'adresse cible et l'instruction de saut, vous pouvez supprimer les NOPs pour tirer l'adresse cible vers l'arrière.

Ce ne serait pas un problème si vous travaillez avec un assembleur qui prend en charge les étiquettes. Vous pouvez simplement le faire JXX someLabel(où JXX est un saut conditionnel) et l'assembleur remplacera le someLabelpar l'adresse de cette étiquette. Cependant, si vous modifiez simplement le code machine assemblé (les opcodes réels) à la main (comme cela arrive parfois lors de l'écriture du shellcode), vous devez également modifier manuellement l'instruction de saut. Soit vous le modifiez, soit vous déplacez l'adresse de code cible à l'aide de NOPs.

Un autre cas d'utilisation pour l' NOPinstruction serait quelque chose appelé un traîneau NOP . En substance, l'idée est de créer un tableau d'instructions suffisamment grand qui ne provoque aucun effet secondaire (commeNOPou incrémenter puis décrémenter un registre) mais augmenter le pointeur d'instruction. Ceci est utile par exemple lorsque l'on veut passer à un certain morceau de code dont l'adresse n'est pas connue. L'astuce consiste à placer ledit traîneau NOP devant le code cible, puis à sauter quelque part vers ledit traîneau. Ce qui se passe, c'est que l'exécution se poursuit, espérons-le, à partir du tableau qui n'a aucun effet secondaire et qu'elle avance instruction par instruction jusqu'à ce qu'elle atteigne le morceau de code souhaité. Cette technique est couramment utilisée dans les exploits de débordement de tampon susmentionnés et en particulier pour contrer les mesures de sécurité telles que ASLR .

Encore une autre utilisation particulière de l' NOPinstruction est lorsque l'on modifie le code d'un programme. Par exemple, vous pouvez remplacer des parties de sauts conditionnels par NOPs et ainsi contourner la condition. Il s'agit d'une méthode souvent utilisée pour " craquer " la protection contre la copie des logiciels. Au plus simple, il s'agit simplement de supprimer la construction du code assembleur pour la if(genuineCopy) ...ligne de code et de remplacer les instructions par NOPs et .. Voilà! Aucun contrôle n'est effectué et la copie non authentique fonctionne!

Notez qu'en substance, les deux exemples de shellcode et de cracking font la même chose; modifier le code existant sans mettre à jour les adresses relatives des opérations qui reposent sur un adressage relatif.

zxcdw
la source
2
C'était une merveilleuse réponse, merci d'avoir pris le temps de l'expliquer! Je comprends enfin!
alvonellos
Certains systèmes en temps réel (les API me viennent à l'esprit) vous permettent de «patcher» une nouvelle logique dans un programme existant pendant son exécution. Ces systèmes laissent des NOP avant chaque petit morceau de logique afin que vous puissiez remplacer le NOP par un saut à la nouvelle logique que vous insérez. À la fin de la nouvelle logique, il passera à la fin de la logique d'origine que vous remplacez. La nouvelle logique aura également un NOP à l'avant afin que vous puissiez également remplacer la nouvelle logique.
Scott Whitlock
10

Un nop peut être utilisé dans un intervalle de retard lorsqu'aucune autre instruction ne peut être réorganisée pour y être placée.

lw   v0,4(v1)
jr   v0

Dans MIPS, ce serait un bogue car au moment où le jr lisait le registre v0, le registre v0 n'avait pas encore été chargé avec la valeur de l'instruction précédente.

La solution serait de:

lw   v0,4(v1)
nop
jr   v0
nop

Ceci remplit les créneaux sourds après le mot de chargement et les instructions de registre de saut avec un nop afin que l'instruction de mot de chargement soit terminée avant l'exécution de la commande de registre de saut.

Pour en savoir plus - un peu sur le remplissage SPARC des créneaux de retard . De ce document:

Que peut-on mettre dans le slot de retard?

  • Quelques instructions utiles qui devraient être exécutées, que vous branchiez ou non.
  • Certaines instructions utiles ne fonctionnent que lorsque vous branchez (ou lorsque vous ne branchez pas), mais ne font aucun mal si elles sont exécutées dans l'autre cas.
  • Lorsque tout le reste échoue, une instruction NOP

Qu'est-ce qui NE DOIT PAS être mis dans la tranche de retard?

  • Tout ce qui définit le CC dont dépend la décision de branche. L'instruction de branchement décide immédiatement de branchement ou non, mais elle ne fait le branchement qu'après l'instruction de retard. (Seule la branche est retardée, pas la décision.)
  • Une autre instruction de branche. (Ce qui se passe si vous faites cela n'est même pas défini! Le résultat est imprévisible!)
  • Une instruction "set". Il s'agit en réalité de deux instructions, pas d'une seule, et seulement la moitié d'entre elles se trouveront dans la plage de retard. (L'assembleur vous en avertira.)

Notez la troisième option dans le quoi mettre dans le slot de retard. Le bug que vous avez vu était probablement quelqu'un remplissant l'une des choses qui ne doivent pas être placées dans le slot de retard. Mettre un nop à cet emplacement résoudrait alors le bogue.

Remarque: après avoir relu la question, c'était pour x86, qui n'a pas de créneaux de retard (la ramification bloque plutôt le pipeline). Ce ne serait donc pas la cause / solution du bogue. Sur les systèmes RISC, cela aurait pu être la réponse.


la source
4
Notez que la question est étiquetée x86 et que x86 n'a pas de créneaux de retard. Jamais non plus, car c'est un changement de rupture.
MSalters
6

au moins une raison d'utiliser NOP est l'alignement. Les processeurs x86 lisent les données de la mémoire principale dans des blocs assez gros, et le début du bloc à lire est toujours aligné, donc si l'on a un bloc de code, ce sera beaucoup lu, ce bloc doit être aligné. Cela se traduira par peu d'accélération.

permeakra
la source
Ce n'est pas exactement que le bloc doit être aligné, c'est que vous ne voulez pas avoir à récupérer les deux derniers octets du bloc précédent. Donc, c'est bien de sauter 0x1002, car il y a toujours 14 octets d'instructions dans le bloc aligné de 16B qui contient l'adresse cible, mais pas bien de sauter 0x099D.
Peter Cordes
3

Un but pour NOP (en assemblée générale, pas seulement x86) c'est d'introduire des délais. Par exemple, vous voulez programmer un microcontrôleur qui doit sortir sur certaines LED avec un retard de 1 s. Ce délai peut être implémenté avec NOP (et branches). Bien sûr, vous pourriez utiliser un ADD ou autre chose, mais cela rendrait le code plus illisible; ou peut-être avez-vous besoin de tous les registres.

m3th0dman
la source
1
Habituellement, pour des périodes de temps longues, comme 1 seconde, des minuteries sont utilisées. Les NOPS sont utilisés pour les époques dans un ordre de grandeur de l'horloge - nano et micro secondes.
mattnz
Cela n'a de sens que sur un microcontrôleur, pas sur un x86 moderne. La plupart des codes x86 ne saturent pas la largeur du pipeline des processeurs superscalaires hors service modernes, donc l'ajout d'un NOP entre chaque instruction dans la plupart du code n'aurait qu'un faible impact (je suppose que le nombre de code "moyen" pourrait être 5 à 20% pour avoir doublé le nombre d'instructions, avec du code ne montrant aucun ralentissement mais quelques boucles serrées montrant presque un ralentissement 2x.) Quoi qu'il en soit, l'ancien code x86 croustillant utilisait traditionnellement l' loopinstruction pour les boucles de retard , pas les NOP.
Peter Cordes
3

En général sur le 80x86, les instructions NOP ne sont pas nécessaires pour la correction du programme, bien que parfois sur certaines machines un NOP stratégiquement placé puisse faire exécuter le code plus rapidement. Sur le 8086, par exemple, le code serait récupéré en morceaux de deux octets, et le processeur avait un tampon interne de "pré-lecture" qui pouvait contenir trois de ces morceaux. Certaines instructions s'exécuteraient plus rapidement qu'elles ne pourraient être récupérées, tandis que d'autres instructions prendraient un certain temps à s'exécuter. Pendant les instructions lentes, le processeur tenterait de remplir le tampon de prélecture, de sorte que si les quelques instructions suivantes étaient rapides, elles pouvaient être exécutées rapidement. Si l'instruction suivant l'instruction lente commence sur une limite de mot pair, les six octets suivants d'instructions seront prélus; s'il démarre sur une limite d'octets impairs, seuls cinq octets seront prélus.

Ces problèmes d'alignement de la mémoire peuvent affecter la vitesse du programme, mais ils n'affectent généralement pas l'exactitude. D'un autre côté, il existe des problèmes liés à la prélecture sur les anciens processeurs où un NOP peut affecter l'exactitude. Si une instruction modifie un octet de code qui a déjà été préchargé, le 8086 (et je pense que les 80286 et 80386) exécutera l'instruction préchargée même si elle ne correspond plus à ce qui est en mémoire. L'ajout d'un NOP ou deux entre l'instruction qui modifie la mémoire et l'octet de code qui est modifié peut empêcher la récupération de l'octet de code jusqu'à ce qu'il soit écrit. Notez, en passant, que de nombreux schémas de protection contre la copie exploitaient ce type de comportement; notez également, cependant, que ce comportement n'est pas garanti. Différentes variantes de processeur peuvent gérer la prélecture différemment, certains peuvent invalider les octets prélus si la mémoire à partir de laquelle ils ont été lus est modifiée, et les interruptions invalideront généralement le tampon de prélecture; le code sera récupéré lorsque les interruptions reviendront.

supercat
la source
3

Il existe un cas spécifique x86 qui n'est toujours pas décrit dans d'autres réponses: la gestion des interruptions. Pour certains styles, il peut y avoir des sections de code lorsque les interruptions sont désactivées car le code principal fonctionne avec certaines données partagées avec les gestionnaires d'interruptions, mais il est raisonnable d'autoriser les interruptions entre ces sections. Si on écrit naïvement


    STI
    CLI

cela ne traitera pas les interruptions en attente car, citant Intel:

Une fois l'indicateur IF défini, le processeur commence à répondre aux interruptions externes masquables après l'exécution de l'instruction suivante.

donc cela doit être réécrit au moins comme:


    STI
    NOP
    CLI

Dans la deuxième variante, toutes les interruptions en attente seront traitées uniquement entre NOP et CLI. (Bien sûr, il peut y avoir de nombreuses variantes alternatives, comme le doublement de l'instruction STI. Mais le NOP explicite est plus évident, du moins pour moi.)

Netch
la source
-2

NOP signifie aucune opération

Il est généralement utilisé pour insérer ou supprimer du code machine ou pour retarder l'exécution d'un code particulier.

Également utilisé par les crackers et les débogueurs pour définir des points d'arrêt.

Donc, probablement en faisant quelque chose comme: XCHG BX, BX entraînera également la même chose.

Cela me semble comme s'il y avait peu d'opérations qui étaient encore en cours et, par conséquent, cela a provoqué une erreur.

Si vous connaissez VB, je peux vous donner un exemple:

Si vous créez un système de connexion en vb et chargez 3 pages ensemble - facebook, youtube et twitter dans 3 onglets différents.

Et utilisez 1 bouton de connexion pour tous. Cela pourrait donner une erreur si votre connexion Internet est lente. Ce qui signifie que l'une des pages n'a pas encore été chargée. Nous avons donc mis Application.DoEvents pour surmonter cela. De la même manière dans l'assemblage, NOP peut être utilisé.

Immersion totale dans l'anime
la source