Scopo dell'istruzione NOP e allineamento dell'istruzione nell'assieme x86


15

È passato circa un anno dall'ultima volta che ho preso un corso di assemblaggio. In quella classe, stavamo usando MASM con le librerie Irvine per facilitare la programmazione.

Dopo aver esaminato la maggior parte delle istruzioni, ha detto che l'istruzione NOP essenzialmente non ha fatto nulla e di non preoccuparsi di usarla. Ad ogni modo, si trattava di medio termine e ha qualche codice di esempio che non funzionerebbe correttamente, quindi ci ha detto di aggiungere un'istruzione NOP e ha funzionato bene. Ho chiesto dopo la lezione perché e cosa ha effettivamente fatto, e lui ha detto che non lo sapeva.

Qualcuno lo sa?


NOP non fa nulla, ma consuma cicli. Non credo che alla tua domanda possa essere data risposta, senza il codice che possiamo solo immaginare. Beh, la mia ipotesi sarebbe una slide del NOP ...
yannis,

11
NOP in realtà fa qualcosa. Aumenta il puntatore istruzioni.
EricSchaefer,

Risposte:


37

Spesso i tempi NOPvengono utilizzati per allineare gli indirizzi delle istruzioni. Questo di solito si verifica ad esempio quando si scrive Shellcode per sfruttare l' overflow del buffer o formattare la vulnerabilità della stringa .

Supponi di avere un salto relativo a 100 byte in avanti e apporta alcune modifiche al codice. È probabile che le tue modifiche incasinino l'indirizzo del bersaglio di salto e come tale dovresti anche cambiare il salto relativo di cui sopra. Qui puoi aggiungere NOPs per spingere in avanti l'indirizzo di destinazione. Se sono presenti più NOPs tra l'indirizzo di destinazione e l'istruzione di salto, è possibile rimuovere la NOPs per spostare indietro l'indirizzo di destinazione.

Questo non sarebbe un problema se si lavora con un assemblatore che supporta le etichette. Puoi semplicemente fare JXX someLabel(dove JXX è un salto condizionale) e l'assemblatore sostituirà il someLabelcon l'indirizzo di quella etichetta. Tuttavia, se si modifica semplicemente manualmente il codice macchina assemblato (i codici operativi effettivi) (come talvolta accade con la scrittura di shellcode), è necessario modificare manualmente le istruzioni di salto. O lo si modifica o si sposta l'indirizzo del codice di destinazione utilizzando NOPs.

Un altro caso d'uso per l' NOPistruzione sarebbe qualcosa chiamato slitta NOP . In sostanza l'idea è quella di creare una serie abbastanza ampia di istruzioni che non causino effetti collaterali (comeNOPo incrementare e quindi decrementare un registro) ma aumentare il puntatore dell'istruzione. Ciò è utile, ad esempio, quando si vuole passare a un determinato codice che non è noto. Il trucco è posizionare la slitta NOP detta davanti al codice target e poi saltare da qualche parte alla slitta detta. Quello che succede è che l'esecuzione continua, si spera, dall'array che non ha effetti collaterali e attraversa in avanti istruzioni per istruzione fino a quando non colpisce il pezzo di codice desiderato. Questa tecnica viene comunemente utilizzata negli exploit di buffer overflow sopra menzionati e soprattutto per contrastare misure di sicurezza come ASLR .

Ancora un altro uso particolare NOPdell'istruzione è quando si modifica il codice di un programma. Ad esempio, è possibile sostituire parti di salti condizionali con NOPse aggirare la condizione. Questo è un metodo spesso usato per " crackare " la protezione della copia del software. In parole povere si tratta solo di rimuovere il costrutto del codice assembly per la if(genuineCopy) ...riga di codice e di sostituire le istruzioni con NOPs e .. Voilà! Non vengono effettuati controlli e le copie non originali funzionano!

Si noti che in sostanza entrambi gli esempi di shellcode e cracking fanno lo stesso; modificare il codice esistente senza aggiornare i relativi indirizzi delle operazioni che si basano sull'indirizzamento relativo.


2
Questa è stata una risposta meravigliosa, grazie per aver dedicato del tempo a spiegarlo! Finalmente capisco!
alvonellos,

