WINDOWS RED HOT CHILI PATCHING
Hace tiempo que leí sobre la manera en que habían abordado en el sistema operativo windows algunos de los problemas que nos podemos encontrar al desarrollar un sistema con el requisito de que permita actualizar sus componentes en caliente.
Desarrollar un sistema que cumpla con dicho requisito puede resultar bastante interesante y además bastante complicado dependiendo de la complejidad del propio sistema a actualizar. Me parece interesante como lo resuelven en Windows así que me he decidió a aprovechar un rato tonto para escribir sobre algunos trucos que han utilizado en dicho sistema operativo.
No voy a contar nada nuevo, pero como lo que voy a contar trata de promover un poco la culturilla general, lo he publicado en una nueva sección que le viene al pelo a dicho propósito.
El caso es que tenemos un modulo que exporta funciones mapeado en memoria y literalmente cientos de hilos invocando a dichas funciones. En un momento dado queremos actualizar una de las funciones sin que ningún hilo se entere absolutamente de nada. ¿Como lo hacemos?
Una respuesta sencilla puede ser: mapeamos código con lo que sería la función actualizada en una nueva posición de memoria y redirigimos el código de la función «vieja» a esa nueva función con un “far jmp”. Pillamos como ejemplo una función cualquiera de la ntoskrnl como «ExAcquireResourceExclusiveLite», la típica función que no vale para nada:
.text:0040339F 90 nop
.text:004033A0 90 nop
.text:004033A1 90 nop
.text:004033A2 90 nop
.text:004033A3 90 nop
ExAcquireResourceExclusiveLite:
.text:004033A4 8B FF mov edi, edi
.text:004033A6 55 push ebp
.text:004033A7 8B EC mov ebp, esp
.text:004033A9 83 EC 0C sub esp, 0Ch
.text:004033AC 53 push ebx
.text:004033AD 56 push esi
.text:004033AE 57 push edi
Si queremos parchear esta función metiéndole un «far jmp», tendremos que escribir 5 bytes machacando el prologo de la misma (es decir las 3 primeras instrucciones). Aquí nos encontramos con el primer problema de sincronía: en una arquitectura de 32 bits no podremos escribir los 5 bytes de forma atómica, a no ser que lo hagamos con «lock cmpxchg8b» o similar que podría no estar disponible en todas las arquitecturas. Al menos necesitaremos dos operaciones para hacerlo y por lo tanto entre que nuestro hilo escribe los primeros 4 bytes del «far jmp» (primera operación) y escribe el último byte (segunda operación) podría pasar a ejecutarse otro hilo habiendo quedado nuestro parcheo a medias. Si al nuevo hilo o cualquier otro que se ejecute a continuación antes de que terminemos el parcheo se le ocurre invocar a la función «ExAcquireResourceExclusiveLite», estaremos bien jodidos. Así que menos mal que
«ExAcquireResourceExclusiveLite» no vale para nada y nadie la usa…
Ahora imaginemos que tenemos la posibilidad de escribir los 5 bytes con una única operación utilizando «lock cmpxchg8b» o algo similar. ¿Estaría el problema resuelto? La respuesta es no. Veámoslo desde otro punto de vista. Hay un hilo (llamémosle «A») que se esta ejecutando e invoca a la función «ExAcquireResourceExclusiveLite». Justo despues de ejecutar la primera instrucción de dicha función («mov edi, edi»), el SO pone dicho hilo a WAITING y nuestro hilo de actualización toma el procesador escribiendo los 5 primeros bytes de la función con el «far jmp». Cuando el hilo «A» resuma su ejecución y quiera ejecutar el «push ebp» que había antes, ahora no se encontrara allí el opcode adecuado, en lugar de eso estará «en mitad» del «far jmp» con el que hemos parcheado la función y el resultado posiblemente será un pete con «invalid instruction».
¿cuantas herramientas puede haber que no tengan en cuenta estos problemas a la hora de hookear? No fallarán casi nunca, pero cuando fallen ya me imagino al desarrollador de turno flipando en colores tratando de imaginarse que coño ha pasado y leyéndose un manual rápido de windbg para estudiar el dump xD
¿Como decidieron resolver esto en Windows? La respuesta esta ahí delante, no tenemos más que fijarnos un poco más en los bytes inmediatamente anteriores a la función «ExAcquireResourceExclusiveLite» y en el prologo de la misma.
Los bytes anteriores a la función son 5 NOPs. Lo primero que vemos en el prologo es otro NOP, esta vez de 2 bytes ya que el «mov edi, edi» no es más que otra manera de escribir un NOP. ¿Para que tanto NOP? Para resolver los problemas de sincronía en la actualización. En lugar de escribir el «far jmp» directamente sobre el prologo de la función, saben que inmediatamente antes la función van a tener al menos 5 NOPs. Lo saben porque es una opción de los compiladores de Microsoft (lo cual no quiere decir que otros compiladores también lo permitan). Así que al actualizar la funcion, escriben los 5 bytes del «far jmp» sobre-escribiendo la ristra de NOPs. Aunque tarden un año en escribir estos 5 bytes con el «far jmp» no pasa nada, el resto de hilos siguen ejecutando la función desde el «mov edi, edi» y no se enteran de nada. Finalmente una vez escrito el «far jmp», parchean el «mov edi, edi» del prologo sustituyéndolo por un «short jmp» para el que son necesarios 2 bytes exactamente igual que para el «mov edi, edi». Se puede realizar por tanto con una única operación y sin dejar ninguna instrucción «a medias» en ninguno de los hilos que pudieran estar en ejecución. Dicho «short jmp» saltará 5 bytes hacía atrás, justo al «far jmp» que se ha escrito anteriormente. Pan, problema resuelto, de manera sencilla a la par que elegante y sin que ningún hilo se haya enterado de nada. Una vez hecho el patch quedaría algo similar a lo siguiente:
.text:0040339F E9 XX XX XX XX jmp XXXXXXXX ; far jmp a la dirección de la nueva función
ExAcquireResourceExclusiveLite:
.text:004033A4 EB FB jmp 0040339F ; salto hacia atras, justo al far jmp anteior
.text:004033A6 55 push ebp
.text:004033A7 8B EC mov ebp, esp
.text:004033A9 83 EC 0C sub esp, 0Ch
.text:004033AC 53 push ebx
.text:004033AD 56 push esi
.text:004033AE 57 push edi
Lo dicho, nada nuevo pero estoy seguro que mucha gente no lo conocía 🙂 Por último un par de referencias:
http://msdn.microsoft.com/en-us/library/ms173524(v=vs.80).aspx
http://software.intel.com/sites/products/documentation/hpc/composerxe/en-us/cpp/lin/copts/common_options/option_hotp
atch.htm