Propósito de la instrucción NOP y la declaración de alineación en el ensamblaje x86

13

Ha pasado más o menos un año desde la última vez que tomé una clase de asamblea. En esa clase, estábamos usando MASM con las bibliotecas de Irvine para facilitar la programación.

Después de que hubiéramos seguido la mayoría de las instrucciones, dijo que la instrucción NOP esencialmente no hacía nada y no tenía que preocuparse por usarla. De todos modos, fue sobre la mitad del período y tiene un código de ejemplo que no funcionaría correctamente, por lo que nos dijo que agregáramos una instrucción NOP y funcionó bien. Le pregunté si estaba después de la clase por qué y qué fue lo que realmente hizo, y él dijo que no sabía.

¿Alguien sabe?

    
pregunta alvonellos 15.09.2012 - 23:09

7 respuestas

35

Muchas veces se usa NOP para alinear direcciones de instrucciones. Esto suele aparecer, por ejemplo, al escribir Shellcode para explotar desbordamiento del búfer o vulnerabilidad de la cadena de formato .

Supongamos que tiene un salto relativo a 100 bytes hacia adelante y realice algunas modificaciones al código. Lo más probable es que sus modificaciones arruinen la dirección del objetivo de salto y, como tal, también tendría que cambiar el salto relativo antes mencionado. Aquí, puede agregar NOP s para adelantar la dirección de destino. Si tiene varios NOP s entre la dirección de destino y la instrucción de salto, puede eliminar el NOP s para tirar de la dirección de destino hacia atrás.

Esto no sería un problema si está trabajando con un ensamblador que admite etiquetas. Simplemente puede hacer JXX someLabel (donde JXX es un salto condicional) y el ensamblador reemplazará el someLabel con la dirección de esa etiqueta. Sin embargo, si simplemente modifica el código de máquina ensamblado (los códigos de operación reales) a mano (como sucede a veces al escribir shellcode), también tiene que cambiar la instrucción de salto manualmente. O lo modificas, o luego mueves la dirección del código de destino usando NOP s.

Otro caso de uso para la instrucción NOP sería algo denominado trineo NOP . En esencia, la idea es crear un conjunto suficientemente grande de instrucciones que no causen efectos secundarios (como NOP o incrementando y luego disminuyendo un registro) pero incrementando el puntero de la instrucción. Esto es útil, por ejemplo, cuando uno quiere saltar a un determinado fragmento de código cuya dirección no se conoce. El truco es colocar el dicho trineo NOP frente al código objetivo y luego saltar a algún lugar al dicho trineo. Lo que sucede es que la ejecución continúa esperanzadamente desde la matriz que no tiene efectos secundarios y atraviesa la instrucción por instrucción hacia adelante hasta que llega a la pieza de código deseada. Esta técnica se usa comúnmente en las vulnerabilidades de desbordamiento de búfer mencionadas anteriormente y, especialmente, para contrarrestar medidas de seguridad como ASLR .

Otro uso particular para la instrucción NOP es cuando uno está modificando el código de algún programa. Por ejemplo, puede reemplazar partes de saltos condicionales con NOP sy, como tal, evitar la condición. Este es un método de uso frecuente cuando " craqueo " protege el software. En la forma más simple, solo se trata de eliminar el código de ensamblaje para la línea de código if(genuineCopy) ... y reemplazar las instrucciones con NOP s y ... ¡Voilà! No se realizan comprobaciones y no funciona la copia original.

Tenga en cuenta que, en esencia, ambos ejemplos de shellcode y cracking hacen lo mismo; modifique el código existente sin actualizar las direcciones relativas de las operaciones que dependen del direccionamiento relativo.

    
respondido por el zxcdw 16.09.2012 - 10:44
9

Se puede usar un nop en aa ranura de retardo cuando no se puede reordenar ninguna otra instrucción para que se coloque allí.

lw   v0,4(v1)
jr   v0

En MIPS, esto sería un error porque en el momento en que el jr estaba leyendo el registro v0, el registro v0 no se ha cargado con el valor de la instrucción anterior.

La forma de solucionar esto sería:

lw   v0,4(v1)
nop
jr   v0
nop

Esto llena los espacios diestros después de la palabra de carga y las instrucciones de registro de salto con un nop para que la instrucción de palabra de carga se complete antes de que se ejecute el comando de registro de salto.

Lecturas adicionales: un poco en SPARC llenado de espacios de retardo . De ese documento:

  

¿Qué se puede poner en la ranura de retardo?

     
  • Algunas instrucciones útiles que deberían ejecutarse independientemente de si se bifurca o no.
  •   
  • Algunas instrucciones que hacen un trabajo útil solo cuando se bifurca (o cuando no se bifurca), pero no hacen ningún daño si se ejecutan en el   otro caso.
  •   
  • Cuando todo lo demás falla, una instrucción NOP
  •   

¿Qué NO DEBE colocarse en la ranura de retardo?

     
  • Todo lo que establece el CC del que depende la decisión de la rama. La instrucción de bifurcación toma la decisión de bifurcar o no   De inmediato, pero en realidad no hace la rama hasta después de la demora   instrucción. (Solo la sucursal se retrasa, no la decisión).
  •   
  • Otra instrucción de rama. (¡Lo que sucede si haces esto ni siquiera está definido! ¡El resultado es impredecible!)
  •   
  • Una instrucción "set". Esto es realmente dos instrucciones, no una, y solo la mitad estará en la ranura de retardo. (El ensamblador avisará   usted acerca de esto.)
  •   

Tenga en cuenta la tercera opción en qué colocar en la ranura de retardo. El error que vio fue probablemente alguien que está llenando una de las cosas que no deben colocarse en la ranura de retardo. Poner un nop en esa ubicación solucionaría el error.

