In this series of entries we are going to show practical examples of how to use Frida to bypass anti-debug techniques that some applications implement. This series begins with a short description of what Frida is, presenting the environment we will use for the examples we show later; followed by a description of anti-debug techniques, firstly in general terms and later detailing a couple of them; and the series will end with a detailed exposition of some techniques and how we can bypass them using Frida.
Without further ado, let’s start.
What is Frida?
Using the same definition as in the official webpage (https://frida.re/), Frida is “a dynamic code instrumentation toolkit”. In other words, it’s a set of tools that allow for the instrumentation of code, giving us some APIs that enable the interception, analysis and modification of parts of the code of an application for Windows, macOS, GNU/Linux, iOS, Android and QNX during its execution. In essence, Frida allows the manipulation, at run time, of what is going to be executed just before it is executed.
Easier to understand with an easy example. We start with a simple C++ program that uses a function to add 2 values and returns the result. The function that we want to manipulate is declared as Add(int,int). First, we will change one of the int parameters, and, secondly, the returned result. Once we have the code compiled, we need to locate the offset of function Add through analysis of the code of our executable. In this case, we use IDA Pro to analyze the code, but any other method or application that would allow us to analyze the executable’s code could be used. We identify the function at offset 0x00401000, and we know that the executable’s base address is 0x00400000, therefore, the offset of the function is 0x00001000.
Thus, when we execute the application with ‘1’ and ‘2’ as arguments, we expect ‘1 + 2 = 3′ as result. However, if we uncoment the first commented line ( args = ptr(‘100’); ) we replace the value of variable op1 with 100 before the function execution, getting a result of ‘1 + 2 = 102’. On the other hand, if we uncoment the second commented line ( retval.replace(‘3210’) ), we replace the return value after function Add is executed but before the result is returned, getting ’1 + 2 = 3210’.
Anti-Debug Techniques Introduction
Once we have seen what Frida is in general terms, we are going to take a look at what anti-debug techniques are and how we are going to classify them in successive posts of this series. Anti-debug techniques are mechanisms that software can implement to try to detect if it is being run under debugger supervision. Debuggers allow us to analyze code dynamically, establish breakpoints, modify and analyze memory sections, etc. Using these techniques, an application can avoid this inspection, difficulting its understanding to a reverse engineer. In forthcoming publications of this series we will talk about anti-debug techniques thoroughly and we will divide them into different groups, depending on the detection mechanism they use.
Techniques based on system calls
We consider in this group the techniques that use functions of the Windows API to get information relative to the debugger presence. There are a lot of functions that can be used with this objective: from functions like IsDebuggerPresent, that returns a boolean value that depends on whether the application is being debugged (True) or it isn’t (False), to functions like FindWindow, that tells us if a window with the name of a well-known debugger (IDA, Ollydbg, Inmunity Debugger, etc.) is present.
Techniques based on memory checks
Methods where the application does explicit verification of some flags in memory that reveal if the process is being debugged or not. Some flags that can be used with this purpose are IsDebugged Flag, Heap Flag or NTGlobalFlag. These flags are members of structures that Windows maintains for each process with information about them. In future posts we will go into detail on this structures.
Techniques based on time
This group includes the methods that use calculations related with time to determine if a process is being debugged. When a process is being debugged, it takes more time to do the same set of instructions than if it isn’t being debugged. This time difference is usually significant. For this reason, the application could check the time at the beginning and at the end of the execution of a set of instructions, and, if it spends more time than a established threshold, it could determine, with high probability, that the process was being debugged.
Techniques based on exceptions
Finally, we group here the set of methods based on triggering exceptions to identify if the process is being debugged or not. There are differences in how a system handles exceptions when a process is being debugged and when it is not. A program can take advantage of this fact to determine whether a debugger is attached or not.
Preparing our testing environment
For implementing our setup, we will use a Windows 10 virtual machine, where we will initially install Python 3.8.6rc1.
Then we install Frida, which can be downloaded directly from GitHub (https://github.com/frida/frida) or installed using the Python pip tool. We use pip because it’s easier than other methods.
We also install Visual Studio Community 2019 to develop the example programs, where we implement some anti-debug techniques to show how it works. These programs will be used to test different methods to bypass those anti-debug techniques. There are different ways to use Frida: we can directly use the executable included in the package (each executable has specific functionality) or use the Python module also included in the package to develop our own interface.
We have chosen the second option, developing a little interface that allow us to spawn new processes or attach to existing ones, injecting one or more scripts that provide some functionality. We choose this way because we want to be able to customize the interface depending on specific requirements that we will encounter in the examples detailed in later posts.