SecureTeam Blog

Stop piracy now. Increase your revenues.

Anti-Debug Protections Done Wrong

17 Apr 2017 By Eran Dror

Lately we’ve been working closely with a security group, working for a well-known Fortune 100 company. Our task was to help the company improve the protection for one of its core products, on which many of its software assets are depending on.

It turns out the team decided to develop their own home grown solution to prevent unauthorized debugging by the competition or by hackers looking to distribute unauthorized copies of their software.

No doubt, a debugger is a powerful tool for watching and interacting with an application, it can be used by an attacker to step through the application code, help in understanding how sensitive algorithms work. Using a debugger one can change application state at runtime in order to bypass licensing or any other checks – therefore the motivation behind placing debugging checks which the team had taken makes perfect sense.

During the examination process, one of the things that immediately caught our attention is the team’s decision to use mscorlib’s Debugger.IsAttached method to detect whether a debugger is attached to the running process.

This pose a major security risk, mainly because by performing a CLR injection attack one can practically change the implementation of the above method, having it return a false response regardless whether a debugger is actually present.

A CLR injection attack is performed by intercepting arbitrary binary functions. Interception code is applied dynamically at runtime by replacing the first few instructions of the target function with an unconditional jump to the user-provided method.

The code of the target method is modified in memory, not on disk, thus facilitating interception of binary functions at a very fine granularity. For example, the methods in a DLL can be modified in one execution of an application, while the original procedures are not detoured in another execution.

To illustrate the point, I’ve created a simple console application demonstrating a CLR injection attack on Debugger.IsAttached method. You can find the source and documentation on Github.


public static void Main(string[] args)
{
    Console.WriteLine(string.Format("Is Debugger Attached: {0}", Debugger.IsAttached));

    var isAttachedMethodInfo = typeof(Debugger).GetProperty("IsAttached").GetGetMethod();

    var myDebuggerIsAttachedMethodInfo = typeof(Program).GetMethod("MyDebuggerIsAttached");

    // Make sure methods are JITted.
    RuntimeHelpers.PrepareMethod(isAttachedMethodInfo.MethodHandle);
    RuntimeHelpers.PrepareMethod(myDebuggerIsAttachedMethodInfo.MethodHandle);

    // Get a pointer to the method's implementation
    var originalPointer = isAttachedMethodInfo.MethodHandle.GetFunctionPointer();

    // Get MyDebuggerIsAttached method address 
    byte[] myDebuggerIsAttachedAddress = BitConverter.GetBytes(myDebuggerIsAttachedMethodInfo.MethodHandle.GetFunctionPointer().ToInt32());

    // Jump to MyDebuggerIsAttached and ret
    var newCodeBytes = new byte[]
    {
        0x68, myDebuggerIsAttachedAddress[0], myDebuggerIsAttachedAddress[1], myDebuggerIsAttachedAddress[2], myDebuggerIsAttachedAddress[3], 0xC3
    };

    uint oldProtect = 0;
    VirtualProtect(originalPointer, 60, 64U, out oldProtect);
    Marshal.Copy(newCodeBytes, 0, originalPointer, 6);
    Console.WriteLine(string.Format("Is Debugger Attached: {0}", Debugger.IsAttached));

    // Run the debug target
    Console.WriteLine("Launching DebugTarget.exe");
    AppDomain.CurrentDomain.ExecuteAssembly(@"DebugTarget.exe");

    Console.WriteLine("Press any key to continue");
    Console.ReadKey();
}
		

A few notes about the implementation:

  • First, we obtain a handle to Debugger.IsAttached MethodInfo.
  • We then call RuntimeHelpers.PrepareMethod method, this will make sure Debugger.IsAttached and our own MyDebuggerIsAttached implementation is compiled to native code before we continue.
  • We place a JMP instruction (0x68) followed by the memory address of MyDebuggerIsAttached into newCodeBytes.
  • Finally, we overwrite the original implementation by our own MyDebuggerIsAttached method.
  • We are now ready to execute our debug target and start a debug session. Any attempt to call Debugger.IsAttached will end up trapped in MyDebuggerIsAttached which will return a negative response regardless whether a debugger is present.