EJEMPLOS PRÁCTICOS DE FRIDA
Frida VS Técnicas Anti-Debug en Windows (I)

En esta serie de entradas trataremos de forma práctica el uso de Frida para la evasión de técnicas anti-debug que puedan implementar algunas de las aplicaciones que son analizadas en nuestro Laboratorio.
La serie irá desde una explicación introductoria de qué es Frida, mostrando el entorno utilizado y cómo lo hemos usado para realizar estas pruebas; siguiendo por una explicación de las técnicas anti-debug, primero a grandes rasgos y luego entrando en detalle en algunas de ellas; y acabando por una explicación detallada de algunas técnicas y cómo evadirlas usando Frida.
Sin más preámbulo, empezamos.

Logo Frida

¿Qué es Frida?

Usando la definición indicada en su página web (https://frida.re/), Frida es “un conjunto de herramientas para la instrumentación dinámica de código” (“it’s a dynamic code instrumentation toolkit”). En otras palabras, es un conjunto de herramientas que permite, mediante una API, la instrumentación de código, permitiendo la interceptación, el análisis y la modificación de partes de código de aplicaciones Windows, macOS, GNU/Linux, iOS, Android y QNX durante su ejecución. En definitiva, Frida permite la manipulación del código que se está ejecutando o que va a ser ejecutado. Mostrando un ejemplo sencillo se puede endender más fácilmente.

Partimos de un código C++ sencillo en el que tenemos una función que realiza una suma de dos valores. La función que queremos manipular es la función Add(int, int) a la cual le modificaremos, en un primer ejemplo, uno de los parámetros; y, en un segundo ejemplo, el resultado devuelto. Una vez compilado, debemos identificar mediante análisis del código en que offset de la función Add en nuestro ejecutable. En este caso lo hemos identificado mediante IDA Pro, pero podría utilizarse cualquier método o aplicación que permitiera el análisis del código. La dirección de memoria identificada es 0x00401000 y la dirección base del ejecutable es 0x00400000, por tanto, el offset de la función se sitúa en 0x00001000.

A continuación, usando Frida podemos interceptar la llamada a la función y modificar su comportamiento. Para ello debemos desarrollar un script de Javascript que haga esta tarea. El script identifica la dirección de memoria base donde se ha cargado el código de nuestro programa, incluye un elemento interceptor en la dirección ‘base + 0x00001000’ y añade el código que queramos hacer a la entrada de la función o, bien, a la salida. Todo esto lo hacemos utilizando el API Javascript que nos aporta Frida (https://frida.re/docs/javascript-api/).

Así, cuando ejecutamos el programa con argumentos ‘1’ y ‘2’ esperamos que el resultado sea ‘1 + 2 = 3’. No obstante, si descomentamos la línea args[0] = ptr(‘100’); sustituiremos el valor de op1 por 100 antes de que se ejecute la función, obteniendo como resultado ‘1 + 2 = 102’; mientras que, si descomentamos la línea retval.replace(‘3210’), sustituiremos el valor de retorno de la función Add una vez ya se ha ejecutado, obteniendo como resultado ’1 + 2 = 3210’.

ejecucion_frida

Introducción a las técnicas anti-debug

Una vez hemos visto que es Frida a grandes rasgos, vamos a darle un vistazo a qué son las técnicas anti-debug y cómo las clasificaremos en los posts posteriores de esta misma serie.
Las técnicas anti-debug son mecanismos mediante los cuales un software puede detectar si está siendo ejecutado bajo la supervisión de un depurador. Los depuradores nos permiten analizar de forma dinámica el código, establecer puntos de ruptura (breakpoints), modificar y analizar partes de la memoria, etc. Utilizando estas técnicas, la aplicación se previene de este análisis, dificultando su entendimiento.
En los próximos posts de esta serie hablaremos de distintos mecanismos anti-debug en detalle y para ello los dividiremos en distintos grupos, en función del mecanismo que usan para la detección del depurador.

Técnicas basadas en llamadas al sistema

Agruparemos en este grupo aquellas técnicas que se basen en el uso de funciones de la API de Windows para obtener información relativa a la presencia de un depurador. Las funciones que pueden ser usadas en este caso son muchas. Desde funciones como IsDebuggerPresent, que devuelven un valor booleano en función de si la aplicación está siendo depurada (True) o si no lo está siendo (False), a funciones como FindWindow, que nos permite buscar el nombre de una ventana que coincida con el nombre de un depurador común, p.e. IDA, Ollydbg, Inmunity Debugger

Técnicas basadas en comprobaciones en la memoria, de forma manual

Son técnicas en las que el programa consulta de forma manual algún flag en memoria que nos da información sobre si el proceso está siendo depurado o no. Algunos flags que pueden ser usado para esta tarea son IsDebugged Flag, Heap Flag o NTGlobalFlag. Estos flags pertenecen a estructuras que Windows mantiene para cada proceso, para guardar información sobre este. En posts sucesivos entraremos más en detalle sobre estas estructuras.

Técnicas basadas en tiempo

Este grupo comprende el conjunto de técnicas relacionadas con cálculos de tiempo para determinar si un proceso está siendo depurado. Cuando un proceso está siendo depurado, el tiempo que tarda en realizar un conjunto de operaciones suele ser significativamente mayor que si no lo está siendo. Es por ello que si se establece un contador de tiempo al inicio de un conjunto de operaciones y uno al final, calculado si la diferencia está por encima de un umbral se puede determinar, con una alta probabilidad de acertar, que el proceso está siendo depurado.

Técnicas basadas en excepciones

En este último grupo incluiremos el conjunto de técnicas basadas en provocar excepciones para identificar si el proceso está siendo depurado o no. Existe diferencia en cómo se tratan las excepciones cuando un proceso está siendo depurado y cuando está siendo ejecutado sin un depurador presente. Es por ello que un software puede aprovechar este hecho para diferenciar si está siendo depurado o no.

Preparando el Laboratorio

Para nuestro setup, utilizaremos una máquina Windows 10, en la que instalaremos Python 3.8.6rc1. Frida puede ser descargado directamente desde GitHub (https://github.com/frida/frida) o puede ser instalado en Windows mediante la herramienta pip de Python. En nuestro caso, usaremos pip para la instalación, dado que resulta más simple.

version_frida

También instalaremos Visual Studio Community 2019 para el desarrollo de los programas de ejemplo, en los que implementaremos algunas técnicas de anti-debug para mostrar el funcionamiento. Estos programas serán los usados para mostrar que los métodos de evasión implementados funcionan.
Existen diversas formas de utilizar Frida: podemos utilizar directamente alguno de los ejecutables que son instalados con el paquete (cada ejecutable proporciona distintas funcionalidades) o, bien, utilizar el módulo de Python que proporciona para crear nuestra propia interfaz.
En Layakk hemos optado por este segundo esquema, desarrollando una pequeña aplicación esqueleto que permite la ejecución (spawn) o la vinculación (attach) de Frida a una aplicación o proceso, inyectando un script que aporta cierta funcionalidad. Se elige esta forma para poder incluir parámetros personalizados al lanzar el programa, según las necesidades que se encuentren en los casos que se expondrán en siguientes posts.