Random IRC quote :      <nullsub> me voy a fumal, que este chat es una mierda

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

11 Comentarios para “WINDOWS RED HOT CHILI PATCHING”

  1. Comment por winsock | 03/15/11 at 10:38 am

    Ahora entiendo para que sirven esos nops, que he visto en tantas ocasiones. La verdad que no te acostaras sin saber algo más.

    Muy chula e interesante la información, sigue así 🙂

    PD: Cojonuda la categoria, pero… no editaré mi comentario a pesar del riesgo.

  2. Comment por Ruben | 03/15/11 at 2:50 pm

    Mola!

    Además es interesante la sección que has creado, para meter apuntes de este tipo. Esta guay. Es una mierda.

  3. Comment por Amian | 03/15/11 at 5:27 pm

    Que rechingada wey, el zorriarte mucho hacia tiempo que no parlaba! A mi gustome el articulo no mas aprendi algo nuebo quisas lo aplique ahorita mismo en un proyesto.

  4. Comment por matalaz | 03/15/11 at 5:46 pm

    Está txulo el post (es una puta mierda). La putada es que no siempre te ponen los nops en todas las librerías y todas las funciones. Ejemplo: Quieres hookear en R3 una función de la ntdll (por ejemplo, ZwCreateFile). Bueno, pues estás fucked por que no tienes los nops famosos.

  5. Comment por inocraM | 03/16/11 at 12:29 am

    Buen post!

    Yo ya habia leido un post muy bueno al respecto en un blog que se llama «It goest to eleven», y que recomiendo a todo el mundo, porque se pueden leer cosas muy interesantes. La entrada concreta a la que me refiero es:
    http://blogs.msdn.com/b/itgoestoeleven/archive/2008/05/14/runtime-code-patching-not-for-the-faint-of-heart.aspx

  6. Comment por Zohiartze Herce | 03/16/11 at 10:34 am

    Matalaz, si hookeas en R3 lo suyo es que lo hagas una vez los modulos esten mapeados en el proceso pero aún no haya comenzado la ejecución de ningún hilo. ¿Pero como hookear en caliente estando seguros de que no jodemos nada? El problema ya lo resolvieron cuando pensarón en como poner breakpoints para depurar: o con registros de depuración o mediante una instrucción cuya longitud sea de un único byte (int 3). Si el debugger tubiese que meter instrucciónes de 2 o más bytes para poner un breakpoint creo estaríamos bien fucked.

  7. Comment por matalaz | 03/16/11 at 12:18 pm

    @Zohiartze Lo que suelo hacer es pausar todos los hilos, inicialmente, y luego poner los hooks.

  8. Comment por erg0t | 03/16/11 at 3:02 pm

    @matalaz Eso de pausar los todos los hilos tampoco te asegura que no quede ninguno «entre medio». Yo por lo general uso la int3 o ya que estamos en ring3 la instruccion «cli»

  9. Comment por inocraM | 03/17/11 at 12:51 am

    @erg0t, totalmente de acuerdo contigo con lo de que pausar los hilos no es una solucion. Lo que no pillo es lo del ¿¿cli en ring3??, ¿para generar un GP-Fault como alternativa al 0xCC? Suponiendo que no estes en virtual mode y que las excepciones virtuales no estan habilitadas… Un poco rebuscado no? xDDDD Tambien se podrian usar las versiones de in/out de un solo byte. Por otro lado, independientemente del metodo usado, tambien podemos encontrarnos con que estamos metiendo nuestra instruccion tras un «pop ss» con lo que el comportamiento variaria.

  10. Comment por Zohiartze Herce | 03/17/11 at 1:20 pm

    inocraM: No veo el problema con el «pop ss». Si pones la «int 3» sobre el «pop ss» la próxima vez no se ejecutará el «pop ss» sino la «int 3». Lo único que quieras hookear justo despues de un «pop ss» por algún oscuro motivo, pero en general para hookear funciones desde su punto de entrada no tienes problema alguno.

  11. Comment por erg0t | 03/17/11 at 3:52 pm

    @inocraM: si es algo rebuscado xD pero me ha dado buenos resultados.. tiene algunas ventajas dependiendo de cual sea el proposito de tus hooks. Por ejemplo al windbg lo vuelve loco a menos que te lo configures para pasar la excepcion xDD

Se han cerrado los comentarios