Alcuni sistemi in tempo reale (mi vengono in mente i PLC) ti consentono di "patchare" la nuova logica in un programma esistente mentre è in esecuzione. Questi sistemi lasciano i NOP prima di ogni piccolo pezzo di logica in modo da poter sovrascrivere il NOP con un salto alla nuova logica che stai inserendo. Alla fine della nuova logica salterà alla fine della logica originale che stai sostituendo. La nuova logica avrà anche un NOP davanti in modo da poter sostituire anche la nuova logica.
Scott Whitlock,

10

Un nop può essere usato in uno slot di ritardo quando nessun'altra istruzione può essere riordinata per essere inserita al suo interno.

lw   v0,4(v1)
jr   v0

In MIPS, questo sarebbe un bug perché al momento in cui jr stava leggendo il registro v0 il registro v0 non era ancora stato caricato con il valore dall'istruzione precedente.

Il modo per risolvere questo sarebbe:

lw   v0,4(v1)
nop
jr   v0
nop

Questo riempie gli slot dealy dopo la parola di caricamento e le istruzioni del registro di salto con un nop in modo che l'istruzione della parola di caricamento sia completata prima dell'esecuzione del comando di registro di salto.

Ulteriori letture - un po 'sul riempimento SPARC degli slot di ritardo . Da quel documento:

Cosa può essere inserito nello slot di ritardo?

  • Alcune istruzioni utili che dovrebbero essere eseguite indipendentemente dal fatto che si ramifichi o meno.
  • Alcune istruzioni utili funzionano solo quando si ramifica (o quando non si ramifica), ma non fa alcun danno se eseguita nell'altro caso.
  • Quando tutto il resto fallisce, un'istruzione NOP

Cosa NON DEVE essere inserito nello slot di ritardo?

  • Tutto ciò che imposta il CC da cui dipende la decisione della filiale. L'istruzione branch prende la decisione se ramificare o meno subito ma in realtà non esegue il branch fino a dopo l'istruzione delay. (Solo la filiale è in ritardo, non la decisione.)
  • Un'altra istruzione di ramo. (Cosa succede se lo fai non è nemmeno definito! Il risultato è imprevedibile!)
  • Un'istruzione "set". Queste sono in realtà due istruzioni, non una, e solo la metà sarà nello slot di ritardo. (L'assemblatore ti avvertirà di questo.)

Nota la terza opzione nel cosa mettere nello slot di ritardo. L'errore che hai visto era probabilmente qualcuno che riempiva una delle cose che non devono essere inserite nello slot di ritardo. Mettere un nop in quella posizione avrebbe quindi risolto il bug.

Nota: dopo aver riletto la domanda, questo era per x86, che non ha slot di ritardo (la ramificazione invece blocca solo la pipeline). Quindi quella non sarebbe la causa / soluzione al bug. Sui sistemi RISC, quella avrebbe potuto essere la risposta.


4
Si noti che la domanda è taggata x86 e x86 non ha slot di ritardo. Nemmeno mai, dato che si tratta di un cambiamento radicale.
Salterio

6

almeno un motivo per usare NOP è l'allineamento. I processori x86 leggono i dati dalla memoria principale in blocchi abbastanza grandi e l'inizio del blocco da leggere è sempre allineato, quindi se uno ha un blocco di codice, questo verrà letto molto, questo blocco dovrebbe essere allineato. Ciò comporterà un piccolo aumento di velocità.


Non è esattamente che il blocco deve essere allineato, è che non si desidera recuperare gli ultimi due byte del blocco precedente. Quindi va bene saltare 0x1002, perché ci sono ancora 14 byte di istruzioni nel blocco allineato di 16B che contiene l'indirizzo di destinazione, ma non va bene saltare a 0x099D.
Peter Cordes,

3

