OllyTheBugger
Cuenta la leyenda que en algunas ocasiones en las frias mañanas de invierno ( y una mierda, a ver si nieva ya de una vez! ) es posible encuentrarse con un bug de esos que no sabes ni por donde agarrarlo, el caso que os presentamos es real y nos tuvo entretenidos a Victor y a mi un buen rato …
Una de las aplicaciones que usamos en un entorno de producción estaba devolviendo unos valores de retorno inesperados, la aplicacion era muy sencilla y hacía uso de otras librerías también programadas por nosotros, con lo cual nos pusimos a depurar para ver dónde podía encontrarse el problema exactamente y ver cuantos programas podían estar afectados por el fallo.
El primer paso fue agregar una int 3 al propio programa, ya que al ser lanzado este desde otro, haciendo uso del JIT podíamos seguir depurando el programa de manera más cómoda y de la forma más parecida a la real en la que este se cargaba en el entorno de producción.
El desensamblado del programa es el que sigue a continuación:
.text:00401000 ; int __cdecl main(int argc,const char **argv,const char *envp)
.text:00401000 _main proc near ; CODE XREF: start+16Ep
.text:00401000
.text:00401000 var_11C = dword ptr -11Ch
.text:00401000 var_10C = dword ptr -10Ch
.text:00401000 Buffer = byte ptr -108h
.text:00401000 var_4 = dword ptr -4
.text:00401000 argc = dword ptr 4
.text:00401000 argv = dword ptr 8
.text:00401000 envp = dword ptr 0Ch
.text:00401000
.text:00401000 000 sub esp, 10Ch
.text:00401006 10C mov eax, dword_407030
.text:0040100B 10C mov [esp+10Ch+var_4], eax
.text:00401012 10C mov eax, [esp+10Ch+argc]
.text:00401019 10C push ebp
.text:0040101A 110 xor ebp, ebp
.text:0040101C 110 cmp eax, 2
.text:0040101F 110 mov [esp+110h+var_10C], ebp
.text:00401023 110 jnz loc_40112F
.text:00401029 110 push ebx
.text:0040102A 114 push esi
.text:0040102B 118 push edi
.text:0040102C 11C int 3 ; <- Nuestra Int 3 🙂
.text:0040102D 11C call CualInitGAS
.text:00401032 11C mov ebx, eax
.text:00401034 11C lea eax, [esp+11Ch+Buffer]
.text:00401038 11C push eax ; lpBuffer
.text:00401039 120 push 104h ; nBufferLength
.text:0040103E 124 call ds:GetCurrentDirectoryA
.text:00401044 11C lea edi, [esp+11Ch+Buffer]
.text:00401048 11C dec edi
.text:00401049 11C lea esp, [esp+0]
.text:00401050
.text:00401050 loc_401050: ; CODE XREF: _main+56j
.text:00401050 11C mov al, [edi+1]
.text:00401053 11C inc edi
.text:00401054 11C test al, al
.text:00401056 11C jnz short loc_401050
.text:00401058 11C mov ecx, ds:dword_405144
.text:0040105E 11C mov dx, ds:word_405148
.text:00401065 11C lea eax, [esp+11Ch+Buffer]
.text:00401069 11C mov [edi], ecx
.text:0040106B 11C push ebx
.text:0040106C 120 push eax
.text:0040106D 124 mov [edi+4], dx
.text:00401071 124 call CualSetTempPath
.text:00401076 124 push ebp
.text:00401077 128 push offset a_PluginsS; ".\\PLUGINS\\S.QAL"
.text:0040107C 12C push ebx
.text:0040107D 130 call LoadPlugin
.text:00401082 130 push ebp
.text:00401083 134 push offset a_PluginsA ; ".\\PLUGINS\\A.QAL"
.text:00401088 138 push ebx
.text:00401089 13C call LoadPlugin
.text:0040108E 13C <-!!! mov ecx, [esp+13Ch+argv] ; <- Accediendo a argv
.text:00401095 13C mov edx, [ecx+4] ; <- argv[1] !!!
.text:00401098 13C push edx
.text:00401099 140 push ebx
.text:0040109A 144 call CualOpenFile
.text:0040109F 144 mov esi, eax
.text:004010A1 144 add esp, 28h
Con el Ollydbg empezamos a depurar el programa hasta que llegamos al momento en el que se accedia a argv[1] viendo que se producía una excepcion, ECX contenía en ese momento el valor 0x2, con lo cual el cálculo de la posición del array de punteros a los parametros se estaba realizando mal, vimos que el dato que en realidad se había obtenido era argc!, ¿como podía ser?, en la pila argc y el puntero a argv están contiguos, lo cual quería decir que la pila se había movido 4 bytes y se había quedado desajustada.
Lo primero que pensamos es que alguna de las funciones que usabamos no se estaba llamando con la convención correcta, en el código están mezcladas funciones stdcall (todas las del API de Win32) y cdecl (las de nuestra librería), quizá en algun momento estabamos llamando a alguna de la manera que no se debía y como resultado la pila se quedaba en ese estado. Una vez comprobamos que no era el caso, pasamos a abrir el programa con el IDA, que haríamos sin el! :-), si observamos el código,vemos que tras las llamadas a CualSetTempPath y las siguientes a LoadPlugin se acaba arreglando la pila de golpe ( en el add esp,28h ), esto es debido a que el binario estaba compilado con optimización ( /O2 concretamente ), antes de que la pila esté como debería estar es donde ese calcula el puntero, esto nos hizo pensar que quizá podría ser un bug del compilador que estuviera prediciendo mal el desplazamiento que tenía que aplicar sobre ESP, en el código observamos que accede a 13Ch + argv lo cual viene a ser 144h, nos pusimos a echar cuentas del estado de la pila en ese momento, despues de un par de minutos discutiendo por que pushes no habíamos contado nos acordamos de que el IDA tiene una opcion para mostrartelo auto-mágicamente! ( podeís activarla en General -> Options -> Stack Pointer ) y se muestra la columna esa que veís a la izquierda del código. Cual fue nuestra sorpresa cuando vimos que no, que la posicion a la que accedía era la correcta…
¿Qué narices podía estar pasando?, between whistles and flutes nos pusimos a depurar también en otro ordenador y aquí fue cuando casi nos da un patatús,,, el programa en el otro ordenador llegaba a ese punto sin problemas!!!, el binario era el mismo exactamente y la instalación parecida si no idéntica, el fallo tenía que estar en algo del sistema operativo o en el debugger!, después de mirar un poco más, llegamos a la raiz del asunto … parecía que el Ollydbg a la hora de obtener el contexto del proceso con la opción de Just In Time debugging, no se detenía en la interrupción, sino que lo hacía una instrucción antes, gracias a esto la instrucción previa «push edi» se ejecutaba dos veces, dejando la pila movida los 4 bytes que comentabamos antes y dejandonos a nosotros con el fallo real por localizar :-(.
La versión afectada por este fallo es la 1.09d, por cierto, ¿alguien sabe si se espera la v2 y para cuando? 😉