Domanda

Sto attraversando MSIL e noto che ci sono molti nop istruzioni. L'articolo MSDN afferma che non prendono alcuna azione e vengono utilizzati per riempire lo spazio se il codice operativo è patchato. Sono molto più utilizzati nelle build di debug che nelle build di rilascio. So che questo tipo di dichiarazioni sono usate nei linguaggi assembly per assicurarsi che un codice operativo si adatti a un limite di parole, ma perché è necessario in MSIL?

È stato utile?

Soluzione

I NOP ??hanno diversi scopi:

  • Consentono al debugger di posizionare un breakpoint su una linea anche se è combinato con altri nel codice generato.
  • Consente al caricatore di patchare un salto con un offset target di dimensioni diverse.
  • Consente di allineare un blocco di codice a un determinato limite, il che può essere utile per la memorizzazione nella cache.
  • Permette il collegamento incrementale per sovrascrivere grossi pezzi di codice con una chiamata a una nuova sezione senza doversi preoccupare della funzione generale che cambia dimensione.

Altri suggerimenti

Ecco come vengono utilizzati i nops dal debug:

I nops sono usati dai compilatori di lingue (C #, VB, ecc.) per definire i punti di sequenza impliciti. Questi indicano al compilatore JIT dove garantire che le istruzioni della macchina possano essere ricondotte alle istruzioni IL.

Inserimento nel blog di Rick Byer su DebuggingModes.IgnoreSymbolStoreSequencePoints , spiega alcuni dettagli.

C # inserisce anche le istruzioni Nops after call in modo che la posizione del sito di ritorno nell'origine sia la chiamata anziché la linea dopo la chiamata.

Fornisce un'opportunità per marcatori basati su linea (ad es. punti di interruzione) nel codice in cui una build di rilascio non emetterebbe nessuno.

Può anche velocizzare l'esecuzione del codice, durante l'ottimizzazione per processori o architetture specifici:

I processori per lungo tempo impiegano più pipeline che funzionano approssimativamente in parallelo, quindi è possibile eseguire contemporaneamente due istruzioni indipendenti. Su un semplice processore con due pipeline, il primo può supportare tutte le istruzioni, mentre il secondo supporta solo un sottoinsieme. Inoltre, ci sono alcune bancarelle tra le condutture quando si deve attendere il risultato di un'istruzione precedente che non è ancora terminata.

In queste circostanze, un nop dedicato può forzare l'istruzione successiva in una pipeline specifica (la prima o non la prima) e migliorare l'associazione delle seguenti istruzioni in modo che il costo della < em> nop è più che ammortizzato.

Amico! No-op è fantastico! È un'istruzione che non fa altro che consumare tempo. Nelle tenebre oscure lo useresti per fare microaggiustamenti nei tempi nei cicli critici o, cosa più importante, come riempitivo nel codice automodificante.

In un processore ho lavorato di recente (per quattro anni) il NOP è stato utilizzato per assicurarsi che l'operazione precedente fosse terminata prima dell'avvio dell'operazione successiva. Ad esempio:

carica il valore da registrare (richiede 8 cicli) no 8 aggiungi 1 per registrarti

Questo ha assicurato che il registro avesse il valore corretto prima dell'operazione di aggiunta.

Un altro uso era quello di compilare unità di esecuzione, come i vettori di interruzione che dovevano avere una certa dimensione (32 byte) perché l'indirizzo per il vettore 0 era, diciamo 0, per il vettore 1 0x20 e così via, quindi il compilatore inseriva i NOP lì dentro se necessario.

Un loro uso classico è che il tuo debugger possa sempre associare una riga di codice sorgente a un'istruzione IL.

Potrebbero usarli per supportare modifica e continua durante il debug. Fornisce al debugger spazio per lavorare per sostituire il vecchio codice con nuovo senza cambiare offset, ecc.

Nop è essenziale nel payload degli exploit buffer overflow.

Come diceva ddaa, nops consente di tenere conto della varianza nello stack, in modo che quando si sovrascrive l'indirizzo di ritorno salta sulla slitta nop (molte nops di fila) e quindi colpisce correttamente il codice eseguibile, anziché saltare a qualche byte nell'istruzione che non è l'inizio.

50 anni in ritardo ma ehi.

Nop è utile se si digita manualmente il codice assembly. Se fosse necessario rimuovere il codice, è possibile escludere i vecchi codici operativi.

in modo simile, è possibile inserire un nuovo codice sovrascrivendo un codice operativo e saltare altrove. Lì inserisci i codici operativi sovrascritti e inserisci il tuo nuovo codice. Quando sei pronto, salti indietro.

A volte dovevi usare gli strumenti disponibili. In alcuni casi questo era solo un editor di codice macchina molto semplice.

Oggi con i compilatori le tecniche non hanno più alcun senso.

Nella scena del software cracking, un metodo classico per sbloccare un'applicazione sarebbe quello di patchare con un NOP la linea che controlla la chiave o la registrazione o il periodo di tempo o quant'altro quindi non farebbe nulla e semplicemente continuerebbe ad avviare l'applicazione come se è registrato.

Ho anche visto NOP nel codice che si modifica per offuscare ciò che fa come segnaposto (veeery old copy protection).

Un uso un po 'non ortodosso sono NOP-Slides , utilizzato in exploit buffer overflow.

Consentono al linker di sostituire un'istruzione più lunga (in genere salto in lungo) con una più breve (salto in breve). Il NOP occupa lo spazio extra - il codice non può essere spostato in quanto impedirebbe agli altri salti di funzionare. Ciò accade al momento del collegamento, quindi il compilatore non può sapere se un salto lungo o corto sarebbe appropriato.

Almeno, questo è uno dei loro usi tradizionali.

Questa non è una risposta alla tua domanda specifica, ma ai vecchi tempi potresti usare un NOP per riempire un branch delay slot , se non sei riuscito a riempirlo con un'istruzione altrimenti utile.

I compilatori .NET allineano l'output MSIL? Immagino che potrebbe essere utile per accelerare l'accesso a IL ... Inoltre, la mia comprensione è che è progettata per essere portatile e sono necessari accessi allineati su alcune altre piattaforme hardware.

Il primo assembly che ho imparato è stato SPARC, quindi ho familiarità con lo slot di ritardo del ramo, se non riesci a riempirlo con un'altra istruzione, di solito l'istruzione che avresti messo sopra l'istruzione di ramo o incrementare un contatore in loop , usi un NOP.

Non ho familiarità con il cracking, ma penso che sia comune sovrascrivere lo stack usando NOP, quindi non devi calcolare esattamente dove inizia la tua funzione dannosa.

Ho usato i NOP per regolare automagicamente la latenza accumulata dopo aver inserito un ISR. Molto utile per inchiodare i tempi morti.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top