Uno scopo per NOP (nell'assemblaggio generale, non solo x86) di introdurre ritardi temporali. Ad esempio, si desidera programmare un microcontrollore che deve emettere ad alcuni LED con un ritardo di 1 s. Questo ritardo può essere implementato con NOP (e filiali). Ovviamente potresti usare ADD o qualcos'altro, ma ciò renderebbe il codice più illeggibile; o forse hai bisogno di tutti i registri.


1
In genere per i frame di lunga durata, ad esempio 1 secondo, vengono utilizzati i timer. I NOPS sono usati per epoche entro un ordine di grandezza dell'orologio - nano e micro secondi.
mattnz,

Questo ha senso solo su un microcontrollore, non su un moderno x86. La maggior parte del codice x86 non satura la larghezza della pipeline delle moderne CPU super-ordinate out-of-order, quindi l'aggiunta di un NOP tra ogni istruzione nella maggior parte del codice avrebbe solo un piccolo impatto (immagino che il numero per il codice "medio" potrebbe essere Dal 5 al 20% per raddoppiare il numero di istruzioni, con alcuni codici che non mostrano alcun rallentamento ma alcuni loop stretti che mostrano quasi un rallentamento di 2x.) Comunque, il vecchio codice x86 crusty tradizionalmente utilizzava le loopistruzioni per i loop di ritardo , non i NOP.
Peter Cordes,

3

In generale su 80x86, le istruzioni NOP non sono necessarie per la correttezza del programma, sebbene occasionalmente su alcune macchine un NOP posizionato strategicamente possa far eseguire il codice più rapidamente. Sull'8086, ad esempio, il codice sarebbe stato recuperato in blocchi di due byte e il processore aveva un buffer "prefetch" interno che poteva contenere tre di questi blocchi. Alcune istruzioni verranno eseguite più velocemente di quanto potrebbero essere recuperate, mentre altre istruzioni richiederebbero un po 'di tempo per essere eseguite. Durante le istruzioni lente, il processore tenterà di riempire il buffer di prefetch, in modo che se le prossime istruzioni fossero veloci potrebbero essere eseguite rapidamente. Se l'istruzione che segue l'istruzione lenta inizia con un limite di parole pari, verranno precaricati i successivi sei byte di istruzioni; se inizia su un limite di byte dispari, verranno preimpostati solo cinque byte.

Tali problemi di allineamento della memoria possono influire sulla velocità del programma, ma generalmente non influiscono sulla correttezza. D'altra parte, ci sono alcuni problemi relativi al prefetch su quei processori più vecchi in cui un NOP potrebbe influire sulla correttezza. Se un'istruzione modifica un byte di codice che è già stato precaricato, l'8086 (e penso che 80286 e 80386) eseguirà l'istruzione prefetch anche se non corrisponde più a ciò che è in memoria. L'aggiunta di uno o due NOP tra l'istruzione che altera la memoria e il byte di codice che viene modificato può impedire il recupero del byte di codice fino a quando non è stato scritto. Si noti, a proposito, che molti schemi di protezione dalla copia hanno sfruttato questo tipo di comportamento; notare, tuttavia, che questo comportamento non è garantito. Diverse varianti di processore possono gestire il prefetch in modo diverso, alcuni possono invalidare i byte precaricati se la memoria da cui sono stati letti viene modificata e gli interruzioni invalideranno generalmente il buffer prefetch; il codice verrà recuperato quando ritornano gli interrupt.


3

Esiste un caso specifico x86 non ancora descritto in altre risposte: gestione degli interrupt. Per alcuni stili, possono esserci sezioni di codice quando gli interrupt sono disabilitati perché il codice principale funziona con alcuni dati condivisi con i gestori di interrupt, ma è ragionevole consentire interruzioni tra tali sezioni. Se uno scrive ingenuamente


    STI
    CLI

questo non elaborerà gli interrupt in sospeso perché, citando Intel:

Dopo aver impostato il flag IF, il processore inizia a rispondere a interruzioni mascherabili esterne dopo l'esecuzione dell'istruzione successiva.

quindi questo deve essere riscritto almeno come:


    STI
    NOP
    CLI

Nella seconda variante, tutti gli interrupt in sospeso verranno elaborati tra NOP e CLI. (Certo, ci possono essere molte varianti alternative, come il raddoppio dell'istruzione STI. Ma il NOP esplicito è più ovvio, almeno per me.)


-2

NOP significa nessuna operazione

Viene generalmente utilizzato per inserire o eliminare il codice macchina o per ritardare l'esecuzione di un determinato codice.

Utilizzato anche da cracker e debugger per impostare punti di interruzione.

Quindi probabilmente facendo qualcosa del genere: XCHG BX, BX si tradurrà anche nello stesso.

Mi sembra che ci siano poche operazioni ancora in corso e quindi ha causato un errore.

Se hai familiarità con VB, posso darti un esempio:

Se crei un sistema di accesso in vb e carichi 3 pagine insieme: Facebook, YouTube e Twitter in 3 diverse schede.

E usa 1 pulsante di accesso per tutti. Potrebbe dare un errore se la tua connessione Internet è lenta. Ciò significa che una delle pagine non è ancora stata caricata. Quindi abbiamo inserito Application.DoEvents per ovviare a questo. Allo stesso modo nell'assembly NOP può essere utilizzato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.