Jugando con técnicas anti-debugging (II)

En el capitulo anterior analizamos la función IsDebuggerPresent() de la API de Windows, jugamos con el crackme en OllyDbg y vimos como hacer un bypass de esta función para lograr nuestro objetivo.

En esta entrega vamos a ver otra técnica que se basa en el chequeo del campo NtGlobalFlag del PEB (Process Environment Block).

El valor de NtGlobalFlag viene determinado por el valor de los siguientes flags:

  • FLG_HEAP_ENABLE_TAIL_CHECK
  • FLG_HEAP_ENABLE_FREE_CHECK
  • FLG_HEAP_VALIDATE_PARAMETERS

Cuando un proceso es creado por el debugger, el valor de NtGlobalFlag es 0x70, resultado de los valores de los flags que lo componen, 0x10, 0x20 y 0x30 respectivamente. Una nota importante a tener en cuenta es que si dicho proceso, en lugar de ser cargado directamente en el debugger es adjuntado (attached), entonces el valor de NtGlobalFlag no es modificado y mantendrá su valor por defecto: 0 (no está siendo debuggeado).

Si recordamos, la función IsDebuggerPresent() devolvía el valor almacenado en el campo BeingDebugged del PEB. Para consultar el valor de NtGlobalFlag no contamos con ninguna función en lenguaje de alto nivel que nos facilite el trabajo (al menos yo no la he encontrado). Así que tendremos que acceder directamente a la estructura del PEB y, para ello, utilizaremos el lenguaje C y NASM.

En mi cuenta de GitHub podéis descargaros los crackmes; el correspondiente a esta entrega es “crackme02_NtGlobalFlag”:

Como podéis ver, los sources constan de un fichero con código en lenguaje C (crackme02_NtGlobalFlag.c) y otro con lenguaje ensamblador (crackme02_NtGlobalFlag_asm.asm). El código en ensamblador es el encargado de obtener el valor de NtGlobalFlag del proceso que se está ejecutando. El código en C simplemente llama a la función en ensamblador y muestra un mensaje con el resultado: si está siendo debuggeado o no.

Una vez que hayáis descargado el crackme, podéis ejecutar directamente el binario compilado, y si no os fiáis, podéis compilar los sources con los siguientes comandos:

Para compilar los sources en Windows, necesitaréis NASM y GCC (MinGW); yo los he instalado en un Windows XP 32 Bits.

El código de “crackme02_NtGlobalFlag.c” puede verse a continuación. El código es muy simple, realiza la llamada a la función «ntglobalfunc()» que se encuentra en el fichero de código ensamblador. Esta función consulta y devuelve el valor de NtGlobalFlag. Acto seguido, se realiza una operación AND a nivel de bits (bitwise) entre el valor devuelto y 0x70. Dependiendo del resultado de esta operación, se mostrará un mensaje u otro.

En los comentarios pueden leerse algunas notas sobre los valores: si el proceso está siendo debuggeado, la variable local NtGlobalFlag almacenará el valor 0x70.

Ahora que hemos descrito el código, vamos a cargar el crackme en OllyDbg. Si lo ejecutamos desde el debugger, mostrará el mensaje «Debugger detectado!!«.

Vamos a inicializarlo de nuevo y vamos a colocarnos en la posición 004013FD (Ctrl+G). En la imagen a continuación podemos ver el código que nos interesa del crackme.

En esta posición hay una llamada a la posición de memoria 00401480, así que vamos a echarle un vistazo. Esta llamada se corresponde con la llamada a la función «ntglobalfunc()«. Para ir a esa posición, o bien lo hacemos como antes, o hacemos clic derecho sobre la llamada y seleccionamos «Follow» (aún no hemos ejecutado el crackme, solo estamos navegando por el código en el debugger).

Os suena el código de la siguiente imagen, ¿verdad?

Vamos a volver a la posición anterior (004013FD) (ya sabéis, Ctrl+G, o pulsamos “-” para retroceder hasta la posición indicada). Después de la llamada que acabamos de ver, podemos ver como el valor retornado en EAX es almacenado en la pila (“ss” apunta a la pila).

A continuación, se realiza la operación AND entre el valor retornado en EAX y 70h, el resultado es almacenado en EAX. ¿Por qué realizamos esta operación? Si recordamos, el valor de NtGlobalFlag es 0 si el proceso no esta siendo debuggeado. Por lo tanto, si tras la operación AND lógica, EAX es igual a 0, significa que el proceso no esta siendo debuggeado, pero esta comprobación se realiza con la siguiente operación TEST.

Con TEST se comprueba si EAX es 0. Si es así, establece el flag ZF=1 y ZF=0 en cualquier otro caso. Después, JE tomará el salto si ZF=1. Dependiendo del resultado del salto, se mostrará un MessageBox con un texto u otro.

Ya os podéis hacer una idea de alguna forma que otra de romper esta técnica anti-debugging. Al igual que en la entrega anterior, podemos modificar el tipo de salto o cambiar el valor del flag ZF que controla el salto. Otra opción es cambiar el valor del registro EAX con un valor distinto de 0, de forma que el resultado de TEST establezca el flag ZF=1.

Vamos a cambiar el tipo de salto por JMP (salto incondicional). Para ello, nos colocamos en la posición 0040140D y hacemos clic derecho y “Assemble”. Cambiamos JE por JMP. Ahora ejecutamos el proceso y deberíamos obtener el mensaje de «Debugger NO detectado!!«. En la siguiente imagen se muestra la fracción de código que hemos modificado.

Como siempre, es recomendable que vayáis monitorizando el valor de EAX en OllyDbg para ver con mayor claridad que valores adquiere en cada operación, algo que ayuda a comprender el proceso de esta técnica.

Existen otras formas de complementar el valor de NtGlobalFlag del PEB para reforzar esta técnica, por ejemplo, mediante las claves de registro y variables de entorno:

  • HKLM\System\CurrentControlSet\Control\SessionManager.
  • HKLM\Software\Microsoft\WindowsNT\CurrentVersion\Image File ExecutionOptions\<filename>.
  • Campos de “Load Configuration Table”: FLG_USER_STACK_TRACE_DB y FLG_HEAP_VALIDATE_PARAMETERS.
  • Variable de entorno “_NO_DEBUG_HEAP”.

Os recomiendo la lectura del paper de Peter Ferrie “The ultimate Anti-debugging Reference” [PDF], en el cual describe una cantidad de técnicas anti-debugging.

La técnica de este post está muy bien descrita en el paper y describe las variaciones con claves de registro y variables de entorno que hemos nombrado anteriormente.

¡Nos vemos en la próxima entrega!