menu
close_24px

BLOG

How to Protect Your Code? (Top 3 Anti-Debugging Techniques)

Explore the top 3 anti-debugging techniques to secure your code from reverse engineering & unauthorized analysis. Get practical strategies from this detailed guide.
  • Posted on: Apr 28, 2022
  • By Samartha J V
  • Read time 3 Mins Read
  • Last updated on: Jan 10, 2025

Threat actors utilize numerous anti-analysis techniques, one of the most common of which is anti-debugging, to make post-detection analysis more difficult. Threat actors have shown to be more inventive in the malware they create and the ways they use to avoid detection and analysis by cybersecurity experts and solutions.

On the other hand, anti-debugging is a burden to malware analysts since it can slow down the process of reverse-engineering the code, making it more challenging to figure out how it works.

What is anti-debugging: How to protect your code?

Hackers often use static analysis and dynamic analysis techniques to learn more about an application and how it works. This assists them in identifying attack vectors that can be exploited to uncover app flaws. This is usually accomplished by attaching a debugger to the app.

Debuggers are helpful in the development process but can also be used for malicious purposes. They can provide hackers access to the app's code and its logic. Anti-debugging is a way of preventing debuggers from attaching to the application. 

Top 3 anti-debugging techniques

Anti-debugging techniques allow programs to defend themselves even when not being developed in a secure environment. Several alternative strategies are used to enable an app to identify the existence of a debugger. We'll look at some of the most essential ones here.

Anti-JDWP debugging 

JDWP stands for "Java Debug Wire Protocol." It is the debug protocol used for communication between a debugger and the Java virtual machine. Java-based Android applications are not difficult to debug. Some of the methods to debug them are listed below.

1. Using the debuggable flag 

The "android: debuggable" attribute in the manifest file of the Android app determines if the debugging using JDWP is enabled for the app. If the value is set to true, debugging is allowed. This means the app has been tampered with.

The code below shows how we can check the same programmatically. 

public static boolean isDebuggable(Context context){ 

 return ((context.getApplicationContext().getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0); 

}

2. Timer checks 

Debugging an app slows down its process execution. We can analyze the time difference between executions to confirm whether the debugger is attached.

Programmatically, we can check the value of Debug.threadCpuTimeNanos as it is the time the app uses to execute.

The following code can be used to do timer checks.

static boolean detect_threadCpuTimeNanos(){ 

 long start = Debug.threadCpuTimeNanos(); 

 for(int i=0; i<1000000; ++i

     continue

 long stop = Debug.threadCpuTimeNanos(); 

 if(stop - start < 10000000) { 

     return false

 } else

     return true

 }

}

3. JDWP related data structures 

The "DvmGlobals" structure in the Dalvik machine can be used to access the global virtual machine state. It gives information about the debugger. Similarly, Android RunTime (ART) has a similar structure that can be exploited to get debugger information. One such example of the structure is shown below.

struct DvmGlobals

 bool jdwpAllowed; // debugging allowed for this process?

 bool jdwpConfigured; // has debugging info been provided?  

 JdwpTransportType jdwpTransport

 bool jdwpServer

 char* jdwpHost

 int jdwpPort

 bool jdwpSuspend

 Thread* threadList

 bool nativeDebuggerActive

 bool debuggerConnected; /* debugger or DDMS is connected */

 bool    debuggerActive; /* debugger is making requests */  

 JdwpState* jdwpState

};

Anti-native debugging 

Apart from JAVA debuggers, there is another category of debuggers based on pTrace, similar to Linux. We can try the following methods to prevent these debuggers from attaching to the applications.

1. Monitoring TracerPid 

We can monitor the TracerPid field found in the process status file. If the value of TracerPid is 0, then there's no attached debugger. But if the value of TracerPid is anything other than 0, then that means some debugger is trying to attach to the process.

The following example shows an example of checking the status file. 

$ adb shell ps -A | grep com.example.test 

u0_a271 17657 573 4302108 50600 ptrace_stop 0 t com.example.test $ adb shell cat /proc/17657/status | grep -e "^TracerPid:" | sed "s/^TracerPid:\t//" TracerPid: 11839 

$ adb shell ps -A | grep 11839 

u0_a271 11839 11837 14024 4548 poll_schedule_timeout 0 S lldb-server

 

2. Creating a dummy process 

Another way we can prevent ptrace-based debuggers is by creating a dummy process. That way, if any debugger tries to attach to the process, then it'll be forced to attach to the dummy child process.

Below is an example of code that can be used to fork a child process. 

void fork_and_attach() { 

 int pid = fork();

 if (pid == 0) { 

     int ppid = getppid(); 

     if (ptrace(PTRACE_ATTACH, ppid, NULL, NULL) == 0) { 

        waitpid(ppid, NULL, 0);

        ptrace(PTRACE_CONT, NULL, NULL); 

     } 

 } 

}

Final thoughts

Almost every software attack starts with reverse-engineering the code to figure out how it works and locate vulnerabilities to exploit. Attackers can tamper with the code, evade security constraints, change app behavior, and steal secret keys and sensitive data once they understand the internal dynamics of the target application. Utilizing the above-mentioned anti-debugging techniques will help prevent this exploitation of vulnerabilities and will be a way to keep your app secure.