¿Porqué todas las funciones de Windows empiezan con la instrucción MOV EDI, EDI?

Si miramos a las funciones desensambladas de las dlls de windows, veremos que empiezan con una instrucción aparentemente inútil: MOV EDI,EDI. Esta instrucción copia el contenido de un registro a él mismo sin ningún flag; no tiene sentido alguno. ¿Porqué está ahí?

La instrucción MOV EDI,EDI es como una instrucción NOP pero de dos bytes, es el espacio necesario de un salto, de manera que la función puede actualizarse "on the fly". La intención es que la esta instrucción será sustituida por la instrucción JMP $-5 para redirigir el control a los siguientes 5 bytes que vienen justamente después del comienzo de la instrucción. Estos cinco bytes son los suficientes para una instrucción de salto, que puede pasar el control a la función colocada en cualquier parte del espacio de memoria.

Aunque esos 5 bytes son el espacio necesario para comenzar una función, el punto de entrada de una función sólo usa dos bytes.

¿Y porqué no usar un desvío simple para esto, no sería más simple?

El problema de los desvíos en una función es que no se puede asegurar que en el momento del cambio de instrucción, otro thread diferente entre a ejecutarse. Se podría evitar este comportamiento suspendiendo todos los threads mientras se está haciendo el cambio de función, pero esto no funcionaría si alguien hiciese un CreateRemoteThread después de que hallamos suspendido todos los threads.

¿Porqué no usar dos NOP en el punto de entrada?

Bueno, porque una isntrucción NOP consume un ciclo de reloj y una tubería, así que dos de ellas consumirían dos ciclos de reloj y dos tuberías. Pero, estas dos NOPs serían paralelizadas, una en cada tubería, y su ejecución sería de tan sólo un ciclo de reloj. Sin embargo, la instrucción MOV EDI, EDI consume un ciclo de reloj y una tubería, sin embargo, se ejecuta en tan sólo medio ciclo.

Por otro lado, las cinco NOPs ántes del comienzo de una función nunca se ejecutan, así que de poco sirven.

Pero lo importante es que el número de ciclos, es que el uso de una NOP de dos bytes evitan el problema de los desvíos: Si el código ha usado dos instrucciones NOP simples, existe el riesgo de que pongamos este parche como un thread que ha terminado la ejecución como si fuese un NOP simple, y está apunto de ejecutar el segundo byte NOP, como resultado del tratamiento del thread, la segunda mitad del JMP $-5 será el comienzo de una nueva instrucción.

Espero que os sirva y guste.

Traducido por: Juan María Laó Ramos.

Artículo original.