Nota: después de volver a leer la pregunta, esto fue para x86, que no tiene ranuras de retardo (la bifurcación en su lugar solo detiene la tubería). Así que esa no sería la causa / solución al error. En los sistemas RISC, esa podría haber sido la respuesta.

    
respondido por el user40980 18.09.2012 - 16:45
6

al menos una razón para usar NOP es la alineación. Los procesadores x86 leen los datos de la memoria principal en bloques bastante grandes, y el inicio del bloque a leer siempre está alineado, por lo que si uno tiene un bloque de código, que se leerá mucho, este bloque debería estar alineado. Esto dará lugar a poca aceleración.

    
respondido por el permeakra 16.09.2012 - 06:48
3

Un propósito para NOP (en conjunto, no solo x86) es introducir retardos de tiempo. Por ejemplo, desea programar un microcontrolador que debe emitir a algunos LED con un retraso de 1 s. Este retraso puede implementarse con NOP (y sucursales). Por supuesto, podrías usar ADD o algo más, pero eso haría que el código sea más ilegible; o tal vez necesitas todos los registros.

    
respondido por el m3th0dman 16.09.2012 - 10:12
3

En general, en el 80x86, las instrucciones NOP no son necesarias para la corrección del programa, aunque ocasionalmente en algunas máquinas, los NOP colocados estratégicamente podrían hacer que el código se ejecute más rápidamente. En el 8086, por ejemplo, el código se buscaría en fragmentos de dos bytes, y el procesador tenía un búfer interno de "captación previa" que podía contener tres fragmentos de este tipo. Algunas instrucciones se ejecutarían más rápido de lo que se podrían obtener, mientras que otras tardarían un tiempo en ejecutarse. Durante las instrucciones lentas, el procesador intentaría llenar el búfer de captación previa, de modo que si las siguientes instrucciones fueran rápidas, pudieran ejecutarse rápidamente. Si la instrucción que sigue a la instrucción lenta comienza en un límite de palabra par, los siguientes seis bytes de instrucciones serán recogidos previamente; si comienza en un límite de byte impar, solo se precargarán cinco bytes. Si recuerdo, un NOP tomó tres ciclos y una recuperación de memoria tomó cuatro, así que si la obtención previa del byte extra guardaría un ciclo de memoria, agregar un "NOP" para hacer que la instrucción después de una lenta comience en un límite de palabra par podría algunas veces guardar un ciclo.

Tales problemas de alineación de la memoria pueden afectar la velocidad del programa, pero generalmente no afectarán la corrección. Por otro lado, hay algunos problemas relacionados con la captación previa en los procesadores más antiguos donde un NOP podría afectar la corrección. Si una instrucción altera un byte de código que ya se ha recuperado previamente, el 8086 (y creo que el 80286 y el 80386) ejecutará la instrucción de prefijado aunque ya no coincida con lo que hay en la memoria. Agregar un NOP o dos entre la instrucción que altera la memoria y el byte de código que se modifica puede evitar que se recupere el byte de código hasta después de que se haya escrito. Tenga en cuenta, por cierto, que muchos esquemas de protección contra copia explotaron este tipo de comportamiento; Tenga en cuenta, sin embargo, que este comportamiento no está garantizado. Diferentes variaciones del procesador pueden manejar la captación previa de manera diferente, algunos pueden invalidar los bytes de captura anticipada si se modifica la memoria desde la cual se leyeron, y las interrupciones generalmente invalidarán el búfer de captación previa; el código se volverá a buscar cuando regresen las interrupciones.

    
respondido por el supercat 18.09.2012 - 18:54
3

Hay un caso específico de x86 aún no descrito en otras respuestas: manejo de interrupciones. Para algunos estilos, puede haber secciones de código cuando las interrupciones están deshabilitadas porque el código principal funciona con algunos datos compartidos con los manejadores de interrupciones, pero es razonable permitir interrupciones entre dichas secciones. Si uno escribe ingenuamente


    STI
    CLI

esto no procesará interrupciones pendientes porque, citando a Intel:

  

Una vez que se establece el indicador IF, el procesador comienza a responder a las interrupciones externas y enmascarables una vez que se ejecuta la siguiente instrucción.

de modo que esto se reescriba al menos como:


    STI
    NOP
    CLI

En la segunda variante, todas las interrupciones pendientes se procesarán solo entre NOP y CLI. (Por supuesto, puede haber muchas variantes alternativas, como duplicar la instrucción STI. Pero el NOP explícito es más obvio, al menos para mí).

    
respondido por el Netch 24.12.2013 - 20:54
-2

NOP significa que no hay operación

Generalmente se usa para insertar o eliminar código de máquina o para retrasar la ejecución de un código en particular.

También es utilizado por crackers y depuradores para establecer puntos de interrupción.

Probablemente haciendo algo como: XCHG BX, BX También resultará en lo mismo.

Me suena como si hubiera pocas operaciones que aún estuvieran en proceso y, por lo tanto, causara un error.

Si está familiarizado con VB, puedo darle un ejemplo:

Si crea un sistema de inicio de sesión en vb y carga 3 páginas juntas: facebook, youtube y twitter en 3 pestañas diferentes.

Y use 1 botón de inicio de sesión para todos. Podría dar un error si su conexión a internet es lenta. Lo que significa que una de las páginas no se ha cargado todavía. Así que ponemos en aplicación los eventos para superar esto. Se puede utilizar la misma forma en el ensamblaje de NOP.

    
respondido por el Total Anime Immersion 16.09.2012 - 10:19

Lea otras preguntas en las etiquetas